@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.
@@ -0,0 +1,275 @@
1
+ import { ensureUndoToolsRegistered } from './chunk-KJ5AOOV7.js';
2
+ import { wrapToolWithActivity } from './chunk-ULJL53DL.js';
3
+ import { textResult, errorResult } from './chunk-4KAIV6OD.js';
4
+
5
+ // src/bridges/terminal.ts
6
+ var DEFAULT_AGENT = { id: "agent", name: "Agent", color: "#a855f7" };
7
+ var truncate = (s, n = 60) => s.length > n ? s.slice(0, n) + "\u2026" : s;
8
+ function registerTerminalBridge(host, options) {
9
+ const agent = { ...DEFAULT_AGENT, ...options.agent ?? {} };
10
+ const pendingMode = options.pendingMode ?? false;
11
+ const screenId = options.screenId ?? options.adapter?.screenId;
12
+ const disposers = [];
13
+ const staged = /* @__PURE__ */ new Map();
14
+ let seq = 0;
15
+ ensureUndoToolsRegistered(host);
16
+ const listTerminals = () => {
17
+ if (options.terminals) return options.terminals();
18
+ if (options.adapter) return [{ id: "terminal", label: "Terminal", active: true, ...options.adapter }];
19
+ return [];
20
+ };
21
+ const resolve = (id) => {
22
+ const list = listTerminals();
23
+ if (typeof id === "string" && id !== "") return list.find((t) => t.id === id);
24
+ return list.find((t) => t.active) ?? list[0];
25
+ };
26
+ const anyMulti = !!options.terminals;
27
+ const canClear = anyMulti || !!options.adapter?.clear;
28
+ const canShells = anyMulti || !!options.adapter?.listShells;
29
+ const canSetShell = anyMulti || !!options.adapter?.setShell;
30
+ const target = (label, terminalId) => ({
31
+ kind: "terminal",
32
+ screenId,
33
+ elementId: terminalId ?? screenId ?? "terminal",
34
+ label: label ?? "terminal"
35
+ });
36
+ const TERMINAL_ARG = {
37
+ terminal: {
38
+ type: "string",
39
+ description: "Terminal id to target (call terminal_list for ids). Omit for the active / only terminal."
40
+ }
41
+ };
42
+ const reg = (name, description, properties, required, handler, isMutation, resolveTarget) => {
43
+ const wrapped = async (args) => {
44
+ try {
45
+ return await handler(args);
46
+ } catch (e) {
47
+ return errorResult(e instanceof Error ? e.message : String(e));
48
+ }
49
+ };
50
+ const final = isMutation ? wrapToolWithActivity(wrapped, {
51
+ toolName: name,
52
+ agent,
53
+ kind: "terminal",
54
+ screenId,
55
+ resolveTarget: ({ args, result }) => resolveTarget?.(args, result) ?? target()
56
+ }) : wrapped;
57
+ disposers.push(
58
+ host.registerTool(
59
+ {
60
+ name,
61
+ description,
62
+ inputSchema: { type: "object", properties, required, additionalProperties: false }
63
+ },
64
+ final
65
+ )
66
+ );
67
+ };
68
+ const need = (args) => {
69
+ const t = resolve(args.terminal);
70
+ if (!t) {
71
+ const ids = listTerminals().map((x) => x.id).join(", ") || "(none)";
72
+ throw new Error(
73
+ typeof args.terminal === "string" && args.terminal ? `Unknown terminal '${args.terminal}'. Available: ${ids}. Use terminal_list.` : "No terminal available."
74
+ );
75
+ }
76
+ return t;
77
+ };
78
+ async function exec(t, kind, data) {
79
+ if (kind === "run") {
80
+ if (t.runCommand) await t.runCommand(data);
81
+ else t.write(data + "\r");
82
+ } else {
83
+ t.write(data);
84
+ }
85
+ }
86
+ async function stageOrExec(t, kind, data) {
87
+ if (!pendingMode) {
88
+ await exec(t, kind, data);
89
+ return textResult(`${kind === "run" ? "ran" : "wrote"} on ${t.id}: ${truncate(data)}`, {
90
+ kind,
91
+ data,
92
+ terminal: t.id,
93
+ executed: true
94
+ });
95
+ }
96
+ const id = `t${++seq}`;
97
+ const entry = { id, kind, data, terminalId: t.id };
98
+ staged.set(id, entry);
99
+ options.onPending?.(entry);
100
+ return textResult(
101
+ `Staged ${kind} on ${t.id} (id ${id}) \u2014 awaiting human confirmation: ${truncate(data)}`,
102
+ { ...entry, pending: true }
103
+ );
104
+ }
105
+ reg(
106
+ "terminal_list",
107
+ "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.",
108
+ {},
109
+ [],
110
+ () => {
111
+ const list = listTerminals().map((t) => ({ id: t.id, label: t.label ?? t.id, active: !!t.active }));
112
+ const text = list.length ? list.map((t) => `${t.active ? "* " : " "}${t.id} \u2014 ${t.label}`).join("\n") : "(no terminals)";
113
+ return textResult(text, { terminals: list });
114
+ },
115
+ false
116
+ );
117
+ reg(
118
+ "terminal_read",
119
+ "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.",
120
+ { ...TERMINAL_ARG, tail: { type: "number", description: "Return only the last N lines." } },
121
+ [],
122
+ (args) => {
123
+ const t = need(args);
124
+ let buf = t.getBuffer();
125
+ const tail = typeof args.tail === "number" ? args.tail : void 0;
126
+ if (tail && tail > 0) buf = buf.split("\n").slice(-tail).join("\n");
127
+ return textResult(buf, { buffer: buf, terminal: t.id });
128
+ },
129
+ false
130
+ );
131
+ reg(
132
+ "terminal_pending",
133
+ "List commands staged for human confirmation (pendingMode).",
134
+ {},
135
+ [],
136
+ () => {
137
+ const list = [...staged.values()];
138
+ return textResult(
139
+ list.length ? list.map((s) => `${s.id}: ${s.kind} on ${s.terminalId} ${truncate(s.data)}`).join("\n") : "(none)",
140
+ { pending: list }
141
+ );
142
+ },
143
+ false
144
+ );
145
+ reg(
146
+ "terminal_write",
147
+ "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.",
148
+ { ...TERMINAL_ARG, data: { type: "string", description: "Raw bytes to write." } },
149
+ ["data"],
150
+ (args) => stageOrExec(need(args), "write", String(args.data)),
151
+ true,
152
+ (args) => target(`write:${String(args.terminal ?? "")}`, resolve(args.terminal)?.id)
153
+ );
154
+ reg(
155
+ "terminal_run",
156
+ "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.",
157
+ { ...TERMINAL_ARG, command: { type: "string", description: "The command line to run." } },
158
+ ["command"],
159
+ (args) => stageOrExec(need(args), "run", String(args.command)),
160
+ true,
161
+ (args) => target(truncate(String(args.command ?? "")), resolve(args.terminal)?.id)
162
+ );
163
+ reg(
164
+ "terminal_confirm",
165
+ "Confirm + execute a staged command by id (pendingMode).",
166
+ { id: { type: "string" } },
167
+ ["id"],
168
+ async (args) => {
169
+ const id = String(args.id);
170
+ const entry = staged.get(id);
171
+ if (!entry) return errorResult(`No staged command ${id}`);
172
+ const t = resolve(entry.terminalId);
173
+ if (!t) return errorResult(`Terminal '${entry.terminalId}' is gone \u2014 cannot run ${id}`);
174
+ staged.delete(id);
175
+ await exec(t, entry.kind, entry.data);
176
+ return textResult(`Confirmed ${id}: ${entry.kind} on ${t.id} ${truncate(entry.data)}`, { ...entry, executed: true });
177
+ },
178
+ true,
179
+ (args) => {
180
+ const e = staged.get(String(args.id));
181
+ return target(`confirm:${String(args.id ?? "")}`, e?.terminalId);
182
+ }
183
+ );
184
+ reg(
185
+ "terminal_reject",
186
+ "Drop a staged command by id without executing it.",
187
+ { id: { type: "string" } },
188
+ ["id"],
189
+ (args) => {
190
+ const id = String(args.id);
191
+ if (!staged.delete(id)) return errorResult(`No staged command ${id}`);
192
+ return textResult(`Rejected ${id}`, { id, rejected: true });
193
+ },
194
+ false
195
+ );
196
+ if (canClear) {
197
+ reg(
198
+ "terminal_clear",
199
+ "Clear a terminal's viewport. Pass `terminal` to target a specific one.",
200
+ { ...TERMINAL_ARG },
201
+ [],
202
+ (args) => {
203
+ const t = need(args);
204
+ if (!t.clear) return errorResult(`Terminal '${t.id}' can't be cleared.`);
205
+ t.clear();
206
+ return textResult(`cleared ${t.id}`, { terminal: t.id });
207
+ },
208
+ true,
209
+ (args) => target(`clear:${String(args.terminal ?? "")}`, resolve(args.terminal)?.id)
210
+ );
211
+ }
212
+ if (canShells) {
213
+ reg(
214
+ "terminal_list_shells",
215
+ "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.",
216
+ { ...TERMINAL_ARG },
217
+ [],
218
+ (args) => {
219
+ const t = need(args);
220
+ if (!t.listShells) return errorResult(`Terminal '${t.id}' has no switchable shells.`);
221
+ const shells = t.listShells();
222
+ const active = t.getShell?.();
223
+ const text = shells.length ? shells.map((s) => `${s.id === active ? "* " : " "}${s.id} \u2014 ${s.label}`).join("\n") : "(none)";
224
+ return textResult(text, { shells, active, terminal: t.id });
225
+ },
226
+ false
227
+ );
228
+ }
229
+ if (canSetShell) {
230
+ reg(
231
+ "terminal_set_shell",
232
+ "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.",
233
+ { ...TERMINAL_ARG, id: { type: "string", description: "Shell id to switch to." } },
234
+ ["id"],
235
+ async (args) => {
236
+ const t = need(args);
237
+ if (!t.setShell) return errorResult(`Terminal '${t.id}' can't switch shells.`);
238
+ const id = String(args.id);
239
+ const shells = t.listShells?.();
240
+ if (shells && shells.length && !shells.some((s) => s.id === id)) {
241
+ return errorResult(`Unknown shell '${id}' for ${t.id}. Use terminal_list_shells for valid ids.`);
242
+ }
243
+ await t.setShell(id);
244
+ return textResult(`Switched ${t.id} shell to ${id}`, { shell: id, terminal: t.id });
245
+ },
246
+ true,
247
+ (args) => target(`shell:${String(args.id ?? "")}`, resolve(args.terminal)?.id)
248
+ );
249
+ }
250
+ return {
251
+ id: "terminal",
252
+ title: "Terminal",
253
+ dispose: () => {
254
+ disposers.forEach((d) => d());
255
+ disposers.length = 0;
256
+ staged.clear();
257
+ },
258
+ confirm: (id) => {
259
+ const e = staged.get(id);
260
+ if (e) {
261
+ const t = resolve(e.terminalId);
262
+ staged.delete(id);
263
+ if (t) void exec(t, e.kind, e.data);
264
+ }
265
+ },
266
+ reject: (id) => {
267
+ staged.delete(id);
268
+ },
269
+ pending: () => [...staged.values()]
270
+ };
271
+ }
272
+
273
+ export { registerTerminalBridge };
274
+ //# sourceMappingURL=chunk-57KAMBAR.js.map
275
+ //# sourceMappingURL=chunk-57KAMBAR.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/bridges/terminal.ts"],"names":[],"mappings":"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","file":"chunk-57KAMBAR.js","sourcesContent":["import { textResult, errorResult } from \"../mcp/server\";\nimport type { ToolHost } from \"../mcp/tool-host\";\nimport type { JsonObject } from \"../mcp/types\";\nimport type { Bridge } from \"./types\";\nimport { wrapToolWithActivity } from \"../presence/wrap-tool-with-activity\";\nimport { ensureUndoToolsRegistered } from \"../undo/undo-tools\";\nimport type { AgentTarget } from \"../presence/types\";\n\n/**\n * A shell/profile an agent can switch a terminal to. Mirrors fancy-term's\n * `ShellProfile` (kept local so the bridge never imports fancy-term).\n */\nexport type TerminalShell = { id: string; label: string; icon?: string };\n\n/**\n * One terminal the bridge can drive. A Human+ app often hosts **several**\n * terminals on a screen (a build pane, a server pane, an agent scratch shell);\n * each is a `TerminalRef` with a stable `id` so an agent can read/write any of\n * them — not just \"its own\". Wire the function fields to that terminal's\n * fancy-term `TerminalHandle`.\n */\nexport type TerminalRef = {\n /** Stable id used to address this terminal (`terminal_list` enumerates them). */\n id: string;\n /** Human label, e.g. \"Build\", \"Server\". Defaults to the id. */\n label?: string;\n /** True for the focused terminal — the default target when no id is passed. */\n active?: boolean;\n /** Read the visible buffer as text (wire to `TerminalHandle.getBuffer`). */\n getBuffer: () => string;\n /** Write raw data / keystrokes (wire to `TerminalHandle.write`). */\n write: (data: string) => void;\n /** Run a command. Defaults to writing `${command}\\r`; override for a real runner. */\n runCommand?: (command: string) => void | Promise<void>;\n /** Clear the viewport (wire to `TerminalHandle.clear`). */\n clear?: () => void;\n /** Current text selection (wire to `TerminalHandle.getSelection`). */\n getSelection?: () => string;\n /** Shells this terminal offers (cmd, PowerShell, …). */\n listShells?: () => TerminalShell[];\n /** Switch this terminal's active shell by id. */\n setShell?: (id: string) => void | Promise<void>;\n /** This terminal's active shell id. */\n getShell?: () => string | undefined;\n};\n\n/**\n * Single-terminal adapter (back-compat). A `TerminalRef` without the\n * `id`/`label`/`active` bookkeeping — pass it as `{ adapter }` and the bridge\n * treats it as the one (active) terminal. Use `{ terminals }` for multiple.\n */\nexport type TerminalBridgeAdapter = Omit<TerminalRef, \"id\" | \"label\" | \"active\"> & {\n /** fancy-screens screen id (optional) so activity events know which screen the terminal lives in. */\n screenId?: string;\n};\n\ntype StagedKind = \"write\" | \"run\";\ntype Staged = { id: string; kind: StagedKind; data: string; terminalId: string };\n\nexport type TerminalBridgeOptions = {\n /** A single terminal (back-compat). Mutually exclusive with `terminals`. */\n adapter?: TerminalBridgeAdapter;\n /**\n * The live list of terminals on the screen. Use this when the app hosts more\n * than one terminal so an agent can `terminal_list` then target any of them by\n * id — i.e. reach into another terminal in the same screen.\n */\n terminals?: () => TerminalRef[];\n /** fancy-screens screen id for activity events (defaults to `adapter.screenId`). */\n screenId?: string;\n agent?: { id: string; name?: string; color?: string };\n /**\n * Trust-but-verify (Human+ contract for inhabited surfaces). When on,\n * `terminal_write` + `terminal_run` don't execute — they **stage** the command\n * (returning a pending id) and fire `onPending`. A human confirms via the\n * `terminal_confirm` tool or the returned bridge's `confirm(id)`. Default off.\n */\n pendingMode?: boolean;\n /** Notified when a command is staged (pendingMode) — show it + offer confirm / reject. */\n onPending?: (pending: Staged) => void;\n};\n\nexport type TerminalBridge = Bridge & {\n /** Execute a staged command by id — wire a host confirm button to this. No-op if not pending. */\n confirm: (id: string) => void;\n /** Drop a staged command by id without executing. */\n reject: (id: string) => void;\n /** Commands currently awaiting confirmation. */\n pending: () => Staged[];\n};\n\nconst DEFAULT_AGENT = { id: \"agent\", name: \"Agent\", color: \"#a855f7\" };\n\nconst truncate = (s: string, n = 60): string => (s.length > n ? s.slice(0, n) + \"…\" : s);\n\n/**\n * registerTerminalBridge — MCP access to one **or many** terminal surfaces on a\n * screen. An agent reads the visible buffer (`terminal_read`), writes input\n * (`terminal_write`), and runs commands (`terminal_run`) through the host; every\n * mutation broadcasts an `AgentActivity` event. With `pendingMode`, destructive\n * actions are staged for human confirmation (`terminal_confirm` / `terminal_reject`\n * / `terminal_pending`).\n *\n * **Multi-terminal:** pass `{ terminals }` (vs a single `{ adapter }`) and every\n * tool takes an optional `terminal` id; `terminal_list` enumerates them. This is\n * how an agent **reaches into another terminal in the same screen** rather than\n * being stuck in one. When a terminal offers shells, the agent can also list\n * (`terminal_list_shells`) and switch (`terminal_set_shell`) its shell. Tool\n * prefix `terminal_*`.\n */\nexport function registerTerminalBridge(host: ToolHost, options: TerminalBridgeOptions): TerminalBridge {\n const agent = { ...DEFAULT_AGENT, ...(options.agent ?? {}) };\n const pendingMode = options.pendingMode ?? false;\n const screenId = options.screenId ?? options.adapter?.screenId;\n const disposers: Array<() => void> = [];\n const staged = new Map<string, Staged>();\n let seq = 0;\n\n // Enables agent_history (a log of what agents did across every bridge).\n ensureUndoToolsRegistered(host);\n\n // The live terminal list — from `terminals()` (multi) or the single `adapter`\n // normalized to one active ref with the id \"terminal\".\n const listTerminals = (): TerminalRef[] => {\n if (options.terminals) return options.terminals();\n if (options.adapter) return [{ id: \"terminal\", label: \"Terminal\", active: true, ...options.adapter }];\n return [];\n };\n\n /** Resolve a terminal by id; with no id, the active one, else the first. */\n const resolve = (id?: unknown): TerminalRef | undefined => {\n const list = listTerminals();\n if (typeof id === \"string\" && id !== \"\") return list.find((t) => t.id === id);\n return list.find((t) => t.active) ?? list[0];\n };\n\n // Whether any host config can offer these capabilities (gates tool registration;\n // per-call we still check the *resolved* terminal supports it).\n const anyMulti = !!options.terminals;\n const canClear = anyMulti || !!options.adapter?.clear;\n const canShells = anyMulti || !!options.adapter?.listShells;\n const canSetShell = anyMulti || !!options.adapter?.setShell;\n\n const target = (label?: string, terminalId?: string): AgentTarget => ({\n kind: \"terminal\",\n screenId,\n elementId: terminalId ?? screenId ?? \"terminal\",\n label: label ?? \"terminal\",\n });\n\n const TERMINAL_ARG = {\n terminal: {\n type: \"string\",\n description: \"Terminal id to target (call terminal_list for ids). Omit for the active / only terminal.\",\n },\n };\n\n const reg = (\n name: string,\n description: string,\n properties: Record<string, unknown>,\n required: string[],\n handler: (args: JsonObject) => Promise<any> | any,\n isMutation: boolean,\n resolveTarget?: (args: JsonObject, result: any) => AgentTarget | null,\n ) => {\n const wrapped = async (args: JsonObject) => {\n try {\n return await handler(args);\n } catch (e) {\n return errorResult(e instanceof Error ? e.message : String(e));\n }\n };\n const final = isMutation\n ? wrapToolWithActivity(wrapped, {\n toolName: name,\n agent,\n kind: \"terminal\",\n screenId,\n resolveTarget: ({ args, result }) => resolveTarget?.(args, result) ?? target(),\n })\n : wrapped;\n disposers.push(\n host.registerTool(\n {\n name,\n description,\n inputSchema: { type: \"object\", properties: properties as any, required, additionalProperties: false },\n },\n final as any,\n ),\n );\n };\n\n /** Resolve the targeted terminal or throw a clear error for the agent. */\n const need = (args: JsonObject): TerminalRef => {\n const t = resolve(args.terminal);\n if (!t) {\n const ids = listTerminals().map((x) => x.id).join(\", \") || \"(none)\";\n throw new Error(\n typeof args.terminal === \"string\" && args.terminal\n ? `Unknown terminal '${args.terminal}'. Available: ${ids}. Use terminal_list.`\n : \"No terminal available.\",\n );\n }\n return t;\n };\n\n async function exec(t: TerminalRef, kind: StagedKind, data: string): Promise<void> {\n if (kind === \"run\") {\n if (t.runCommand) await t.runCommand(data);\n else t.write(data + \"\\r\");\n } else {\n t.write(data);\n }\n }\n\n async function stageOrExec(t: TerminalRef, kind: StagedKind, data: string) {\n if (!pendingMode) {\n await exec(t, kind, data);\n return textResult(`${kind === \"run\" ? \"ran\" : \"wrote\"} on ${t.id}: ${truncate(data)}`, {\n kind,\n data,\n terminal: t.id,\n executed: true,\n });\n }\n const id = `t${++seq}`;\n const entry: Staged = { id, kind, data, terminalId: t.id };\n staged.set(id, entry);\n options.onPending?.(entry);\n return textResult(\n `Staged ${kind} on ${t.id} (id ${id}) — awaiting human confirmation: ${truncate(data)}`,\n { ...entry, pending: true },\n );\n }\n\n // ── List ──────────────────────────────────────────────────────────────────\n reg(\n \"terminal_list\",\n \"List the terminals on this screen (id, label, which is active) — so you can reach into another terminal, not just the active one. Pass the chosen id as `terminal` to the other tools.\",\n {},\n [],\n () => {\n const list = listTerminals().map((t) => ({ id: t.id, label: t.label ?? t.id, active: !!t.active }));\n const text = list.length\n ? list.map((t) => `${t.active ? \"* \" : \" \"}${t.id} — ${t.label}`).join(\"\\n\")\n : \"(no terminals)\";\n return textResult(text, { terminals: list });\n },\n false,\n );\n\n // ── Read ──────────────────────────────────────────────────────────────────\n reg(\n \"terminal_read\",\n \"Read a terminal's visible buffer as text — what the user sees. Pass `tail` for only the last N lines, `terminal` to read a specific one.\",\n { ...TERMINAL_ARG, tail: { type: \"number\", description: \"Return only the last N lines.\" } },\n [],\n (args) => {\n const t = need(args);\n let buf = t.getBuffer();\n const tail = typeof args.tail === \"number\" ? args.tail : undefined;\n if (tail && tail > 0) buf = buf.split(\"\\n\").slice(-tail).join(\"\\n\");\n return textResult(buf, { buffer: buf, terminal: t.id });\n },\n false,\n );\n\n reg(\n \"terminal_pending\",\n \"List commands staged for human confirmation (pendingMode).\",\n {},\n [],\n () => {\n const list = [...staged.values()];\n return textResult(\n list.length ? list.map((s) => `${s.id}: ${s.kind} on ${s.terminalId} ${truncate(s.data)}`).join(\"\\n\") : \"(none)\",\n { pending: list },\n );\n },\n false,\n );\n\n // ── Mutations ───────────────────────────────────────────────────────────────\n reg(\n \"terminal_write\",\n \"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.\",\n { ...TERMINAL_ARG, data: { type: \"string\", description: \"Raw bytes to write.\" } },\n [\"data\"],\n (args) => stageOrExec(need(args), \"write\", String(args.data)),\n true,\n (args) => target(`write:${String(args.terminal ?? \"\")}`, resolve(args.terminal)?.id),\n );\n\n reg(\n \"terminal_run\",\n \"Run a shell command in a terminal — writes the command + Enter (or the host's runner). Pass `terminal` to target a specific one. In pendingMode this stages it for confirmation.\",\n { ...TERMINAL_ARG, command: { type: \"string\", description: \"The command line to run.\" } },\n [\"command\"],\n (args) => stageOrExec(need(args), \"run\", String(args.command)),\n true,\n (args) => target(truncate(String(args.command ?? \"\")), resolve(args.terminal)?.id),\n );\n\n reg(\n \"terminal_confirm\",\n \"Confirm + execute a staged command by id (pendingMode).\",\n { id: { type: \"string\" } },\n [\"id\"],\n async (args) => {\n const id = String(args.id);\n const entry = staged.get(id);\n if (!entry) return errorResult(`No staged command ${id}`);\n const t = resolve(entry.terminalId);\n if (!t) return errorResult(`Terminal '${entry.terminalId}' is gone — cannot run ${id}`);\n staged.delete(id);\n await exec(t, entry.kind, entry.data);\n return textResult(`Confirmed ${id}: ${entry.kind} on ${t.id} ${truncate(entry.data)}`, { ...entry, executed: true });\n },\n true,\n (args) => {\n const e = staged.get(String(args.id));\n return target(`confirm:${String(args.id ?? \"\")}`, e?.terminalId);\n },\n );\n\n reg(\n \"terminal_reject\",\n \"Drop a staged command by id without executing it.\",\n { id: { type: \"string\" } },\n [\"id\"],\n (args) => {\n const id = String(args.id);\n if (!staged.delete(id)) return errorResult(`No staged command ${id}`);\n return textResult(`Rejected ${id}`, { id, rejected: true });\n },\n false,\n );\n\n if (canClear) {\n reg(\n \"terminal_clear\",\n \"Clear a terminal's viewport. Pass `terminal` to target a specific one.\",\n { ...TERMINAL_ARG },\n [],\n (args) => {\n const t = need(args);\n if (!t.clear) return errorResult(`Terminal '${t.id}' can't be cleared.`);\n t.clear();\n return textResult(`cleared ${t.id}`, { terminal: t.id });\n },\n true,\n (args) => target(`clear:${String(args.terminal ?? \"\")}`, resolve(args.terminal)?.id),\n );\n }\n\n // ── Shells ──────────────────────────────────────────────────────────────────\n if (canShells) {\n reg(\n \"terminal_list_shells\",\n \"List the shells a terminal can switch to (cmd, PowerShell, Git Bash, …) — id + label, active one marked. Pass `terminal` to target a specific one.\",\n { ...TERMINAL_ARG },\n [],\n (args) => {\n const t = need(args);\n if (!t.listShells) return errorResult(`Terminal '${t.id}' has no switchable shells.`);\n const shells = t.listShells();\n const active = t.getShell?.();\n const text = shells.length\n ? shells.map((s) => `${s.id === active ? \"* \" : \" \"}${s.id} — ${s.label}`).join(\"\\n\")\n : \"(none)\";\n return textResult(text, { shells, active, terminal: t.id });\n },\n false,\n );\n }\n\n if (canSetShell) {\n reg(\n \"terminal_set_shell\",\n \"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.\",\n { ...TERMINAL_ARG, id: { type: \"string\", description: \"Shell id to switch to.\" } },\n [\"id\"],\n async (args) => {\n const t = need(args);\n if (!t.setShell) return errorResult(`Terminal '${t.id}' can't switch shells.`);\n const id = String(args.id);\n const shells = t.listShells?.();\n if (shells && shells.length && !shells.some((s) => s.id === id)) {\n return errorResult(`Unknown shell '${id}' for ${t.id}. Use terminal_list_shells for valid ids.`);\n }\n await t.setShell(id);\n return textResult(`Switched ${t.id} shell to ${id}`, { shell: id, terminal: t.id });\n },\n true,\n (args) => target(`shell:${String(args.id ?? \"\")}`, resolve(args.terminal)?.id),\n );\n }\n\n return {\n id: \"terminal\",\n title: \"Terminal\",\n dispose: () => {\n disposers.forEach((d) => d());\n disposers.length = 0;\n staged.clear();\n },\n confirm: (id: string) => {\n const e = staged.get(id);\n if (e) {\n const t = resolve(e.terminalId);\n staged.delete(id);\n if (t) void exec(t, e.kind, e.data);\n }\n },\n reject: (id: string) => {\n staged.delete(id);\n },\n pending: () => [...staged.values()],\n };\n}\n"]}
@@ -0,0 +1,62 @@
1
+ // src/connectors/mcpb.ts
2
+ var MCPB_MANIFEST_VERSION = "0.2";
3
+ var MCPB_MIN_NODE = ">=18.0.0";
4
+ var DEFAULT_MCPB_ENTRY_POINT = "server/proxy.js";
5
+ function buildMcpbManifest(input) {
6
+ const entryPoint = input.entryPoint ?? DEFAULT_MCPB_ENTRY_POINT;
7
+ return {
8
+ manifest_version: MCPB_MANIFEST_VERSION,
9
+ name: input.name,
10
+ display_name: input.display_name ?? input.name,
11
+ version: input.version,
12
+ description: input.description,
13
+ ...input.long_description ? { long_description: input.long_description } : {},
14
+ author: input.author,
15
+ ...input.homepage ? { homepage: input.homepage } : {},
16
+ ...input.documentation ? { documentation: input.documentation } : {},
17
+ ...input.support ? { support: input.support } : {},
18
+ server: {
19
+ type: "node",
20
+ entry_point: entryPoint,
21
+ mcp_config: {
22
+ command: "npx",
23
+ args: ["-y", "mcp-remote", input.mcpUrl]
24
+ }
25
+ },
26
+ tools: input.tools ?? [],
27
+ tools_generated: false,
28
+ prompts_generated: false,
29
+ ...input.keywords ? { keywords: input.keywords } : {},
30
+ license: input.license ?? "MIT",
31
+ compatibility: {
32
+ claude_desktop: ">=0.10.0",
33
+ platforms: ["darwin", "win32", "linux"],
34
+ runtimes: { node: MCPB_MIN_NODE }
35
+ }
36
+ };
37
+ }
38
+ function buildMcpbProxyStub(mcpUrl) {
39
+ const urlLiteral = JSON.stringify(mcpUrl);
40
+ return `#!/usr/bin/env node
41
+ // MCPB proxy shim (generated by @particle-academy/agent-integrations).
42
+ //
43
+ // MCPB (Claude Desktop Extensions) only supports local stdio servers, but this
44
+ // MCP server is a remote HTTP endpoint. The manifest's \`mcp_config\` invokes
45
+ // \`npx -y mcp-remote <url>\` to bridge the gap \u2014 this file is the entry_point
46
+ // fallback the manifest validator requires. If you're seeing this run,
47
+ // mcp_config wasn't honored; spawn mcp-remote directly so the bundle still works.
48
+
49
+ const { spawn } = require("node:child_process");
50
+
51
+ const url = ${urlLiteral};
52
+ const child = spawn("npx", ["-y", "mcp-remote", url], { stdio: "inherit" });
53
+
54
+ child.on("exit", (code) => process.exit(code ?? 0));
55
+ process.on("SIGINT", () => child.kill("SIGINT"));
56
+ process.on("SIGTERM", () => child.kill("SIGTERM"));
57
+ `;
58
+ }
59
+
60
+ export { DEFAULT_MCPB_ENTRY_POINT, MCPB_MANIFEST_VERSION, MCPB_MIN_NODE, buildMcpbManifest, buildMcpbProxyStub };
61
+ //# sourceMappingURL=chunk-GO2Y6H6U.js.map
62
+ //# sourceMappingURL=chunk-GO2Y6H6U.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/connectors/mcpb.ts"],"names":[],"mappings":";AAiBO,IAAM,qBAAA,GAAwB;AAG9B,IAAM,aAAA,GAAgB;AAmCtB,IAAM,wBAAA,GAA2B;AAOjC,SAAS,kBACd,KAAA,EACyB;AACzB,EAAA,MAAM,UAAA,GAAa,MAAM,UAAA,IAAc,wBAAA;AACvC,EAAA,OAAO;AAAA,IACL,gBAAA,EAAkB,qBAAA;AAAA,IAClB,MAAM,KAAA,CAAM,IAAA;AAAA,IACZ,YAAA,EAAc,KAAA,CAAM,YAAA,IAAgB,KAAA,CAAM,IAAA;AAAA,IAC1C,SAAS,KAAA,CAAM,OAAA;AAAA,IACf,aAAa,KAAA,CAAM,WAAA;AAAA,IACnB,GAAI,MAAM,gBAAA,GACN,EAAE,kBAAkB,KAAA,CAAM,gBAAA,KAC1B,EAAC;AAAA,IACL,QAAQ,KAAA,CAAM,MAAA;AAAA,IACd,GAAI,MAAM,QAAA,GAAW,EAAE,UAAU,KAAA,CAAM,QAAA,KAAa,EAAC;AAAA,IACrD,GAAI,MAAM,aAAA,GAAgB,EAAE,eAAe,KAAA,CAAM,aAAA,KAAkB,EAAC;AAAA,IACpE,GAAI,MAAM,OAAA,GAAU,EAAE,SAAS,KAAA,CAAM,OAAA,KAAY,EAAC;AAAA,IAClD,MAAA,EAAQ;AAAA,MACN,IAAA,EAAM,MAAA;AAAA,MACN,WAAA,EAAa,UAAA;AAAA,MACb,UAAA,EAAY;AAAA,QACV,OAAA,EAAS,KAAA;AAAA,QACT,IAAA,EAAM,CAAC,IAAA,EAAM,YAAA,EAAc,MAAM,MAAM;AAAA;AACzC,KACF;AAAA,IACA,KAAA,EAAO,KAAA,CAAM,KAAA,IAAS,EAAC;AAAA,IACvB,eAAA,EAAiB,KAAA;AAAA,IACjB,iBAAA,EAAmB,KAAA;AAAA,IACnB,GAAI,MAAM,QAAA,GAAW,EAAE,UAAU,KAAA,CAAM,QAAA,KAAa,EAAC;AAAA,IACrD,OAAA,EAAS,MAAM,OAAA,IAAW,KAAA;AAAA,IAC1B,aAAA,EAAe;AAAA,MACb,cAAA,EAAgB,UAAA;AAAA,MAChB,SAAA,EAAW,CAAC,QAAA,EAAU,OAAA,EAAS,OAAO,CAAA;AAAA,MACtC,QAAA,EAAU,EAAE,IAAA,EAAM,aAAA;AAAc;AAClC,GACF;AACF;AAOO,SAAS,mBAAmB,MAAA,EAAwB;AAEzD,EAAA,MAAM,UAAA,GAAa,IAAA,CAAK,SAAA,CAAU,MAAM,CAAA;AACxC,EAAA,OAAO,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA,YAAA,EAWK,UAAU,CAAA;AAAA;;AAAA;AAAA;AAAA;AAAA,CAAA;AAOxB","file":"chunk-GO2Y6H6U.js","sourcesContent":["// MCPB (Claude Desktop \"Extensions\" bundle) manifest + proxy generation.\n//\n// PURE — no filesystem, no child_process — so it's trivially testable. The\n// `writeMcpbBundle` Node helper (./build) layers fs + the official mcpb CLI on\n// top of these.\n//\n// The hard fact MCPB forces on you: it is STDIO-ONLY. The manifest's\n// `server.type` is one of `node` / `python` / `binary` / `uv` — there is no\n// `type: \"http\"`. So to bundle a REMOTE MCP server (what fancy-* apps almost\n// always are) you ship a thin `node` server whose `mcp_config` runs\n// `npx -y mcp-remote <url>`, bridging stdio→HTTP. The manifest validator still\n// requires `entry_point`, so an (otherwise unused) proxy stub is emitted too.\n//\n// Future-proof: when MCPB grows a real `type: \"http\"`, drop the proxy and point\n// straight at the URL — the call site here doesn't change.\n\n/** The MCPB manifest schema version this helper emits. */\nexport const MCPB_MANIFEST_VERSION = \"0.2\";\n\n/** Minimum Node the mcp-remote proxy needs on the user's machine. */\nexport const MCPB_MIN_NODE = \">=18.0.0\";\n\n/** A tool advertised in the bundle manifest (display-only metadata). */\nexport interface McpbTool {\n name: string;\n description?: string;\n}\n\n/** Inputs for {@link buildMcpbManifest} / `writeMcpbBundle`. */\nexport interface McpbManifestInput {\n /** Machine name, e.g. `\"decksmith\"` (lowercase, no spaces). */\n name: string;\n /** Human display name, e.g. `\"Decksmith\"`. Defaults to `name`. */\n display_name?: string;\n /** Bundle version, e.g. `\"0.2.0\"`. */\n version: string;\n /** Short one-line description. */\n description: string;\n /** Optional longer description shown on the extension's detail view. */\n long_description?: string;\n author: { name: string; url?: string; email?: string };\n homepage?: string;\n documentation?: string;\n support?: string;\n /** The remote MCP endpoint the bundle proxies to. */\n mcpUrl: string;\n /** Advertised tools (display metadata only). */\n tools?: McpbTool[];\n keywords?: string[];\n license?: string;\n /** Entry-point path inside the bundle. Defaults to `\"server/proxy.js\"`. */\n entryPoint?: string;\n}\n\n/** The default entry-point path the proxy stub is written to. */\nexport const DEFAULT_MCPB_ENTRY_POINT = \"server/proxy.js\";\n\n/**\n * Build the full MCPB `manifest.json` object for a remote MCP server, wrapping\n * it with `npx -y mcp-remote <url>` (stdio→HTTP). Returns a plain object ready\n * to `JSON.stringify`.\n */\nexport function buildMcpbManifest(\n input: McpbManifestInput,\n): Record<string, unknown> {\n const entryPoint = input.entryPoint ?? DEFAULT_MCPB_ENTRY_POINT;\n return {\n manifest_version: MCPB_MANIFEST_VERSION,\n name: input.name,\n display_name: input.display_name ?? input.name,\n version: input.version,\n description: input.description,\n ...(input.long_description\n ? { long_description: input.long_description }\n : {}),\n author: input.author,\n ...(input.homepage ? { homepage: input.homepage } : {}),\n ...(input.documentation ? { documentation: input.documentation } : {}),\n ...(input.support ? { support: input.support } : {}),\n server: {\n type: \"node\",\n entry_point: entryPoint,\n mcp_config: {\n command: \"npx\",\n args: [\"-y\", \"mcp-remote\", input.mcpUrl],\n },\n },\n tools: input.tools ?? [],\n tools_generated: false,\n prompts_generated: false,\n ...(input.keywords ? { keywords: input.keywords } : {}),\n license: input.license ?? \"MIT\",\n compatibility: {\n claude_desktop: \">=0.10.0\",\n platforms: [\"darwin\", \"win32\", \"linux\"],\n runtimes: { node: MCPB_MIN_NODE },\n },\n };\n}\n\n/**\n * The `server/proxy.js` stub. MCPB requires an `entry_point` file even though\n * `mcp_config.command` overrides it; if it ever DOES run, it spawns\n * `npx -y mcp-remote <url>` itself so the bundle still works.\n */\nexport function buildMcpbProxyStub(mcpUrl: string): string {\n // JSON.stringify gives us a safely-quoted JS string literal for the URL.\n const urlLiteral = JSON.stringify(mcpUrl);\n return `#!/usr/bin/env node\n// MCPB proxy shim (generated by @particle-academy/agent-integrations).\n//\n// MCPB (Claude Desktop Extensions) only supports local stdio servers, but this\n// MCP server is a remote HTTP endpoint. The manifest's \\`mcp_config\\` invokes\n// \\`npx -y mcp-remote <url>\\` to bridge the gap — this file is the entry_point\n// fallback the manifest validator requires. If you're seeing this run,\n// mcp_config wasn't honored; spawn mcp-remote directly so the bundle still works.\n\nconst { spawn } = require(\"node:child_process\");\n\nconst url = ${urlLiteral};\nconst child = spawn(\"npx\", [\"-y\", \"mcp-remote\", url], { stdio: \"inherit\" });\n\nchild.on(\"exit\", (code) => process.exit(code ?? 0));\nprocess.on(\"SIGINT\", () => child.kill(\"SIGINT\"));\nprocess.on(\"SIGTERM\", () => child.kill(\"SIGTERM\"));\n`;\n}\n"]}
@@ -0,0 +1,56 @@
1
+ import { M as McpbManifestInput } from '../mcpb-BXOrsRnv.cjs';
2
+ export { a as McpbTool } from '../mcpb-BXOrsRnv.cjs';
3
+
4
+ interface WriteMcpbBundleOptions {
5
+ /** Where to write the packed `.mcpb` (absolute or cwd-relative). */
6
+ outFile: string;
7
+ /** The manifest inputs (server name, version, mcpUrl, tools, …). */
8
+ manifest: McpbManifestInput;
9
+ /**
10
+ * The mcpb CLI to shell out to. Default: `["npx", "-y", "@anthropic-ai/mcpb"]`.
11
+ * Pass a locally-installed binary path (string) or argv (array) to avoid the
12
+ * npx network fetch.
13
+ */
14
+ mcpbBin?: string | string[];
15
+ /** Run `mcpb validate` before packing. Default true. */
16
+ validate?: boolean;
17
+ /** Keep the temp work dir instead of removing it (debugging). Default false. */
18
+ keepWorkDir?: boolean;
19
+ /** Working directory the CLI runs in. Default `process.cwd()`. */
20
+ cwd?: string;
21
+ }
22
+ interface WriteMcpbBundleResult {
23
+ /** Absolute path to the written `.mcpb`. */
24
+ outFile: string;
25
+ /** Size of the written bundle in bytes. */
26
+ bytes: number;
27
+ /** The manifest object that was packed. */
28
+ manifest: Record<string, unknown>;
29
+ /** The work dir used (returned even after cleanup, for logging). */
30
+ workDir: string;
31
+ }
32
+ /**
33
+ * Generate a `manifest.json` + `server/proxy.js` for a remote MCP server, then
34
+ * validate and pack them into a `.mcpb` using the official `@anthropic-ai/mcpb`
35
+ * CLI. Returns the output path + size.
36
+ *
37
+ * ```ts
38
+ * import { writeMcpbBundle } from "@particle-academy/agent-integrations/connectors/build";
39
+ *
40
+ * await writeMcpbBundle({
41
+ * outFile: "public/decksmith.mcpb",
42
+ * manifest: {
43
+ * name: "decksmith",
44
+ * display_name: "Decksmith",
45
+ * version: "0.2.0",
46
+ * description: "Agent-driven slide deck builder.",
47
+ * author: { name: "Particle Academy", url: "https://decksmith.dev" },
48
+ * mcpUrl: "https://decksmith.dev/mcp",
49
+ * tools: [{ name: "start_session", description: "…" }],
50
+ * },
51
+ * });
52
+ * ```
53
+ */
54
+ declare function writeMcpbBundle(opts: WriteMcpbBundleOptions): Promise<WriteMcpbBundleResult>;
55
+
56
+ export { McpbManifestInput, type WriteMcpbBundleOptions, type WriteMcpbBundleResult, writeMcpbBundle };
@@ -0,0 +1,56 @@
1
+ import { M as McpbManifestInput } from '../mcpb-BXOrsRnv.js';
2
+ export { a as McpbTool } from '../mcpb-BXOrsRnv.js';
3
+
4
+ interface WriteMcpbBundleOptions {
5
+ /** Where to write the packed `.mcpb` (absolute or cwd-relative). */
6
+ outFile: string;
7
+ /** The manifest inputs (server name, version, mcpUrl, tools, …). */
8
+ manifest: McpbManifestInput;
9
+ /**
10
+ * The mcpb CLI to shell out to. Default: `["npx", "-y", "@anthropic-ai/mcpb"]`.
11
+ * Pass a locally-installed binary path (string) or argv (array) to avoid the
12
+ * npx network fetch.
13
+ */
14
+ mcpbBin?: string | string[];
15
+ /** Run `mcpb validate` before packing. Default true. */
16
+ validate?: boolean;
17
+ /** Keep the temp work dir instead of removing it (debugging). Default false. */
18
+ keepWorkDir?: boolean;
19
+ /** Working directory the CLI runs in. Default `process.cwd()`. */
20
+ cwd?: string;
21
+ }
22
+ interface WriteMcpbBundleResult {
23
+ /** Absolute path to the written `.mcpb`. */
24
+ outFile: string;
25
+ /** Size of the written bundle in bytes. */
26
+ bytes: number;
27
+ /** The manifest object that was packed. */
28
+ manifest: Record<string, unknown>;
29
+ /** The work dir used (returned even after cleanup, for logging). */
30
+ workDir: string;
31
+ }
32
+ /**
33
+ * Generate a `manifest.json` + `server/proxy.js` for a remote MCP server, then
34
+ * validate and pack them into a `.mcpb` using the official `@anthropic-ai/mcpb`
35
+ * CLI. Returns the output path + size.
36
+ *
37
+ * ```ts
38
+ * import { writeMcpbBundle } from "@particle-academy/agent-integrations/connectors/build";
39
+ *
40
+ * await writeMcpbBundle({
41
+ * outFile: "public/decksmith.mcpb",
42
+ * manifest: {
43
+ * name: "decksmith",
44
+ * display_name: "Decksmith",
45
+ * version: "0.2.0",
46
+ * description: "Agent-driven slide deck builder.",
47
+ * author: { name: "Particle Academy", url: "https://decksmith.dev" },
48
+ * mcpUrl: "https://decksmith.dev/mcp",
49
+ * tools: [{ name: "start_session", description: "…" }],
50
+ * },
51
+ * });
52
+ * ```
53
+ */
54
+ declare function writeMcpbBundle(opts: WriteMcpbBundleOptions): Promise<WriteMcpbBundleResult>;
55
+
56
+ export { McpbManifestInput, type WriteMcpbBundleOptions, type WriteMcpbBundleResult, writeMcpbBundle };