whale-code 6.4.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.
Files changed (319) hide show
  1. package/README.md +95 -0
  2. package/bin/swag-agent.js +9 -0
  3. package/bin/swagmanager-mcp.js +321 -0
  4. package/dist/cli/app.d.ts +26 -0
  5. package/dist/cli/app.js +64 -0
  6. package/dist/cli/chat/AgentSelector.d.ts +14 -0
  7. package/dist/cli/chat/AgentSelector.js +14 -0
  8. package/dist/cli/chat/ChatApp.d.ts +9 -0
  9. package/dist/cli/chat/ChatApp.js +267 -0
  10. package/dist/cli/chat/ChatInput.d.ts +39 -0
  11. package/dist/cli/chat/ChatInput.js +509 -0
  12. package/dist/cli/chat/MarkdownText.d.ts +10 -0
  13. package/dist/cli/chat/MarkdownText.js +20 -0
  14. package/dist/cli/chat/MessageList.d.ts +37 -0
  15. package/dist/cli/chat/MessageList.js +80 -0
  16. package/dist/cli/chat/ModelSelector.d.ts +20 -0
  17. package/dist/cli/chat/ModelSelector.js +73 -0
  18. package/dist/cli/chat/RewindViewer.d.ts +26 -0
  19. package/dist/cli/chat/RewindViewer.js +185 -0
  20. package/dist/cli/chat/StoreSelector.d.ts +14 -0
  21. package/dist/cli/chat/StoreSelector.js +24 -0
  22. package/dist/cli/chat/StreamingText.d.ts +12 -0
  23. package/dist/cli/chat/StreamingText.js +12 -0
  24. package/dist/cli/chat/SubagentPanel.d.ts +45 -0
  25. package/dist/cli/chat/SubagentPanel.js +110 -0
  26. package/dist/cli/chat/TeamPanel.d.ts +21 -0
  27. package/dist/cli/chat/TeamPanel.js +42 -0
  28. package/dist/cli/chat/ToolIndicator.d.ts +25 -0
  29. package/dist/cli/chat/ToolIndicator.js +436 -0
  30. package/dist/cli/chat/hooks/useAgentLoop.d.ts +39 -0
  31. package/dist/cli/chat/hooks/useAgentLoop.js +382 -0
  32. package/dist/cli/chat/hooks/useSlashCommands.d.ts +37 -0
  33. package/dist/cli/chat/hooks/useSlashCommands.js +387 -0
  34. package/dist/cli/commands/config-cmd.d.ts +10 -0
  35. package/dist/cli/commands/config-cmd.js +99 -0
  36. package/dist/cli/commands/doctor.d.ts +14 -0
  37. package/dist/cli/commands/doctor.js +172 -0
  38. package/dist/cli/commands/init.d.ts +16 -0
  39. package/dist/cli/commands/init.js +278 -0
  40. package/dist/cli/commands/mcp.d.ts +12 -0
  41. package/dist/cli/commands/mcp.js +162 -0
  42. package/dist/cli/login/LoginApp.d.ts +7 -0
  43. package/dist/cli/login/LoginApp.js +157 -0
  44. package/dist/cli/print-mode.d.ts +31 -0
  45. package/dist/cli/print-mode.js +202 -0
  46. package/dist/cli/serve-mode.d.ts +37 -0
  47. package/dist/cli/serve-mode.js +636 -0
  48. package/dist/cli/services/agent-definitions.d.ts +25 -0
  49. package/dist/cli/services/agent-definitions.js +91 -0
  50. package/dist/cli/services/agent-events.d.ts +178 -0
  51. package/dist/cli/services/agent-events.js +175 -0
  52. package/dist/cli/services/agent-loop.d.ts +90 -0
  53. package/dist/cli/services/agent-loop.js +762 -0
  54. package/dist/cli/services/agent-worker-base.d.ts +97 -0
  55. package/dist/cli/services/agent-worker-base.js +220 -0
  56. package/dist/cli/services/auth-service.d.ts +30 -0
  57. package/dist/cli/services/auth-service.js +160 -0
  58. package/dist/cli/services/background-processes.d.ts +126 -0
  59. package/dist/cli/services/background-processes.js +318 -0
  60. package/dist/cli/services/browser-auth.d.ts +24 -0
  61. package/dist/cli/services/browser-auth.js +180 -0
  62. package/dist/cli/services/claude-md-loader.d.ts +16 -0
  63. package/dist/cli/services/claude-md-loader.js +58 -0
  64. package/dist/cli/services/config-store.d.ts +47 -0
  65. package/dist/cli/services/config-store.js +79 -0
  66. package/dist/cli/services/debug-log.d.ts +10 -0
  67. package/dist/cli/services/debug-log.js +52 -0
  68. package/dist/cli/services/error-logger.d.ts +58 -0
  69. package/dist/cli/services/error-logger.js +269 -0
  70. package/dist/cli/services/file-history.d.ts +21 -0
  71. package/dist/cli/services/file-history.js +83 -0
  72. package/dist/cli/services/format-server-response.d.ts +16 -0
  73. package/dist/cli/services/format-server-response.js +440 -0
  74. package/dist/cli/services/git-context.d.ts +11 -0
  75. package/dist/cli/services/git-context.js +66 -0
  76. package/dist/cli/services/hooks.d.ts +85 -0
  77. package/dist/cli/services/hooks.js +258 -0
  78. package/dist/cli/services/interactive-tools.d.ts +125 -0
  79. package/dist/cli/services/interactive-tools.js +260 -0
  80. package/dist/cli/services/keybinding-manager.d.ts +52 -0
  81. package/dist/cli/services/keybinding-manager.js +115 -0
  82. package/dist/cli/services/local-tools.d.ts +22 -0
  83. package/dist/cli/services/local-tools.js +697 -0
  84. package/dist/cli/services/lsp-manager.d.ts +18 -0
  85. package/dist/cli/services/lsp-manager.js +717 -0
  86. package/dist/cli/services/mcp-client.d.ts +48 -0
  87. package/dist/cli/services/mcp-client.js +157 -0
  88. package/dist/cli/services/memory-manager.d.ts +16 -0
  89. package/dist/cli/services/memory-manager.js +57 -0
  90. package/dist/cli/services/model-manager.d.ts +18 -0
  91. package/dist/cli/services/model-manager.js +71 -0
  92. package/dist/cli/services/model-router.d.ts +26 -0
  93. package/dist/cli/services/model-router.js +149 -0
  94. package/dist/cli/services/permission-modes.d.ts +13 -0
  95. package/dist/cli/services/permission-modes.js +43 -0
  96. package/dist/cli/services/rewind.d.ts +84 -0
  97. package/dist/cli/services/rewind.js +194 -0
  98. package/dist/cli/services/ripgrep.d.ts +28 -0
  99. package/dist/cli/services/ripgrep.js +138 -0
  100. package/dist/cli/services/sandbox.d.ts +29 -0
  101. package/dist/cli/services/sandbox.js +97 -0
  102. package/dist/cli/services/server-tools.d.ts +61 -0
  103. package/dist/cli/services/server-tools.js +543 -0
  104. package/dist/cli/services/session-persistence.d.ts +23 -0
  105. package/dist/cli/services/session-persistence.js +99 -0
  106. package/dist/cli/services/subagent-worker.d.ts +19 -0
  107. package/dist/cli/services/subagent-worker.js +41 -0
  108. package/dist/cli/services/subagent.d.ts +47 -0
  109. package/dist/cli/services/subagent.js +647 -0
  110. package/dist/cli/services/system-prompt.d.ts +7 -0
  111. package/dist/cli/services/system-prompt.js +198 -0
  112. package/dist/cli/services/team-lead.d.ts +73 -0
  113. package/dist/cli/services/team-lead.js +512 -0
  114. package/dist/cli/services/team-state.d.ts +77 -0
  115. package/dist/cli/services/team-state.js +398 -0
  116. package/dist/cli/services/teammate.d.ts +31 -0
  117. package/dist/cli/services/teammate.js +689 -0
  118. package/dist/cli/services/telemetry.d.ts +61 -0
  119. package/dist/cli/services/telemetry.js +209 -0
  120. package/dist/cli/services/tools/agent-tools.d.ts +14 -0
  121. package/dist/cli/services/tools/agent-tools.js +347 -0
  122. package/dist/cli/services/tools/file-ops.d.ts +15 -0
  123. package/dist/cli/services/tools/file-ops.js +487 -0
  124. package/dist/cli/services/tools/search-tools.d.ts +8 -0
  125. package/dist/cli/services/tools/search-tools.js +186 -0
  126. package/dist/cli/services/tools/shell-exec.d.ts +10 -0
  127. package/dist/cli/services/tools/shell-exec.js +168 -0
  128. package/dist/cli/services/tools/task-manager.d.ts +28 -0
  129. package/dist/cli/services/tools/task-manager.js +209 -0
  130. package/dist/cli/services/tools/web-tools.d.ts +11 -0
  131. package/dist/cli/services/tools/web-tools.js +395 -0
  132. package/dist/cli/setup/SetupApp.d.ts +9 -0
  133. package/dist/cli/setup/SetupApp.js +191 -0
  134. package/dist/cli/shared/MatrixIntro.d.ts +4 -0
  135. package/dist/cli/shared/MatrixIntro.js +83 -0
  136. package/dist/cli/shared/Theme.d.ts +74 -0
  137. package/dist/cli/shared/Theme.js +127 -0
  138. package/dist/cli/shared/WhaleBanner.d.ts +10 -0
  139. package/dist/cli/shared/WhaleBanner.js +12 -0
  140. package/dist/cli/shared/markdown.d.ts +21 -0
  141. package/dist/cli/shared/markdown.js +756 -0
  142. package/dist/cli/status/StatusApp.d.ts +4 -0
  143. package/dist/cli/status/StatusApp.js +105 -0
  144. package/dist/cli/stores/StoreApp.d.ts +7 -0
  145. package/dist/cli/stores/StoreApp.js +81 -0
  146. package/dist/index.d.ts +15 -0
  147. package/dist/index.js +538 -0
  148. package/dist/local-agent/connection.d.ts +48 -0
  149. package/dist/local-agent/connection.js +332 -0
  150. package/dist/local-agent/discovery.d.ts +18 -0
  151. package/dist/local-agent/discovery.js +146 -0
  152. package/dist/local-agent/executor.d.ts +34 -0
  153. package/dist/local-agent/executor.js +241 -0
  154. package/dist/local-agent/index.d.ts +14 -0
  155. package/dist/local-agent/index.js +198 -0
  156. package/dist/node/adapters/base.d.ts +35 -0
  157. package/dist/node/adapters/base.js +10 -0
  158. package/dist/node/adapters/discord.d.ts +29 -0
  159. package/dist/node/adapters/discord.js +299 -0
  160. package/dist/node/adapters/email.d.ts +23 -0
  161. package/dist/node/adapters/email.js +218 -0
  162. package/dist/node/adapters/imessage.d.ts +17 -0
  163. package/dist/node/adapters/imessage.js +118 -0
  164. package/dist/node/adapters/slack.d.ts +26 -0
  165. package/dist/node/adapters/slack.js +259 -0
  166. package/dist/node/adapters/sms.d.ts +23 -0
  167. package/dist/node/adapters/sms.js +161 -0
  168. package/dist/node/adapters/telegram.d.ts +17 -0
  169. package/dist/node/adapters/telegram.js +101 -0
  170. package/dist/node/adapters/webchat.d.ts +27 -0
  171. package/dist/node/adapters/webchat.js +160 -0
  172. package/dist/node/adapters/whatsapp.d.ts +28 -0
  173. package/dist/node/adapters/whatsapp.js +230 -0
  174. package/dist/node/cli.d.ts +2 -0
  175. package/dist/node/cli.js +325 -0
  176. package/dist/node/config.d.ts +17 -0
  177. package/dist/node/config.js +31 -0
  178. package/dist/node/runtime.d.ts +50 -0
  179. package/dist/node/runtime.js +351 -0
  180. package/dist/server/handlers/__test-utils__/mock-supabase.d.ts +11 -0
  181. package/dist/server/handlers/__test-utils__/mock-supabase.js +393 -0
  182. package/dist/server/handlers/analytics.d.ts +17 -0
  183. package/dist/server/handlers/analytics.js +266 -0
  184. package/dist/server/handlers/api-keys.d.ts +6 -0
  185. package/dist/server/handlers/api-keys.js +221 -0
  186. package/dist/server/handlers/billing.d.ts +33 -0
  187. package/dist/server/handlers/billing.js +272 -0
  188. package/dist/server/handlers/browser.d.ts +10 -0
  189. package/dist/server/handlers/browser.js +517 -0
  190. package/dist/server/handlers/catalog.d.ts +99 -0
  191. package/dist/server/handlers/catalog.js +976 -0
  192. package/dist/server/handlers/comms.d.ts +254 -0
  193. package/dist/server/handlers/comms.js +588 -0
  194. package/dist/server/handlers/creations.d.ts +6 -0
  195. package/dist/server/handlers/creations.js +479 -0
  196. package/dist/server/handlers/crm.d.ts +89 -0
  197. package/dist/server/handlers/crm.js +538 -0
  198. package/dist/server/handlers/discovery.d.ts +6 -0
  199. package/dist/server/handlers/discovery.js +288 -0
  200. package/dist/server/handlers/embeddings.d.ts +92 -0
  201. package/dist/server/handlers/embeddings.js +197 -0
  202. package/dist/server/handlers/enrichment.d.ts +8 -0
  203. package/dist/server/handlers/enrichment.js +768 -0
  204. package/dist/server/handlers/image-gen.d.ts +6 -0
  205. package/dist/server/handlers/image-gen.js +409 -0
  206. package/dist/server/handlers/inventory.d.ts +319 -0
  207. package/dist/server/handlers/inventory.js +447 -0
  208. package/dist/server/handlers/kali.d.ts +10 -0
  209. package/dist/server/handlers/kali.js +210 -0
  210. package/dist/server/handlers/llm-providers.d.ts +6 -0
  211. package/dist/server/handlers/llm-providers.js +673 -0
  212. package/dist/server/handlers/local-agent.d.ts +6 -0
  213. package/dist/server/handlers/local-agent.js +118 -0
  214. package/dist/server/handlers/meta-ads.d.ts +111 -0
  215. package/dist/server/handlers/meta-ads.js +2279 -0
  216. package/dist/server/handlers/nodes.d.ts +33 -0
  217. package/dist/server/handlers/nodes.js +699 -0
  218. package/dist/server/handlers/operations.d.ts +138 -0
  219. package/dist/server/handlers/operations.js +131 -0
  220. package/dist/server/handlers/platform.d.ts +23 -0
  221. package/dist/server/handlers/platform.js +227 -0
  222. package/dist/server/handlers/supply-chain.d.ts +19 -0
  223. package/dist/server/handlers/supply-chain.js +327 -0
  224. package/dist/server/handlers/transcription.d.ts +17 -0
  225. package/dist/server/handlers/transcription.js +121 -0
  226. package/dist/server/handlers/video-gen.d.ts +6 -0
  227. package/dist/server/handlers/video-gen.js +466 -0
  228. package/dist/server/handlers/voice.d.ts +8 -0
  229. package/dist/server/handlers/voice.js +1146 -0
  230. package/dist/server/handlers/workflow-steps.d.ts +86 -0
  231. package/dist/server/handlers/workflow-steps.js +2349 -0
  232. package/dist/server/handlers/workflows.d.ts +7 -0
  233. package/dist/server/handlers/workflows.js +989 -0
  234. package/dist/server/index.d.ts +1 -0
  235. package/dist/server/index.js +2427 -0
  236. package/dist/server/lib/batch-client.d.ts +80 -0
  237. package/dist/server/lib/batch-client.js +467 -0
  238. package/dist/server/lib/code-worker-pool.d.ts +31 -0
  239. package/dist/server/lib/code-worker-pool.js +224 -0
  240. package/dist/server/lib/code-worker.d.ts +1 -0
  241. package/dist/server/lib/code-worker.js +188 -0
  242. package/dist/server/lib/compaction-service.d.ts +32 -0
  243. package/dist/server/lib/compaction-service.js +162 -0
  244. package/dist/server/lib/logger.d.ts +19 -0
  245. package/dist/server/lib/logger.js +46 -0
  246. package/dist/server/lib/otel.d.ts +38 -0
  247. package/dist/server/lib/otel.js +126 -0
  248. package/dist/server/lib/pg-rate-limiter.d.ts +21 -0
  249. package/dist/server/lib/pg-rate-limiter.js +86 -0
  250. package/dist/server/lib/prompt-sanitizer.d.ts +37 -0
  251. package/dist/server/lib/prompt-sanitizer.js +177 -0
  252. package/dist/server/lib/provider-capabilities.d.ts +85 -0
  253. package/dist/server/lib/provider-capabilities.js +190 -0
  254. package/dist/server/lib/provider-failover.d.ts +74 -0
  255. package/dist/server/lib/provider-failover.js +210 -0
  256. package/dist/server/lib/rate-limiter.d.ts +39 -0
  257. package/dist/server/lib/rate-limiter.js +147 -0
  258. package/dist/server/lib/server-agent-loop.d.ts +107 -0
  259. package/dist/server/lib/server-agent-loop.js +667 -0
  260. package/dist/server/lib/server-subagent.d.ts +78 -0
  261. package/dist/server/lib/server-subagent.js +203 -0
  262. package/dist/server/lib/session-checkpoint.d.ts +51 -0
  263. package/dist/server/lib/session-checkpoint.js +145 -0
  264. package/dist/server/lib/ssrf-guard.d.ts +13 -0
  265. package/dist/server/lib/ssrf-guard.js +240 -0
  266. package/dist/server/lib/supabase-client.d.ts +7 -0
  267. package/dist/server/lib/supabase-client.js +78 -0
  268. package/dist/server/lib/template-resolver.d.ts +31 -0
  269. package/dist/server/lib/template-resolver.js +215 -0
  270. package/dist/server/lib/utils.d.ts +16 -0
  271. package/dist/server/lib/utils.js +147 -0
  272. package/dist/server/local-agent-gateway.d.ts +82 -0
  273. package/dist/server/local-agent-gateway.js +426 -0
  274. package/dist/server/providers/anthropic.d.ts +20 -0
  275. package/dist/server/providers/anthropic.js +199 -0
  276. package/dist/server/providers/bedrock.d.ts +20 -0
  277. package/dist/server/providers/bedrock.js +194 -0
  278. package/dist/server/providers/gemini.d.ts +24 -0
  279. package/dist/server/providers/gemini.js +486 -0
  280. package/dist/server/providers/openai.d.ts +24 -0
  281. package/dist/server/providers/openai.js +522 -0
  282. package/dist/server/providers/registry.d.ts +32 -0
  283. package/dist/server/providers/registry.js +58 -0
  284. package/dist/server/providers/shared.d.ts +32 -0
  285. package/dist/server/providers/shared.js +124 -0
  286. package/dist/server/providers/types.d.ts +92 -0
  287. package/dist/server/providers/types.js +12 -0
  288. package/dist/server/proxy-handlers.d.ts +6 -0
  289. package/dist/server/proxy-handlers.js +89 -0
  290. package/dist/server/tool-router.d.ts +149 -0
  291. package/dist/server/tool-router.js +803 -0
  292. package/dist/server/validation.d.ts +24 -0
  293. package/dist/server/validation.js +301 -0
  294. package/dist/server/worker.d.ts +19 -0
  295. package/dist/server/worker.js +201 -0
  296. package/dist/setup.d.ts +8 -0
  297. package/dist/setup.js +181 -0
  298. package/dist/shared/agent-core.d.ts +157 -0
  299. package/dist/shared/agent-core.js +534 -0
  300. package/dist/shared/anthropic-types.d.ts +105 -0
  301. package/dist/shared/anthropic-types.js +7 -0
  302. package/dist/shared/api-client.d.ts +90 -0
  303. package/dist/shared/api-client.js +379 -0
  304. package/dist/shared/constants.d.ts +33 -0
  305. package/dist/shared/constants.js +80 -0
  306. package/dist/shared/sse-parser.d.ts +26 -0
  307. package/dist/shared/sse-parser.js +259 -0
  308. package/dist/shared/tool-dispatch.d.ts +52 -0
  309. package/dist/shared/tool-dispatch.js +191 -0
  310. package/dist/shared/types.d.ts +72 -0
  311. package/dist/shared/types.js +7 -0
  312. package/dist/updater.d.ts +25 -0
  313. package/dist/updater.js +140 -0
  314. package/dist/webchat/widget.d.ts +0 -0
  315. package/dist/webchat/widget.js +397 -0
  316. package/package.json +95 -0
  317. package/src/cli/services/builtin-skills/commit.md +19 -0
  318. package/src/cli/services/builtin-skills/review-pr.md +21 -0
  319. package/src/cli/services/builtin-skills/review.md +18 -0
package/dist/index.js ADDED
@@ -0,0 +1,538 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * SwagManager MCP Server
4
+ *
5
+ * Thin proxy that connects any MCP client (Claude Code, Cursor, etc.)
6
+ * to the SwagManager platform.
7
+ *
8
+ * - Tool DEFINITIONS loaded from ai_tool_registry (database-driven)
9
+ * - Tool EXECUTION proxied to the Fly.io server (server-driven)
10
+ * - Auth via browser-based OAuth (`whale_login` tool) or env vars
11
+ *
12
+ * When tools change on the server, this MCP server automatically picks
13
+ * them up — no code changes, no rebuild, no redeploy.
14
+ */
15
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
16
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
17
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
18
+ import { createClient } from "@supabase/supabase-js";
19
+ import { createRequire } from "module";
20
+ import { startUpdateLoop } from "./updater.js";
21
+ import { resolveConfig, loadConfig, updateConfig } from "./cli/services/config-store.js";
22
+ import { getValidToken, getStoresForUser, selectStore } from "./cli/services/auth-service.js";
23
+ import { signInWithBrowser } from "./cli/services/browser-auth.js";
24
+ import { LocalAgentConnection } from "./local-agent/connection.js";
25
+ import { initErrorLogger, setErrorLoggerUser, captureError } from "./cli/services/error-logger.js";
26
+ const require = createRequire(import.meta.url);
27
+ const PKG_VERSION = require("../package.json").version;
28
+ // ============================================================================
29
+ // CONFIGURATION — env vars → ~/.swagmanager/config.json (set by `whale login`)
30
+ // ============================================================================
31
+ let config = resolveConfig();
32
+ let rawConfig = loadConfig();
33
+ const SUPABASE_URL = config.supabaseUrl;
34
+ let INITIAL_SUPABASE_KEY = config.supabaseKey;
35
+ let STORE_ID = config.storeId;
36
+ const IS_SERVICE_ROLE = !!process.env.SUPABASE_SERVICE_ROLE_KEY;
37
+ // User identity — loaded from config (set by `whale login`)
38
+ let USER_ID = rawConfig.user_id || null;
39
+ let USER_EMAIL = rawConfig.email || null;
40
+ // ============================================================================
41
+ // AUTH STATE — tracks whether we can make authenticated API calls
42
+ // ============================================================================
43
+ let isAuthenticated = !!(SUPABASE_URL && INITIAL_SUPABASE_KEY);
44
+ let supabase = null;
45
+ if (isAuthenticated) {
46
+ supabase = createClient(SUPABASE_URL, INITIAL_SUPABASE_KEY);
47
+ if (IS_SERVICE_ROLE && !USER_ID) {
48
+ console.error("[MCP] WARNING: Using service role key without user login. Tools will work but store scoping is disabled.");
49
+ console.error("[MCP] For proper store-scoped access, use the whale_login tool or run `whale login` first.");
50
+ }
51
+ }
52
+ else {
53
+ console.error("[MCP] Not authenticated. Use the whale_login tool to sign in, or run `whale login` in a terminal.");
54
+ }
55
+ /** Reload config from disk and update in-memory state */
56
+ function reloadConfig() {
57
+ config = resolveConfig();
58
+ rawConfig = loadConfig();
59
+ INITIAL_SUPABASE_KEY = config.supabaseKey;
60
+ STORE_ID = config.storeId;
61
+ USER_ID = rawConfig.user_id || null;
62
+ USER_EMAIL = rawConfig.email || null;
63
+ if (config.supabaseUrl && config.supabaseKey) {
64
+ supabase = createClient(config.supabaseUrl, config.supabaseKey);
65
+ isAuthenticated = true;
66
+ }
67
+ }
68
+ /** Get a fresh auth token — service role keys are static, JWTs auto-refresh */
69
+ async function getAuthToken() {
70
+ if (IS_SERVICE_ROLE)
71
+ return INITIAL_SUPABASE_KEY;
72
+ // JWT user — refresh if needed
73
+ const token = await getValidToken();
74
+ return token || INITIAL_SUPABASE_KEY;
75
+ }
76
+ // Fly.io server URL for tool execution
77
+ const SERVER_URL = config.serverUrl;
78
+ // Session ID for tracing — links all tool calls in one conversation
79
+ const SESSION_ID = crypto.randomUUID();
80
+ const WHALE_LOGIN_TOOL = {
81
+ name: "whale_login",
82
+ description: "Log in to your WhaleTools account. Opens a browser for secure authentication. Use this when you see 'not authenticated' errors.",
83
+ inputSchema: { type: "object", properties: {} },
84
+ };
85
+ const WHALE_STATUS_TOOL = {
86
+ name: "whale_status",
87
+ description: "Check current authentication status, active store, and account info.",
88
+ inputSchema: { type: "object", properties: {} },
89
+ };
90
+ const WHALE_STORES_TOOL = {
91
+ name: "whale_stores",
92
+ description: "List your stores or switch the active store.",
93
+ inputSchema: {
94
+ type: "object",
95
+ properties: {
96
+ store_id: {
97
+ type: "string",
98
+ description: "Store ID to switch to. Omit to list all available stores.",
99
+ },
100
+ },
101
+ },
102
+ };
103
+ const LOCAL_TOOLS = [WHALE_LOGIN_TOOL, WHALE_STATUS_TOOL, WHALE_STORES_TOOL];
104
+ // ============================================================================
105
+ // REMOTE TOOL DEFINITIONS (loaded from database)
106
+ // ============================================================================
107
+ let toolDefinitions = [];
108
+ let toolsLoadedAt = 0;
109
+ const TOOL_CACHE_TTL = 300_000; // 5 minutes — tool defs rarely change mid-session
110
+ async function loadToolDefinitions(force = false) {
111
+ if (!isAuthenticated || !supabase)
112
+ return [];
113
+ if (!force && toolDefinitions.length > 0 && Date.now() - toolsLoadedAt < TOOL_CACHE_TTL) {
114
+ return toolDefinitions;
115
+ }
116
+ try {
117
+ const { data, error } = await supabase
118
+ .from("ai_tool_registry")
119
+ .select("name, description, definition")
120
+ .eq("is_active", true)
121
+ .neq("tool_mode", "code");
122
+ if (error) {
123
+ console.error("[MCP] Failed to load tools from registry:", error.message);
124
+ return toolDefinitions; // Return stale cache on error
125
+ }
126
+ toolDefinitions = (data || []).map(t => ({
127
+ name: t.name,
128
+ description: t.description || t.definition?.description || `Execute ${t.name}`,
129
+ inputSchema: t.definition?.input_schema || { type: "object", properties: {} }
130
+ }));
131
+ toolsLoadedAt = Date.now();
132
+ return toolDefinitions;
133
+ }
134
+ catch (err) {
135
+ console.error("[MCP] Error loading tool definitions:", err);
136
+ return toolDefinitions;
137
+ }
138
+ }
139
+ async function executeToolRemote(toolName, args, storeId, traceId) {
140
+ try {
141
+ const token = await getAuthToken();
142
+ const response = await fetch(SERVER_URL, {
143
+ method: "POST",
144
+ headers: {
145
+ "Content-Type": "application/json",
146
+ "Authorization": `Bearer ${token}`,
147
+ },
148
+ body: JSON.stringify({
149
+ mode: "tool",
150
+ tool_name: toolName,
151
+ args,
152
+ store_id: storeId,
153
+ conversation_id: SESSION_ID,
154
+ trace_id: traceId,
155
+ userId: USER_ID,
156
+ userEmail: USER_EMAIL,
157
+ source: "whale-code-mcp",
158
+ }),
159
+ });
160
+ const result = await response.json();
161
+ return result;
162
+ }
163
+ catch (err) {
164
+ return {
165
+ success: false,
166
+ error: `Server tool call failed: ${err.message}`,
167
+ };
168
+ }
169
+ }
170
+ // ============================================================================
171
+ // LOCAL TOOL HANDLERS
172
+ // ============================================================================
173
+ async function handleWhaleLogin() {
174
+ try {
175
+ const result = await signInWithBrowser(config.platformUrl, {
176
+ onBrowserOpening: (url) => {
177
+ console.error(`[MCP] Opening browser for login: ${url}`);
178
+ },
179
+ onWaitingForCallback: () => {
180
+ console.error("[MCP] Waiting for browser login...");
181
+ },
182
+ onExchangingCode: () => {
183
+ console.error("[MCP] Exchanging auth code...");
184
+ },
185
+ });
186
+ if (!result.success) {
187
+ return {
188
+ content: [{ type: "text", text: `Login failed: ${result.error}\n\nYou can also try running \`whale login\` in a terminal.` }],
189
+ isError: true,
190
+ };
191
+ }
192
+ // Reload config after successful login
193
+ reloadConfig();
194
+ // Force-reload tool definitions
195
+ const tools = await loadToolDefinitions(true);
196
+ // Notify MCP client that tools have changed
197
+ server.notification({ method: "notifications/tools/list_changed" });
198
+ const cfg = result.config;
199
+ let msg = `Logged in as ${cfg.email || "unknown"}`;
200
+ if (cfg.store_name || cfg.store_id) {
201
+ msg += `\nActive store: ${cfg.store_name || cfg.store_id}`;
202
+ }
203
+ msg += `\n${tools.length} tools now available.`;
204
+ if (result.stores && result.stores.length > 1) {
205
+ msg += `\n\nYou have ${result.stores.length} stores. Use whale_stores to switch:`;
206
+ for (const s of result.stores) {
207
+ msg += `\n - ${s.name} (${s.id})`;
208
+ }
209
+ }
210
+ return { content: [{ type: "text", text: msg }] };
211
+ }
212
+ catch (err) {
213
+ return {
214
+ content: [{ type: "text", text: `Login error: ${err instanceof Error ? err.message : String(err)}` }],
215
+ isError: true,
216
+ };
217
+ }
218
+ }
219
+ function handleWhaleStatus() {
220
+ const cfg = loadConfig();
221
+ const lines = [];
222
+ if (isAuthenticated) {
223
+ lines.push("Authenticated: yes");
224
+ lines.push(`User: ${cfg.email || "(unknown)"}`);
225
+ lines.push(`User ID: ${cfg.user_id || "(unknown)"}`);
226
+ lines.push(`Store: ${cfg.store_name || cfg.store_id || "(none selected)"}`);
227
+ lines.push(`Auth method: ${IS_SERVICE_ROLE ? "service role key (env var)" : "user JWT (whale login)"}`);
228
+ if (cfg.expires_at) {
229
+ const exp = new Date(cfg.expires_at * 1000);
230
+ lines.push(`Token expires: ${exp.toISOString()}`);
231
+ }
232
+ }
233
+ else {
234
+ lines.push("Authenticated: no");
235
+ lines.push("Use whale_login to sign in.");
236
+ }
237
+ lines.push(`MCP version: ${PKG_VERSION}`);
238
+ lines.push(`Server: ${SERVER_URL}`);
239
+ lines.push(`Tools loaded: ${toolDefinitions.length}`);
240
+ return { content: [{ type: "text", text: lines.join("\n") }] };
241
+ }
242
+ async function handleWhaleStores(args) {
243
+ if (!isAuthenticated) {
244
+ return {
245
+ content: [{ type: "text", text: "Not authenticated. Use whale_login first." }],
246
+ isError: true,
247
+ };
248
+ }
249
+ const switchToId = args.store_id;
250
+ if (switchToId) {
251
+ // Switch store
252
+ // Verify the store exists for this user
253
+ const token = await getAuthToken();
254
+ const stores = await getStoresForUser(token, USER_ID || "");
255
+ const target = stores.find(s => s.id === switchToId);
256
+ if (!target) {
257
+ return {
258
+ content: [{ type: "text", text: `Store not found: ${switchToId}\nAvailable stores: ${stores.map(s => `${s.name} (${s.id})`).join(", ")}` }],
259
+ isError: true,
260
+ };
261
+ }
262
+ selectStore(target.id, target.name);
263
+ reloadConfig();
264
+ // Notify MCP client that tools may have changed context
265
+ server.notification({ method: "notifications/tools/list_changed" });
266
+ return {
267
+ content: [{ type: "text", text: `Switched to store: ${target.name} (${target.id})` }],
268
+ };
269
+ }
270
+ // List stores
271
+ const token = await getAuthToken();
272
+ const stores = await getStoresForUser(token, USER_ID || "");
273
+ if (stores.length === 0) {
274
+ return { content: [{ type: "text", text: "No stores found for your account." }] };
275
+ }
276
+ const cfg = loadConfig();
277
+ const lines = ["Your stores:"];
278
+ for (const s of stores) {
279
+ const active = s.id === cfg.store_id ? " (active)" : "";
280
+ lines.push(` - ${s.name}${s.slug ? ` [${s.slug}]` : ""}: ${s.id}${active}`);
281
+ }
282
+ lines.push("\nTo switch: use whale_stores with store_id parameter");
283
+ return { content: [{ type: "text", text: lines.join("\n") }] };
284
+ }
285
+ // ============================================================================
286
+ // MCP SERVER
287
+ // ============================================================================
288
+ const server = new Server({ name: "whale", version: PKG_VERSION }, { capabilities: { tools: {} } });
289
+ // List available tools — local auth tools always present; remote tools when authenticated
290
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
291
+ if (!isAuthenticated) {
292
+ console.error(`[MCP] Returning ${LOCAL_TOOLS.length} tools (not authenticated)`);
293
+ return {
294
+ tools: LOCAL_TOOLS.map(t => ({
295
+ name: t.name,
296
+ description: t.description,
297
+ inputSchema: t.inputSchema,
298
+ })),
299
+ };
300
+ }
301
+ const remoteTools = await loadToolDefinitions();
302
+ const allTools = [...LOCAL_TOOLS, ...remoteTools];
303
+ console.error(`[MCP] Returning ${allTools.length} tools (${LOCAL_TOOLS.length} local + ${remoteTools.length} remote)`);
304
+ return {
305
+ tools: allTools.map(t => ({
306
+ name: t.name,
307
+ description: t.description,
308
+ inputSchema: t.inputSchema,
309
+ })),
310
+ };
311
+ });
312
+ // Execute a tool — local auth tools handled here, everything else proxied to Fly.io
313
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
314
+ const toolName = request.params.name;
315
+ const toolArgs = (request.params.arguments || {});
316
+ // ── Local tools (handled in-process) ──
317
+ if (toolName === "whale_login") {
318
+ return handleWhaleLogin();
319
+ }
320
+ if (toolName === "whale_status") {
321
+ return handleWhaleStatus();
322
+ }
323
+ if (toolName === "whale_stores") {
324
+ return handleWhaleStores(toolArgs);
325
+ }
326
+ // ── Auth gate for remote tools ──
327
+ if (!isAuthenticated) {
328
+ return {
329
+ content: [{
330
+ type: "text",
331
+ text: "Not authenticated. Please use the whale_login tool first to sign in to your WhaleTools account.",
332
+ }],
333
+ isError: true,
334
+ };
335
+ }
336
+ // ── Remote tool execution ──
337
+ const startTime = Date.now();
338
+ const traceId = crypto.randomUUID();
339
+ // Allow tool-level store_id override (e.g. multi-store users switching context)
340
+ const effectiveStoreId = toolArgs.store_id || STORE_ID || undefined;
341
+ if (toolArgs.store_id) {
342
+ delete toolArgs.store_id; // Don't send as a tool arg — it's a routing param
343
+ }
344
+ console.error(`[MCP] Executing: ${toolName} → Fly.io [${traceId.slice(0, 8)}] store=${effectiveStoreId?.slice(0, 8) || "none"}`);
345
+ const result = await executeToolRemote(toolName, toolArgs, effectiveStoreId, traceId);
346
+ const durationMs = Date.now() - startTime;
347
+ // Fire-and-forget telemetry
348
+ logMcpToolCall(toolName, toolArgs.action, result.success, durationMs, traceId, result.error).catch(() => { });
349
+ if (result.success) {
350
+ return {
351
+ content: [{
352
+ type: "text",
353
+ text: typeof result.data === "string"
354
+ ? result.data
355
+ : JSON.stringify(result.data, null, 2),
356
+ }],
357
+ };
358
+ }
359
+ else {
360
+ return {
361
+ content: [{
362
+ type: "text",
363
+ text: JSON.stringify({ error: result.error }),
364
+ }],
365
+ isError: true,
366
+ };
367
+ }
368
+ });
369
+ /**
370
+ * Fire-and-forget MCP proxy telemetry — logs to audit_logs as a third service
371
+ * ("mcp-proxy") alongside "whale-cli" and "agent-server", capturing the full
372
+ * network round-trip latency from MCP client → Fly.io server → response.
373
+ */
374
+ async function logMcpToolCall(toolName, action, success, durationMs, traceId, error) {
375
+ if (!supabase)
376
+ return;
377
+ try {
378
+ const now = new Date();
379
+ const startTime = new Date(now.getTime() - durationMs);
380
+ const bytes = new Uint8Array(8);
381
+ crypto.getRandomValues(bytes);
382
+ const spanId = Array.from(bytes).map(b => b.toString(16).padStart(2, "0")).join("");
383
+ await supabase.from("audit_logs").insert({
384
+ action: `mcp.tool.${toolName}${action ? `.${action}` : ""}`,
385
+ severity: success ? "info" : "error",
386
+ store_id: STORE_ID || null,
387
+ user_id: USER_ID,
388
+ user_email: USER_EMAIL,
389
+ resource_type: "whale_code_mcp",
390
+ resource_id: toolName,
391
+ request_id: traceId,
392
+ conversation_id: SESSION_ID,
393
+ source: "whale_code_mcp",
394
+ details: {
395
+ session_id: SESSION_ID,
396
+ source: "whale_code_mcp",
397
+ tool_name: toolName,
398
+ action: action || null,
399
+ },
400
+ error_message: error || null,
401
+ duration_ms: durationMs,
402
+ trace_id: traceId,
403
+ span_id: spanId,
404
+ span_kind: "CLIENT",
405
+ service_name: "whale-code-mcp",
406
+ service_version: PKG_VERSION,
407
+ status_code: success ? "OK" : "ERROR",
408
+ start_time: startTime.toISOString(),
409
+ end_time: now.toISOString(),
410
+ });
411
+ }
412
+ catch {
413
+ // Telemetry must never break tool execution
414
+ }
415
+ }
416
+ // ============================================================================
417
+ // LOCAL AGENT (auto-spawned — shares this process with MCP server)
418
+ // ============================================================================
419
+ let localAgent = null;
420
+ /**
421
+ * Get or create a store API key for the local agent WebSocket connection.
422
+ * Persisted to ~/.swagmanager/config.json so we reuse across restarts.
423
+ */
424
+ async function getOrCreateAgentApiKey() {
425
+ const rawConfig = loadConfig();
426
+ if (rawConfig.agent_api_key)
427
+ return rawConfig.agent_api_key;
428
+ // Auto-generate via the server's api_keys tool
429
+ try {
430
+ const result = await executeToolRemote("api_keys", {
431
+ action: "generate",
432
+ name: "local-agent-auto",
433
+ scopes: ["*"],
434
+ key_type: "live",
435
+ }, STORE_ID || undefined);
436
+ if (result.success && result.data) {
437
+ const data = result.data;
438
+ const key = data.key_value || data.api_key || data.key;
439
+ if (key) {
440
+ updateConfig({ agent_api_key: key });
441
+ console.error("[MCP] Auto-generated local agent API key");
442
+ return key;
443
+ }
444
+ }
445
+ console.error("[MCP] Failed to auto-generate agent API key:", result.error);
446
+ return null;
447
+ }
448
+ catch (err) {
449
+ console.error("[MCP] Error generating agent API key:", err.message);
450
+ return null;
451
+ }
452
+ }
453
+ /**
454
+ * Derive WebSocket URL from the HTTP server URL.
455
+ * https://whale-agent.fly.dev → wss://whale-agent.fly.dev/agent/ws
456
+ */
457
+ function getAgentWsUrl() {
458
+ const base = SERVER_URL.replace(/\/$/, "");
459
+ const wsBase = base.replace(/^https:/, "wss:").replace(/^http:/, "ws:");
460
+ return `${wsBase}/agent/ws`;
461
+ }
462
+ /**
463
+ * Start the local agent WebSocket connection in the background.
464
+ * Non-blocking — MCP server continues even if this fails.
465
+ */
466
+ async function startLocalAgent() {
467
+ const apiKey = await getOrCreateAgentApiKey();
468
+ if (!apiKey) {
469
+ console.error("[MCP] Skipping local agent — no API key available");
470
+ return;
471
+ }
472
+ const wsUrl = getAgentWsUrl();
473
+ console.error(`[MCP] Starting local agent → ${wsUrl}`);
474
+ localAgent = new LocalAgentConnection({
475
+ apiKey,
476
+ serverUrl: wsUrl,
477
+ onConnected: () => console.error("[MCP] Local agent connected — WhaleChat can now use local tools"),
478
+ onDisconnected: (reason) => console.error(`[MCP] Local agent disconnected: ${reason}`),
479
+ onError: (err) => console.error(`[MCP] Local agent error: ${err.message}`),
480
+ onLog: (msg) => console.error(msg),
481
+ });
482
+ try {
483
+ await localAgent.connect();
484
+ }
485
+ catch (err) {
486
+ console.error(`[MCP] Local agent failed to connect: ${err.message} (non-fatal)`);
487
+ }
488
+ }
489
+ // ============================================================================
490
+ // STARTUP
491
+ // ============================================================================
492
+ async function main() {
493
+ // Initialize error logging for MCP mode
494
+ initErrorLogger({ serviceName: "whale-code-mcp" });
495
+ if (USER_ID || USER_EMAIL) {
496
+ setErrorLoggerUser(USER_ID || undefined, USER_EMAIL || undefined, STORE_ID || undefined);
497
+ }
498
+ console.error(`[MCP] Whale MCP Server v${PKG_VERSION}`);
499
+ console.error(`[MCP] Supabase: ${SUPABASE_URL}`);
500
+ console.error(`[MCP] Server: ${SERVER_URL}`);
501
+ console.error(`[MCP] Store: ${STORE_ID || "(default)"}`);
502
+ console.error(`[MCP] User: ${USER_EMAIL || "(unknown)"} [${USER_ID?.slice(0, 8) || "no-id"}]`);
503
+ console.error(`[MCP] Session: ${SESSION_ID}`);
504
+ console.error(`[MCP] Authenticated: ${isAuthenticated ? "yes" : "no — use whale_login tool"}`);
505
+ // Pre-load tools from database (only if authenticated)
506
+ if (isAuthenticated) {
507
+ const tools = await loadToolDefinitions(true);
508
+ console.error(`[MCP] Loaded ${tools.length} tools from registry`);
509
+ }
510
+ else {
511
+ console.error(`[MCP] Skipping tool load — not authenticated. ${LOCAL_TOOLS.length} auth tools available.`);
512
+ }
513
+ // Start OTA update checker (non-blocking, runs in background)
514
+ startUpdateLoop(true);
515
+ // Connect via stdio
516
+ const transport = new StdioServerTransport();
517
+ await server.connect(transport);
518
+ console.error("[MCP] Ready");
519
+ // Start local agent in background — only if authenticated
520
+ if (isAuthenticated) {
521
+ startLocalAgent().catch((err) => {
522
+ console.error("[MCP] Local agent startup error:", err.message);
523
+ });
524
+ }
525
+ }
526
+ // Graceful shutdown — disconnect local agent
527
+ process.on("SIGINT", () => {
528
+ localAgent?.disconnect();
529
+ });
530
+ process.on("SIGTERM", () => {
531
+ localAgent?.disconnect();
532
+ });
533
+ main().catch((err) => {
534
+ console.error("[MCP] Fatal:", err);
535
+ captureError({ error: err instanceof Error ? err : new Error(String(err)), severity: "fatal", tags: { source: "mcp_main" } });
536
+ // Give error logger a moment to flush before exit
537
+ setTimeout(() => process.exit(1), 500);
538
+ });
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Local Agent Connection — maintains WebSocket connection to SwagManager cloud.
3
+ *
4
+ * Features:
5
+ * - Outbound WSS connection (no firewall issues)
6
+ * - Auto-reconnection with exponential backoff
7
+ * - Heartbeat/pong handling
8
+ * - Command routing to executor
9
+ */
10
+ export interface AgentConfig {
11
+ apiKey: string;
12
+ serverUrl?: string;
13
+ autoReconnect?: boolean;
14
+ onConnected?: () => void;
15
+ onDisconnected?: (reason: string) => void;
16
+ onError?: (err: Error) => void;
17
+ onLog?: (msg: string) => void;
18
+ }
19
+ export declare class LocalAgentConnection {
20
+ private ws;
21
+ private config;
22
+ private reconnectAttempts;
23
+ private reconnectTimer;
24
+ private intentionalClose;
25
+ private discoveredTools;
26
+ private _agentId;
27
+ constructor(config: AgentConfig);
28
+ /**
29
+ * Connect to the SwagManager cloud gateway.
30
+ */
31
+ connect(): Promise<void>;
32
+ /**
33
+ * Disconnect gracefully.
34
+ */
35
+ disconnect(): void;
36
+ /**
37
+ * Check if connected.
38
+ */
39
+ get connected(): boolean;
40
+ private handleMessage;
41
+ private handleExec;
42
+ private handleToolExec;
43
+ private handleDiscover;
44
+ private send;
45
+ private sendResult;
46
+ private scheduleReconnect;
47
+ private log;
48
+ }