/* ============================================================
   Connect an external agent · MCP / CLI / API
   The portability promise made tangible: scoped, propose-only
   access an outside agent can wire up in one of three ways.
   ============================================================ */
/* The three ways an outside agent wires up to Yoshi. The config a connection
   shows is built from its own name + key, so the credential travels with it. */
const METHOD_OPTIONS = [{ value: "MCP", label: "MCP" }, { value: "SDK", label: "SDK" }, { value: "API", label: "API" }];
const METHOD_HINT = {
  MCP: "Paste Yoshi into your agent's MCP config, then authorize it inside the agent. The connection shows up in Active connections once you approve.",
  SDK: "Call Yoshi from your own code. SDKs authenticate with an API key, so there's nothing to register here.",
  API: "Call the REST API directly. Name the connection and mint its scoped key.",
};

// MCP config is identical for everyone — a server URL, authorized inside the
// agent over OAuth. No token to paste, no name to choose: the agent reports its
// own name back over the protocol.
const MCP_CONFIG = `{
  "mcpServers": {
    "yoshi": {
      "url": "https://agents.yoshi.invalid/mcp"
    }
  }
}`;

// The Yoshi SDK across languages — each authenticates with a scoped API key.
// The Yoshi SDK + CLI, installed with your package manager. Each authenticates
// with a scoped API key.
const SDK_MANAGERS = [
  { id: "pip", label: "pip", cmd: "pip install yoshi" },
  { id: "npm", label: "npm", cmd: "npm install @yoshi-ai/sdk" },
  { id: "brew", label: "Homebrew", cmd: "brew install yoshi-ai-dev/yoshi-cli/yoshi" },
  { id: "go", label: "Go", cmd: "go install github.com/yoshi-ai-dev/yoshi-cli/cmd/yoshi@latest" },
];
const OPENCLAW_SETUP = "mcporter auth https://agents.yoshi.invalid/mcp/";
const BUILD_SETUP = `Yoshi API brief
Build an external agent that connects to Yoshi over MCP, an SDK, or a scoped API key.
Access is propose-only: the agent can use authorized Yoshi context and draft money moves, but only the user can approve execution.
Use the setup details in Yoshi Agents for the connection method, then ask the user for a scoped API key before making API calls.`;
const CHATGPT_APP_URL = "https://chatgpt.com/apps/yoshi/asdk_app_69b99bd669a481919598a5495d38a0f7";

// Legacy multi-language snippets (unused).
const SDK_LANGS = [
  { value: "python", label: "Python" },
  { value: "cli", label: "CLI" },
  { value: "npm", label: "npm" },
  { value: "go", label: "Go" },
];
const SDK_CODE = {
  python: `$ pip install yoshi

from yoshi import Yoshi
client = Yoshi(api_key="ys_demo_…")
client.propose.trade(symbol="VTI", usd=500)`,
  cli: `$ npm i -g @yoshi/cli
$ export YOSHI_API_KEY=ys_demo_…
$ yoshi propose trade --symbol VTI --usd 500`,
  npm: `$ npm i @yoshi/sdk

import { Yoshi } from "@yoshi/sdk";
const yoshi = new Yoshi({ apiKey: "ys_demo_…" });
await yoshi.propose.trade({ symbol: "VTI", usd: 500 });`,
  go: `$ go get github.com/yoshi/yoshi-go

client := yoshi.New("ys_demo_…")
client.Propose.Trade(yoshi.Trade{Symbol: "VTI", USD: 500})`,
};

// A copy-paste API quickstart — the docs snippet that used to live here.
const API_DOCS = `$ curl https://api.yoshi.invalid/v1/propose/trade \\
    -H "Authorization: Bearer ys_demo_…" \\
    -d symbol=VTI -d usd=500`;
const API_DOCS_URL = "https://docs.yoshi.invalid/api";

const CodeBlock = ({ code }) => {
  const [copied, setCopied] = useState(false);
  const copy = () => { try { navigator.clipboard && navigator.clipboard.writeText(code); } catch (_) {} setCopied(true); setTimeout(() => setCopied(false), 1600); };
  return (
    <div style={{ margin: "12px 20px 0", background: "var(--terminal-fill)" }}>
      <div style={{ display: "flex", justifyContent: "flex-end", padding: "8px 8px 0" }}>
        <button className="press" onClick={copy} style={{ background: "color-mix(in srgb, var(--terminal-ink) 14%, transparent)", border: "none", color: "var(--terminal-ink)", fontFamily: "var(--f-display)", fontSize: 10, fontWeight: 600, letterSpacing: "0.06em", textTransform: "uppercase", padding: "5px 9px", cursor: "pointer" }}>{copied ? <CopyButtonLabel copied size={11}>Copied</CopyButtonLabel> : <CopyButtonLabel size={11}>Copy</CopyButtonLabel>}</button>
      </div>
      <pre style={{ margin: 0, padding: "6px 15px 14px", color: "var(--terminal-ink)", fontFamily: "var(--f-mono)", fontSize: 11.5, lineHeight: 1.7, whiteSpace: "pre-wrap", wordBreak: "break-word" }}>{code}</pre>
    </div>
  );
};

/* small muted explainer line used across the setup panels */
const SetupNote = ({ children }) =>
  <div style={{ fontFamily: "var(--f-display)", fontSize: 11.5, color: "var(--ink-3)", lineHeight: 1.5, margin: "10px 20px 0" }}>{children}</div>;

/* A direct shortcut into the ChatGPT Yoshi app — gets MCP users closest to done. */
const ChatGptShortcut = () =>
  <a href="https://chatgpt.com/g/yoshi" target="_blank" rel="noopener noreferrer" className="press"
    style={{ display: "flex", alignItems: "center", gap: 11, margin: "12px 20px 0", padding: "12px 13px", background: "var(--bg-card)", border: "1px solid var(--rule-2)", borderRadius: 12, textDecoration: "none", color: "inherit", cursor: "pointer" }}>
    <span style={{ width: 32, height: 32, flex: "none", border: "1px solid var(--rule-2)", borderRadius: 9, display: "grid", placeItems: "center", color: "var(--ink)" }}>
      <Icon name="message" size={18} stroke={1.5} />
    </span>
    <div style={{ flex: 1, minWidth: 0 }}>
      <div style={{ fontFamily: "var(--f-display)", fontSize: 13, fontWeight: 600, letterSpacing: "-0.01em" }}>Open the Yoshi app in ChatGPT</div>
      <div style={{ fontFamily: "var(--f-display)", fontSize: 11, color: "var(--ink-3)", marginTop: 2 }}>Authorize in a tap, no config to paste</div>
    </div>
    <Icon name="back" size={15} color="var(--ink-3)" style={{ transform: "scaleX(-1)" }} />
  </a>;

/* MCP setup · copy the config, authorize inside the agent. No name, no commit —
   the connection lands in Active connections once the user approves. */
const McpSetup = ({ shortcut }) =>
  <>
    <CodeBlock code={MCP_CONFIG} />
  </>;

/* SDK setup · pick a language, copy the snippet. SDKs authenticate with an API
   key, so there's no registration step. */
const SdkSetup = () => {
  const [lang, setLang] = useState("python");
  return (
    <>
      <div style={{ padding: "12px 20px 0" }}>
        <Segmented size="sm" options={SDK_LANGS} value={lang} onChange={setLang} />
      </div>
      <CodeBlock code={SDK_CODE[lang]} />
      <SetupNote>SDKs authenticate with a scoped API key. Create one under the API tab, then drop it in.</SetupNote>
    </>
  );
};

/* The API quickstart docs, with a copy action and a link to the full reference. */
const ApiDocs = () =>
  <>
    <div style={{ padding: "18px 20px 0" }}><Eyebrow>API reference</Eyebrow></div>
    <CodeBlock code={API_DOCS} />
    <a href={API_DOCS_URL} target="_blank" rel="noopener noreferrer" className="press"
      style={{ display: "inline-flex", alignItems: "center", gap: 6, margin: "10px 20px 0", fontFamily: "var(--f-display)", fontSize: 11.5, fontWeight: 600, color: "var(--accent)", textDecoration: "none" }}>
      View full API reference <Icon name="back" size={13} color="var(--accent)" style={{ transform: "scaleX(-1)" }} />
    </a>
  </>;

const AGENT_PROVIDERS = [
{ id: "chatgpt", name: "ChatGPT", icon: "message" },
{ id: "claude", name: "Claude", icon: "robot" },
{ id: "perplexity", name: "Perplexity", icon: "search" },
{ id: "cursor", name: "Cursor", icon: "terminal" },
{ id: "openclaw", name: "OpenClaw", icon: "bolt" },
{ id: "hermes", name: "Hermes Agent", icon: "send" }];

const PROVIDER_IDS = AGENT_PROVIDERS.map((p) => p.id);
const PROVIDER_ICON = Object.fromEntries(AGENT_PROVIDERS.map((p) => [p.id, p.icon]));
const ICON_FOR_VIA = { MCP: "connect", SDK: "terminal", API: "key" };
const iconForConn = (c) => c.icon || PROVIDER_ICON[c.id] || ICON_FOR_VIA[c.via] || "connect";

// A connection is one authorized link to Yoshi. MCP connections are named by
// the service that reports in over the protocol (ChatGPT, OpenClaw, …); API
// connections are named by you when you mint the key. Some are authorized
// entirely outside the app and simply appear here once they land.
const AGENT_CONNECTIONS_SEED = [
{ id: "chatgpt", name: "ChatGPT", via: "MCP", scope: "Propose-only", last: "Active now", status: "active" },
{ id: "claude", name: "Claude", via: "MCP", scope: "Propose-only", last: "Active now", status: "active" },
{ id: "openclaw", name: "OpenClaw", via: "MCP", scope: "Propose-only", last: "Connected May 30", status: "active" },
{ id: "k_ray", name: "Raycast agent", via: "API", scope: "Propose-only", last: "Used 2h ago", status: "active", custom: true, token: "ys_demo_7f3a9c2b6d14e8f0a7c92" },
{ id: "k_scripts", name: "Personal scripts", via: "API", scope: "Read-only", last: "Used May 28", status: "active", custom: true, token: "ys_demo_2b8e44170c9ad3f6b8e41d" }];


const maskKey = (t) => {
  const i = t.indexOf("_demo_");
  const prefix = i >= 0 ? t.slice(0, i + 6) : t.slice(0, 8);
  return prefix + "••••••••" + t.slice(-4);
};
const genToken = () => {
  const hex = "0123456789abcdef";
  let s = "ys_demo_";
  for (let i = 0; i < 28; i++) s += hex[Math.floor(Math.random() * 16)];
  return s;
};

/* API key UI for one connection: mint the key, reveal it once, then it stays
   masked. The key belongs to the connection, not a separate registry. */
const ApiKeyArea = ({ token, fresh, onCreate, onRegenerate, ctaLabel }) => {
  const [copied, setCopied] = useState(false);
  const copy = (val) => { try { navigator.clipboard && navigator.clipboard.writeText(val); } catch (_) {} setCopied(true); setTimeout(() => setCopied(false), 1600); };

  // a key was just minted this session — revealed in full, exactly once
  if (fresh)
    return (
      <div style={{ margin: "12px 20px 0", border: "1px solid var(--accent)", background: "color-mix(in srgb, var(--accent) 7%, var(--bg-card))", padding: "12px 13px" }}>
        <div style={{ fontFamily: "var(--f-display)", fontSize: 11.5, fontWeight: 600, color: "var(--ink-2)", lineHeight: 1.45 }}>Key created. Copy it now; you won't see it in full again.</div>
        <div style={{ display: "flex", alignItems: "center", gap: 9, marginTop: 10 }}>
          <code style={{ flex: 1, minWidth: 0, fontFamily: "var(--f-mono)", fontSize: 11.5, color: "var(--ink)", wordBreak: "break-all", lineHeight: 1.4 }}>{fresh}</code>
          <button className="press" onClick={() => copy(fresh)} style={{ flex: "none", background: "var(--ink)", color: "var(--bg)", border: "none", fontFamily: "var(--f-display)", fontSize: 10, fontWeight: 600, letterSpacing: "0.06em", textTransform: "uppercase", padding: "6px 10px", cursor: "pointer" }}>{copied ? <CopyButtonLabel copied size={11}>Copied</CopyButtonLabel> : <CopyButtonLabel size={11}>Copy</CopyButtonLabel>}</button>
        </div>
      </div>
    );

  // an existing key, kept masked for safety
  if (token)
    return (
      <div style={{ margin: "12px 20px 0", border: "1px solid var(--rule-2)", background: "var(--bg-card)", padding: "12px 13px" }}>
        <div style={{ display: "flex", alignItems: "center", gap: 9 }}>
          <Icon name="key" size={15} color="var(--ink-3)" stroke={1.5} />
          <code style={{ flex: 1, minWidth: 0, fontFamily: "var(--f-mono)", fontSize: 11.5, color: "var(--ink-2)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{maskKey(token)}</code>
          <button className="press" onClick={onRegenerate} style={{ flex: "none", background: "none", border: "1px solid var(--rule-2)", borderRadius: 999, padding: "4px 11px", fontFamily: "var(--f-display)", fontSize: 10.5, fontWeight: 600, color: "var(--ink)", cursor: "pointer", whiteSpace: "nowrap" }}>Regenerate</button>
        </div>
        <div style={{ fontFamily: "var(--f-display)", fontSize: 10.5, color: "var(--ink-3)", marginTop: 8, lineHeight: 1.45 }}>Hidden for safety. Regenerate to issue a new key; the old one stops working.</div>
      </div>
    );

  // no key yet
  return (
    <div style={{ margin: "12px 20px 0", border: "1px dashed var(--rule-2)", padding: "14px 13px", textAlign: "center" }}>
      <div style={{ fontFamily: "var(--f-display)", fontSize: 12, color: "var(--ink-3)", lineHeight: 1.45, marginBottom: 11 }}>This connection authenticates with a scoped API key.</div>
      <Btn onClick={onCreate} style={{ padding: "9px 16px", fontSize: 12.5 }}>{ctaLabel || "Create key"} <Icon name="key" size={14} color="var(--accent-ink)" /></Btn>
    </div>
  );
};

/* Add a connection · choose how the agent talks to Yoshi. MCP and SDK are
   copy-only (they authorize elsewhere and land in Active connections later);
   API is the one type you actually create here, minting a named, scoped key. */
const AddConnectionView = ({ onCancel, onCreateApi }) => {
  const [via, setVia] = useState("MCP");
  const [name, setName] = useState("");
  const [token, setToken] = useState(null);
  const [fresh, setFresh] = useState(null);
  const named = name.trim().length > 0;
  const createKey = () => {
    if (!named || token) return;
    const t = genToken();
    setToken(t); setFresh(t);
    onCreateApi({ name: name.trim(), token: t });
  };

  return (
    <div className="push-enter" style={{ position: "absolute", inset: 0, zIndex: 340, background: "var(--bg)", display: "flex", flexDirection: "column" }} data-screen-label="Add connection">
      {window.StatusBar && <window.StatusBar />}
      <div style={{ flex: "none", padding: "6px 16px 10px", borderBottom: "1px solid var(--rule)", display: "flex", alignItems: "center", gap: 8, minHeight: 44 }}>
        <button className="press" onClick={onCancel} aria-label="Back" style={{ background: "none", border: "none", display: "flex", color: "var(--ink)", padding: 6, margin: "0 -6px" }}><Icon name="back" size={20} /></button>
        <span style={{ fontFamily: "var(--f-display)", fontSize: 17, fontWeight: 600, letterSpacing: "-0.02em", flex: 1 }}>Add connection</span>
      </div>

      <div className="scroll" style={{ overflowY: "auto", padding: "0 0 24px" }}>
        <div style={{ padding: "18px 20px 0" }}>
          <Eyebrow>Connect with</Eyebrow>
          <div style={{ marginTop: 8 }}>
            <Segmented options={METHOD_OPTIONS} value={via} onChange={(v) => setVia(v)} />
          </div>
          <div style={{ fontFamily: "var(--f-display)", fontSize: 12, color: "var(--ink-3)", marginTop: 9, lineHeight: 1.5 }}>{METHOD_HINT[via]}</div>
        </div>

        {via === "MCP" && <McpSetup shortcut />}
        {via === "SDK" && <SdkSetup />}
        {via === "API" &&
          <>
            <div style={{ padding: "18px 20px 0" }}>
              <Eyebrow>Name</Eyebrow>
              <input autoFocus value={name} disabled={!!token} onChange={(e) => setName(e.target.value)} placeholder="e.g. Raycast agent"
                style={{ width: "100%", marginTop: 8, padding: "12px 13px", background: "var(--bg-card)", border: "1px solid var(--rule-2)", outline: "none", color: token ? "var(--ink-3)" : "var(--ink)", fontFamily: "var(--f-display)", fontSize: 14.5 }} />
            </div>
            {!token ?
              <div style={{ padding: "14px 20px 0" }}>
                <Btn full disabled={!named} onClick={createKey}>Create API key <Icon name="key" size={14} color="var(--accent-ink)" /></Btn>
                {!named && <div style={{ fontFamily: "var(--f-display)", fontSize: 10.5, color: "var(--ink-3)", textAlign: "center", marginTop: 9 }}>Name the connection to create its key.</div>}
              </div> :
              <ApiKeyArea token={token} fresh={fresh} onCreate={createKey} onRegenerate={createKey} />}
            <ApiDocs />
            {token &&
              <div style={{ padding: "22px 20px 0" }}>
                <div style={{ fontFamily: "var(--f-display)", fontSize: 11.5, color: "var(--accent-pos)", textAlign: "center", marginBottom: 11 }}>Added to Active connections.</div>
                <Btn full kind="ghost" onClick={onCancel}>Done</Btn>
              </div>}
          </>}

        <div style={{ display: "flex", gap: 9, alignItems: "flex-start", margin: "22px 20px 0", padding: "10px 12px", background: "var(--bg-2)" }}>
          <Icon name="shield" size={16} color="var(--ink-3)" stroke={1.5} style={{ marginTop: 1 }} />
          <span style={{ fontFamily: "var(--f-display)", fontSize: 11, color: "var(--ink-3)", lineHeight: 1.45 }}>Propose-only access. An agent never holds or moves your money on its own. Revoke anytime.</span>
        </div>
      </div>
    </div>
  );
};

/* A connected agent, opened by its edit pencil: rename it, grab its MCP / CLI /
   API setup, manage its key, or revoke it. */
const ConnectionDetailView = ({ conn, onBack, onRename, onRevoke, onSetToken }) => {
  const [editing, setEditing] = useState(false);
  const [draft, setDraft] = useState(conn.name);
  const [fresh, setFresh] = useState(null);
  const [confirmRevoke, setConfirmRevoke] = useState(false);
  const mint = () => { const t = genToken(); onSetToken(conn.id, t); setFresh(t); };
  const saveName = () => { const n = draft.trim(); if (n) onRename(conn.id, n); setEditing(false); };

  return (
    <div className="push-enter" style={{ position: "absolute", inset: 0, zIndex: 340, background: "var(--bg)", display: "flex", flexDirection: "column" }} data-screen-label="Connection">
      {window.StatusBar && <window.StatusBar />}
      <div style={{ flex: "none", padding: "6px 16px 10px", borderBottom: "1px solid var(--rule)", display: "flex", alignItems: "center", gap: 8, minHeight: 44 }}>
        <button className="press" onClick={onBack} aria-label="Back" style={{ background: "none", border: "none", display: "flex", color: "var(--ink)", padding: 6, margin: "0 -6px" }}><Icon name="back" size={20} /></button>
        <span style={{ fontFamily: "var(--f-display)", fontSize: 17, fontWeight: 600, letterSpacing: "-0.02em", flex: 1 }}>Connection</span>
      </div>

      <div className="scroll" style={{ overflowY: "auto", padding: "0 0 24px" }}>
        {/* identity */}
        <div style={{ padding: "18px 20px 0", display: "flex", alignItems: "center", gap: 12 }}>
          <span style={{ width: 42, height: 42, flex: "none", border: "1px solid var(--rule-2)", borderRadius: 12, display: "grid", placeItems: "center", color: "var(--ink)" }}>
            <Icon name={iconForConn(conn)} size={21} stroke={1.5} />
          </span>
          <div style={{ minWidth: 0, flex: 1 }}>
            <div style={{ fontFamily: "var(--f-display)", fontSize: 17, fontWeight: 600, letterSpacing: "-0.02em", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{conn.name}</div>
            <span style={{ display: "inline-flex", alignItems: "center", gap: 5, fontFamily: "var(--f-display)", fontSize: 11, fontWeight: 600, color: "var(--accent-pos)", marginTop: 3 }}>
              <span style={{ width: 5, height: 5, borderRadius: 999, background: "var(--accent-pos)" }} /> {conn.via} · {conn.scope}
            </span>
          </div>
        </div>

        {/* alias · only available now that the connection exists */}
        <div style={{ padding: "18px 20px 0" }}>
          <Eyebrow>Alias</Eyebrow>
          {editing ?
            <div style={{ display: "flex", gap: 8, marginTop: 8 }}>
              <input autoFocus value={draft} onChange={(e) => setDraft(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") saveName(); }}
                style={{ flex: 1, minWidth: 0, padding: "10px 12px", background: "var(--bg-card)", border: "1px solid var(--accent)", outline: "none", color: "var(--ink)", fontFamily: "var(--f-display)", fontSize: 14 }} />
              <Btn onClick={saveName} style={{ padding: "0 15px", fontSize: 12.5 }}>Save</Btn>
            </div>
            :
            <div style={{ display: "flex", alignItems: "center", gap: 10, marginTop: 8, padding: "11px 13px", background: "var(--bg-card)", border: "1px solid var(--rule-2)" }}>
              <span style={{ flex: 1, minWidth: 0, fontFamily: "var(--f-display)", fontSize: 14, fontWeight: 600, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{conn.name}</span>
              <button className="press" onClick={() => { setDraft(conn.name); setEditing(true); }} style={{ display: "inline-flex", alignItems: "center", gap: 5, background: "none", border: "1px solid var(--rule-2)", borderRadius: 999, padding: "4px 11px", fontFamily: "var(--f-display)", fontSize: 10.5, fontWeight: 600, color: "var(--ink)", cursor: "pointer" }}>Rename <Icon name="pencil" size={12} color="var(--ink)" /></button>
            </div>}
        </div>

        {/* setup · scoped to how this connection actually talks to Yoshi */}
        <div style={{ padding: "20px 20px 0" }}>
          <Eyebrow>Setup</Eyebrow>
          <div style={{ fontFamily: "var(--f-display)", fontSize: 12, color: "var(--ink-3)", marginTop: 9, lineHeight: 1.5 }}>{METHOD_HINT[conn.via]}</div>
        </div>
        {conn.via === "MCP" && <McpSetup />}
        {conn.via === "API" &&
          <>
            <ApiKeyArea token={conn.token} fresh={fresh} onCreate={mint} onRegenerate={mint} ctaLabel="Create key" />
            <ApiDocs />
          </>}

        {/* revoke */}
        <div style={{ padding: "24px 20px 0" }}>
          {confirmRevoke ?
            <div style={{ border: "1px solid color-mix(in srgb, var(--signal-neg) 40%, transparent)", padding: "13px 14px" }}>
              <div style={{ fontFamily: "var(--f-display)", fontSize: 12.5, color: "var(--ink-2)", lineHeight: 1.45 }}>Revoke {conn.name}? It loses access immediately and any key stops working.</div>
              <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 9, marginTop: 12 }}>
                <Btn kind="ghost" onClick={() => setConfirmRevoke(false)}>Keep</Btn>
                <Btn kind="danger" onClick={() => onRevoke(conn.id)}>Revoke</Btn>
              </div>
            </div>
            :
            <Btn kind="danger" full onClick={() => setConfirmRevoke(true)}>Revoke connection</Btn>}
        </div>
      </div>
    </div>
  );
};

const ConnectAgentsSheetOld = ({ onClose, nav }) => {
  const [conns, setConns] = useState(AGENT_CONNECTIONS_SEED);
  const [view, setView] = useState(null);       // null | "add" | { detail: id }

  const connById = (id) => conns.find((c) => c.id === id);
  // API is the only type created in-app: minting its key registers it. MCP and
  // SDK connections aren't added here at all — they arrive once authorized.
  const addApi = ({ name, token }) => {
    const id = "c_" + Date.now();
    setConns((cs) => [...cs, { id, name, via: "API", scope: "Propose-only", last: "Active now", status: "active", custom: true, token }]);
  };
  const renameConn = (id, name) => setConns((cs) => cs.map((c) => c.id === id ? { ...c, name } : c));
  const setToken = (id, token) => setConns((cs) => cs.map((c) => c.id === id ? { ...c, token } : c));
  const revokeConn = (id) => { setConns((cs) => cs.filter((c) => c.id !== id)); setView(null); };

  const detailConn = view && view.detail ? connById(view.detail) : null;

  const ConnTile = ({ c }) => (
    <button className="press" onClick={() => setView({ detail: c.id })} style={{
      height: 104, position: "relative",
      display: "flex", flexDirection: "column", alignItems: "flex-start", justifyContent: "space-between", gap: 6, padding: "12px",
      background: "color-mix(in srgb, var(--accent-pos) 9%, var(--bg-card))",
      border: "1px solid color-mix(in srgb, var(--accent-pos) 50%, var(--rule-2))",
      borderRadius: 12, cursor: "pointer", textAlign: "left"
    }}>
      <span role="button" aria-label={`Edit ${c.name}`} title={`Edit ${c.name}`} onClick={(e) => { e.stopPropagation(); setView({ detail: c.id }); }}
        style={{ position: "absolute", top: 6, right: 6, width: 22, height: 22, borderRadius: 999, display: "grid", placeItems: "center", background: "var(--bg-2)", color: "var(--ink-2)", cursor: "pointer" }}>
        <Icon name="pencil" size={12} stroke={1.6} />
      </span>
      <span style={{ width: 30, height: 30, display: "grid", placeItems: "center", color: "var(--ink)", flex: "none" }}>
        <Icon name={iconForConn(c)} size={19} stroke={1.5} />
      </span>
      <div style={{ width: "100%", minWidth: 0 }}>
        <div style={{ fontFamily: "var(--f-display)", fontSize: 13, fontWeight: 600, letterSpacing: "-0.01em", lineHeight: 1.15, overflowWrap: "break-word" }}>{c.name}</div>
        <span style={{ display: "inline-flex", alignItems: "center", gap: 4, fontFamily: "var(--f-display)", fontSize: 10.5, fontWeight: 600, color: "var(--accent-pos)", marginTop: 2 }}>
          <span style={{ width: 5, height: 5, borderRadius: 999, background: "var(--accent-pos)" }} /> {c.via}
        </span>
      </div>
    </button>);

  return (
    <>
      <div className="push-enter" style={{ position: "absolute", inset: 0, zIndex: 320, background: "var(--bg)", display: "flex", flexDirection: "column" }} data-screen-label="Agents">
        {window.StatusBar && <window.StatusBar />}
        <div style={{ flex: "none", padding: "6px 16px 10px", borderBottom: "1px solid var(--rule)", display: "flex", alignItems: "center", gap: 10, minHeight: 44 }}>
          <YouButton nav={nav} />
          <span style={{ fontFamily: "var(--f-display)", fontSize: 17, fontWeight: 600, letterSpacing: "-0.02em", flex: 1 }}>Agents</span>
          <button className="press" onClick={onClose} style={{ background: "none", border: "none", display: "flex", color: "var(--ink-3)" }}><Icon name="close" size={20} /></button>
        </div>

        <div className="scroll" style={{ overflowY: "auto", padding: "0 0 24px" }}>
          <div style={{ fontFamily: "var(--f-display)", fontSize: 12.5, color: "var(--ink-2)", padding: "4px 20px 0", lineHeight: 1.5 }}>Connect Yoshi to your agents over MCP, an SDK, or an API key. Authorize Yoshi inside the agent and the connection turns up here, named by the service itself.</div>

          {/* Active connections — service-named MCP links + your own API keys, including
               anything authorized entirely outside the app. */}
          <div style={{ display: "flex", alignItems: "baseline", gap: 8, padding: "18px 20px 0" }}>
            <Eyebrow>Active connections</Eyebrow>
            <span style={{ fontFamily: "var(--f-mono)", fontSize: 12, fontWeight: 600, color: "var(--accent-pos)" }}>{conns.length}</span>
          </div>
          <div style={{ display: "grid", gridTemplateColumns: "repeat(3, minmax(0, 1fr))", gap: 9, padding: "12px 20px 4px" }}>
            {conns.map((c) => <ConnTile key={c.id} c={c} />)}

            {/* add tile */}
            <button className="press" onClick={() => setView("add")} style={{
              height: 104, display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: 8,
              background: "transparent", border: "1px dashed var(--rule-2)",
              borderRadius: 12, cursor: "pointer", color: "var(--ink-2)"
            }}>
              <Icon name="plus" size={22} stroke={1.5} color="var(--ink-2)" />
              <span style={{ fontFamily: "var(--f-display)", fontSize: 11.5, fontWeight: 600 }}>Add</span>
            </button>
          </div>

          {/* scoped-access note */}
          <div style={{ display: "flex", gap: 9, alignItems: "flex-start", margin: "20px 20px 0", padding: "10px 12px", background: "var(--bg-2)" }}>
            <Icon name="shield" size={16} color="var(--ink-3)" stroke={1.5} style={{ marginTop: 1 }} />
            <span style={{ fontFamily: "var(--f-display)", fontSize: 11, color: "var(--ink-3)", lineHeight: 1.45 }}>Propose-only access. An agent never holds or moves your money on its own. Revoke any connection anytime.</span>
          </div>
        </div>
      </div>

      {/* add / detail overlays */}
      {view === "add" && <AddConnectionView onCancel={() => setView(null)} onCreateApi={addApi} />}
      {detailConn && <ConnectionDetailView conn={detailConn} onBack={() => setView(null)} onRename={renameConn} onRevoke={revokeConn} onSetToken={setToken} />}
    </>);

};

/* ============================================================
   Agents · connect methods (MCP / SDKs / API key) + agent apps
   ============================================================ */
const useCopy = () => {
  const [copied, setCopied] = useState(false);
  const copy = (val) => { try { navigator.clipboard && navigator.clipboard.writeText(val); } catch (_) {} setCopied(true); setTimeout(() => setCopied(false), 1600); };
  return [copied, copy];
};

const YOSHI_METHODS = [
  { id: "mcp", name: "Yoshi MCP", icon: "connect", desc: "agents.yoshi.invalid/mcp", action: "copy", copy: MCP_CONFIG, cta: "Copy URL" },
  { id: "build", name: "Yoshi API brief", icon: "easel", desc: "Give an agent the API context", action: "copy", copy: BUILD_SETUP, cta: "Copy" },
  { id: "sdk", name: "Yoshi SDKs", icon: "terminal", desc: "pip · npm · Homebrew · Go", action: "sdk", cta: "View SDKs" },
];

const AGENT_APP_ICONS = {
  chatgpt: "assets/agent-chatgpt.svg",
  claude: "assets/agent-claude.svg",
  perplexity: "assets/agent-perplexity.svg",
  openclaw: "assets/agent-openclaw.svg",
};
const AGENT_APP_ICON_FILTER = "brightness(0) invert(1)";

const AGENT_APPS = [
  { id: "chatgpt", name: "ChatGPT", icon: "message", iconSrc: AGENT_APP_ICONS.chatgpt, iconFilter: AGENT_APP_ICON_FILTER, kind: "authorize", url: CHATGPT_APP_URL },
  { id: "claude", name: "Claude", icon: "robot", iconSrc: AGENT_APP_ICONS.claude, iconFilter: AGENT_APP_ICON_FILTER, kind: "connected" },
  { id: "perplexity", name: "Perplexity", icon: "search", iconSrc: AGENT_APP_ICONS.perplexity, iconFilter: AGENT_APP_ICON_FILTER, kind: "soon" },
  { id: "cursor", name: "Cursor", icon: "terminal", kind: "soon" },
  { id: "openclaw", name: "OpenClaw", icon: "bolt", iconSrc: AGENT_APP_ICONS.openclaw, iconFilter: AGENT_APP_ICON_FILTER, kind: "copy", copy: OPENCLAW_SETUP },
];

const TILE_ACTION_HEIGHT = window.__YOSHI_WEB ? 38 : 28;

const CopyButtonLabel = ({ children, copied = false, size = 12 }) => (
  <span style={{ display: "inline-flex", alignItems: "center", justifyContent: "center", gap: 5, minWidth: 0, maxWidth: "100%" }}>
    <Icon name={copied ? "check" : "copy"} size={size} stroke={1.5} />
    <span style={{ minWidth: 0, overflow: "hidden", textOverflow: "ellipsis" }}>{children}</span>
  </span>
);

const TileBtn = ({ children, onClick, href, tone = "default" }) => {
  const web = !!window.__YOSHI_WEB;
  const st = { display: "inline-flex", alignItems: "center", justifyContent: "center", gap: 6, width: "100%", height: TILE_ACTION_HEIGHT, minHeight: TILE_ACTION_HEIGHT, boxSizing: "border-box", padding: web ? "0 12px" : "0 8px", borderRadius: web ? 10 : 9, cursor: "pointer", fontFamily: "var(--f-display)", fontSize: web ? 13 : 11, fontWeight: 600, lineHeight: 1, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", border: "1px solid var(--rule-2)", background: "color-mix(in srgb, var(--ink) 4%, var(--bg-card))", color: "var(--ink)", textDecoration: "none" };
  if (tone === "accent") { st.background = "var(--accent)"; st.color = "var(--accent-ink)"; st.border = "none"; }
  if (tone === "pos") { st.color = "var(--accent-pos)"; st.borderColor = "color-mix(in srgb, var(--accent-pos) 45%, var(--rule-2))"; }
  if (tone === "danger") { st.color = "var(--signal-neg)"; st.borderColor = "color-mix(in srgb, var(--signal-neg) 40%, transparent)"; }
  const label = <span style={{ display: "inline-flex", alignItems: "center", justifyContent: "center", gap: 5, minWidth: 0, maxWidth: "100%", overflow: "hidden", textOverflow: "ellipsis" }}>{children}</span>;
  if (href) return <a className="press" href={href} target="_blank" rel="noopener noreferrer" style={st} onClick={(e) => { e.stopPropagation(); onClick && onClick(e); }}>{label}</a>;
  return <button className="press" onClick={(e) => { e.stopPropagation(); onClick && onClick(e); }} style={st}>{label}</button>;
};

const TileActionSlot = ({ children }) => (
  <div style={{ flex: "none", width: "100%", height: TILE_ACTION_HEIGHT, minHeight: TILE_ACTION_HEIGHT, display: "flex", alignItems: "stretch" }}>
    {children}
  </div>
);

const AppIcon = ({ src, alt = "", size, filter }) => (
  <img src={src} alt={alt} style={{ width: size, height: size, display: "block", objectFit: "contain", filter }} />
);

const ConnectTile = ({ icon, iconSrc, iconFilter, name, desc, primary, onClick, children }) => {
  const web = !!window.__YOSHI_WEB;
  return (
  <div onClick={onClick} role={onClick ? "button" : undefined} tabIndex={onClick ? 0 : undefined} onKeyDown={onClick ? (e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); onClick(e); } } : undefined} className={onClick ? "press" : undefined} style={{ position: "relative", display: "flex", flexDirection: "column", gap: web ? 10 : 6, padding: web ? 16 : 9, aspectRatio: "1 / 1", borderRadius: 14, overflow: "hidden", cursor: onClick ? "pointer" : "default", background: primary ? "color-mix(in srgb, var(--accent-pos) 9%, var(--bg-card))" : "var(--bg-card)", border: "1px solid " + (primary ? "color-mix(in srgb, var(--accent-pos) 45%, var(--rule-2))" : "var(--rule-2)") }}>
    {primary && <span style={{ position: "absolute", top: 0, left: 0, right: 0, height: 2, background: "var(--accent-pos)" }} />}
    <span style={{ width: web ? 30 : 24, height: web ? 30 : 24, flex: "none", display: "grid", placeItems: "center", color: "var(--ink)" }}>
      {iconSrc ? <AppIcon src={iconSrc} size={web ? 24 : 19} filter={iconFilter} /> : <Icon name={icon} size={web ? 24 : 19} stroke={1.5} />}
    </span>
    <div style={{ flex: 1, minWidth: 0 }}>
      <div style={{ fontFamily: "var(--f-display)", fontSize: web ? 16 : 12, fontWeight: 600, letterSpacing: 0, lineHeight: 1.15, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{name}</div>
    </div>
    <TileActionSlot>{children}</TileActionSlot>
  </div>
  );
};

const MethodTile = ({ m, onOpen }) => {
  const [copied, copy] = useCopy();
  const activate = () => {
    if (m.action === "copy") copy(m.copy);
    else onOpen(m.action);
  };
  const body = m.action === "copy"
    ? <TileBtn onClick={activate} tone={copied ? "pos" : "default"}>{copied ? <CopyButtonLabel copied>Copied</CopyButtonLabel> : <CopyButtonLabel>{m.cta}</CopyButtonLabel>}</TileBtn>
    : <TileBtn onClick={activate}>{m.cta}</TileBtn>;
  return <ConnectTile icon={m.icon} name={m.name} desc={m.desc} onClick={activate}>{body}</ConnectTile>;
};

const ConnectedStatus = ({ label = "Connected", mono = false }) => (
  <span style={{ display: "inline-flex", alignItems: "center", gap: 6, minHeight: window.__YOSHI_WEB ? 18 : 14, fontFamily: mono ? "var(--f-mono)" : "var(--f-display)", fontSize: window.__YOSHI_WEB ? 11 : 10, fontWeight: 600, letterSpacing: mono ? "0.08em" : 0, color: "var(--accent-pos)" }}>
    <span className={mono ? "yo-mk-pulse" : undefined} style={{ width: mono ? 7 : 6, height: mono ? 7 : 6, borderRadius: 999, background: "var(--accent-pos)" }} />{label}
  </span>
);

const AppTile = ({ app, onAuthorize, onEdit }) => {
  const [copied, copy] = useCopy();
  const [soon, setSoon] = useState(false);
  const authorize = () => onAuthorize && onAuthorize(app.id);
  const editApp = (event) => onEdit && onEdit(app.id, event);
  const openAuthorizedApp = () => {
    authorize();
    if (app.url) window.open(app.url, "_blank", "noopener,noreferrer");
  };
  const copySetup = () => copy(app.copy);
  const markSoon = () => { setSoon(true); setTimeout(() => setSoon(false), 1600); };
  const tileAction = app.connected
    ? editApp
    : app.kind === "connected"
      ? authorize
      : app.kind === "authorize"
        ? openAuthorizedApp
        : app.kind === "copy"
          ? copySetup
          : markSoon;
  let action;
  if (app.connected)
    action = <ConnectionMiniButton tone="danger" onClick={editApp}>Revoke</ConnectionMiniButton>;
  else if (app.kind === "connected")
    action = <TileBtn onClick={authorize}>Authorize</TileBtn>;
  else if (app.kind === "authorize")
    action = <TileBtn href={app.url} onClick={authorize}>Authorize</TileBtn>;
  else if (app.kind === "copy")
    action = <TileBtn onClick={copySetup} tone={copied ? "pos" : "default"}>{copied ? <CopyButtonLabel copied>Copied</CopyButtonLabel> : <CopyButtonLabel>Copy</CopyButtonLabel>}</TileBtn>;
  else
    action = <TileBtn onClick={markSoon}>{soon ? "Coming soon" : "Authorize"}</TileBtn>;
  return <ConnectTile icon={app.icon} iconSrc={app.iconSrc} iconFilter={app.iconFilter} name={app.name} primary={app.connected} onClick={tileAction}>{action}</ConnectTile>;
};

const SubHead = ({ title, onBack }) => (
  <div style={{ flex: "none", padding: "6px 16px 10px", borderBottom: "1px solid var(--rule)", display: "flex", alignItems: "center", gap: 8, minHeight: 44 }}>
    <button className="press" onClick={onBack} aria-label="Back" style={{ background: "none", border: "none", display: "flex", color: "var(--ink)", padding: 6, margin: "0 -6px" }}><Icon name="back" size={20} /></button>
    <span style={{ fontFamily: "var(--f-display)", fontSize: 17, fontWeight: 600, letterSpacing: "-0.02em", flex: 1 }}>{title}</span>
  </div>
);

const getActionAnchor = (event) => {
  const rect = event?.currentTarget?.getBoundingClientRect?.();
  if (!rect) return null;
  return { left: rect.left, right: rect.right, bottom: rect.bottom };
};

const getMenuStyle = (anchor) => {
  const menuWidth = 232;
  const margin = 14;
  if (!anchor || typeof window === "undefined") {
    return { top: 160, left: "50%", transform: "translateX(-50%)", width: menuWidth };
  }
  const left = Math.min(Math.max(margin, anchor.right - menuWidth), window.innerWidth - menuWidth - margin);
  const top = Math.min(anchor.bottom + 8, window.innerHeight - 180);
  return { top, left, width: menuWidth };
};

const ActionMenuRow = ({ icon, label, tone = "default", onClick, divided }) => {
  const danger = tone === "danger";
  return (
    <button role="menuitem" className="press" onClick={onClick} style={{ width: "100%", display: "flex", alignItems: "center", gap: 10, padding: "12px 13px", background: "none", border: "none", borderTop: divided ? "1px solid var(--rule)" : "none", color: danger ? "var(--signal-neg)" : "var(--ink)", fontFamily: "var(--f-display)", fontSize: 13.5, fontWeight: 600, textAlign: "left", cursor: "pointer" }}>
      <Icon name={icon} size={16} color={danger ? "var(--signal-neg)" : "var(--ink)"} stroke={1.5} />
      <span style={{ flex: 1 }}>{label}</span>
    </button>
  );
};

const DesktopActionMenu = ({ anchor, label, subject, icon, iconSrc, iconFilter, meta, onClose, children }) => ReactDOM.createPortal(
  <div data-screen-label={label}>
    <div onClick={onClose} style={{ position: "fixed", inset: 0, zIndex: 360 }} />
    <div role="menu" aria-label={label} className="push-enter" style={{ position: "fixed", zIndex: 361, ...getMenuStyle(anchor), background: "var(--bg-card)", border: "1px solid var(--rule-2)", borderRadius: 10, boxShadow: "0 18px 42px -22px rgba(0,0,0,0.65)", overflow: "hidden" }}>
      {subject &&
      <div style={{ display: "flex", alignItems: "center", gap: 10, padding: "12px 13px", borderBottom: "1px solid var(--rule)" }}>
        <span style={{ width: 30, height: 30, flex: "none", border: "1px solid var(--rule-2)", borderRadius: 8, display: "grid", placeItems: "center", color: "var(--ink)" }}>
          {iconSrc ? <AppIcon src={iconSrc} size={18} filter={iconFilter} /> : <Icon name={icon} size={17} stroke={1.5} />}
        </span>
        <div style={{ minWidth: 0, flex: 1 }}>
          <div style={{ fontFamily: "var(--f-display)", fontSize: 13.5, fontWeight: 600, letterSpacing: 0, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{subject}</div>
          {meta && <div style={{ fontFamily: "var(--f-mono)", fontSize: 10.5, color: "var(--ink-3)", marginTop: 2, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{meta}</div>}
        </div>
      </div>}
      {children}
    </div>
  </div>,
  document.body
);

const CenterDialog = ({ label, onClose, width = 390, children }) => (
  <div style={{ position: "fixed", inset: 0, zIndex: 360, display: "flex", alignItems: "center", justifyContent: "center", padding: 24 }} data-screen-label={label}>
    <div onClick={onClose} style={{ position: "absolute", inset: 0, background: "rgba(0,0,0,0.45)", backdropFilter: "blur(2px)", WebkitBackdropFilter: "blur(2px)" }} />
    <div role="dialog" aria-modal="true" aria-label={label} className="push-enter" style={{ position: "relative", width: `min(${width}px, calc(100vw - 48px))`, maxHeight: "min(760px, 88vh)", boxSizing: "border-box", background: "var(--bg)", border: "1px solid var(--rule-2)", borderRadius: 16, boxShadow: "0 30px 80px -28px rgba(0,0,0,0.75)", overflow: "hidden", display: "flex", flexDirection: "column" }}>
      {children}
    </div>
  </div>
);

const EditActionsMenu = ({ anchor, subject, icon, iconSrc, iconFilter, showRename = false, onClose, onRename, onRevoke }) => (
  <DesktopActionMenu anchor={anchor} label="Edit actions" subject={subject} icon={icon} iconSrc={iconSrc} iconFilter={iconFilter} onClose={onClose}>
    {showRename && <ActionMenuRow icon="pencil" label="Rename" onClick={onRename} />}
    <ActionMenuRow icon="close" label="Revoke" tone="danger" divided={showRename} onClick={onRevoke} />
  </DesktopActionMenu>
);

const InstallRow = ({ m }) => {
  const [copied, copy] = useCopy();
  return (
    <div style={{ margin: "12px 20px 0", border: "1px solid var(--rule-2)", background: "var(--bg-card)" }}>
      <div style={{ display: "flex", alignItems: "center", gap: 8, padding: "10px 12px 6px" }}>
        <Eyebrow style={{ flex: 1 }}>{m.label}</Eyebrow>
        <button className="press" onClick={() => copy(m.cmd)} style={{ flex: "none", background: copied ? "var(--accent-pos)" : "var(--bg-2)", color: copied ? "var(--bg)" : "var(--ink)", border: "none", fontFamily: "var(--f-display)", fontSize: 10, fontWeight: 600, letterSpacing: "0.06em", textTransform: "uppercase", padding: "5px 9px", cursor: "pointer", borderRadius: 8 }}>{copied ? <CopyButtonLabel copied size={11}>Copied</CopyButtonLabel> : <CopyButtonLabel size={11}>Copy</CopyButtonLabel>}</button>
      </div>
      <pre style={{ margin: 0, padding: "0 13px 13px", color: "var(--ink)", fontFamily: "var(--f-mono)", fontSize: 12, lineHeight: 1.6, whiteSpace: "pre-wrap", wordBreak: "break-all" }}>$ {m.cmd}</pre>
    </div>
  );
};

const SdkDialog = ({ onClose }) => (
  <CenterDialog label="Yoshi SDKs" onClose={onClose} width={520}>
      <div style={{ flex: "none", padding: "18px 20px 14px", display: "flex", alignItems: "center", gap: 12, borderBottom: "1px solid var(--rule)" }}>
        <div style={{ minWidth: 0, flex: 1 }}>
          <div style={{ fontFamily: "var(--f-display)", fontSize: 17, fontWeight: 600, letterSpacing: "-0.02em" }}>Yoshi SDKs</div>
          <div style={{ fontFamily: "var(--f-display)", fontSize: 12, color: "var(--ink-3)", marginTop: 3 }}>Install commands</div>
        </div>
        <button className="press" onClick={onClose} aria-label="Close" style={{ flex: "none", background: "none", border: "none", color: "var(--ink-3)", padding: 4, display: "flex" }}><Icon name="close" size={19} /></button>
      </div>
      <div className="scroll" style={{ overflowY: "auto", padding: "0 0 24px" }}>
        <div style={{ fontFamily: "var(--f-display)", fontSize: 12.5, color: "var(--ink-2)", padding: "2px 20px 0", lineHeight: 1.5 }}>Install the Yoshi SDK and CLI with your package manager. Each authenticates with a scoped API key.</div>
      {SDK_MANAGERS.map((m) => <InstallRow key={m.id} m={m} />)}
      <SetupNote>Create a scoped key under Yoshi API key, then drop it into your environment as YOSHI_API_KEY.</SetupNote>
      </div>
  </CenterDialog>
);

const ApiKeyView = ({ onBack }) => {
  const [token, setToken] = useState(null);
  const [fresh, setFresh] = useState(null);
  const mint = () => { const t = genToken(); setToken(t); setFresh(t); };
  return (
    <div className="push-enter" style={{ position: "absolute", inset: 0, zIndex: 340, background: "var(--bg)", display: "flex", flexDirection: "column" }} data-screen-label="Yoshi API key">
      {window.StatusBar && <window.StatusBar />}
      <SubHead title="Yoshi API key" onBack={onBack} />
      <div className="scroll" style={{ overflowY: "auto", padding: "0 0 24px" }}>
        <div style={{ fontFamily: "var(--f-display)", fontSize: 12.5, color: "var(--ink-2)", padding: "16px 20px 0", lineHeight: 1.5 }}>Mint a scoped, propose-only key for your own scripts and SDK calls. You'll see it in full once; store it somewhere safe.</div>
        <ApiKeyArea token={token} fresh={fresh} onCreate={mint} onRegenerate={mint} ctaLabel="Create API key" />
        <ApiDocs />
        <div style={{ display: "flex", gap: 9, alignItems: "flex-start", margin: "22px 20px 0", padding: "10px 12px", background: "var(--bg-2)" }}>
          <Icon name="shield" size={16} color="var(--ink-3)" stroke={1.5} style={{ marginTop: 1 }} />
          <span style={{ fontFamily: "var(--f-display)", fontSize: 11, color: "var(--ink-3)", lineHeight: 1.45 }}>Propose-only access. An agent never holds or moves your money on its own. Revoke anytime.</span>
        </div>
      </div>
    </div>
  );
};

/* the example live connection — created from a Copy setup and named by the
   user during the MCP handshake. */
const AGENT_CONNECTIONS_LIVE = [
  { id: "c1", name: "portfolio-copilot", sub: "Yoshi MCP · named at setup · connected May 28", live: true },
];

const ConnectionMiniButton = ({ children, onClick, tone = "default" }) => <TileBtn onClick={onClick} tone={tone}>{children}</TileBtn>;

/* a live connection renders as a tile alongside the others — the user named
   it during the MCP handshake after tapping Copy setup */
const ConnectionTile = ({ c, onEdit }) => (
  <ConnectTile icon="connect" name={c.name} primary onClick={(event) => onEdit && onEdit(c.id, event)}>
    <ConnectionMiniButton onClick={(event) => onEdit && onEdit(c.id, event)}>Edit</ConnectionMiniButton>
  </ConnectTile>
);

const ConnectionRenameView = ({ conn, onBack, onSave }) => {
  const [draft, setDraft] = useState(conn.name);
  const nextName = draft.trim();
  const save = () => { if (!nextName) return; onSave(conn.id, nextName); };
  return (
    <CenterDialog label="Rename connection" onClose={onBack}>
        <div style={{ padding: "18px 20px 20px" }}>
          <div style={{ fontFamily: "var(--f-display)", fontSize: 16, fontWeight: 600, letterSpacing: "-0.015em", marginBottom: 16 }}>Edit nickname</div>
          <div style={{ fontFamily: "var(--f-display)", fontSize: 11, fontWeight: 600, color: "var(--ink-2)", marginBottom: 6 }}>Agent nickname</div>
          <input autoFocus value={draft} onChange={(e) => setDraft(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") save(); }}
            style={{ width: "100%", padding: "12px 13px", background: "var(--bg-2)", border: "1px solid var(--accent)", borderRadius: 10, outline: "none", fontFamily: "var(--f-display)", fontSize: 15, fontWeight: 600, color: "var(--ink)" }} />
          <button className="press" onClick={save} style={{ width: "100%", marginTop: 14, padding: "12px", background: "var(--accent)", border: "none", borderRadius: 10, fontFamily: "var(--f-display)", fontSize: 14, fontWeight: 600, cursor: "pointer", color: "var(--accent-ink)" }}>Save</button>
        </div>
    </CenterDialog>
  );
};

const RevokeConnectionConfirmDialog = ({ subject, onBack, onApprove }) => (
  <CenterDialog label="Revoke connection" onClose={onBack}>
      <div style={{ padding: "18px 20px 20px" }}>
        <div style={{ fontFamily: "var(--f-display)", fontSize: 16, fontWeight: 600, letterSpacing: "-0.015em", marginBottom: 8 }}>Revoke connection?</div>
        <p style={{ fontFamily: "var(--f-display)", fontSize: 13.5, lineHeight: 1.5, color: "var(--ink-2)", margin: 0 }}>Are you sure you want to revoke the connection to {subject}?</p>
        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 9, marginTop: 16 }}>
          <button className="press" onClick={onBack} style={{ width: "100%", padding: "12px", background: "transparent", border: "1px solid color-mix(in srgb, var(--signal-neg) 40%, transparent)", borderRadius: 10, fontFamily: "var(--f-display)", fontSize: 14, fontWeight: 600, cursor: "pointer", color: "var(--signal-neg)" }}>Decline</button>
          <button className="press" onClick={onApprove} style={{ width: "100%", padding: "12px", background: "var(--accent)", border: "none", borderRadius: 10, fontFamily: "var(--f-display)", fontSize: 14, fontWeight: 600, cursor: "pointer", color: "var(--accent-ink)" }}>Approve</button>
        </div>
      </div>
  </CenterDialog>
);

const ConnectionRevokeView = ({ conn, onBack, onConfirm }) => {
  const [gate, setGate] = useState(false);
  return (
    <>
      <RevokeConnectionConfirmDialog subject={conn.name} onBack={onBack} onApprove={() => setGate(true)} />
      {gate && <PasskeyGate title={`Revoke ${conn.name}`} detail={`${conn.name} loses access immediately and will be removed from Agents.`} cta="Confirm with passkey" onSuccess={() => { setGate(false); onConfirm(conn.id); }} onCancel={() => setGate(false)} />}
    </>
  );
};

const AgentConnectionsSection = () => (
  <>
    <div style={{ padding: "20px 20px 0" }}><Eyebrow>Agent connections</Eyebrow></div>
    <div style={{ margin: "10px 20px 0", border: "1px solid var(--rule-2)", borderRadius: 12, background: "var(--bg-card)", overflow: "hidden" }}>
      {AGENT_CONNECTIONS_LIVE.map((c, i) => (
        <div key={c.id} style={{ display: "flex", alignItems: "center", gap: 12, padding: "13px 14px", borderTop: i ? "1px solid var(--rule)" : "none" }}>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ fontFamily: "var(--f-mono)", fontSize: 13, fontWeight: 600, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{c.name}</div>
            <div style={{ fontFamily: "var(--f-display)", fontSize: 11, color: "var(--ink-3)", marginTop: 3 }}>{c.sub}</div>
          </div>
          <span style={{ flex: "none", display: "inline-flex", alignItems: "center", gap: 6, fontFamily: "var(--f-mono)", fontSize: 10, fontWeight: 600, letterSpacing: "0.08em", color: "var(--accent-pos)" }}>
            <span className="yo-mk-pulse" style={{ width: 7, height: 7, borderRadius: 999, background: "var(--accent-pos)", opacity: 1 }} />LIVE
          </span>
        </div>
      ))}
    </div>
    <SetupNote>A connection lands here after the agent completes the handshake from a copied setup. You name it during setup.</SetupNote>
  </>
);

/* API keys — a flat registry at the bottom of the page. Generate, name, copy
   once, then rename or revoke any time. */
const API_KEYS_SEED = [
  { id: "k1", name: "Local scripts", token: "ys_demo_8c2e96b1d4f07a35ce12", created: "May 12" },
];
const maskToken = (t) => "ys_demo_••••••••" + String(t).slice(-4);

const ApiKeyActionsMenu = ({ apiKey, anchor, onClose, onRename, onRevoke }) => (
  <DesktopActionMenu anchor={anchor} label="API key actions" subject={apiKey.name} icon="key" meta={maskToken(apiKey.token)} onClose={onClose}>
    <ActionMenuRow icon="pencil" label="Rename" onClick={onRename} />
    <ActionMenuRow icon="close" label="Revoke" tone="danger" divided onClick={onRevoke} />
  </DesktopActionMenu>
);

const ApiKeyRenameDialog = ({ draft, onDraft, onBack, onSave }) => (
  <CenterDialog label="Rename API key" onClose={onBack}>
      <div style={{ padding: "18px 20px 20px" }}>
        <div style={{ fontFamily: "var(--f-display)", fontSize: 16, fontWeight: 600, letterSpacing: "-0.015em", marginBottom: 16 }}>Edit nickname</div>
        <div style={{ fontFamily: "var(--f-display)", fontSize: 11, fontWeight: 600, color: "var(--ink-2)", marginBottom: 6 }}>API key nickname</div>
        <input autoFocus value={draft} onChange={(e) => onDraft(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") onSave(); }}
          style={{ width: "100%", padding: "12px 13px", background: "var(--bg-2)", border: "1px solid var(--accent)", borderRadius: 10, outline: "none", fontFamily: "var(--f-display)", fontSize: 15, fontWeight: 600, color: "var(--ink)" }} />
        <button className="press" onClick={onSave} style={{ width: "100%", marginTop: 14, padding: "12px", background: "var(--accent)", border: "none", borderRadius: 10, fontFamily: "var(--f-display)", fontSize: 14, fontWeight: 600, cursor: "pointer", color: "var(--accent-ink)" }}>Save</button>
      </div>
  </CenterDialog>
);

const ApiKeyRevokeDialog = ({ apiKey, onBack, onApprove }) => (
  <CenterDialog label="Revoke API key" onClose={onBack}>
      <div style={{ padding: "18px 20px 20px" }}>
        <div style={{ fontFamily: "var(--f-display)", fontSize: 16, fontWeight: 600, letterSpacing: "-0.015em", marginBottom: 8 }}>Revoke API key?</div>
        <p style={{ fontFamily: "var(--f-display)", fontSize: 13.5, lineHeight: 1.5, color: "var(--ink-2)", margin: 0 }}>Are you sure you want to revoke this API key?</p>
        <p style={{ fontFamily: "var(--f-display)", fontSize: 12, lineHeight: 1.45, color: "var(--ink-3)", margin: "6px 0 0" }}>{apiKey.name} loses access immediately.</p>
        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 9, marginTop: 16 }}>
          <button className="press" onClick={onBack} style={{ width: "100%", padding: "12px", background: "transparent", border: "1px solid color-mix(in srgb, var(--signal-neg) 40%, transparent)", borderRadius: 10, fontFamily: "var(--f-display)", fontSize: 14, fontWeight: 600, cursor: "pointer", color: "var(--signal-neg)" }}>Decline</button>
          <button className="press" onClick={onApprove} style={{ width: "100%", padding: "12px", background: "var(--accent)", border: "none", borderRadius: 10, fontFamily: "var(--f-display)", fontSize: 14, fontWeight: 600, cursor: "pointer", color: "var(--accent-ink)" }}>Approve</button>
        </div>
      </div>
  </CenterDialog>
);

const ApiKeysSection = () => {
  const [keys, setKeys] = useState(API_KEYS_SEED);
  const [creating, setCreating] = useState(false);
  const [newName, setNewName] = useState("");
  const [freshId, setFreshId] = useState(null);
  const [renamingId, setRenamingId] = useState(null);
  const [renameVal, setRenameVal] = useState("");
  const [managedKeyId, setManagedKeyId] = useState(null);
  const [managedKeyAnchor, setManagedKeyAnchor] = useState(null);
  const [revokingKeyId, setRevokingKeyId] = useState(null);
  const [copiedId, setCopiedId] = useState(null);
  const keyTarget = managedKeyId && keys.find((k) => k.id === managedKeyId);
  const renamingKey = renamingId && keys.find((k) => k.id === renamingId);
  const revokingKey = revokingKeyId && keys.find((k) => k.id === revokingKeyId);
  const copy = (id, val) => { try { navigator.clipboard && navigator.clipboard.writeText(val); } catch (_) {} setCopiedId(id); setTimeout(() => setCopiedId(null), 1600); };
  const create = () => {
    const nm = newName.trim(); if (!nm) return;
    const id = "k" + Date.now();
    setKeys((ks) => [...ks, { id, name: nm, token: genToken(), created: "Just now" }]);
    setFreshId(id); setCreating(false); setNewName("");
  };
  const saveRename = () => {
    const nm = renameVal.trim();
    if (nm) setKeys((ks) => ks.map((k) => k.id === renamingId ? { ...k, name: nm } : k));
    setRenamingId(null); setRenameVal("");
  };
  const revokeKey = (id) => {
    setKeys((ks) => ks.filter((x) => x.id !== id));
    setRevokingKeyId(null); setManagedKeyId(null); setManagedKeyAnchor(null);
  };
  const closeKeyActions = () => { setManagedKeyId(null); setManagedKeyAnchor(null); };
  const keyEditBtn = { flex: "none", display: "inline-flex", alignItems: "center", justifyContent: "center", minWidth: 66, height: TILE_ACTION_HEIGHT, boxSizing: "border-box", padding: "0 12px", background: "color-mix(in srgb, var(--ink) 4%, var(--bg-card))", border: "1px solid var(--rule-2)", borderRadius: 9, cursor: "pointer", fontFamily: "var(--f-display)", fontSize: 11, fontWeight: 600, color: "var(--ink)" };
  return (
    <>
      <div style={{ padding: "20px 20px 0", display: "flex", alignItems: "center" }}>
        <Eyebrow style={{ flex: 1 }}>API keys</Eyebrow>
        {!creating &&
        <button className="press" onClick={() => { setCreating(true); setFreshId(null); }} style={{ background: "var(--accent)", color: "var(--accent-ink)", border: "none", borderRadius: 8, padding: "6px 11px", fontFamily: "var(--f-display)", fontSize: 11.5, fontWeight: 600, cursor: "pointer" }}>Generate key</button>}
      </div>
      <div style={{ margin: "10px 20px 0", border: "1px solid var(--rule-2)", borderRadius: 12, background: "var(--bg-card)", overflow: "hidden" }}>
        {creating &&
        <div style={{ padding: "12px 14px", borderBottom: keys.length ? "1px solid var(--rule)" : "none" }}>
          <div style={{ fontFamily: "var(--f-display)", fontSize: 11, fontWeight: 600, color: "var(--ink-2)", marginBottom: 6 }}>Name this key</div>
          <div style={{ display: "flex", gap: 8 }}>
            <input autoFocus value={newName} onChange={(e) => setNewName(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") create(); }} placeholder="e.g. Trading bot"
              style={{ flex: 1, minWidth: 0, padding: "9px 11px", background: "var(--bg-2)", border: "1px solid var(--rule-2)", borderRadius: 9, outline: "none", color: "var(--ink)", fontFamily: "var(--f-display)", fontSize: 13 }} />
            <button className="press" onClick={create} disabled={!newName.trim()} style={{ flex: "none", background: newName.trim() ? "var(--accent)" : "var(--rule-2)", color: newName.trim() ? "var(--accent-ink)" : "var(--ink-3)", border: "none", borderRadius: 9, padding: "0 14px", fontFamily: "var(--f-display)", fontSize: 12, fontWeight: 600, cursor: "pointer" }}>Generate</button>
            <button className="press" onClick={() => { setCreating(false); setNewName(""); }} style={{ flex: "none", background: "none", border: "none", color: "var(--ink-3)", fontFamily: "var(--f-display)", fontSize: 12, cursor: "pointer" }}>Cancel</button>
          </div>
        </div>}
        {keys.length === 0 && !creating &&
        <div style={{ padding: "18px 14px", textAlign: "center", fontFamily: "var(--f-display)", fontSize: 12, color: "var(--ink-3)" }}>No keys yet. Generate one to call the API directly.</div>}
        {keys.map((k, i) => {
	          const canEditKey = freshId !== k.id;
	          const openKeyActions = (event) => { setManagedKeyId(k.id); setManagedKeyAnchor(getActionAnchor(event)); };
	          return (
	            <div key={k.id} onClick={canEditKey ? openKeyActions : undefined} role={canEditKey ? "button" : undefined} tabIndex={canEditKey ? 0 : undefined} onKeyDown={canEditKey ? (e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); openKeyActions(e); } } : undefined} className={canEditKey ? "press" : undefined} style={{ padding: "12px 14px", borderTop: i || creating ? "1px solid var(--rule)" : "none", cursor: canEditKey ? "pointer" : "default" }}>
	              <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
	                <div style={{ flex: 1, minWidth: 0, fontFamily: "var(--f-display)", fontSize: 13.5, fontWeight: 600, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{k.name}</div>
	                {canEditKey && <button className="press" onClick={(e) => { e.stopPropagation(); openKeyActions(e); }} style={keyEditBtn}>Edit</button>}
	              </div>
	              {freshId === k.id ? (
	                <div style={{ marginTop: 8, padding: "9px 11px", background: "var(--bg-2)", border: "1px solid var(--accent)", borderRadius: 9 }}>
	                  <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
	                    <code style={{ flex: 1, minWidth: 0, fontFamily: "var(--f-mono)", fontSize: 11.5, color: "var(--ink)", wordBreak: "break-all" }}>{k.token}</code>
	                    <button className="press" onClick={() => copy(k.id, k.token)} style={{ flex: "none", background: copiedId === k.id ? "var(--accent-pos)" : "var(--accent)", color: "var(--accent-ink)", border: "none", borderRadius: 8, padding: "5px 10px", fontFamily: "var(--f-display)", fontSize: 11, fontWeight: 600, cursor: "pointer" }}>{copiedId === k.id ? <CopyButtonLabel copied>Copied</CopyButtonLabel> : <CopyButtonLabel>Copy</CopyButtonLabel>}</button>
	                  </div>
	                  <div style={{ display: "flex", alignItems: "center", gap: 8, marginTop: 7 }}>
	                    <div style={{ flex: 1, minWidth: 0, fontFamily: "var(--f-display)", fontSize: 10.5, color: "var(--ink-3)", lineHeight: 1.35 }}>Copy it now. For your security, you won't see the full key again.</div>
	                    <button className="press" onClick={() => setFreshId(null)} style={{ flex: "none", background: "transparent", border: "1px solid var(--rule-2)", borderRadius: 8, padding: "5px 10px", fontFamily: "var(--f-display)", fontSize: 11, fontWeight: 600, cursor: "pointer", color: "var(--ink)" }}>Done</button>
	                  </div>
	                </div>
	              ) : (
	                <div style={{ fontFamily: "var(--f-mono)", fontSize: 11, color: "var(--ink-3)", marginTop: 4 }}>{maskToken(k.token)} · Created {k.created}</div>
	              )}
	            </div>
	          );
	        })}
      </div>
      {managedKeyId && keyTarget && <ApiKeyActionsMenu apiKey={keyTarget} anchor={managedKeyAnchor} onClose={closeKeyActions} onRename={() => { setRenamingId(keyTarget.id); setRenameVal(keyTarget.name); closeKeyActions(); }} onRevoke={() => { setRevokingKeyId(keyTarget.id); closeKeyActions(); }} />}
      {renamingId && renamingKey && <ApiKeyRenameDialog apiKey={renamingKey} draft={renameVal} onDraft={setRenameVal} onBack={() => { setRenamingId(null); setRenameVal(""); }} onSave={saveRename} />}
      {revokingKeyId && revokingKey && <ApiKeyRevokeDialog apiKey={revokingKey} onBack={() => setRevokingKeyId(null)} onApprove={() => revokeKey(revokingKey.id)} />}
      <SetupNote>Keys are scoped and propose-only. An agent calling with your key can draft moves; only you can approve them.</SetupNote>
    </>
  );
};

const ConnectAgentsSheet = ({ onClose, nav }) => {
  const [view, setView] = useState(null);
  const [apps, setApps] = useState(() => AGENT_APPS.map((app) => ({ ...app, connected: app.kind === "connected" })));
  const [managedAppId, setManagedAppId] = useState(null);
  const [managedAppAnchor, setManagedAppAnchor] = useState(null);
  const [revokingAppId, setRevokingAppId] = useState(null);
  const [connections, setConnections] = useState(AGENT_CONNECTIONS_LIVE);
  const [managedConnection, setManagedConnection] = useState(null);
  const appTarget = managedAppId && apps.find((app) => app.id === managedAppId);
  const revokingApp = revokingAppId && apps.find((app) => app.id === revokingAppId);
  const connectionTarget = managedConnection && connections.find((c) => c.id === managedConnection.id);
  const authorizeApp = (id) => {
    setApps((as) => as.map((app) => app.id === id ? { ...app, connected: true } : app));
  };
  const revokeApp = (id) => {
    setApps((as) => as.map((app) => app.id === id ? { ...app, connected: false } : app));
    setManagedAppId(null);
    setManagedAppAnchor(null);
    setRevokingAppId(null);
  };
  const renameConnection = (id, name) => {
    setConnections((cs) => cs.map((c) => c.id === id ? { ...c, name } : c));
    setManagedConnection(null);
  };
  const revokeConnection = (id) => {
    setConnections((cs) => cs.filter((x) => x.id !== id));
    setManagedConnection(null);
  };
  const openAppActions = (id, event) => {
    setManagedAppId(id);
    setManagedAppAnchor(getActionAnchor(event));
  };
  const closeAppActions = () => {
    setManagedAppId(null);
    setManagedAppAnchor(null);
  };
  return (
    <>
      <div className="push-enter" style={{ position: "absolute", inset: 0, zIndex: 320, background: "var(--bg)", display: "flex", flexDirection: "column" }} data-screen-label="Agents">
        {window.StatusBar && <window.StatusBar />}
        <div style={{ flex: "none", padding: window.__YOSHI_WEB ? "26px 20px 6px" : "6px 16px 10px", borderBottom: window.__YOSHI_WEB ? "none" : "1px solid var(--rule)", display: "flex", alignItems: "center", gap: 10, minHeight: 44 }}>
          {!window.__YOSHI_WEB && <YouButton nav={nav} />}
          <span style={{ fontFamily: "var(--f-display)", fontSize: window.__YOSHI_WEB ? 21 : 17, fontWeight: window.__YOSHI_WEB ? 700 : 600, letterSpacing: window.__YOSHI_WEB ? "-0.025em" : "-0.02em", flex: 1 }}>Agents</span>
          {!window.__YOSHI_WEB && <button className="press" onClick={onClose} style={{ background: "none", border: "none", display: "flex", color: "var(--ink-3)" }}><Icon name="close" size={20} /></button>}
        </div>

        <div className="scroll" style={{ overflowY: "auto", padding: "0 0 24px" }}>
          <div style={{ fontFamily: "var(--f-display)", fontSize: 12.5, color: "var(--ink-2)", padding: "4px 20px 0", lineHeight: 1.5 }}>Connect Yoshi to your agents. Set it up yourself over MCP, an SDK, or an API key, or authorize a supported app.</div>

          <div style={{ display: "grid", gridTemplateColumns: "repeat(3, minmax(0, 1fr))", gap: 9, padding: "16px 20px 4px" }}>
            {YOSHI_METHODS.map((m) => <MethodTile key={m.id} m={m} onOpen={setView} />)}
            {apps.map((a) => <AppTile key={a.id} app={a} onAuthorize={authorizeApp} onEdit={openAppActions} />)}
            {connections.map((c) => <ConnectionTile key={c.id} c={c} onEdit={(id, event) => setManagedConnection({ type: "actions", id, anchor: getActionAnchor(event) })} />)}
          </div>

          <ApiKeysSection />

          <div style={{ display: "flex", gap: 9, alignItems: "flex-start", margin: "20px 20px 0", padding: "10px 12px", background: "var(--bg-2)" }}>
            <Icon name="shield" size={16} color="var(--ink-3)" stroke={1.5} style={{ marginTop: 1 }} />
            <span style={{ fontFamily: "var(--f-display)", fontSize: 11, color: "var(--ink-3)", lineHeight: 1.45 }}>Propose-only access. An agent never holds or moves your money on its own. Revoke any connection anytime.</span>
          </div>
        </div>
      </div>

      {view === "sdk" && <SdkDialog onClose={() => setView(null)} />}
      {view === "api" && <ApiKeyView onBack={() => setView(null)} />}
      {managedAppId && appTarget && <EditActionsMenu anchor={managedAppAnchor} subject={appTarget.name} icon={appTarget.icon} iconSrc={appTarget.iconSrc} iconFilter={appTarget.iconFilter} onClose={closeAppActions} onRevoke={() => { closeAppActions(); setRevokingAppId(appTarget.id); }} />}
      {revokingAppId && revokingApp && <RevokeConnectionConfirmDialog subject={revokingApp.name} onBack={() => setRevokingAppId(null)} onApprove={() => revokeApp(revokingApp.id)} />}
      {managedConnection?.type === "actions" && connectionTarget && <EditActionsMenu anchor={managedConnection.anchor} subject={connectionTarget.name} icon="connect" showRename onClose={() => setManagedConnection(null)} onRename={() => setManagedConnection({ type: "rename", id: connectionTarget.id })} onRevoke={() => setManagedConnection({ type: "revoke", id: connectionTarget.id })} />}
      {managedConnection?.type === "rename" && connectionTarget && <ConnectionRenameView conn={connectionTarget} onBack={() => setManagedConnection(null)} onSave={renameConnection} />}
      {managedConnection?.type === "revoke" && connectionTarget && <ConnectionRevokeView conn={connectionTarget} onBack={() => setManagedConnection(null)} onConfirm={revokeConnection} />}
    </>);
};

Object.assign(window, { ConnectAgentsSheet });
