@valni/cli-darwin-x64 0.0.0-stage → 0.1.2

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.
Files changed (236) hide show
  1. package/LICENSE +53 -0
  2. package/package.json +18 -5
  3. package/runtime/LICENSE +53 -0
  4. package/runtime/README.md +674 -0
  5. package/runtime/assets/clankolas.png +0 -0
  6. package/runtime/docs/extensions.md +2910 -0
  7. package/runtime/docs/json.md +82 -0
  8. package/runtime/docs/models.md +540 -0
  9. package/runtime/docs/packages.md +228 -0
  10. package/runtime/docs/rpc.md +1480 -0
  11. package/runtime/docs/sdk.md +1183 -0
  12. package/runtime/docs/session-format.md +422 -0
  13. package/runtime/docs/settings.md +315 -0
  14. package/runtime/docs/shell-aliases.md +13 -0
  15. package/runtime/docs/terminal-setup.md +142 -0
  16. package/runtime/docs/termux.md +127 -0
  17. package/runtime/docs/tmux.md +63 -0
  18. package/runtime/docs/tui.md +927 -0
  19. package/runtime/docs/windows.md +17 -0
  20. package/runtime/examples/README.md +25 -0
  21. package/runtime/examples/extensions/README.md +212 -0
  22. package/runtime/examples/extensions/auto-commit-on-exit.ts +49 -0
  23. package/runtime/examples/extensions/bash-spawn-hook.ts +30 -0
  24. package/runtime/examples/extensions/bookmark.ts +50 -0
  25. package/runtime/examples/extensions/border-status-editor.ts +145 -0
  26. package/runtime/examples/extensions/built-in-tool-renderer.ts +249 -0
  27. package/runtime/examples/extensions/claude-rules.ts +86 -0
  28. package/runtime/examples/extensions/commands.ts +72 -0
  29. package/runtime/examples/extensions/confirm-destructive.ts +59 -0
  30. package/runtime/examples/extensions/custom-footer.ts +64 -0
  31. package/runtime/examples/extensions/custom-header.ts +73 -0
  32. package/runtime/examples/extensions/custom-provider-anthropic/index.ts +604 -0
  33. package/runtime/examples/extensions/custom-provider-anthropic/package-lock.json +24 -0
  34. package/runtime/examples/extensions/custom-provider-anthropic/package.json +19 -0
  35. package/runtime/examples/extensions/dirty-repo-guard.ts +56 -0
  36. package/runtime/examples/extensions/doom-overlay/README.md +46 -0
  37. package/runtime/examples/extensions/doom-overlay/doom/build/doom.js +21 -0
  38. package/runtime/examples/extensions/doom-overlay/doom/build/doom.wasm +0 -0
  39. package/runtime/examples/extensions/doom-overlay/doom/build.sh +152 -0
  40. package/runtime/examples/extensions/doom-overlay/doom/doomgeneric_pi.c +72 -0
  41. package/runtime/examples/extensions/doom-overlay/doom-component.ts +132 -0
  42. package/runtime/examples/extensions/doom-overlay/doom-engine.ts +173 -0
  43. package/runtime/examples/extensions/doom-overlay/doom-keys.ts +104 -0
  44. package/runtime/examples/extensions/doom-overlay/index.ts +74 -0
  45. package/runtime/examples/extensions/doom-overlay/wad-finder.ts +51 -0
  46. package/runtime/examples/extensions/dynamic-resources/SKILL.md +8 -0
  47. package/runtime/examples/extensions/dynamic-resources/dynamic.json +79 -0
  48. package/runtime/examples/extensions/dynamic-resources/dynamic.md +5 -0
  49. package/runtime/examples/extensions/dynamic-resources/index.ts +15 -0
  50. package/runtime/examples/extensions/dynamic-tools.ts +74 -0
  51. package/runtime/examples/extensions/entry-renderer.ts +41 -0
  52. package/runtime/examples/extensions/event-bus.ts +43 -0
  53. package/runtime/examples/extensions/file-trigger.ts +41 -0
  54. package/runtime/examples/extensions/git-checkpoint.ts +53 -0
  55. package/runtime/examples/extensions/git-merge-and-resolve.ts +115 -0
  56. package/runtime/examples/extensions/github-issue-autocomplete.ts +185 -0
  57. package/runtime/examples/extensions/gondolin/index.ts +531 -0
  58. package/runtime/examples/extensions/gondolin/package-lock.json +185 -0
  59. package/runtime/examples/extensions/gondolin/package.json +19 -0
  60. package/runtime/examples/extensions/hello.ts +26 -0
  61. package/runtime/examples/extensions/hidden-thinking-label.ts +53 -0
  62. package/runtime/examples/extensions/inline-bash.ts +94 -0
  63. package/runtime/examples/extensions/input-transform-streaming.ts +39 -0
  64. package/runtime/examples/extensions/input-transform.ts +43 -0
  65. package/runtime/examples/extensions/interactive-shell.ts +196 -0
  66. package/runtime/examples/extensions/mac-system-theme.ts +47 -0
  67. package/runtime/examples/extensions/message-renderer.ts +59 -0
  68. package/runtime/examples/extensions/minimal-mode.ts +426 -0
  69. package/runtime/examples/extensions/modal-editor.ts +85 -0
  70. package/runtime/examples/extensions/model-status.ts +31 -0
  71. package/runtime/examples/extensions/notify.ts +55 -0
  72. package/runtime/examples/extensions/overlay-qa-tests.ts +1450 -0
  73. package/runtime/examples/extensions/overlay-test.ts +153 -0
  74. package/runtime/examples/extensions/permission-gate.ts +34 -0
  75. package/runtime/examples/extensions/pirate.ts +47 -0
  76. package/runtime/examples/extensions/plan-mode/README.md +66 -0
  77. package/runtime/examples/extensions/plan-mode/index.ts +390 -0
  78. package/runtime/examples/extensions/plan-mode/utils.ts +168 -0
  79. package/runtime/examples/extensions/preset.ts +436 -0
  80. package/runtime/examples/extensions/project-trust.ts +64 -0
  81. package/runtime/examples/extensions/prompt-customizer.ts +97 -0
  82. package/runtime/examples/extensions/protected-paths.ts +30 -0
  83. package/runtime/examples/extensions/provider-payload.ts +18 -0
  84. package/runtime/examples/extensions/question.ts +278 -0
  85. package/runtime/examples/extensions/questionnaire.ts +440 -0
  86. package/runtime/examples/extensions/rainbow-editor.ts +88 -0
  87. package/runtime/examples/extensions/reload-runtime.ts +37 -0
  88. package/runtime/examples/extensions/rpc-demo.ts +118 -0
  89. package/runtime/examples/extensions/sandbox/index.ts +321 -0
  90. package/runtime/examples/extensions/sandbox/package-lock.json +92 -0
  91. package/runtime/examples/extensions/sandbox/package.json +19 -0
  92. package/runtime/examples/extensions/send-user-message.ts +97 -0
  93. package/runtime/examples/extensions/session-name.ts +27 -0
  94. package/runtime/examples/extensions/shutdown-command.ts +63 -0
  95. package/runtime/examples/extensions/snake.ts +343 -0
  96. package/runtime/examples/extensions/space-invaders.ts +560 -0
  97. package/runtime/examples/extensions/ssh.ts +220 -0
  98. package/runtime/examples/extensions/status-line.ts +32 -0
  99. package/runtime/examples/extensions/structured-output.ts +65 -0
  100. package/runtime/examples/extensions/subagent/README.md +175 -0
  101. package/runtime/examples/extensions/subagent/agents/planner.md +37 -0
  102. package/runtime/examples/extensions/subagent/agents/reviewer.md +35 -0
  103. package/runtime/examples/extensions/subagent/agents/scout.md +50 -0
  104. package/runtime/examples/extensions/subagent/agents/worker.md +24 -0
  105. package/runtime/examples/extensions/subagent/agents.ts +126 -0
  106. package/runtime/examples/extensions/subagent/index.ts +1009 -0
  107. package/runtime/examples/extensions/subagent/prompts/implement-and-review.md +10 -0
  108. package/runtime/examples/extensions/subagent/prompts/implement.md +10 -0
  109. package/runtime/examples/extensions/subagent/prompts/scout-and-plan.md +9 -0
  110. package/runtime/examples/extensions/system-prompt-header.ts +17 -0
  111. package/runtime/examples/extensions/tic-tac-toe.ts +1008 -0
  112. package/runtime/examples/extensions/timed-confirm.ts +70 -0
  113. package/runtime/examples/extensions/titlebar-spinner.ts +58 -0
  114. package/runtime/examples/extensions/todo.ts +297 -0
  115. package/runtime/examples/extensions/tool-override.ts +144 -0
  116. package/runtime/examples/extensions/tools.ts +146 -0
  117. package/runtime/examples/extensions/trigger-compact.ts +50 -0
  118. package/runtime/examples/extensions/truncated-tool.ts +195 -0
  119. package/runtime/examples/extensions/widget-placement.ts +9 -0
  120. package/runtime/examples/extensions/with-deps/index.ts +32 -0
  121. package/runtime/examples/extensions/with-deps/package-lock.json +31 -0
  122. package/runtime/examples/extensions/with-deps/package.json +22 -0
  123. package/runtime/examples/extensions/working-indicator.ts +123 -0
  124. package/runtime/examples/extensions/working-message-test.ts +25 -0
  125. package/runtime/examples/rpc-extension-ui.ts +632 -0
  126. package/runtime/examples/sdk/01-minimal.ts +26 -0
  127. package/runtime/examples/sdk/02-custom-model.ts +43 -0
  128. package/runtime/examples/sdk/03-custom-prompt.ts +70 -0
  129. package/runtime/examples/sdk/04-skills.ts +55 -0
  130. package/runtime/examples/sdk/05-tools.ts +48 -0
  131. package/runtime/examples/sdk/06-extensions.ts +94 -0
  132. package/runtime/examples/sdk/07-context-files.ts +42 -0
  133. package/runtime/examples/sdk/08-prompt-templates.ts +51 -0
  134. package/runtime/examples/sdk/09-api-keys-and-oauth.ts +34 -0
  135. package/runtime/examples/sdk/10-settings.ts +53 -0
  136. package/runtime/examples/sdk/11-sessions.ts +52 -0
  137. package/runtime/examples/sdk/12-full-control.ts +71 -0
  138. package/runtime/examples/sdk/13-session-runtime.ts +67 -0
  139. package/runtime/examples/sdk/README.md +140 -0
  140. package/runtime/export-html/ansi-to-html.js +249 -0
  141. package/runtime/export-html/index.js +226 -0
  142. package/runtime/export-html/template.css +1066 -0
  143. package/runtime/export-html/template.html +55 -0
  144. package/runtime/export-html/template.js +1864 -0
  145. package/runtime/export-html/tool-renderer.js +108 -0
  146. package/runtime/export-html/vendor/highlight.min.js +1213 -0
  147. package/runtime/export-html/vendor/marked.min.js +78 -0
  148. package/runtime/extensions/pi-switchboard/.github/workflows/ci.yml +51 -0
  149. package/runtime/extensions/pi-switchboard/LICENSE +22 -0
  150. package/runtime/extensions/pi-switchboard/README.md +85 -0
  151. package/runtime/extensions/pi-switchboard/extensions/switchboard/catalog.ts +183 -0
  152. package/runtime/extensions/pi-switchboard/extensions/switchboard/config.ts +97 -0
  153. package/runtime/extensions/pi-switchboard/extensions/switchboard/connections.ts +386 -0
  154. package/runtime/extensions/pi-switchboard/extensions/switchboard/constants.ts +12 -0
  155. package/runtime/extensions/pi-switchboard/extensions/switchboard/device-auth.ts +127 -0
  156. package/runtime/extensions/pi-switchboard/extensions/switchboard/envelope.ts +199 -0
  157. package/runtime/extensions/pi-switchboard/extensions/switchboard/errors.ts +180 -0
  158. package/runtime/extensions/pi-switchboard/extensions/switchboard/index.ts +125 -0
  159. package/runtime/extensions/pi-switchboard/extensions/switchboard/participant.ts +17 -0
  160. package/runtime/extensions/pi-switchboard/extensions/switchboard/sessionStream.ts +131 -0
  161. package/runtime/extensions/pi-switchboard/extensions/switchboard/toolProxy.ts +136 -0
  162. package/runtime/extensions/pi-switchboard/extensions/switchboard/tools.ts +16 -0
  163. package/runtime/extensions/pi-switchboard/extensions/switchboard/types.ts +37 -0
  164. package/runtime/extensions/pi-switchboard/package.json +30 -0
  165. package/runtime/extensions/pi-switchboard/tsconfig.json +13 -0
  166. package/runtime/extensions/switchboard-local/.github/workflows/ci.yml +80 -0
  167. package/runtime/extensions/switchboard-local/.github/workflows/release.yml +46 -0
  168. package/runtime/extensions/switchboard-local/README.md +98 -0
  169. package/runtime/extensions/switchboard-local/extensions/local/engine.ts +28 -0
  170. package/runtime/extensions/switchboard-local/extensions/local/engines/gguf.ts +324 -0
  171. package/runtime/extensions/switchboard-local/extensions/local/engines/llamaBuild.ts +62 -0
  172. package/runtime/extensions/switchboard-local/extensions/local/engines/mlx.ts +245 -0
  173. package/runtime/extensions/switchboard-local/extensions/local/engines/select.ts +38 -0
  174. package/runtime/extensions/switchboard-local/extensions/local/engines/support.ts +151 -0
  175. package/runtime/extensions/switchboard-local/extensions/local/hf.ts +106 -0
  176. package/runtime/extensions/switchboard-local/extensions/local/index.ts +67 -0
  177. package/runtime/extensions/switchboard-local/extensions/local/nativeStream.ts +602 -0
  178. package/runtime/extensions/switchboard-local/extensions/local/paths.ts +44 -0
  179. package/runtime/extensions/switchboard-local/extensions/local/provider.ts +57 -0
  180. package/runtime/extensions/switchboard-local/extensions/local/store.ts +82 -0
  181. package/runtime/extensions/switchboard-local/extensions/local/ui.ts +60 -0
  182. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard/dist/apiTypes.js +74 -0
  183. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard/dist/client.js +292 -0
  184. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard/dist/deviceAuth.js +155 -0
  185. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard/dist/errors.js +95 -0
  186. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard/dist/index.js +8 -0
  187. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard/dist/messages.js +253 -0
  188. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard/dist/models.js +19 -0
  189. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard/dist/native.js +886 -0
  190. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard/dist/wire.js +85 -0
  191. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard/package.json +39 -0
  192. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/package.json +29 -0
  193. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/anthropic.ts +127 -0
  194. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/google.ts +111 -0
  195. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/index.ts +8 -0
  196. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/modelRecord.test.ts +25 -0
  197. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/modelRecord.ts +57 -0
  198. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/openaiGeneric.ts +103 -0
  199. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/openaiPro.ts +89 -0
  200. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/profile.ts +120 -0
  201. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/routing.test.ts +82 -0
  202. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/routing.ts +74 -0
  203. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/validateConfig.google.test.ts +137 -0
  204. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/validateConfig.openai.test.ts +241 -0
  205. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/validateConfig.test.ts +174 -0
  206. package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/validateConfig.ts +438 -0
  207. package/runtime/extensions/switchboard-local/package.json +34 -0
  208. package/runtime/extensions/switchboard-local/tsconfig.json +13 -0
  209. package/runtime/native/darwin/prebuilds/darwin-x64/darwin-modifiers.node +0 -0
  210. package/runtime/node_modules/@mariozechner/clipboard/.yarnrc.yml +1 -0
  211. package/runtime/node_modules/@mariozechner/clipboard/Cargo.toml +45 -0
  212. package/runtime/node_modules/@mariozechner/clipboard/README.md +60 -0
  213. package/runtime/node_modules/@mariozechner/clipboard/build.rs +5 -0
  214. package/runtime/node_modules/@mariozechner/clipboard/clipboard.darwin-x64.node +0 -0
  215. package/runtime/node_modules/@mariozechner/clipboard/exp.ts +12 -0
  216. package/runtime/node_modules/@mariozechner/clipboard/index.js +606 -0
  217. package/runtime/node_modules/@mariozechner/clipboard/package.json +56 -0
  218. package/runtime/node_modules/@mariozechner/clipboard/src/clipboard_rs/common.rs +307 -0
  219. package/runtime/node_modules/@mariozechner/clipboard/src/clipboard_rs/mod.rs +93 -0
  220. package/runtime/node_modules/@mariozechner/clipboard/src/clipboard_rs/platform/ios.rs +313 -0
  221. package/runtime/node_modules/@mariozechner/clipboard/src/clipboard_rs/platform/macos.rs +471 -0
  222. package/runtime/node_modules/@mariozechner/clipboard/src/clipboard_rs/platform/mod.rs +252 -0
  223. package/runtime/node_modules/@mariozechner/clipboard/src/clipboard_rs/platform/wayland.rs +431 -0
  224. package/runtime/node_modules/@mariozechner/clipboard/src/clipboard_rs/platform/win.rs +712 -0
  225. package/runtime/node_modules/@mariozechner/clipboard/src/clipboard_rs/platform/x11.rs +916 -0
  226. package/runtime/node_modules/@mariozechner/clipboard/src/lib.rs +194 -0
  227. package/runtime/node_modules/@mariozechner/clipboard-darwin-x64/README.md +3 -0
  228. package/runtime/node_modules/@mariozechner/clipboard-darwin-x64/clipboard.darwin-x64.node +0 -0
  229. package/runtime/node_modules/@mariozechner/clipboard-darwin-x64/package.json +22 -0
  230. package/runtime/package.json +104 -0
  231. package/runtime/photon_rs_bg.wasm +0 -0
  232. package/runtime/theme/dark.json +87 -0
  233. package/runtime/theme/light.json +86 -0
  234. package/runtime/theme/theme-schema.json +340 -0
  235. package/runtime/valni +4 -0
  236. package/README.md +0 -3
@@ -0,0 +1,136 @@
1
+ import type { AgentToolResult, ExtensionContext } from "@earendil-works/pi-coding-agent";
2
+ import { resolveBaseUrl, resolveSessionId } from "./config.ts";
3
+ import { PROVIDER_ID, spawnedConnectionId } from "./constants.ts";
4
+
5
+ const MCP_PATH = "/mcp";
6
+ const DISCOVER_TIMEOUT_MS = 10_000;
7
+ const SESSION_HEADER = "X-Switchboard-Session";
8
+ const CONNECTION_TOOLS = new Set(["send_message", "close_automation_connection"]);
9
+ const CONNECTION_ID_PARAM = "connection_id";
10
+
11
+ function hasConnectionId(record: Record<string, unknown>): boolean {
12
+ const existing = record[CONNECTION_ID_PARAM];
13
+ return typeof existing === "string" && existing.trim() !== "";
14
+ }
15
+
16
+ export function withSpawnedConnectionId(name: string, params: unknown, connectionId: string | null): unknown {
17
+ if (!CONNECTION_TOOLS.has(name) || connectionId === null) return params;
18
+ if (params === null || typeof params !== "object" || Array.isArray(params)) return params;
19
+ const record = params as Record<string, unknown>;
20
+ if (hasConnectionId(record)) return params;
21
+ return { ...record, [CONNECTION_ID_PARAM]: connectionId };
22
+ }
23
+
24
+ export interface AdvertisedTool {
25
+ name: string;
26
+ description: string;
27
+ parameters: Record<string, unknown>;
28
+ version?: string;
29
+ }
30
+
31
+ export async function discoverTools(baseUrl: string, bearer: string): Promise<AdvertisedTool[]> {
32
+ let response: Response;
33
+ try {
34
+ response = await fetch(`${baseUrl}${MCP_PATH}`, {
35
+ method: "POST",
36
+ headers: { "Content-Type": "application/json", Accept: "application/json", Authorization: `Bearer ${bearer}` },
37
+ body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "tools/list", params: {} }),
38
+ signal: AbortSignal.timeout(DISCOVER_TIMEOUT_MS),
39
+ });
40
+ } catch {
41
+ return [];
42
+ }
43
+ if (!response.ok) return [];
44
+ const body = (await response.json().catch(() => null)) as {
45
+ result?: { tools?: Array<{ name?: unknown; description?: unknown; inputSchema?: unknown }> };
46
+ } | null;
47
+ const tools = body?.result?.tools;
48
+ if (!Array.isArray(tools)) return [];
49
+ const advertised: AdvertisedTool[] = [];
50
+ for (const tool of tools) {
51
+ if (typeof tool.name !== "string" || tool.name.length === 0) continue;
52
+ advertised.push({
53
+ name: tool.name,
54
+ description: typeof tool.description === "string" ? tool.description : "",
55
+ parameters:
56
+ tool.inputSchema !== null && typeof tool.inputSchema === "object" && !Array.isArray(tool.inputSchema)
57
+ ? (tool.inputSchema as Record<string, unknown>)
58
+ : {},
59
+ });
60
+ }
61
+ return advertised;
62
+ }
63
+
64
+ function textResult(text: string, details: unknown): AgentToolResult<unknown> {
65
+ return { content: [{ type: "text", text }], details };
66
+ }
67
+
68
+ export function toAgentContent(content: unknown): AgentToolResult<unknown>["content"] {
69
+ if (!Array.isArray(content)) return [{ type: "text", text: JSON.stringify(content) }];
70
+ const items: AgentToolResult<unknown>["content"] = [];
71
+ for (const raw of content) {
72
+ if (raw === null || typeof raw !== "object") continue;
73
+ const item = raw as { type?: unknown; text?: unknown; data?: unknown; mimeType?: unknown };
74
+ if (item.type === "text" && typeof item.text === "string") {
75
+ items.push({ type: "text", text: item.text });
76
+ } else if (item.type === "image" && typeof item.data === "string") {
77
+ items.push({ type: "image", data: item.data, mimeType: typeof item.mimeType === "string" ? item.mimeType : "image/png" });
78
+ } else {
79
+ items.push({ type: "text", text: JSON.stringify(raw) });
80
+ }
81
+ }
82
+ return items.length > 0 ? items : [{ type: "text", text: JSON.stringify(content) }];
83
+ }
84
+
85
+ export async function resolveInvokeToken(ctx: ExtensionContext): Promise<string | null> {
86
+ let resolution: Awaited<ReturnType<ExtensionContext["modelRegistry"]["getProviderAuth"]>>;
87
+ try {
88
+ resolution = await ctx.modelRegistry.getProviderAuth(PROVIDER_ID);
89
+ } catch {
90
+ return null;
91
+ }
92
+ return resolution?.auth.apiKey ?? null;
93
+ }
94
+
95
+ export function makeProxyExecute(name: string) {
96
+ return async (
97
+ _toolCallId: string,
98
+ params: unknown,
99
+ signal: AbortSignal | undefined,
100
+ _onUpdate: unknown,
101
+ ctx: ExtensionContext,
102
+ ): Promise<AgentToolResult<unknown>> => {
103
+ const token = await resolveInvokeToken(ctx);
104
+ if (!token) return textResult(`Not signed in to Switchboard — cannot run ${name}. Run /login in pi, or set SWITCHBOARD_API_KEY for key-based use.`, null);
105
+ const headers: Record<string, string> = { "Content-Type": "application/json", Accept: "application/json", Authorization: `Bearer ${token}` };
106
+ const sessionId = resolveSessionId();
107
+ if (sessionId !== null) headers[SESSION_HEADER] = sessionId;
108
+
109
+ const args = withSpawnedConnectionId(name, params, spawnedConnectionId());
110
+ let response: Response;
111
+ try {
112
+ response = await fetch(`${resolveBaseUrl()}${MCP_PATH}`, {
113
+ method: "POST",
114
+ headers,
115
+ body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "tools/call", params: { name, arguments: args } }),
116
+ signal,
117
+ });
118
+ } catch (error) {
119
+ return textResult(`Could not reach Switchboard to run ${name}: ${String(error)}`, null);
120
+ }
121
+
122
+ const envelope = (await response.json().catch(() => null)) as {
123
+ result?: { content?: unknown; isError?: boolean };
124
+ error?: { message?: unknown; code?: unknown };
125
+ } | null;
126
+ if (envelope?.error) {
127
+ const message = typeof envelope.error.message === "string" ? envelope.error.message : `Tool ${name} failed.`;
128
+ const code = typeof envelope.error.code === "number" || typeof envelope.error.code === "string" ? ` [${envelope.error.code}]` : "";
129
+ return textResult(`${message}${code}`, envelope);
130
+ }
131
+ if (!response.ok || !envelope?.result) {
132
+ return textResult(`Tool ${name} failed (HTTP ${response.status}).`, envelope);
133
+ }
134
+ return { content: toAgentContent(envelope.result.content), details: envelope.result };
135
+ };
136
+ }
@@ -0,0 +1,16 @@
1
+ import { Type } from "@earendil-works/pi-ai";
2
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
3
+ import { type AdvertisedTool, makeProxyExecute } from "./toolProxy.ts";
4
+
5
+ export function registerServerTools(pi: ExtensionAPI, tools: AdvertisedTool[]): void {
6
+ for (const tool of tools) {
7
+ if (!tool.name) continue;
8
+ pi.registerTool({
9
+ name: tool.name,
10
+ label: tool.name,
11
+ description: tool.description,
12
+ parameters: Type.Unsafe(tool.parameters),
13
+ execute: makeProxyExecute(tool.name),
14
+ });
15
+ }
16
+ }
@@ -0,0 +1,37 @@
1
+ import type { Model } from "@earendil-works/pi-ai";
2
+
3
+ export const KIND_TO_API = {
4
+ anthropic: "anthropic-messages",
5
+ openai_generic: "openai-completions",
6
+ openai_pro: "openai-responses",
7
+ } as const;
8
+
9
+ export type SwitchboardKind = keyof typeof KIND_TO_API;
10
+
11
+ export interface KindProfile {
12
+ model: string;
13
+ maxTokensCeiling?: number;
14
+ vision?: boolean;
15
+ thinking?: { modes: string[] } | false;
16
+ reasoningEffort?: string[] | false;
17
+ }
18
+
19
+ export interface ModelRecord {
20
+ id: string;
21
+ kind: Record<string, KindProfile>;
22
+ }
23
+
24
+ export interface ModelRecordPrice {
25
+ input_micro_cents_per_mtok: number;
26
+ output_micro_cents_per_mtok: number;
27
+ cached_input_micro_cents_per_mtok: number | null;
28
+ effective_at: number;
29
+ }
30
+
31
+ export interface ModelsPage {
32
+ models: ModelRecord[];
33
+ prices: Record<string, ModelRecordPrice>;
34
+ context_windows?: Record<string, number>;
35
+ }
36
+
37
+ export type RegistryModel = Model<(typeof KIND_TO_API)[SwitchboardKind]>;
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "pi-switchboard",
3
+ "version": "0.11.2",
4
+ "type": "module",
5
+ "description": "Route the pi coding agent through Switchboard: one prepaid balance, spend controls, and a ledger for every model in the catalog",
6
+ "keywords": [
7
+ "pi-package"
8
+ ],
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://github.com/Valni-Labs/pi-switchboard"
12
+ },
13
+ "engines": {
14
+ "node": ">=22.19.0"
15
+ },
16
+ "scripts": {
17
+ "test": "npm run typecheck && node --test \"extensions/switchboard/tests/*.test.ts\"",
18
+ "typecheck": "tsc --noEmit"
19
+ },
20
+ "pi": {
21
+ "extensions": [
22
+ "./extensions"
23
+ ]
24
+ },
25
+ "devDependencies": {
26
+ "@earendil-works/pi-ai": "0.82.1",
27
+ "@earendil-works/pi-coding-agent": "0.82.1",
28
+ "typescript": "5.9.3"
29
+ }
30
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es2023",
4
+ "module": "nodenext",
5
+ "moduleResolution": "nodenext",
6
+ "allowImportingTsExtensions": true,
7
+ "noEmit": true,
8
+ "strict": true,
9
+ "skipLibCheck": true,
10
+ "types": ["node"]
11
+ },
12
+ "include": ["extensions/**/*.ts"]
13
+ }
@@ -0,0 +1,80 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ concurrency:
10
+ group: ci-${{ github.ref }}
11
+ cancel-in-progress: true
12
+
13
+ jobs:
14
+ typecheck-test:
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - name: Checkout
18
+ uses: actions/checkout@v4
19
+ with:
20
+ persist-credentials: false
21
+
22
+ - name: Setup Node.js
23
+ uses: actions/setup-node@v4
24
+ with:
25
+ node-version: 22
26
+ cache: npm
27
+
28
+ - name: Verify dependency token
29
+ env:
30
+ TOKEN: ${{ secrets.VALNI_REPO_READ_TOKEN }}
31
+ run: |
32
+ if [ -z "$TOKEN" ]; then
33
+ echo "::error::VALNI_REPO_READ_TOKEN is empty or not in scope for this repository"
34
+ exit 1
35
+ fi
36
+ # Both are pulled by the lockfile, and a fine-grained token can hold one without the other.
37
+ for repo in switchboard-ts SwitchboardNative; do
38
+ if ! git ls-remote "https://oauth2:${TOKEN}@github.com/Valni-Labs/${repo}.git" HEAD > /dev/null 2>&1; then
39
+ echo "::error::VALNI_REPO_READ_TOKEN cannot read Valni-Labs/${repo}"
40
+ exit 1
41
+ fi
42
+ done
43
+
44
+ - name: Install dependencies
45
+ env:
46
+ TOKEN: ${{ secrets.VALNI_REPO_READ_TOKEN }}
47
+ run: |
48
+ # Keep the credential in a throwaway config for this step only. Later steps run
49
+ # code from the pull request, which must never be able to read the token.
50
+ CONFIG="$(mktemp)"
51
+ export GIT_CONFIG_GLOBAL="$CONFIG"
52
+ trap 'rm -f "$CONFIG"' EXIT
53
+ git config --global url."https://oauth2:${TOKEN}@github.com/".insteadOf "ssh://git@github.com/"
54
+ git config --global --add url."https://oauth2:${TOKEN}@github.com/".insteadOf "git@github.com:"
55
+ npm ci --ignore-scripts
56
+
57
+ - name: Confirm no credential survived the install
58
+ run: |
59
+ if [ -f "$HOME/.gitconfig" ] && grep -q "oauth2:" "$HOME/.gitconfig"; then
60
+ echo "::error::a token was left in $HOME/.gitconfig"
61
+ exit 1
62
+ fi
63
+
64
+ - name: Typecheck and test
65
+ run: npm test
66
+
67
+ - name: Confirm the extension loads in pi
68
+ run: |
69
+ npx pi --no-extensions --extension ./extensions/local/index.ts --list-models 2> >(tee /tmp/pi-stderr.txt >&2)
70
+ # The version line is the reliable signal. Stock pi swallows extension load
71
+ # errors on --list-models, so its absence is how a broken extension shows up.
72
+ if ! grep -q "switchboard-local v" /tmp/pi-stderr.txt; then
73
+ echo "::error::the extension never ran; it printed no version line"
74
+ exit 1
75
+ fi
76
+ # Hosts that do report load failures, such as valni-cli, are caught here too.
77
+ if grep -q "Failed to load extension" /tmp/pi-stderr.txt; then
78
+ echo "::error::the extension failed to load in pi"
79
+ exit 1
80
+ fi
@@ -0,0 +1,46 @@
1
+ name: Release
2
+
3
+ on:
4
+ workflow_dispatch:
5
+
6
+ jobs:
7
+ release:
8
+ runs-on: ubuntu-latest
9
+ permissions:
10
+ contents: write
11
+ steps:
12
+ - name: Checkout
13
+ uses: actions/checkout@v4
14
+ with:
15
+ fetch-depth: 0
16
+
17
+ - name: Read the version
18
+ id: version
19
+ run: echo "tag=v$(node -p "require('./package.json').version")" >> "$GITHUB_OUTPUT"
20
+
21
+ - name: Refuse to release a tag that already exists
22
+ run: |
23
+ if git rev-parse "${{ steps.version.outputs.tag }}" >/dev/null 2>&1; then
24
+ echo "::error::${{ steps.version.outputs.tag }} already exists. Bump the version in package.json first."
25
+ exit 1
26
+ fi
27
+
28
+ - name: Refuse to release a commit CI has not passed
29
+ env:
30
+ GH_TOKEN: ${{ github.token }}
31
+ run: |
32
+ STATE="$(gh api "repos/${{ github.repository }}/commits/${{ github.sha }}/check-runs" \
33
+ --jq '[.check_runs[] | select(.name == "typecheck-test") | .conclusion] | first // "missing"')"
34
+ if [ "$STATE" != "success" ]; then
35
+ echo "::error::CI on ${{ github.sha }} is '${STATE}', not success"
36
+ exit 1
37
+ fi
38
+
39
+ - name: Tag and publish the release
40
+ env:
41
+ GH_TOKEN: ${{ github.token }}
42
+ run: |
43
+ gh release create "${{ steps.version.outputs.tag }}" \
44
+ --target "${{ github.sha }}" \
45
+ --title "${{ steps.version.outputs.tag }}" \
46
+ --generate-notes
@@ -0,0 +1,98 @@
1
+ # switchboard-local
2
+
3
+ Local models in the pi model picker. You paste a Hugging Face link, it downloads, and the model
4
+ appears in `/model` at $0. No account, no network at inference time.
5
+
6
+ ```
7
+ pi install https://github.com/Valni-Labs/switchboard-local
8
+ ```
9
+
10
+ That follows `main`. To pin to a release instead:
11
+
12
+ ```
13
+ pi install https://github.com/Valni-Labs/switchboard-local@v0.2.0
14
+ ```
15
+
16
+ ## Use it
17
+
18
+ ```
19
+ /switchboard-local
20
+ ```
21
+
22
+ Pick **Add a model…** and paste a link or a bare `owner/repository`. Or skip the menu:
23
+
24
+ ```
25
+ /switchboard-local huggingface.co/mlx-community/Llama-3.2-1B-Instruct-4bit
26
+ ```
27
+
28
+ The first model you add also installs the engine. That happens once and takes a few minutes.
29
+ Afterwards `/model` lists every local model you have, alongside your cloud models.
30
+
31
+ ## What it installs
32
+
33
+ Everything it manages lives in `~/.switchboard-local/`, outside the package, so `pi update` never
34
+ wipes it.
35
+
36
+ | Path | What |
37
+ | --- | --- |
38
+ | `~/.switchboard-local/mlx-venv` | Python environment plus `mlx-lm` |
39
+ | `~/.switchboard-local/llama` | llama.cpp build and GGUF weights |
40
+ | `~/.switchboard-local/state.json` | Ports, engine process ids, llama.cpp build |
41
+
42
+ MLX weights go to the standard Hugging Face cache (`~/.cache/huggingface/hub`), so anything you
43
+ already downloaded is picked up automatically.
44
+
45
+ ## Engines
46
+
47
+ Two engines, picked for you. Paste any model and the right one runs it.
48
+
49
+ | Engine | Format | Machines | Acceleration |
50
+ | --- | --- | --- | --- |
51
+ | MLX | safetensors, such as `mlx-community/*` | Apple Silicon | Metal |
52
+ | GGUF | `.gguf`, such as `unsloth/*-GGUF` | macOS, Windows, Linux | Metal, Vulkan, CPU |
53
+
54
+ MLX exists only on Apple Silicon, so GGUF is what covers Windows and Linux. The extension reads
55
+ the repository and routes to whichever engine can run it, and says so plainly when neither can.
56
+
57
+ For GGUF you may name a quantization, or leave it out and get `Q4_K_M` when it exists, else the
58
+ smallest:
59
+
60
+ ```
61
+ /switchboard-local unsloth/Qwen3-4B-Instruct-2507-GGUF:Q4_K_M
62
+ /switchboard-local unsloth/Qwen3-4B-Instruct-2507-GGUF
63
+ ```
64
+
65
+ The llama.cpp build is downloaded for you. On Windows and Linux it uses the Vulkan build, which
66
+ gives GPU speed on NVIDIA, AMD and Intel without a CUDA install, and falls back to CPU if Vulkan
67
+ will not start.
68
+
69
+ ## How it works
70
+
71
+ pi is Node and cannot load a model into itself, so each engine runs as a child process bound to
72
+ `127.0.0.1`, and every downloaded model is registered against the engine that holds it. pi's own
73
+ OpenAI client does the talking, which is why streaming, native tool calls and token counts all
74
+ work without any protocol code here.
75
+
76
+ Nothing is sent anywhere. The only network traffic is downloading the model from Hugging Face.
77
+
78
+ - Models cost `$0.00` and are listed straight from the engine, so the picker always matches disk.
79
+ - The provider declares a local credential, so local models need no login and work signed out.
80
+ - Engines start on demand and stop when pi exits, including runs with no session. Each runs in its
81
+ own process group, so a stop takes its workers with it and never holds pi open.
82
+ - Several pi sessions share one engine rather than loading a model twice. The engine is stopped by
83
+ the last session using it, so quitting one window never pulls a model out from under another.
84
+
85
+ ## Not yet
86
+
87
+ - `$0` usage events into the Switchboard ledger. Inference would still stay on the machine; only
88
+ the token count would be reported afterwards.
89
+ - Reasoning models are listed without a thinking level, so pi offers no thinking-level selector.
90
+
91
+ ## Develop
92
+
93
+ ```
94
+ npm test
95
+ pi -ne -e ./extensions/local/index.ts --list-models
96
+ ```
97
+
98
+ `-ne` disables other extensions so you are testing this one alone.
@@ -0,0 +1,28 @@
1
+ export interface Progress {
2
+ label: string;
3
+ fraction?: number;
4
+ }
5
+
6
+ export interface LocalModelInfo {
7
+ id: string;
8
+ engine: LocalEngineId;
9
+ baseUrl: string;
10
+ contextWindow: number;
11
+ vision?: boolean;
12
+ }
13
+
14
+ export type LocalEngineId = "mlx" | "gguf";
15
+
16
+ export interface LocalEngine {
17
+ readonly id: LocalEngineId;
18
+ readonly label: string;
19
+ supported(): boolean;
20
+ unsupportedReason(): string;
21
+ isInstalled(): boolean;
22
+ isRunning(): boolean;
23
+ ensureReady(onProgress: (progress: Progress) => void, signal?: AbortSignal): Promise<void>;
24
+ baseUrl(): string;
25
+ addModel(repoId: string, onProgress: (progress: Progress) => void, signal?: AbortSignal): Promise<void>;
26
+ listModels(signal?: AbortSignal): Promise<LocalModelInfo[]>;
27
+ stop(): Promise<void>;
28
+ }