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
@@ -0,0 +1,318 @@
1
+ /**
2
+ * Background Process Management — Claude Code-style async shell execution
3
+ *
4
+ * Enables running long-running processes (dev servers, watchers, builds)
5
+ * without blocking the agent loop.
6
+ *
7
+ * Tools:
8
+ * - run_command with run_in_background: true
9
+ * - bash_output: Read output from running/completed process
10
+ * - kill_shell: Terminate a background process
11
+ */
12
+ import { spawn } from "child_process";
13
+ import { readFileSync, existsSync } from "fs";
14
+ // ============================================================================
15
+ // PROCESS REGISTRY — in-memory store of running/completed processes
16
+ // ============================================================================
17
+ const processes = new Map();
18
+ const MAX_BUFFER_LINES = 10000;
19
+ const MAX_PROCESSES = 20;
20
+ // ============================================================================
21
+ // HELPERS
22
+ // ============================================================================
23
+ function generateProcessId() {
24
+ return `shell-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
25
+ }
26
+ function cleanOldProcesses() {
27
+ // Remove oldest completed/failed processes if we're at capacity
28
+ const procs = Array.from(processes.values());
29
+ const completed = procs
30
+ .filter((p) => p.status !== "running")
31
+ .sort((a, b) => a.startedAt.getTime() - b.startedAt.getTime());
32
+ while (processes.size >= MAX_PROCESSES && completed.length > 0) {
33
+ const oldest = completed.shift();
34
+ processes.delete(oldest.id);
35
+ }
36
+ }
37
+ // ============================================================================
38
+ // SPAWN BACKGROUND PROCESS
39
+ // ============================================================================
40
+ export async function spawnBackground(command, options = {}) {
41
+ cleanOldProcesses();
42
+ const id = generateProcessId();
43
+ const cwd = options.cwd || process.cwd();
44
+ const timeout = options.timeout || 600_000; // 10 minutes default
45
+ const proc = {
46
+ id,
47
+ command,
48
+ cwd,
49
+ startedAt: new Date(),
50
+ status: "running",
51
+ outputBuffer: [],
52
+ errorBuffer: [],
53
+ process: null,
54
+ lastReadIndex: 0,
55
+ lastErrorReadIndex: 0,
56
+ };
57
+ // Spawn with shell
58
+ let child;
59
+ try {
60
+ child = spawn(command, [], {
61
+ shell: true,
62
+ cwd,
63
+ env: { ...process.env, FORCE_COLOR: "0" },
64
+ stdio: ["ignore", "pipe", "pipe"],
65
+ });
66
+ }
67
+ catch (err) {
68
+ return { id, message: `Failed to spawn: ${err.message}`, status: "failed" };
69
+ }
70
+ proc.process = child;
71
+ // Capture stdout
72
+ child.stdout?.on("data", (data) => {
73
+ const lines = data.toString().split("\n");
74
+ for (const line of lines) {
75
+ if (line.trim()) {
76
+ proc.outputBuffer.push(line);
77
+ if (proc.outputBuffer.length > MAX_BUFFER_LINES) {
78
+ proc.outputBuffer.splice(0, proc.outputBuffer.length - MAX_BUFFER_LINES);
79
+ }
80
+ }
81
+ }
82
+ });
83
+ // Capture stderr
84
+ child.stderr?.on("data", (data) => {
85
+ const lines = data.toString().split("\n");
86
+ for (const line of lines) {
87
+ if (line.trim()) {
88
+ proc.errorBuffer.push(line);
89
+ if (proc.errorBuffer.length > MAX_BUFFER_LINES) {
90
+ proc.errorBuffer.splice(0, proc.errorBuffer.length - MAX_BUFFER_LINES);
91
+ }
92
+ }
93
+ }
94
+ });
95
+ // Handle exit
96
+ child.on("exit", (code) => {
97
+ proc.status = code === 0 ? "completed" : "failed";
98
+ proc.exitCode = code ?? undefined;
99
+ proc.process = null;
100
+ });
101
+ child.on("error", (err) => {
102
+ proc.status = "failed";
103
+ proc.errorBuffer.push(`Process error: ${err.message}`);
104
+ proc.process = null;
105
+ });
106
+ // Timeout kill
107
+ setTimeout(() => {
108
+ if (proc.status === "running" && proc.process) {
109
+ proc.process.kill("SIGTERM");
110
+ proc.status = "killed";
111
+ proc.errorBuffer.push(`Process killed after ${timeout}ms timeout`);
112
+ }
113
+ }, timeout);
114
+ processes.set(id, proc);
115
+ // ── Validation wait — give process 1.5s to start or fail ──
116
+ await new Promise(resolve => setTimeout(resolve, 1500));
117
+ // Build result with validation
118
+ const lines = [];
119
+ if (proc.status === "failed") {
120
+ lines.push(`✕ Process failed immediately`);
121
+ lines.push(` Command: ${command}`);
122
+ if (proc.errorBuffer.length > 0)
123
+ lines.push(` Error: ${proc.errorBuffer.join("\n ")}`);
124
+ if (proc.exitCode !== undefined)
125
+ lines.push(` Exit code: ${proc.exitCode}`);
126
+ return { id, message: lines.join("\n"), status: "failed" };
127
+ }
128
+ lines.push(`✓ Background process running`);
129
+ lines.push(` PID: ${child.pid || "?"}`);
130
+ lines.push(` ID: ${id}`);
131
+ lines.push(` Command: ${command}`);
132
+ if (proc.outputBuffer.length > 0) {
133
+ lines.push(` Initial output (${proc.outputBuffer.length} lines):`);
134
+ for (const l of proc.outputBuffer.slice(0, 8)) {
135
+ lines.push(` ${l}`);
136
+ }
137
+ if (proc.outputBuffer.length > 8)
138
+ lines.push(` ... +${proc.outputBuffer.length - 8} more lines`);
139
+ }
140
+ if (proc.errorBuffer.length > 0) {
141
+ lines.push(` Stderr (${proc.errorBuffer.length} lines):`);
142
+ for (const l of proc.errorBuffer.slice(0, 4)) {
143
+ lines.push(` ${l}`);
144
+ }
145
+ }
146
+ lines.push(` Use bash_output("${id}") to check output, kill_shell("${id}") to stop.`);
147
+ return { id, message: lines.join("\n"), status: "running" };
148
+ }
149
+ // ============================================================================
150
+ // READ OUTPUT
151
+ // ============================================================================
152
+ export function readProcessOutput(id, options = {}) {
153
+ const proc = processes.get(id);
154
+ if (!proc) {
155
+ // Try partial match
156
+ const match = Array.from(processes.keys()).find(k => k.includes(id));
157
+ if (match)
158
+ return readProcessOutput(match, options);
159
+ return { error: `Process not found: ${id}. Use list_shells to see available processes.` };
160
+ }
161
+ // Get new output since last read (separate indices for stdout/stderr)
162
+ const newStdout = proc.outputBuffer.slice(proc.lastReadIndex);
163
+ const newStderr = proc.errorBuffer.slice(proc.lastErrorReadIndex);
164
+ // Update read indices
165
+ proc.lastReadIndex = proc.outputBuffer.length;
166
+ proc.lastErrorReadIndex = proc.errorBuffer.length;
167
+ // Apply filter if provided
168
+ let filteredOutput = newStdout;
169
+ let filteredErrors = newStderr;
170
+ if (options.filter) {
171
+ try {
172
+ const regex = new RegExp(options.filter, "i");
173
+ filteredOutput = newStdout.filter((line) => regex.test(line));
174
+ filteredErrors = newStderr.filter((line) => regex.test(line));
175
+ }
176
+ catch {
177
+ // Invalid regex, return unfiltered
178
+ }
179
+ }
180
+ return {
181
+ id,
182
+ status: proc.status,
183
+ newOutput: filteredOutput.join("\n"),
184
+ newErrors: filteredErrors.join("\n"),
185
+ exitCode: proc.exitCode,
186
+ };
187
+ }
188
+ // ============================================================================
189
+ // KILL PROCESS
190
+ // ============================================================================
191
+ export function killProcess(id) {
192
+ const proc = processes.get(id);
193
+ if (!proc) {
194
+ return { success: false, message: `Process not found: ${id}` };
195
+ }
196
+ if (proc.status !== "running") {
197
+ return { success: false, message: `Process already ${proc.status}` };
198
+ }
199
+ if (proc.process) {
200
+ proc.process.kill("SIGTERM");
201
+ // Force kill after 5 seconds if still running
202
+ setTimeout(() => {
203
+ if (proc.process) {
204
+ proc.process.kill("SIGKILL");
205
+ }
206
+ }, 5000);
207
+ }
208
+ proc.status = "killed";
209
+ return { success: true, message: `Process ${id} killed` };
210
+ }
211
+ // ============================================================================
212
+ // LIST PROCESSES
213
+ // ============================================================================
214
+ export function listProcesses() {
215
+ const now = Date.now();
216
+ return Array.from(processes.values()).map((p) => {
217
+ const runtimeMs = now - p.startedAt.getTime();
218
+ const runtimeSec = Math.floor(runtimeMs / 1000);
219
+ const runtime = runtimeSec < 60
220
+ ? `${runtimeSec}s`
221
+ : runtimeSec < 3600
222
+ ? `${Math.floor(runtimeSec / 60)}m ${runtimeSec % 60}s`
223
+ : `${Math.floor(runtimeSec / 3600)}h ${Math.floor((runtimeSec % 3600) / 60)}m`;
224
+ return {
225
+ id: p.id,
226
+ pid: p.process?.pid,
227
+ command: p.command.length > 50 ? p.command.slice(0, 47) + "..." : p.command,
228
+ status: p.status,
229
+ startedAt: p.startedAt.toISOString(),
230
+ runtime,
231
+ outputLines: p.outputBuffer.length,
232
+ errorLines: p.errorBuffer.length,
233
+ };
234
+ });
235
+ }
236
+ const bgAgents = new Map();
237
+ export function registerBackgroundAgent(id, type, outputFile) {
238
+ bgAgents.set(id, { id, type, outputFile, startTime: Date.now(), status: "running" });
239
+ }
240
+ export function getAgentStatus(id) {
241
+ return bgAgents.get(id) || null;
242
+ }
243
+ export function markAgentDone(id, success) {
244
+ const agent = bgAgents.get(id);
245
+ if (agent)
246
+ agent.status = success ? "completed" : "failed";
247
+ }
248
+ export function readAgentOutput(id) {
249
+ const agent = bgAgents.get(id);
250
+ if (!agent)
251
+ return null;
252
+ try {
253
+ const content = existsSync(agent.outputFile) ? readFileSync(agent.outputFile, "utf-8") : "(no output yet)";
254
+ return { status: agent.status, output: content };
255
+ }
256
+ catch {
257
+ return { status: agent.status, output: "(failed to read output file)" };
258
+ }
259
+ }
260
+ export function listBackgroundAgents() {
261
+ return Array.from(bgAgents.values());
262
+ }
263
+ export function stopBackgroundAgent(id) {
264
+ const agent = bgAgents.get(id);
265
+ if (!agent)
266
+ return { success: false, message: `Agent not found: ${id}` };
267
+ if (agent.status !== "running")
268
+ return { success: false, message: `Agent already ${agent.status}` };
269
+ // We can't kill the async promise, but we mark it as failed so it won't be waited on
270
+ agent.status = "failed";
271
+ return { success: true, message: `Agent ${id} marked as stopped` };
272
+ }
273
+ // ============================================================================
274
+ // TOOL DEFINITIONS
275
+ // ============================================================================
276
+ export const BACKGROUND_TOOL_DEFINITIONS = [
277
+ {
278
+ name: "bash_output",
279
+ description: "Read output from a running or completed background shell process. Returns only NEW output since the last read.",
280
+ input_schema: {
281
+ type: "object",
282
+ properties: {
283
+ bash_id: {
284
+ type: "string",
285
+ description: "The process ID returned when starting the background process",
286
+ },
287
+ filter: {
288
+ type: "string",
289
+ description: "Optional regex to filter output lines (only matching lines returned)",
290
+ },
291
+ },
292
+ required: ["bash_id"],
293
+ },
294
+ },
295
+ {
296
+ name: "kill_shell",
297
+ description: "Terminate a running background shell process",
298
+ input_schema: {
299
+ type: "object",
300
+ properties: {
301
+ shell_id: {
302
+ type: "string",
303
+ description: "The process ID to kill",
304
+ },
305
+ },
306
+ required: ["shell_id"],
307
+ },
308
+ },
309
+ {
310
+ name: "list_shells",
311
+ description: "List all background shell processes (running and recent completed)",
312
+ input_schema: {
313
+ type: "object",
314
+ properties: {},
315
+ required: [],
316
+ },
317
+ },
318
+ ];
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Browser-based OAuth login for CLI and MCP
3
+ *
4
+ * Opens the user's browser to whaletools.dev/auth/cli, which handles
5
+ * email/password (and future SSO), then redirects back to a localhost
6
+ * callback with a one-time code. The CLI exchanges that code for tokens.
7
+ *
8
+ * Same pattern as: `gh auth login`, `vercel login`, `claude login`
9
+ */
10
+ import { type SwagManagerConfig } from "./config-store.js";
11
+ import type { StoreInfo } from "./auth-service.js";
12
+ export interface BrowserAuthCallbacks {
13
+ onBrowserOpening?: (url: string) => void;
14
+ onWaitingForCallback?: () => void;
15
+ onExchangingCode?: () => void;
16
+ }
17
+ export interface BrowserAuthResult {
18
+ success: boolean;
19
+ error?: string;
20
+ config?: SwagManagerConfig;
21
+ stores?: StoreInfo[];
22
+ }
23
+ export declare function openBrowser(url: string): void;
24
+ export declare function signInWithBrowser(platformUrl?: string, callbacks?: BrowserAuthCallbacks, timeoutMs?: number): Promise<BrowserAuthResult>;
@@ -0,0 +1,180 @@
1
+ /**
2
+ * Browser-based OAuth login for CLI and MCP
3
+ *
4
+ * Opens the user's browser to whaletools.dev/auth/cli, which handles
5
+ * email/password (and future SSO), then redirects back to a localhost
6
+ * callback with a one-time code. The CLI exchanges that code for tokens.
7
+ *
8
+ * Same pattern as: `gh auth login`, `vercel login`, `claude login`
9
+ */
10
+ import http from "node:http";
11
+ import { exec } from "node:child_process";
12
+ import { loadConfig, saveConfig } from "./config-store.js";
13
+ const DEFAULT_PLATFORM_URL = "https://whaletools.dev";
14
+ const DEFAULT_TIMEOUT_MS = 120_000; // 2 minutes
15
+ // ============================================================================
16
+ // OPEN BROWSER (cross-platform)
17
+ // ============================================================================
18
+ export function openBrowser(url) {
19
+ const platform = process.platform;
20
+ const cmd = platform === "darwin" ? "open" :
21
+ platform === "win32" ? "start" :
22
+ "xdg-open";
23
+ exec(`${cmd} "${url}"`, (err) => {
24
+ if (err) {
25
+ // Non-fatal — user can manually open the URL
26
+ console.error(`[browser-auth] Failed to open browser: ${err.message}`);
27
+ }
28
+ });
29
+ }
30
+ // ============================================================================
31
+ // BROWSER AUTH FLOW
32
+ // ============================================================================
33
+ export async function signInWithBrowser(platformUrl, callbacks, timeoutMs) {
34
+ const platform = platformUrl || process.env.WHALETOOLS_PLATFORM_URL || loadConfig().platform_url || DEFAULT_PLATFORM_URL;
35
+ const timeout = timeoutMs || DEFAULT_TIMEOUT_MS;
36
+ const state = crypto.randomUUID();
37
+ return new Promise((resolve) => {
38
+ let settled = false;
39
+ let timeoutId;
40
+ let httpServer;
41
+ function finish(result) {
42
+ if (settled)
43
+ return;
44
+ settled = true;
45
+ clearTimeout(timeoutId);
46
+ try {
47
+ httpServer?.close();
48
+ }
49
+ catch { }
50
+ resolve(result);
51
+ }
52
+ // Start localhost callback server on random port
53
+ httpServer = http.createServer(async (req, res) => {
54
+ const url = new URL(req.url || "/", `http://localhost`);
55
+ if (url.pathname !== "/callback") {
56
+ res.writeHead(404);
57
+ res.end("Not found");
58
+ return;
59
+ }
60
+ const code = url.searchParams.get("code");
61
+ const returnedState = url.searchParams.get("state");
62
+ // CSRF check
63
+ if (returnedState !== state) {
64
+ res.writeHead(400, { "Content-Type": "text/html" });
65
+ res.end(errorPage("Security error: state mismatch. Please try again."));
66
+ finish({ success: false, error: "State mismatch — possible CSRF attack" });
67
+ return;
68
+ }
69
+ if (!code) {
70
+ res.writeHead(400, { "Content-Type": "text/html" });
71
+ res.end(errorPage("Missing auth code. Please try again."));
72
+ finish({ success: false, error: "No auth code received" });
73
+ return;
74
+ }
75
+ // Show success page immediately (before exchange — reduces perceived latency)
76
+ res.writeHead(200, { "Content-Type": "text/html" });
77
+ res.end(successPage());
78
+ callbacks?.onExchangingCode?.();
79
+ // Exchange the one-time code for tokens
80
+ try {
81
+ const exchangeRes = await fetch(`${platform}/api/auth/cli/exchange`, {
82
+ method: "POST",
83
+ headers: { "Content-Type": "application/json" },
84
+ body: JSON.stringify({ code }),
85
+ });
86
+ const data = await exchangeRes.json();
87
+ if (!exchangeRes.ok || data.error) {
88
+ finish({ success: false, error: data.error || "Failed to exchange auth code" });
89
+ return;
90
+ }
91
+ // data = { access_token, refresh_token, user_id, email, expires_at, stores }
92
+ const config = {
93
+ ...loadConfig(),
94
+ access_token: data.access_token,
95
+ refresh_token: data.refresh_token,
96
+ user_id: data.user_id,
97
+ email: data.email,
98
+ expires_at: data.expires_at,
99
+ platform_url: platform !== DEFAULT_PLATFORM_URL ? platform : undefined,
100
+ };
101
+ const stores = data.stores || [];
102
+ // Auto-select store if user has exactly one
103
+ if (stores.length === 1) {
104
+ config.store_id = stores[0].id;
105
+ config.store_name = stores[0].name;
106
+ }
107
+ saveConfig(config);
108
+ finish({
109
+ success: true,
110
+ config,
111
+ stores: stores.length > 1 ? stores : undefined,
112
+ });
113
+ }
114
+ catch (err) {
115
+ finish({
116
+ success: false,
117
+ error: `Code exchange failed: ${err instanceof Error ? err.message : String(err)}`,
118
+ });
119
+ }
120
+ });
121
+ httpServer.listen(0, "127.0.0.1", () => {
122
+ const addr = httpServer.address();
123
+ if (!addr || typeof addr === "string") {
124
+ finish({ success: false, error: "Failed to bind callback server" });
125
+ return;
126
+ }
127
+ const port = addr.port;
128
+ const loginUrl = `${platform}/auth/cli?port=${port}&state=${state}`;
129
+ callbacks?.onBrowserOpening?.(loginUrl);
130
+ openBrowser(loginUrl);
131
+ callbacks?.onWaitingForCallback?.();
132
+ });
133
+ // Timeout
134
+ timeoutId = setTimeout(() => {
135
+ finish({
136
+ success: false,
137
+ error: "Login timed out. Please try again with `whale login`.",
138
+ });
139
+ }, timeout);
140
+ // Handle server errors
141
+ httpServer.on("error", (err) => {
142
+ finish({ success: false, error: `Callback server error: ${err.message}` });
143
+ });
144
+ });
145
+ }
146
+ // ============================================================================
147
+ // HTML PAGES (served by localhost callback)
148
+ // ============================================================================
149
+ function successPage() {
150
+ return `<!DOCTYPE html>
151
+ <html><head><title>Whale Code — Signed In</title>
152
+ <style>
153
+ body { background: #0a0a0a; color: #f5f5f7; font-family: -apple-system, BlinkMacSystemFont, sans-serif;
154
+ display: flex; align-items: center; justify-content: center; min-height: 100vh; margin: 0; }
155
+ .box { text-align: center; max-width: 400px; }
156
+ h1 { font-size: 1.5rem; margin-bottom: 0.5rem; }
157
+ p { color: #a1a1a6; font-size: 0.9rem; }
158
+ .check { font-size: 3rem; margin-bottom: 1rem; color: #30d158; }
159
+ </style></head>
160
+ <body><div class="box">
161
+ <div class="check">&#10003;</div>
162
+ <h1>Signed in!</h1>
163
+ <p>You can close this window and return to your terminal.</p>
164
+ </div></body></html>`;
165
+ }
166
+ function errorPage(message) {
167
+ return `<!DOCTYPE html>
168
+ <html><head><title>Whale Code — Error</title>
169
+ <style>
170
+ body { background: #0a0a0a; color: #f5f5f7; font-family: -apple-system, BlinkMacSystemFont, sans-serif;
171
+ display: flex; align-items: center; justify-content: center; min-height: 100vh; margin: 0; }
172
+ .box { text-align: center; max-width: 400px; }
173
+ h1 { font-size: 1.5rem; margin-bottom: 0.5rem; color: #ff453a; }
174
+ p { color: #a1a1a6; font-size: 0.9rem; }
175
+ </style></head>
176
+ <body><div class="box">
177
+ <h1>Authentication Error</h1>
178
+ <p>${message}</p>
179
+ </div></body></html>`;
180
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * CLAUDE.md Loader — auto-load project instructions from cwd + parents
3
+ *
4
+ * Extracted from agent-loop.ts for single-responsibility.
5
+ * All consumers should import from agent-loop.ts (re-export facade).
6
+ */
7
+ export declare function loadClaudeMd(): {
8
+ content: string;
9
+ path: string;
10
+ } | null;
11
+ export declare function reloadClaudeMd(): {
12
+ content: string;
13
+ path: string;
14
+ } | null;
15
+ /** Reset CLAUDE.md cache (called by resetSessionState) */
16
+ export declare function resetClaudeMdCache(): void;
@@ -0,0 +1,58 @@
1
+ /**
2
+ * CLAUDE.md Loader — auto-load project instructions from cwd + parents
3
+ *
4
+ * Extracted from agent-loop.ts for single-responsibility.
5
+ * All consumers should import from agent-loop.ts (re-export facade).
6
+ */
7
+ import { readFileSync, existsSync } from "fs";
8
+ import { join, resolve, dirname } from "path";
9
+ /** CLI-only: cached CLAUDE.md content (cleared by reloadClaudeMd/resetClaudeMdCache) */
10
+ let cachedClaudeMd = null;
11
+ /** CLI-only: cached CLAUDE.md path */
12
+ let cachedClaudeMdPath = null;
13
+ function findClaudeMd(startDir) {
14
+ const cwd = startDir || process.cwd();
15
+ const checked = new Set();
16
+ // Walk up from cwd looking for CLAUDE.md
17
+ let dir = resolve(cwd);
18
+ while (dir && !checked.has(dir)) {
19
+ checked.add(dir);
20
+ const candidates = [
21
+ join(dir, "CLAUDE.md"),
22
+ join(dir, ".claude", "CLAUDE.md"),
23
+ ];
24
+ for (const candidate of candidates) {
25
+ if (existsSync(candidate)) {
26
+ try {
27
+ const content = readFileSync(candidate, "utf-8");
28
+ if (content.trim())
29
+ return { content: content.trim(), path: candidate };
30
+ }
31
+ catch { /* skip unreadable */ }
32
+ }
33
+ }
34
+ const parent = dirname(dir);
35
+ if (parent === dir)
36
+ break;
37
+ dir = parent;
38
+ }
39
+ return null;
40
+ }
41
+ export function loadClaudeMd() {
42
+ if (cachedClaudeMd !== null)
43
+ return cachedClaudeMd ? { content: cachedClaudeMd, path: cachedClaudeMdPath } : null;
44
+ const result = findClaudeMd();
45
+ cachedClaudeMd = result?.content || "";
46
+ cachedClaudeMdPath = result?.path || null;
47
+ return result;
48
+ }
49
+ export function reloadClaudeMd() {
50
+ cachedClaudeMd = null;
51
+ cachedClaudeMdPath = null;
52
+ return loadClaudeMd();
53
+ }
54
+ /** Reset CLAUDE.md cache (called by resetSessionState) */
55
+ export function resetClaudeMdCache() {
56
+ cachedClaudeMd = null;
57
+ cachedClaudeMdPath = null;
58
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Config Store
3
+ *
4
+ * Persistent configuration at ~/.swagmanager/config.json
5
+ *
6
+ * v2.0: Raw Supabase/Anthropic keys (for MCP server env vars)
7
+ * v2.1: Auth tokens from login flow (for CLI chat/status)
8
+ *
9
+ * Environment variables always override file-based config for MCP server mode.
10
+ */
11
+ export interface SwagManagerConfig {
12
+ supabase_url?: string;
13
+ supabase_key?: string;
14
+ anthropic_api_key?: string;
15
+ default_agent_id?: string;
16
+ access_token?: string;
17
+ refresh_token?: string;
18
+ user_id?: string;
19
+ email?: string;
20
+ store_id?: string;
21
+ store_name?: string;
22
+ expires_at?: number;
23
+ default_model?: string;
24
+ thinking_enabled?: boolean;
25
+ permission_mode?: string;
26
+ agent_api_key?: string;
27
+ platform_url?: string;
28
+ }
29
+ export declare function loadConfig(): SwagManagerConfig;
30
+ export declare function saveConfig(config: SwagManagerConfig): void;
31
+ export declare function updateConfig(partial: Partial<SwagManagerConfig>): void;
32
+ export declare function clearConfig(): void;
33
+ export interface ResolvedConfig {
34
+ supabaseUrl: string;
35
+ supabaseKey: string;
36
+ storeId: string;
37
+ anthropicApiKey: string;
38
+ defaultAgentId: string;
39
+ serverUrl: string;
40
+ platformUrl: string;
41
+ }
42
+ /** Default Fly.io agent server URL */
43
+ export declare const WHALE_SERVER_URL = "https://whale-agent.fly.dev";
44
+ export declare function resolveConfig(): ResolvedConfig;
45
+ export declare function getConfigPath(): string;
46
+ /** Lazy proxy URL — avoids reading config at import time */
47
+ export declare function getProxyUrl(): string;