{"$schema":"https:\/\/ui.particle.academy\/schema\/registry-item.json","name":"share-controls","type":"registry:ui","title":"ShareControls","description":"Start\/stop relay sessions.","package":"agent-integrations","dependencies":[],"registryDependencies":["sharing"],"files":[{"path":"components\/fancy\/share-controls\/ShareControls.tsx","content":"import { type CSSProperties, useState } from \"react\";\r\nimport type { SessionDescriptor } from \"..\/..\/sharing\/token\";\r\nimport { buildShareConfig, buildShareUrl } from \"..\/..\/sharing\/token\";\r\n\r\nexport type ShareControlsProps = {\r\n  \/** The active session, or null when not sharing yet. *\/\r\n  session: SessionDescriptor | null;\r\n  onStart: () => void;\r\n  onStop: () => void;\r\n  \/** Optional connection-state badge text. *\/\r\n  status?: string;\r\n  \/** Override the URL base used in the share URL. *\/\r\n  shareBaseUrl?: string;\r\n  className?: string;\r\n  style?: CSSProperties;\r\n};\r\n\r\ntype Tab = \"prompt\" | \"url\" | \"json\" | \"curl\";\r\n\r\n\/**\r\n * ShareControls \u2014 the host-facing UI for turning sharing on\/off and\r\n * surfacing the resulting connection details (URL \/ JSON \/ cURL).\r\n *\/\r\nexport function ShareControls({\r\n  session,\r\n  onStart,\r\n  onStop,\r\n  status,\r\n  shareBaseUrl,\r\n  className,\r\n  style,\r\n}: ShareControlsProps) {\r\n  const [tab, setTab] = useState<Tab>(\"prompt\");\r\n\r\n  if (!session) {\r\n    return (\r\n      <div className={[\"fai-share fai-share--idle\", className ?? \"\"].filter(Boolean).join(\" \")} style={style}>\r\n        <button type=\"button\" className=\"fai-share__start\" onClick={onStart}>\r\n          Start shared session\r\n        <\/button>\r\n        <p className=\"fai-share__hint\">\r\n          Generates a session id + secret token. Hand the <strong>Agent prompt<\/strong> to an AI agent, share the URL with humans, or give the JSON config to an MCP-capable client.\r\n        <\/p>\r\n      <\/div>\r\n    );\r\n  }\r\n\r\n  const url = buildShareUrl(session, shareBaseUrl);\r\n  const config = buildShareConfig(session);\r\n  const curl = buildCurlRecipe(session);\r\n  const prompt = buildAgentPrompt(url);\r\n\r\n  return (\r\n    <div className={[\"fai-share fai-share--active\", className ?? \"\"].filter(Boolean).join(\" \")} style={style}>\r\n      <div className=\"fai-share__header\">\r\n        <div>\r\n          <strong>Sharing<\/strong>\r\n          <span className=\"fai-share__id\">\r\n            session <code>{session.id}<\/code> \u00b7 token <code>{session.display}\u2026<\/code>\r\n          <\/span>\r\n        <\/div>\r\n        <div className=\"fai-share__header-actions\">\r\n          {status && <span className=\"fai-share__status\">{status}<\/span>}\r\n          <button type=\"button\" className=\"fai-share__stop\" onClick={onStop}>\r\n            Stop\r\n          <\/button>\r\n        <\/div>\r\n      <\/div>\r\n\r\n      <div className=\"fai-share__tabs\" role=\"tablist\">\r\n        <TabButton tab=\"prompt\" active={tab} setTab={setTab}>Agent prompt<\/TabButton>\r\n        <TabButton tab=\"url\" active={tab} setTab={setTab}>URL<\/TabButton>\r\n        <TabButton tab=\"json\" active={tab} setTab={setTab}>JSON<\/TabButton>\r\n        <TabButton tab=\"curl\" active={tab} setTab={setTab}>cURL recipe<\/TabButton>\r\n      <\/div>\r\n\r\n      <div className=\"fai-share__panel\">\r\n        {tab === \"prompt\" && (\r\n          <CopyBox\r\n            label=\"Paste this straight into an AI agent \u2014 it connects over MCP, no browser\"\r\n            value={prompt}\r\n            multiline\r\n          \/>\r\n        )}\r\n        {tab === \"url\" && <CopyBox label=\"Open this URL in another tab to join the session\" value={url} \/>}\r\n        {tab === \"json\" && (\r\n          <CopyBox\r\n            label=\"Paste into Claude Desktop \/ Cline MCP server config\"\r\n            value={JSON.stringify(config, null, 2)}\r\n          \/>\r\n        )}\r\n        {tab === \"curl\" && (\r\n          <CopyBox\r\n            label=\"Connect from a terminal (verifies the relay is reachable)\"\r\n            value={curl}\r\n            multiline\r\n          \/>\r\n        )}\r\n      <\/div>\r\n    <\/div>\r\n  );\r\n}\r\n\r\nfunction TabButton({ tab, active, setTab, children }: { tab: Tab; active: Tab; setTab: (t: Tab) => void; children: React.ReactNode }) {\r\n  return (\r\n    <button\r\n      type=\"button\"\r\n      role=\"tab\"\r\n      aria-selected={tab === active}\r\n      className={`fai-share__tab${tab === active ? \" is-active\" : \"\"}`}\r\n      onClick={() => setTab(tab)}\r\n    >\r\n      {children}\r\n    <\/button>\r\n  );\r\n}\r\n\r\nfunction CopyBox({ label, value, multiline }: { label: string; value: string; multiline?: boolean }) {\r\n  const [copied, setCopied] = useState(false);\r\n  const copy = async () => {\r\n    try {\r\n      await navigator.clipboard.writeText(value);\r\n      setCopied(true);\r\n      setTimeout(() => setCopied(false), 1200);\r\n    } catch {\r\n      \/\/ ignore\r\n    }\r\n  };\r\n  return (\r\n    <div>\r\n      <div className=\"fai-share__panel-label\">{label}<\/div>\r\n      <div className=\"fai-share__copy\">\r\n        <pre className={`fai-share__pre${multiline ? \" is-multi\" : \"\"}`}>{value}<\/pre>\r\n        <button type=\"button\" className=\"fai-share__copy-btn\" onClick={copy}>\r\n          {copied ? \"Copied\" : \"Copy\"}\r\n        <\/button>\r\n      <\/div>\r\n    <\/div>\r\n  );\r\n}\r\n\r\n\/**\r\n * Build a copy-paste natural-language prompt to hand directly to an AI agent.\r\n * Agents handed a bare URL tend to try to *open it in a browser* \u2014 this spells\r\n * out that the link is a live MCP co-browsing session it should drive over MCP\r\n * tools (via the zero-install relay client), never visit.\r\n *\/\r\nexport function buildAgentPrompt(url: string): string {\r\n  return [\r\n    `You're joining a live Human+ co-browsing session on a web app. This is an MCP`,\r\n    `(Model Context Protocol) session \u2014 NOT a website to visit. Do NOT open the URL`,\r\n    `in a browser and do NOT use any browser\/Playwright tool. You drive the real,`,\r\n    `running app through MCP tools while a human watches and can take over.`,\r\n    ``,\r\n    `Session link (carries the session id + token \u2014 treat it as a secret):`,\r\n    `  ${url}`,\r\n    ``,\r\n    `Connect by running the relay client as an MCP server (zero install):`,\r\n    `  npx -y mcp-relay-client \"${url}\"`,\r\n    ``,\r\n    `That exposes the app's own tools (e.g. page_describe, page_read, nav_visit,`,\r\n    `page_click, page_set_field, page_submit, plus surface tools like whiteboard_*).`,\r\n    `Then:`,\r\n    `  1. Call page_describe first to see the current page and its interactive handles.`,\r\n    `  2. Act only on the STABLE HANDLES the tools return \u2014 never guess DOM selectors.`,\r\n    `  3. Navigate with nav_visit, type with page_set_field, click with page_click.`,\r\n    `     Submits and destructive clicks are staged for the human to confirm.`,\r\n    ``,\r\n    `If you can't register an MCP server but can run a shell, drive it directly:`,\r\n    `  curl -O https:\/\/raw.githubusercontent.com\/Particle-Academy\/mcp-relay-client\/main\/connect.sh`,\r\n    `  bash connect.sh \"${url}\" tools`,\r\n    `  bash connect.sh \"${url}\" call page_describe '{}'`,\r\n  ].join(\"\\n\");\r\n}\r\n\r\n\/** Build a copy-paste cURL recipe for connecting an external MCP client. *\/\r\nfunction buildCurlRecipe(session: SessionDescriptor): string {\r\n  const base =\r\n    typeof window !== \"undefined\"\r\n      ? `${window.location.protocol}\/\/${window.location.host}`\r\n      : \"http:\/\/localhost\";\r\n  const inbox = `${base}\/agent-relay\/${session.id}\/inbox?token=${session.token}`;\r\n  const events = `${base}\/agent-relay\/${session.id}\/events?token=${session.token}`;\r\n  return [\r\n    `# 1) In one terminal, subscribe to server-pushed frames (SSE)`,\r\n    `curl -N \"${events}\"`,\r\n    ``,\r\n    `# 2) In another terminal, send an initialize handshake`,\r\n    `curl -X POST \"${inbox}\" \\\\`,\r\n    `  -H 'content-type: application\/json' \\\\`,\r\n    `  -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"initialize\",\"params\":{}}'`,\r\n    ``,\r\n    `# 3) List the tools the bridge exposes`,\r\n    `curl -X POST \"${inbox}\" \\\\`,\r\n    `  -H 'content-type: application\/json' \\\\`,\r\n    `  -d '{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"tools\/list\"}'`,\r\n    ``,\r\n    `# 4) Add a sticky note`,\r\n    `curl -X POST \"${inbox}\" \\\\`,\r\n    `  -H 'content-type: application\/json' \\\\`,\r\n    `  -d '{\"jsonrpc\":\"2.0\",\"id\":3,\"method\":\"tools\/call\",\"params\":{\"name\":\"whiteboard_add_sticky\",\"arguments\":{\"x\":300,\"y\":300,\"text\":\"hello from curl\"}}}'`,\r\n  ].join(\"\\n\");\r\n}\r\n","type":"registry:ui","target":"components\/fancy\/share-controls\/ShareControls.tsx"},{"path":"components\/fancy\/share-controls\/index.ts","content":"export { ShareControls, buildAgentPrompt } from \".\/ShareControls\";\r\nexport type { ShareControlsProps } from \".\/ShareControls\";\r\n","type":"registry:ui","target":"components\/fancy\/share-controls\/index.ts"}]}