@particle-academy/agent-integrations 0.11.1 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1953,19 +1953,39 @@ function serialize(entry) {
1953
1953
  var DEFAULT_AGENT8 = { id: "agent", name: "Agent", color: "#a855f7" };
1954
1954
  var truncate = (s, n = 60) => s.length > n ? s.slice(0, n) + "\u2026" : s;
1955
1955
  function registerTerminalBridge(host, options) {
1956
- const { adapter } = options;
1957
1956
  const agent = { ...DEFAULT_AGENT8, ...options.agent ?? {} };
1958
1957
  const pendingMode = options.pendingMode ?? false;
1958
+ const screenId = options.screenId ?? options.adapter?.screenId;
1959
1959
  const disposers = [];
1960
1960
  const staged = /* @__PURE__ */ new Map();
1961
1961
  let seq = 0;
1962
1962
  ensureUndoToolsRegistered(host);
1963
- const target = (label) => ({
1963
+ const listTerminals = () => {
1964
+ if (options.terminals) return options.terminals();
1965
+ if (options.adapter) return [{ id: "terminal", label: "Terminal", active: true, ...options.adapter }];
1966
+ return [];
1967
+ };
1968
+ const resolve = (id) => {
1969
+ const list = listTerminals();
1970
+ if (typeof id === "string" && id !== "") return list.find((t) => t.id === id);
1971
+ return list.find((t) => t.active) ?? list[0];
1972
+ };
1973
+ const anyMulti = !!options.terminals;
1974
+ const canClear = anyMulti || !!options.adapter?.clear;
1975
+ const canShells = anyMulti || !!options.adapter?.listShells;
1976
+ const canSetShell = anyMulti || !!options.adapter?.setShell;
1977
+ const target = (label, terminalId) => ({
1964
1978
  kind: "terminal",
1965
- screenId: adapter.screenId,
1966
- elementId: adapter.screenId ?? "terminal",
1979
+ screenId,
1980
+ elementId: terminalId ?? screenId ?? "terminal",
1967
1981
  label: label ?? "terminal"
1968
1982
  });
1983
+ const TERMINAL_ARG = {
1984
+ terminal: {
1985
+ type: "string",
1986
+ description: "Terminal id to target (call terminal_list for ids). Omit for the active / only terminal."
1987
+ }
1988
+ };
1969
1989
  const reg = (name, description, properties, required, handler, isMutation, resolveTarget) => {
1970
1990
  const wrapped = async (args) => {
1971
1991
  try {
@@ -1978,7 +1998,7 @@ function registerTerminalBridge(host, options) {
1978
1998
  toolName: name,
1979
1999
  agent,
1980
2000
  kind: "terminal",
1981
- screenId: adapter.screenId,
2001
+ screenId,
1982
2002
  resolveTarget: ({ args, result }) => resolveTarget?.(args, result) ?? target()
1983
2003
  }) : wrapped;
1984
2004
  disposers.push(
@@ -1992,38 +2012,66 @@ function registerTerminalBridge(host, options) {
1992
2012
  )
1993
2013
  );
1994
2014
  };
1995
- async function exec(kind, data) {
2015
+ const need = (args) => {
2016
+ const t = resolve(args.terminal);
2017
+ if (!t) {
2018
+ const ids = listTerminals().map((x) => x.id).join(", ") || "(none)";
2019
+ throw new Error(
2020
+ typeof args.terminal === "string" && args.terminal ? `Unknown terminal '${args.terminal}'. Available: ${ids}. Use terminal_list.` : "No terminal available."
2021
+ );
2022
+ }
2023
+ return t;
2024
+ };
2025
+ async function exec(t, kind, data) {
1996
2026
  if (kind === "run") {
1997
- if (adapter.runCommand) await adapter.runCommand(data);
1998
- else adapter.write(data + "\r");
2027
+ if (t.runCommand) await t.runCommand(data);
2028
+ else t.write(data + "\r");
1999
2029
  } else {
2000
- adapter.write(data);
2030
+ t.write(data);
2001
2031
  }
2002
2032
  }
2003
- async function stageOrExec(kind, data) {
2033
+ async function stageOrExec(t, kind, data) {
2004
2034
  if (!pendingMode) {
2005
- await exec(kind, data);
2006
- return textResult(`${kind === "run" ? "ran" : "wrote"}: ${truncate(data)}`, { kind, data, executed: true });
2035
+ await exec(t, kind, data);
2036
+ return textResult(`${kind === "run" ? "ran" : "wrote"} on ${t.id}: ${truncate(data)}`, {
2037
+ kind,
2038
+ data,
2039
+ terminal: t.id,
2040
+ executed: true
2041
+ });
2007
2042
  }
2008
2043
  const id = `t${++seq}`;
2009
- const entry = { id, kind, data };
2044
+ const entry = { id, kind, data, terminalId: t.id };
2010
2045
  staged.set(id, entry);
2011
2046
  options.onPending?.(entry);
2012
2047
  return textResult(
2013
- `Staged ${kind} (id ${id}) \u2014 awaiting human confirmation: ${truncate(data)}`,
2048
+ `Staged ${kind} on ${t.id} (id ${id}) \u2014 awaiting human confirmation: ${truncate(data)}`,
2014
2049
  { ...entry, pending: true }
2015
2050
  );
2016
2051
  }
2052
+ reg(
2053
+ "terminal_list",
2054
+ "List the terminals on this screen (id, label, which is active) \u2014 so you can reach into another terminal, not just the active one. Pass the chosen id as `terminal` to the other tools.",
2055
+ {},
2056
+ [],
2057
+ () => {
2058
+ const list = listTerminals().map((t) => ({ id: t.id, label: t.label ?? t.id, active: !!t.active }));
2059
+ const text = list.length ? list.map((t) => `${t.active ? "* " : " "}${t.id} \u2014 ${t.label}`).join("\n") : "(no terminals)";
2060
+ return textResult(text, { terminals: list });
2061
+ },
2062
+ false
2063
+ );
2017
2064
  reg(
2018
2065
  "terminal_read",
2019
- "Read the visible terminal buffer as text \u2014 what the user sees. Pass `tail` for only the last N lines.",
2020
- { tail: { type: "number", description: "Return only the last N lines." } },
2066
+ "Read a terminal's visible buffer as text \u2014 what the user sees. Pass `tail` for only the last N lines, `terminal` to read a specific one.",
2067
+ { ...TERMINAL_ARG, tail: { type: "number", description: "Return only the last N lines." } },
2021
2068
  [],
2022
2069
  (args) => {
2023
- let buf = adapter.getBuffer();
2070
+ const t = need(args);
2071
+ let buf = t.getBuffer();
2024
2072
  const tail = typeof args.tail === "number" ? args.tail : void 0;
2025
2073
  if (tail && tail > 0) buf = buf.split("\n").slice(-tail).join("\n");
2026
- return textResult(buf, { buffer: buf });
2074
+ return textResult(buf, { buffer: buf, terminal: t.id });
2027
2075
  },
2028
2076
  false
2029
2077
  );
@@ -2035,7 +2083,7 @@ function registerTerminalBridge(host, options) {
2035
2083
  () => {
2036
2084
  const list = [...staged.values()];
2037
2085
  return textResult(
2038
- list.length ? list.map((s) => `${s.id}: ${s.kind} ${truncate(s.data)}`).join("\n") : "(none)",
2086
+ list.length ? list.map((s) => `${s.id}: ${s.kind} on ${s.terminalId} ${truncate(s.data)}`).join("\n") : "(none)",
2039
2087
  { pending: list }
2040
2088
  );
2041
2089
  },
@@ -2043,20 +2091,21 @@ function registerTerminalBridge(host, options) {
2043
2091
  );
2044
2092
  reg(
2045
2093
  "terminal_write",
2046
- "Write raw data / keystrokes to the terminal (input, control chars, ANSI). In pendingMode this stages instead of executing.",
2047
- { data: { type: "string", description: "Raw bytes to write." } },
2094
+ "Write raw data / keystrokes to a terminal (input, control chars, ANSI). Pass `terminal` to target a specific one. In pendingMode this stages instead of executing.",
2095
+ { ...TERMINAL_ARG, data: { type: "string", description: "Raw bytes to write." } },
2048
2096
  ["data"],
2049
- (args) => stageOrExec("write", String(args.data)),
2050
- true
2097
+ (args) => stageOrExec(need(args), "write", String(args.data)),
2098
+ true,
2099
+ (args) => target(`write:${String(args.terminal ?? "")}`, resolve(args.terminal)?.id)
2051
2100
  );
2052
2101
  reg(
2053
2102
  "terminal_run",
2054
- "Run a shell command \u2014 writes the command followed by Enter (or the host's command runner). In pendingMode this stages it for confirmation.",
2055
- { command: { type: "string", description: "The command line to run." } },
2103
+ "Run a shell command in a terminal \u2014 writes the command + Enter (or the host's runner). Pass `terminal` to target a specific one. In pendingMode this stages it for confirmation.",
2104
+ { ...TERMINAL_ARG, command: { type: "string", description: "The command line to run." } },
2056
2105
  ["command"],
2057
- (args) => stageOrExec("run", String(args.command)),
2106
+ (args) => stageOrExec(need(args), "run", String(args.command)),
2058
2107
  true,
2059
- (args) => target(truncate(String(args.command ?? "")))
2108
+ (args) => target(truncate(String(args.command ?? "")), resolve(args.terminal)?.id)
2060
2109
  );
2061
2110
  reg(
2062
2111
  "terminal_confirm",
@@ -2067,11 +2116,17 @@ function registerTerminalBridge(host, options) {
2067
2116
  const id = String(args.id);
2068
2117
  const entry = staged.get(id);
2069
2118
  if (!entry) return errorResult(`No staged command ${id}`);
2119
+ const t = resolve(entry.terminalId);
2120
+ if (!t) return errorResult(`Terminal '${entry.terminalId}' is gone \u2014 cannot run ${id}`);
2070
2121
  staged.delete(id);
2071
- await exec(entry.kind, entry.data);
2072
- return textResult(`Confirmed ${id}: ${entry.kind} ${truncate(entry.data)}`, { ...entry, executed: true });
2122
+ await exec(t, entry.kind, entry.data);
2123
+ return textResult(`Confirmed ${id}: ${entry.kind} on ${t.id} ${truncate(entry.data)}`, { ...entry, executed: true });
2073
2124
  },
2074
- true
2125
+ true,
2126
+ (args) => {
2127
+ const e = staged.get(String(args.id));
2128
+ return target(`confirm:${String(args.id ?? "")}`, e?.terminalId);
2129
+ }
2075
2130
  );
2076
2131
  reg(
2077
2132
  "terminal_reject",
@@ -2085,51 +2140,58 @@ function registerTerminalBridge(host, options) {
2085
2140
  },
2086
2141
  false
2087
2142
  );
2088
- if (adapter.clear) {
2143
+ if (canClear) {
2089
2144
  reg(
2090
2145
  "terminal_clear",
2091
- "Clear the terminal viewport.",
2092
- {},
2146
+ "Clear a terminal's viewport. Pass `terminal` to target a specific one.",
2147
+ { ...TERMINAL_ARG },
2093
2148
  [],
2094
- () => {
2095
- adapter.clear();
2096
- return textResult("cleared");
2149
+ (args) => {
2150
+ const t = need(args);
2151
+ if (!t.clear) return errorResult(`Terminal '${t.id}' can't be cleared.`);
2152
+ t.clear();
2153
+ return textResult(`cleared ${t.id}`, { terminal: t.id });
2097
2154
  },
2098
- true
2155
+ true,
2156
+ (args) => target(`clear:${String(args.terminal ?? "")}`, resolve(args.terminal)?.id)
2099
2157
  );
2100
2158
  }
2101
- if (adapter.listShells) {
2159
+ if (canShells) {
2102
2160
  reg(
2103
2161
  "terminal_list_shells",
2104
- "List the shells the host can switch to (cmd, PowerShell, Git Bash, \u2026) \u2014 id + label, with the active one marked.",
2105
- {},
2162
+ "List the shells a terminal can switch to (cmd, PowerShell, Git Bash, \u2026) \u2014 id + label, active one marked. Pass `terminal` to target a specific one.",
2163
+ { ...TERMINAL_ARG },
2106
2164
  [],
2107
- () => {
2108
- const shells = adapter.listShells();
2109
- const active = adapter.getShell?.();
2165
+ (args) => {
2166
+ const t = need(args);
2167
+ if (!t.listShells) return errorResult(`Terminal '${t.id}' has no switchable shells.`);
2168
+ const shells = t.listShells();
2169
+ const active = t.getShell?.();
2110
2170
  const text = shells.length ? shells.map((s) => `${s.id === active ? "* " : " "}${s.id} \u2014 ${s.label}`).join("\n") : "(none)";
2111
- return textResult(text, { shells, active });
2171
+ return textResult(text, { shells, active, terminal: t.id });
2112
2172
  },
2113
2173
  false
2114
2174
  );
2115
2175
  }
2116
- if (adapter.setShell) {
2176
+ if (canSetShell) {
2117
2177
  reg(
2118
2178
  "terminal_set_shell",
2119
- "Switch the active shell by id (e.g. 'powershell', 'git-bash'). Call terminal_list_shells first for valid ids. The host reconnects its backend to the chosen shell.",
2120
- { id: { type: "string", description: "Shell id to switch to." } },
2179
+ "Switch a terminal's active shell by id (e.g. 'powershell', 'git-bash'). Call terminal_list_shells first for valid ids. Pass `terminal` to target a specific one.",
2180
+ { ...TERMINAL_ARG, id: { type: "string", description: "Shell id to switch to." } },
2121
2181
  ["id"],
2122
2182
  async (args) => {
2183
+ const t = need(args);
2184
+ if (!t.setShell) return errorResult(`Terminal '${t.id}' can't switch shells.`);
2123
2185
  const id = String(args.id);
2124
- const shells = adapter.listShells?.();
2186
+ const shells = t.listShells?.();
2125
2187
  if (shells && shells.length && !shells.some((s) => s.id === id)) {
2126
- return errorResult(`Unknown shell '${id}'. Use terminal_list_shells for valid ids.`);
2188
+ return errorResult(`Unknown shell '${id}' for ${t.id}. Use terminal_list_shells for valid ids.`);
2127
2189
  }
2128
- await adapter.setShell(id);
2129
- return textResult(`Switched shell to ${id}`, { shell: id });
2190
+ await t.setShell(id);
2191
+ return textResult(`Switched ${t.id} shell to ${id}`, { shell: id, terminal: t.id });
2130
2192
  },
2131
2193
  true,
2132
- (args) => target(`shell:${String(args.id ?? "")}`)
2194
+ (args) => target(`shell:${String(args.id ?? "")}`, resolve(args.terminal)?.id)
2133
2195
  );
2134
2196
  }
2135
2197
  return {
@@ -2143,8 +2205,9 @@ function registerTerminalBridge(host, options) {
2143
2205
  confirm: (id) => {
2144
2206
  const e = staged.get(id);
2145
2207
  if (e) {
2208
+ const t = resolve(e.terminalId);
2146
2209
  staged.delete(id);
2147
- void exec(e.kind, e.data);
2210
+ if (t) void exec(t, e.kind, e.data);
2148
2211
  }
2149
2212
  },
2150
2213
  reject: (id) => {
@@ -2753,6 +2816,342 @@ function attachSseRelay(server, options) {
2753
2816
  return transport;
2754
2817
  }
2755
2818
 
2819
+ // src/connectors/targets.ts
2820
+ var CLAUDE_CONNECTORS_URL = "https://claude.ai/settings/connectors";
2821
+ function encodeBase64Json(value) {
2822
+ const json = JSON.stringify(value);
2823
+ if (typeof btoa === "function") {
2824
+ return btoa(unescape(encodeURIComponent(json)));
2825
+ }
2826
+ return Buffer.from(json, "utf8").toString("base64");
2827
+ }
2828
+ function buildCursorDeeplink(server) {
2829
+ const config = encodeBase64Json({ url: server.url });
2830
+ return `cursor://anysphere.cursor-deeplink/mcp/install?name=${encodeURIComponent(
2831
+ server.name
2832
+ )}&config=${config}`;
2833
+ }
2834
+ function buildVscodeDeeplink(server, opts = {}) {
2835
+ const scheme = opts.insiders ? "vscode-insiders" : "vscode";
2836
+ const payload = encodeURIComponent(
2837
+ JSON.stringify({ name: server.name, url: server.url })
2838
+ );
2839
+ return `${scheme}://mcp/install?${payload}`;
2840
+ }
2841
+ function slugifyServerName(name) {
2842
+ const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
2843
+ return slug || "mcp-server";
2844
+ }
2845
+ function buildManualConfig(server) {
2846
+ return {
2847
+ mcpServers: {
2848
+ [slugifyServerName(server.name)]: {
2849
+ command: "npx",
2850
+ args: ["-y", "mcp-remote", server.url]
2851
+ }
2852
+ }
2853
+ };
2854
+ }
2855
+ function buildManualConfigSnippet(server) {
2856
+ return JSON.stringify(buildManualConfig(server), null, 2);
2857
+ }
2858
+ var CONNECTOR_TARGETS = {
2859
+ "claude-web": {
2860
+ id: "claude-web",
2861
+ label: "Add to Claude",
2862
+ mechanism: "copy-open",
2863
+ hint: "Copy the MCP URL and open Claude's Connectors page \u2014 click 'Add custom connector' and paste."
2864
+ },
2865
+ "claude-desktop": {
2866
+ id: "claude-desktop",
2867
+ label: "Claude Desktop",
2868
+ mechanism: "download",
2869
+ hint: "Download a .mcpb bundle and double-click it to install in Claude Desktop."
2870
+ },
2871
+ cursor: {
2872
+ id: "cursor",
2873
+ label: "Add to Cursor",
2874
+ mechanism: "deeplink",
2875
+ hint: "Open Cursor with this MCP server pre-filled \u2014 confirm to install."
2876
+ },
2877
+ vscode: {
2878
+ id: "vscode",
2879
+ label: "Add to VS Code",
2880
+ mechanism: "deeplink",
2881
+ hint: "Open VS Code with this MCP server pre-filled \u2014 confirm to install."
2882
+ },
2883
+ manual: {
2884
+ id: "manual",
2885
+ label: "Manual setup",
2886
+ mechanism: "snippet",
2887
+ hint: "Show a config snippet to paste into any stdio MCP client."
2888
+ }
2889
+ };
2890
+ function connectorHref(client, server, opts = {}) {
2891
+ switch (client) {
2892
+ case "cursor":
2893
+ return buildCursorDeeplink(server);
2894
+ case "vscode":
2895
+ return buildVscodeDeeplink(server, opts);
2896
+ default:
2897
+ return null;
2898
+ }
2899
+ }
2900
+ function ClaudeMark(props) {
2901
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": true, ...props, children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 2 L 14 10 L 22 12 L 14 14 L 12 22 L 10 14 L 2 12 L 10 10 Z" }) });
2902
+ }
2903
+ function CursorMark(props) {
2904
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": true, ...props, children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M3 3 L 20 11 L 12 13 L 9 21 Z" }) });
2905
+ }
2906
+ function VscodeMark(props) {
2907
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": true, ...props, children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M17 2 L 22 4.5 V 19.5 L 17 22 L 6.5 13.2 L 3 16 L 1.5 15 V 9 L 3 8 L 6.5 10.8 Z M 17 6.5 L 10 12 L 17 17.5 Z" }) });
2908
+ }
2909
+ function DesktopMark(props) {
2910
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": true, ...props, children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M3 4 H 21 A 1 1 0 0 1 22 5 V 16 A 1 1 0 0 1 21 17 H 14 V 19 H 16 V 21 H 8 V 19 H 10 V 17 H 3 A 1 1 0 0 1 2 16 V 5 A 1 1 0 0 1 3 4 Z" }) });
2911
+ }
2912
+ function WrenchMark(props) {
2913
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": true, ...props, children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M21 4 a 5 5 0 0 1 -6.5 6.5 L 6 19 l -3 -3 l 8.5 -8.5 A 5 5 0 0 1 17 1 l -2.5 2.5 l 1.5 3 l 3 1.5 Z" }) });
2914
+ }
2915
+ var CONNECTOR_GLYPHS = {
2916
+ "claude-web": ClaudeMark,
2917
+ "claude-desktop": DesktopMark,
2918
+ cursor: CursorMark,
2919
+ vscode: VscodeMark,
2920
+ manual: WrenchMark
2921
+ };
2922
+ var DEFAULT_CLIENTS = [
2923
+ "claude-web",
2924
+ "cursor",
2925
+ "vscode",
2926
+ "manual"
2927
+ ];
2928
+ function ConnectorButtons({
2929
+ serverName,
2930
+ mcpUrl,
2931
+ clients,
2932
+ mcpbDownloadUrl,
2933
+ claudeConnectorsUrl = CLAUDE_CONNECTORS_URL,
2934
+ vscodeInsiders,
2935
+ onCopy,
2936
+ onAction,
2937
+ labels,
2938
+ className,
2939
+ style
2940
+ }) {
2941
+ const server = { name: serverName, url: mcpUrl };
2942
+ const [copied, setCopied] = react.useState(null);
2943
+ const [manualOpen, setManualOpen] = react.useState(false);
2944
+ const manualId = react.useId();
2945
+ const list = (clients ?? defaultClients(mcpbDownloadUrl)).filter(
2946
+ (c) => c === "claude-desktop" ? !!mcpbDownloadUrl : true
2947
+ );
2948
+ const flashCopied = (target) => {
2949
+ setCopied(target);
2950
+ window.setTimeout(() => setCopied((c) => c === target ? null : c), 2e3);
2951
+ };
2952
+ const copy = async (value, target) => {
2953
+ try {
2954
+ await navigator.clipboard?.writeText(value);
2955
+ flashCopied(target);
2956
+ onCopy?.(target);
2957
+ } catch {
2958
+ }
2959
+ };
2960
+ const labelFor = (c) => labels?.[c] ?? CONNECTOR_TARGETS[c].label;
2961
+ return /* @__PURE__ */ jsxRuntime.jsx(
2962
+ "div",
2963
+ {
2964
+ className: ["fai-connect", className].filter(Boolean).join(" "),
2965
+ style,
2966
+ children: list.map((client) => {
2967
+ const meta = CONNECTOR_TARGETS[client];
2968
+ const Glyph = CONNECTOR_GLYPHS[client];
2969
+ const base = `fai-connect__btn fai-connect__btn--${client}`;
2970
+ const href = connectorHref(client, server, { insiders: vscodeInsiders });
2971
+ if (href) {
2972
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2973
+ "a",
2974
+ {
2975
+ href,
2976
+ className: base,
2977
+ title: meta.hint,
2978
+ onClick: () => onAction?.(client),
2979
+ children: [
2980
+ /* @__PURE__ */ jsxRuntime.jsx(Glyph, { className: "fai-connect__glyph" }),
2981
+ labelFor(client)
2982
+ ]
2983
+ },
2984
+ client
2985
+ );
2986
+ }
2987
+ if (client === "claude-desktop") {
2988
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2989
+ "a",
2990
+ {
2991
+ href: mcpbDownloadUrl,
2992
+ download: true,
2993
+ className: base,
2994
+ title: meta.hint,
2995
+ onClick: () => onAction?.(client),
2996
+ children: [
2997
+ /* @__PURE__ */ jsxRuntime.jsx(Glyph, { className: "fai-connect__glyph" }),
2998
+ labelFor(client)
2999
+ ]
3000
+ },
3001
+ client
3002
+ );
3003
+ }
3004
+ if (client === "claude-web") {
3005
+ return /* @__PURE__ */ jsxRuntime.jsxs(
3006
+ "button",
3007
+ {
3008
+ type: "button",
3009
+ className: base,
3010
+ title: meta.hint,
3011
+ onClick: () => {
3012
+ void copy(mcpUrl, client);
3013
+ window.open(
3014
+ claudeConnectorsUrl,
3015
+ "_blank",
3016
+ "noopener,noreferrer"
3017
+ );
3018
+ onAction?.(client);
3019
+ },
3020
+ children: [
3021
+ /* @__PURE__ */ jsxRuntime.jsx(Glyph, { className: "fai-connect__glyph" }),
3022
+ copied === client ? "Copied \u2014 paste in Claude" : labelFor(client)
3023
+ ]
3024
+ },
3025
+ client
3026
+ );
3027
+ }
3028
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fai-connect__manual-wrap", children: [
3029
+ /* @__PURE__ */ jsxRuntime.jsxs(
3030
+ "button",
3031
+ {
3032
+ type: "button",
3033
+ className: base,
3034
+ title: meta.hint,
3035
+ "aria-expanded": manualOpen,
3036
+ "aria-controls": manualId,
3037
+ onClick: () => {
3038
+ setManualOpen((o) => !o);
3039
+ onAction?.(client);
3040
+ },
3041
+ children: [
3042
+ /* @__PURE__ */ jsxRuntime.jsx(Glyph, { className: "fai-connect__glyph" }),
3043
+ labelFor(client)
3044
+ ]
3045
+ }
3046
+ ),
3047
+ manualOpen && /* @__PURE__ */ jsxRuntime.jsx(
3048
+ ManualPopover,
3049
+ {
3050
+ id: manualId,
3051
+ snippet: buildManualConfigSnippet(server),
3052
+ copied: copied === client,
3053
+ onCopy: () => copy(buildManualConfigSnippet(server), client),
3054
+ onClose: () => setManualOpen(false)
3055
+ }
3056
+ )
3057
+ ] }, client);
3058
+ })
3059
+ }
3060
+ );
3061
+ }
3062
+ function defaultClients(mcpbDownloadUrl) {
3063
+ return mcpbDownloadUrl ? ["claude-web", "claude-desktop", "cursor", "vscode", "manual"] : DEFAULT_CLIENTS;
3064
+ }
3065
+ function ManualPopover({
3066
+ id,
3067
+ snippet,
3068
+ copied,
3069
+ onCopy,
3070
+ onClose
3071
+ }) {
3072
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { id, className: "fai-connect__popover", role: "dialog", children: [
3073
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fai-connect__popover-head", children: [
3074
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Add to any stdio MCP client" }),
3075
+ /* @__PURE__ */ jsxRuntime.jsx(
3076
+ "button",
3077
+ {
3078
+ type: "button",
3079
+ className: "fai-connect__popover-close",
3080
+ "aria-label": "Close",
3081
+ onClick: onClose,
3082
+ children: "\xD7"
3083
+ }
3084
+ )
3085
+ ] }),
3086
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "fai-connect__popover-hint", children: [
3087
+ "Paste into ",
3088
+ /* @__PURE__ */ jsxRuntime.jsx("code", { children: "claude_desktop_config.json" }),
3089
+ " (or any stdio MCP client config). Needs Node 18+."
3090
+ ] }),
3091
+ /* @__PURE__ */ jsxRuntime.jsx("pre", { className: "fai-connect__snippet", children: snippet }),
3092
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "fai-connect__copy-btn", onClick: onCopy, children: copied ? "Copied" : "Copy snippet" })
3093
+ ] });
3094
+ }
3095
+
3096
+ // src/connectors/mcpb.ts
3097
+ var MCPB_MANIFEST_VERSION = "0.2";
3098
+ var MCPB_MIN_NODE = ">=18.0.0";
3099
+ var DEFAULT_MCPB_ENTRY_POINT = "server/proxy.js";
3100
+ function buildMcpbManifest(input) {
3101
+ const entryPoint = input.entryPoint ?? DEFAULT_MCPB_ENTRY_POINT;
3102
+ return {
3103
+ manifest_version: MCPB_MANIFEST_VERSION,
3104
+ name: input.name,
3105
+ display_name: input.display_name ?? input.name,
3106
+ version: input.version,
3107
+ description: input.description,
3108
+ ...input.long_description ? { long_description: input.long_description } : {},
3109
+ author: input.author,
3110
+ ...input.homepage ? { homepage: input.homepage } : {},
3111
+ ...input.documentation ? { documentation: input.documentation } : {},
3112
+ ...input.support ? { support: input.support } : {},
3113
+ server: {
3114
+ type: "node",
3115
+ entry_point: entryPoint,
3116
+ mcp_config: {
3117
+ command: "npx",
3118
+ args: ["-y", "mcp-remote", input.mcpUrl]
3119
+ }
3120
+ },
3121
+ tools: input.tools ?? [],
3122
+ tools_generated: false,
3123
+ prompts_generated: false,
3124
+ ...input.keywords ? { keywords: input.keywords } : {},
3125
+ license: input.license ?? "MIT",
3126
+ compatibility: {
3127
+ claude_desktop: ">=0.10.0",
3128
+ platforms: ["darwin", "win32", "linux"],
3129
+ runtimes: { node: MCPB_MIN_NODE }
3130
+ }
3131
+ };
3132
+ }
3133
+ function buildMcpbProxyStub(mcpUrl) {
3134
+ const urlLiteral = JSON.stringify(mcpUrl);
3135
+ return `#!/usr/bin/env node
3136
+ // MCPB proxy shim (generated by @particle-academy/agent-integrations).
3137
+ //
3138
+ // MCPB (Claude Desktop Extensions) only supports local stdio servers, but this
3139
+ // MCP server is a remote HTTP endpoint. The manifest's \`mcp_config\` invokes
3140
+ // \`npx -y mcp-remote <url>\` to bridge the gap \u2014 this file is the entry_point
3141
+ // fallback the manifest validator requires. If you're seeing this run,
3142
+ // mcp_config wasn't honored; spawn mcp-remote directly so the bundle still works.
3143
+
3144
+ const { spawn } = require("node:child_process");
3145
+
3146
+ const url = ${urlLiteral};
3147
+ const child = spawn("npx", ["-y", "mcp-remote", url], { stdio: "inherit" });
3148
+
3149
+ child.on("exit", (code) => process.exit(code ?? 0));
3150
+ process.on("SIGINT", () => child.kill("SIGINT"));
3151
+ process.on("SIGTERM", () => child.kill("SIGTERM"));
3152
+ `;
3153
+ }
3154
+
2756
3155
  Object.defineProperty(exports, "clearUndoStack", {
2757
3156
  enumerable: true,
2758
3157
  get: function () { return fancyAutoCommon.clearStack; }
@@ -2797,7 +3196,17 @@ exports.AgentActivityHighlight = AgentActivityHighlight;
2797
3196
  exports.AgentCursor = AgentCursor;
2798
3197
  exports.AgentPanel = AgentPanel;
2799
3198
  exports.BridgedForm = BridgedForm;
3199
+ exports.CLAUDE_CONNECTORS_URL = CLAUDE_CONNECTORS_URL;
3200
+ exports.CONNECTOR_GLYPHS = CONNECTOR_GLYPHS;
3201
+ exports.CONNECTOR_TARGETS = CONNECTOR_TARGETS;
3202
+ exports.ClaudeMark = ClaudeMark;
3203
+ exports.ConnectorButtons = ConnectorButtons;
3204
+ exports.CursorMark = CursorMark;
3205
+ exports.DEFAULT_MCPB_ENTRY_POINT = DEFAULT_MCPB_ENTRY_POINT;
3206
+ exports.DesktopMark = DesktopMark;
2800
3207
  exports.InProcessTransport = InProcessTransport;
3208
+ exports.MCPB_MANIFEST_VERSION = MCPB_MANIFEST_VERSION;
3209
+ exports.MCPB_MIN_NODE = MCPB_MIN_NODE;
2801
3210
  exports.MCP_PROTOCOL_VERSION = MCP_PROTOCOL_VERSION;
2802
3211
  exports.MicroMcpServer = MicroMcpServer;
2803
3212
  exports.RelayTransport = RelayTransport;
@@ -2805,13 +3214,23 @@ exports.ScreensActivityBridge = ScreensActivityBridge;
2805
3214
  exports.ShareControls = ShareControls;
2806
3215
  exports.SseRelayTransport = SseRelayTransport;
2807
3216
  exports.ToolRegistry = ToolRegistry;
3217
+ exports.VscodeMark = VscodeMark;
3218
+ exports.WrenchMark = WrenchMark;
2808
3219
  exports.attachInProcess = attachInProcess;
2809
3220
  exports.attachRelay = attachRelay;
2810
3221
  exports.attachSseRelay = attachSseRelay;
3222
+ exports.buildCursorDeeplink = buildCursorDeeplink;
3223
+ exports.buildManualConfig = buildManualConfig;
3224
+ exports.buildManualConfigSnippet = buildManualConfigSnippet;
3225
+ exports.buildMcpbManifest = buildMcpbManifest;
3226
+ exports.buildMcpbProxyStub = buildMcpbProxyStub;
2811
3227
  exports.buildShareConfig = buildShareConfig;
2812
3228
  exports.buildShareUrl = buildShareUrl;
3229
+ exports.buildVscodeDeeplink = buildVscodeDeeplink;
3230
+ exports.connectorHref = connectorHref;
2813
3231
  exports.createSessionDescriptor = createSessionDescriptor;
2814
3232
  exports.describeSession = describeSession;
3233
+ exports.encodeBase64Json = encodeBase64Json;
2815
3234
  exports.ensureUndoToolsRegistered = ensureUndoToolsRegistered;
2816
3235
  exports.errorResult = errorResult;
2817
3236
  exports.readSessionFromUrl = readSessionFromUrl;
@@ -2825,6 +3244,7 @@ exports.registerSlidesBridge = registerSlidesBridge;
2825
3244
  exports.registerTerminalBridge = registerTerminalBridge;
2826
3245
  exports.registerUndoTools = registerUndoTools;
2827
3246
  exports.rpcError = rpcError;
3247
+ exports.slugifyServerName = slugifyServerName;
2828
3248
  exports.textResult = textResult;
2829
3249
  exports.useAgentActivity = useAgentActivity;
2830
3250
  exports.useAgentActivityForScreen = useAgentActivityForScreen;