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,395 @@
1
+ /**
2
+ * Web Tools — web_fetch and web_search implementations
3
+ *
4
+ * Extracted from local-tools.ts for single-responsibility.
5
+ */
6
+ import { lookup } from "node:dns/promises";
7
+ import { getValidToken, createAuthenticatedClient } from "../auth-service.js";
8
+ import { resolveConfig } from "../config-store.js";
9
+ // ============================================================================
10
+ // SSRF PROTECTION
11
+ // ============================================================================
12
+ export function isBlockedUrl(urlStr) {
13
+ try {
14
+ const u = new URL(urlStr);
15
+ const host = u.hostname.toLowerCase();
16
+ // Block localhost
17
+ if (host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "[::1]" || host === "0.0.0.0")
18
+ return true;
19
+ // Block private IPv4 ranges
20
+ if (/^10\./.test(host))
21
+ return true;
22
+ if (/^172\.(1[6-9]|2\d|3[01])\./.test(host))
23
+ return true;
24
+ if (/^192\.168\./.test(host))
25
+ return true;
26
+ // Block IPv6 private
27
+ if (/^fe80:/i.test(host) || /^\[fe80:/i.test(host))
28
+ return true;
29
+ if (/^fc00:/i.test(host) || /^\[fc00:/i.test(host))
30
+ return true;
31
+ if (/^fd/i.test(host) || /^\[fd/i.test(host))
32
+ return true;
33
+ // Block cloud metadata
34
+ if (host === "169.254.169.254" || /^169\.254\./.test(host))
35
+ return true;
36
+ // Block IPv6-mapped IPv4 (e.g. ::ffff:127.0.0.1, ::ffff:169.254.169.254)
37
+ if (host.includes("::ffff:"))
38
+ return true;
39
+ // Block decimal/hex IP representations (e.g. 2130706433 = 127.0.0.1, 0x7f000001)
40
+ if (/^\d+$/.test(host) || /^0x[0-9a-f]+$/i.test(host))
41
+ return true;
42
+ // Block octal IPs (e.g., 0177.0.0.1 = 127.0.0.1)
43
+ if (/^0\d+\./.test(host))
44
+ return true;
45
+ // Block short zero address forms (e.g. 0, 0.0, 0.0.0)
46
+ if (/^0(\.0)*$/.test(host))
47
+ return true;
48
+ // Block internal TLDs
49
+ if (host.endsWith(".internal") || host.endsWith(".local"))
50
+ return true;
51
+ // Block non-HTTP(S)
52
+ if (u.protocol !== "http:" && u.protocol !== "https:")
53
+ return true;
54
+ return false;
55
+ }
56
+ catch {
57
+ return true;
58
+ }
59
+ }
60
+ async function resolveAndCheckUrl(url) {
61
+ if (isBlockedUrl(url))
62
+ return true;
63
+ try {
64
+ const { hostname } = new URL(url);
65
+ // Skip IP-based hostnames (already checked by isBlockedUrl)
66
+ if (/^[\d.]+$/.test(hostname) || hostname.includes(":"))
67
+ return false;
68
+ const { address } = await lookup(hostname);
69
+ // IPv6 private/loopback detection
70
+ if (address.includes(":")) {
71
+ const lower = address.toLowerCase();
72
+ if (lower === "::1" || lower === "::" || lower.startsWith("fe80") ||
73
+ lower.startsWith("fc") || lower.startsWith("fd") ||
74
+ lower.startsWith("::ffff:"))
75
+ return true;
76
+ return false;
77
+ }
78
+ // IPv4 private/internal ranges
79
+ const parts = address.split(".").map(Number);
80
+ if (parts[0] === 10)
81
+ return true; // 10.0.0.0/8
82
+ if (parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31)
83
+ return true; // 172.16.0.0/12
84
+ if (parts[0] === 192 && parts[1] === 168)
85
+ return true; // 192.168.0.0/16
86
+ if (parts[0] === 127)
87
+ return true; // 127.0.0.0/8
88
+ if (parts[0] === 169 && parts[1] === 254)
89
+ return true; // link-local
90
+ if (address === "0.0.0.0")
91
+ return true;
92
+ return false;
93
+ }
94
+ catch {
95
+ return false; // DNS resolution failed, allow (will fail at fetch anyway)
96
+ }
97
+ }
98
+ // ============================================================================
99
+ // WEB FETCH
100
+ // ============================================================================
101
+ export async function webFetch(input) {
102
+ const url = input.url;
103
+ if (!url)
104
+ return { success: false, output: "url is required" };
105
+ if (await resolveAndCheckUrl(url)) {
106
+ return { success: false, output: "URL blocked: cannot fetch localhost, private IPs, or internal addresses" };
107
+ }
108
+ try {
109
+ const controller = new AbortController();
110
+ const fetchTimer = setTimeout(() => controller.abort(), 30000); // 30s timeout
111
+ let response = await fetch(url, {
112
+ headers: {
113
+ "User-Agent": "WhaleCode/3.0 (CLI Agent)",
114
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
115
+ },
116
+ signal: controller.signal,
117
+ redirect: "manual",
118
+ });
119
+ clearTimeout(fetchTimer);
120
+ // Handle redirects manually to prevent SSRF via redirect to internal addresses
121
+ if (response.status >= 300 && response.status < 400) {
122
+ const location = response.headers.get("location");
123
+ if (!location) {
124
+ return { success: false, output: `Redirect ${response.status} with no Location header` };
125
+ }
126
+ // Resolve relative redirects against the original URL
127
+ const resolvedLocation = new URL(location, url).toString();
128
+ if (await resolveAndCheckUrl(resolvedLocation)) {
129
+ return { success: false, output: "Redirect target is a blocked address" };
130
+ }
131
+ const redirectController = new AbortController();
132
+ const redirectTimer = setTimeout(() => redirectController.abort(), 30000);
133
+ response = await fetch(resolvedLocation, {
134
+ headers: {
135
+ "User-Agent": "WhaleCode/3.0 (CLI Agent)",
136
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
137
+ },
138
+ signal: redirectController.signal,
139
+ redirect: "manual",
140
+ });
141
+ clearTimeout(redirectTimer);
142
+ // If still redirecting, give up rather than following an unbounded chain
143
+ if (response.status >= 300 && response.status < 400) {
144
+ return { success: false, output: "Too many redirects" };
145
+ }
146
+ }
147
+ if (!response.ok) {
148
+ return { success: false, output: `HTTP ${response.status}: ${response.statusText}` };
149
+ }
150
+ const contentType = response.headers.get("content-type") || "";
151
+ const body = await response.text();
152
+ // JSON — return as-is (pretty-printed)
153
+ if (contentType.includes("application/json")) {
154
+ try {
155
+ const parsed = JSON.parse(body);
156
+ const pretty = JSON.stringify(parsed, null, 2);
157
+ return { success: true, output: pretty.slice(0, 80000) };
158
+ }
159
+ catch {
160
+ return { success: true, output: body.slice(0, 80000) };
161
+ }
162
+ }
163
+ // Plain text — return as-is
164
+ if (contentType.includes("text/plain")) {
165
+ return { success: true, output: body.slice(0, 80000) };
166
+ }
167
+ // HTML — convert to readable text
168
+ const text = htmlToText(body);
169
+ const truncated = text.length > 500_000 ? text.slice(0, 500_000) + "\n\n... (safety truncated)" : text;
170
+ return { success: true, output: `# ${url}\n\n${truncated}` };
171
+ }
172
+ catch (err) {
173
+ return { success: false, output: `Fetch error: ${err.message || err}` };
174
+ }
175
+ }
176
+ // ============================================================================
177
+ // HTML TO TEXT
178
+ // ============================================================================
179
+ /** Remove all instances of a tag and its content, handling nesting (innermost first) */
180
+ function removeNestedTag(html, tag) {
181
+ const pattern = new RegExp(`<${tag}[^>]*>(?:(?!<${tag}[\\s>/])[\\s\\S])*?<\\/${tag}>`, "gi");
182
+ let result = html;
183
+ let prev = "";
184
+ let safety = 0;
185
+ while (result !== prev && safety++ < 50) {
186
+ prev = result;
187
+ result = result.replace(pattern, "");
188
+ }
189
+ result = result.replace(new RegExp(`<${tag}[^>]*>`, "gi"), "");
190
+ return result;
191
+ }
192
+ function stripTags(html) {
193
+ return html.replace(/<[^>]+>/g, "");
194
+ }
195
+ function decodeEntities(text) {
196
+ return text
197
+ .replace(/&nbsp;/g, " ")
198
+ .replace(/&amp;/g, "&")
199
+ .replace(/&lt;/g, "<")
200
+ .replace(/&gt;/g, ">")
201
+ .replace(/&quot;/g, '"')
202
+ .replace(/&#39;/g, "'")
203
+ .replace(/&#x27;/g, "'")
204
+ .replace(/&#x([0-9a-fA-F]+);/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)))
205
+ .replace(/&#(\d+);/g, (_, code) => String.fromCharCode(parseInt(code)));
206
+ }
207
+ /** Enhanced HTML -> readable text/markdown converter */
208
+ export function htmlToText(html) {
209
+ let c = html;
210
+ // 1. Extract main content area (skips nav, sidebar, footer automatically)
211
+ const mainMatch = c.match(/<main[^>]*>([\s\S]*)<\/main>/i)
212
+ || c.match(/<article[^>]*>([\s\S]*)<\/article>/i);
213
+ if (mainMatch) {
214
+ c = mainMatch[1];
215
+ }
216
+ else {
217
+ const bodyMatch = c.match(/<body[^>]*>([\s\S]*)<\/body>/i);
218
+ if (bodyMatch)
219
+ c = bodyMatch[1];
220
+ }
221
+ // 2. Remove non-content elements (nesting-aware)
222
+ for (const tag of [
223
+ "script", "style", "nav", "footer", "aside", "header",
224
+ "form", "svg", "iframe", "select", "button", "noscript",
225
+ ]) {
226
+ c = removeNestedTag(c, tag);
227
+ }
228
+ // 3. Remove HTML comments
229
+ c = c.replace(/<!--[\s\S]*?-->/g, "");
230
+ // 4. Convert semantic elements -> markdown
231
+ // Code blocks first (preserve contents)
232
+ c = c.replace(/<pre[^>]*>([\s\S]*?)<\/pre>/gi, (_, inner) => "\n```\n" + stripTags(inner).trim() + "\n```\n");
233
+ c = c.replace(/<code[^>]*>([\s\S]*?)<\/code>/gi, "`$1`");
234
+ // Headings
235
+ c = c.replace(/<h([1-6])[^>]*>([\s\S]*?)<\/h\1>/gi, (_, level, text) => "\n" + "#".repeat(parseInt(level)) + " " + stripTags(text).trim() + "\n\n");
236
+ // Links — skip empty, anchor-only, and javascript: hrefs
237
+ c = c.replace(/<a[^>]*href="([^"]*)"[^>]*>([\s\S]*?)<\/a>/gi, (_, href, text) => {
238
+ const linkText = stripTags(text).trim();
239
+ if (!linkText)
240
+ return "";
241
+ if (href.startsWith("#") || href.startsWith("javascript:"))
242
+ return linkText;
243
+ return `[${linkText}](${href})`;
244
+ });
245
+ // Tables -> pipe-delimited markdown
246
+ c = c.replace(/<table[^>]*>([\s\S]*?)<\/table>/gi, (_, tableContent) => {
247
+ const rows = [];
248
+ const rowRegex = /<tr[^>]*>([\s\S]*?)<\/tr>/gi;
249
+ let rowMatch;
250
+ let isFirstRow = true;
251
+ while ((rowMatch = rowRegex.exec(tableContent)) !== null) {
252
+ const cells = [];
253
+ const cellRegex = /<(?:td|th)[^>]*>([\s\S]*?)<\/(?:td|th)>/gi;
254
+ let cellMatch;
255
+ while ((cellMatch = cellRegex.exec(rowMatch[1])) !== null) {
256
+ cells.push(stripTags(cellMatch[1]).trim());
257
+ }
258
+ if (cells.length > 0) {
259
+ rows.push("| " + cells.join(" | ") + " |");
260
+ if (isFirstRow) {
261
+ rows.push("| " + cells.map(() => "---").join(" | ") + " |");
262
+ isFirstRow = false;
263
+ }
264
+ }
265
+ }
266
+ return "\n" + rows.join("\n") + "\n";
267
+ });
268
+ // List items
269
+ c = c.replace(/<li[^>]*>([\s\S]*?)<\/li>/gi, (_, text) => "- " + stripTags(text).trim() + "\n");
270
+ // Bold, italic
271
+ c = c.replace(/<(strong|b)[^>]*>([\s\S]*?)<\/\1>/gi, "**$2**");
272
+ c = c.replace(/<(em|i)[^>]*>([\s\S]*?)<\/\1>/gi, "*$2*");
273
+ // Images -> alt text
274
+ c = c.replace(/<img[^>]*alt="([^"]*)"[^>]*>/gi, "[$1]");
275
+ c = c.replace(/<img[^>]*>/gi, "");
276
+ // Horizontal rules
277
+ c = c.replace(/<hr\s*\/?>/gi, "\n---\n");
278
+ // 5. Block elements -> newlines (replace tags independently, NOT as pairs)
279
+ c = c.replace(/<\/(?:p|div|section|article|main|blockquote|dd|dt|figcaption|figure|details|summary)>/gi, "\n\n");
280
+ c = c.replace(/<(?:p|div|section|article|main|blockquote|dd|dt|figcaption|figure|details|summary)[^>]*>/gi, "");
281
+ c = c.replace(/<br\s*\/?>/gi, "\n");
282
+ c = c.replace(/<\/(?:li|tr|thead|tbody|tfoot|ul|ol|dl)>/gi, "\n");
283
+ // 6. Strip all remaining tags
284
+ c = c.replace(/<[^>]+>/g, "");
285
+ // 7. Decode HTML entities
286
+ c = decodeEntities(c);
287
+ // 8. Clean whitespace
288
+ c = c
289
+ .replace(/[ \t]+/g, " ")
290
+ .replace(/ *\n */g, "\n")
291
+ .replace(/\n{3,}/g, "\n\n")
292
+ .trim();
293
+ return c;
294
+ }
295
+ // ============================================================================
296
+ // WEB SEARCH (Exa API)
297
+ // ============================================================================
298
+ // Cache the Exa API key within a session
299
+ let cachedExaKey = null;
300
+ async function getExaApiKey() {
301
+ if (cachedExaKey)
302
+ return cachedExaKey;
303
+ try {
304
+ const config = resolveConfig();
305
+ // Tier 1: Service role key (MCP server mode)
306
+ if (config.supabaseUrl && config.supabaseKey) {
307
+ const { createClient } = await import("@supabase/supabase-js");
308
+ const client = createClient(config.supabaseUrl, config.supabaseKey, {
309
+ auth: { persistSession: false, autoRefreshToken: false },
310
+ });
311
+ const { data } = await client.from("platform_secrets").select("value").eq("key", "exa_api_key").single();
312
+ if (data?.value) {
313
+ cachedExaKey = data.value;
314
+ return cachedExaKey;
315
+ }
316
+ }
317
+ // Tier 2: User JWT
318
+ const token = await getValidToken();
319
+ if (token) {
320
+ const client = createAuthenticatedClient(token);
321
+ const { data } = await client.from("platform_secrets").select("value").eq("key", "exa_api_key").single();
322
+ if (data?.value) {
323
+ cachedExaKey = data.value;
324
+ return cachedExaKey;
325
+ }
326
+ }
327
+ }
328
+ catch { /* swallow */ }
329
+ // Tier 3: Environment variable fallback
330
+ const envKey = process.env["EXA_API_KEY"];
331
+ if (envKey) {
332
+ cachedExaKey = envKey;
333
+ return cachedExaKey;
334
+ }
335
+ return null;
336
+ }
337
+ export async function webSearch(input) {
338
+ const query = input.query;
339
+ if (!query)
340
+ return { success: false, output: "query is required" };
341
+ const allowedDomains = input.allowed_domains;
342
+ const blockedDomains = input.blocked_domains;
343
+ const apiKey = await getExaApiKey();
344
+ if (!apiKey) {
345
+ return { success: false, output: "Exa API key not configured. Add 'exa_api_key' to platform_secrets table." };
346
+ }
347
+ try {
348
+ const searchBody = {
349
+ query,
350
+ numResults: 10,
351
+ type: "auto",
352
+ contents: { text: { maxCharacters: 1200, includeHtmlTags: false } },
353
+ };
354
+ if (allowedDomains?.length)
355
+ searchBody.includeDomains = allowedDomains;
356
+ if (blockedDomains?.length)
357
+ searchBody.excludeDomains = blockedDomains;
358
+ const response = await fetch("https://api.exa.ai/search", {
359
+ method: "POST",
360
+ headers: {
361
+ "x-api-key": apiKey,
362
+ "Content-Type": "application/json",
363
+ "Accept": "application/json",
364
+ },
365
+ body: JSON.stringify(searchBody),
366
+ signal: AbortSignal.timeout(15000),
367
+ });
368
+ if (!response.ok) {
369
+ const errBody = await response.text();
370
+ return { success: false, output: `Exa API error (${response.status}): ${errBody}` };
371
+ }
372
+ const data = await response.json();
373
+ const results = (data.results || []).map((r, i) => {
374
+ const parts = [
375
+ `${i + 1}. **${r.title || "Untitled"}**`,
376
+ ` ${r.url}`,
377
+ ];
378
+ if (r.publishedDate)
379
+ parts.push(` Published: ${r.publishedDate}`);
380
+ if (r.text)
381
+ parts.push(` ${r.text.slice(0, 500)}`);
382
+ return parts.join("\n");
383
+ });
384
+ return {
385
+ success: true,
386
+ output: `Found ${results.length} results for "${query}":\n\n${results.join("\n\n")}`,
387
+ };
388
+ }
389
+ catch (err) {
390
+ if (err.name === "TimeoutError" || err.message?.includes("timeout")) {
391
+ return { success: false, output: "Exa search timed out (15s)" };
392
+ }
393
+ return { success: false, output: `Web search error: ${err.message || err}` };
394
+ }
395
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Setup Wizard (Ink)
3
+ *
4
+ * Step-by-step wizard: detect CLIs → collect Supabase creds → collect Anthropic key
5
+ * → pick CLIs to install → write configs + ~/.swagmanager/config.json
6
+ *
7
+ * Usage: npx swagmanager-mcp setup
8
+ */
9
+ export declare function SetupApp(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,191 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Setup Wizard (Ink)
4
+ *
5
+ * Step-by-step wizard: detect CLIs → collect Supabase creds → collect Anthropic key
6
+ * → pick CLIs to install → write configs + ~/.swagmanager/config.json
7
+ *
8
+ * Usage: npx swagmanager-mcp setup
9
+ */
10
+ import { useState, useEffect } from "react";
11
+ import { Box, Text, useApp, useInput } from "ink";
12
+ import TextInput from "ink-text-input";
13
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
14
+ import { homedir } from "os";
15
+ import { dirname, join } from "path";
16
+ import { saveConfig } from "../services/config-store.js";
17
+ import { colors, symbols } from "../shared/Theme.js";
18
+ const home = homedir();
19
+ function detectCLIs() {
20
+ return [
21
+ {
22
+ name: "Claude Code",
23
+ configPath: join(home, ".claude", "settings.json"),
24
+ configKey: "mcpServers",
25
+ detected: existsSync(join(home, ".claude")),
26
+ },
27
+ {
28
+ name: "Claude Desktop",
29
+ configPath: process.platform === "darwin"
30
+ ? join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json")
31
+ : join(home, "AppData", "Roaming", "Claude", "claude_desktop_config.json"),
32
+ configKey: "mcpServers",
33
+ detected: existsSync(process.platform === "darwin"
34
+ ? join(home, "Library", "Application Support", "Claude")
35
+ : join(home, "AppData", "Roaming", "Claude")),
36
+ },
37
+ {
38
+ name: "Cursor",
39
+ configPath: join(home, ".cursor", "mcp.json"),
40
+ configKey: "mcpServers",
41
+ detected: existsSync(join(home, ".cursor")),
42
+ },
43
+ {
44
+ name: "Windsurf",
45
+ configPath: join(home, ".codeium", "windsurf", "mcp_config.json"),
46
+ configKey: "mcpServers",
47
+ detected: existsSync(join(home, ".codeium", "windsurf")),
48
+ },
49
+ {
50
+ name: "Gemini CLI",
51
+ configPath: join(home, ".gemini", "settings.json"),
52
+ configKey: "mcpServers",
53
+ detected: existsSync(join(home, ".gemini")),
54
+ },
55
+ ];
56
+ }
57
+ function readJSON(path) {
58
+ try {
59
+ return JSON.parse(readFileSync(path, "utf-8"));
60
+ }
61
+ catch {
62
+ return {};
63
+ }
64
+ }
65
+ function writeJSON(path, data) {
66
+ const dir = dirname(path);
67
+ if (!existsSync(dir))
68
+ mkdirSync(dir, { recursive: true });
69
+ writeFileSync(path, JSON.stringify(data, null, 2) + "\n", "utf-8");
70
+ }
71
+ export function SetupApp() {
72
+ const { exit } = useApp();
73
+ const [step, setStep] = useState("detect");
74
+ const [clis, setClis] = useState([]);
75
+ const [supabaseUrl, setSupabaseUrl] = useState("");
76
+ const [supabaseKey, setSupabaseKey] = useState("");
77
+ const [storeId, setStoreId] = useState("");
78
+ const [anthropicKey, setAnthropicKey] = useState("");
79
+ const [selectedClis, setSelectedClis] = useState(new Set());
80
+ const [installedCount, setInstalledCount] = useState(0);
81
+ const [inputValue, setInputValue] = useState("");
82
+ // Detect CLIs on mount
83
+ useEffect(() => {
84
+ const detected = detectCLIs();
85
+ setClis(detected);
86
+ // Pre-select all detected CLIs
87
+ setSelectedClis(new Set(detected.filter(c => c.detected).map(c => c.name)));
88
+ // Skip detection screen, go straight to input
89
+ const timer = setTimeout(() => setStep("supabase_url"), 50);
90
+ return () => clearTimeout(timer);
91
+ }, []);
92
+ // Auto-exit after done
93
+ useEffect(() => {
94
+ if (step === "done") {
95
+ const timer = setTimeout(() => exit(), 200);
96
+ return () => clearTimeout(timer);
97
+ }
98
+ }, [step, exit]);
99
+ const handleSubmit = (value) => {
100
+ switch (step) {
101
+ case "supabase_url":
102
+ if (!value.trim())
103
+ return;
104
+ setSupabaseUrl(value.trim());
105
+ setInputValue("");
106
+ setStep("supabase_key");
107
+ break;
108
+ case "supabase_key":
109
+ if (!value.trim())
110
+ return;
111
+ setSupabaseKey(value.trim());
112
+ setInputValue("");
113
+ setStep("store_id");
114
+ break;
115
+ case "store_id":
116
+ setStoreId(value.trim());
117
+ setInputValue("");
118
+ setStep("anthropic_key");
119
+ break;
120
+ case "anthropic_key":
121
+ setAnthropicKey(value.trim());
122
+ setInputValue("");
123
+ setStep("select_clis");
124
+ break;
125
+ }
126
+ };
127
+ const installToSelected = () => {
128
+ const serverEntry = {
129
+ type: "stdio",
130
+ command: "npx",
131
+ args: ["-y", "swagmanager-mcp"],
132
+ env: {
133
+ SUPABASE_URL: supabaseUrl,
134
+ SUPABASE_SERVICE_ROLE_KEY: supabaseKey,
135
+ ...(storeId ? { STORE_ID: storeId } : {}),
136
+ },
137
+ };
138
+ let count = 0;
139
+ for (const cli of clis) {
140
+ if (!selectedClis.has(cli.name))
141
+ continue;
142
+ const config = readJSON(cli.configPath);
143
+ if (!config[cli.configKey])
144
+ config[cli.configKey] = {};
145
+ config[cli.configKey].swagmanager = serverEntry;
146
+ writeJSON(cli.configPath, config);
147
+ count++;
148
+ }
149
+ // Also save to ~/.swagmanager/config.json
150
+ saveConfig({
151
+ supabase_url: supabaseUrl,
152
+ supabase_key: supabaseKey,
153
+ ...(storeId ? { store_id: storeId } : {}),
154
+ ...(anthropicKey ? { anthropic_api_key: anthropicKey } : {}),
155
+ });
156
+ setInstalledCount(count);
157
+ setStep("done");
158
+ };
159
+ const detectedClis = clis.filter(c => c.detected);
160
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Text, { color: colors.brand, bold: true, children: "SwagManager MCP \u2014 Setup" }), _jsx(Text, { color: colors.dim, children: symbols.divider.repeat(40) }), _jsx(Box, { height: 1 }), clis.length > 0 && (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { color: colors.muted, children: "Detected CLIs:" }), clis.map(cli => (_jsxs(Text, { color: cli.detected ? colors.success : colors.dim, children: [" ", cli.detected ? symbols.check : symbols.cross, " ", cli.name] }, cli.name)))] })), step === "supabase_url" && (_jsxs(Box, { children: [_jsx(Text, { color: colors.text, children: "Supabase URL: " }), _jsx(TextInput, { value: inputValue, onChange: setInputValue, onSubmit: handleSubmit })] })), step !== "supabase_url" && supabaseUrl && (_jsxs(Text, { color: colors.muted, children: ["Supabase URL: ", supabaseUrl.slice(0, 40), "..."] })), step === "supabase_key" && (_jsxs(Box, { children: [_jsx(Text, { color: colors.text, children: "Service Role Key: " }), _jsx(TextInput, { value: inputValue, onChange: setInputValue, onSubmit: handleSubmit })] })), step !== "supabase_key" && step !== "supabase_url" && supabaseKey && (_jsxs(Text, { color: colors.muted, children: ["Service Role Key: ", symbols.check, " Set"] })), step === "store_id" && (_jsxs(Box, { children: [_jsx(Text, { color: colors.text, children: "Store ID (optional, enter to skip): " }), _jsx(TextInput, { value: inputValue, onChange: setInputValue, onSubmit: handleSubmit })] })), step === "anthropic_key" && (_jsxs(Box, { children: [_jsx(Text, { color: colors.text, children: "Anthropic API Key (for chat mode, enter to skip): " }), _jsx(TextInput, { value: inputValue, onChange: setInputValue, onSubmit: handleSubmit })] })), step === "select_clis" && detectedClis.length > 0 && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: colors.text, children: "Install to detected CLIs? (Enter to confirm)" }), _jsx(Box, { height: 1 }), _jsx(CLISelector, { clis: detectedClis, selected: selectedClis, onToggle: (name) => {
161
+ setSelectedClis(prev => {
162
+ const next = new Set(prev);
163
+ if (next.has(name))
164
+ next.delete(name);
165
+ else
166
+ next.add(name);
167
+ return next;
168
+ });
169
+ }, onConfirm: installToSelected })] })), step === "select_clis" && detectedClis.length === 0 && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: colors.warning, children: "No MCP clients detected. Saving config only." }), _jsx(ConfirmButton, { onConfirm: installToSelected })] })), step === "done" && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { height: 1 }), _jsxs(Text, { color: colors.success, bold: true, children: [symbols.check, " Setup complete!"] }), installedCount > 0 && (_jsxs(Text, { color: colors.muted, children: ["Installed to ", installedCount, " CLI", installedCount > 1 ? "s" : "", ". Restart to load."] })), _jsx(Text, { color: colors.muted, children: "Config saved to ~/.swagmanager/config.json" }), anthropicKey && _jsx(Text, { color: colors.muted, children: "Chat mode ready: npx swagmanager-mcp chat" })] }))] }));
170
+ }
171
+ function CLISelector({ clis, selected, onToggle, onConfirm, }) {
172
+ const [cursor, setCursor] = useState(0);
173
+ useInput((input, key) => {
174
+ if (key.upArrow)
175
+ setCursor(c => Math.max(0, c - 1));
176
+ else if (key.downArrow)
177
+ setCursor(c => Math.min(clis.length, c + 1));
178
+ else if (input === " " && cursor < clis.length)
179
+ onToggle(clis[cursor].name);
180
+ else if (key.return)
181
+ onConfirm();
182
+ });
183
+ return (_jsxs(Box, { flexDirection: "column", children: [clis.map((cli, i) => (_jsxs(Text, { color: i === cursor ? colors.brand : colors.text, children: [i === cursor ? symbols.chevron : " ", " [", selected.has(cli.name) ? "x" : " ", "] ", cli.name] }, cli.name))), _jsxs(Text, { color: cursor === clis.length ? colors.brand : colors.success, children: [cursor === clis.length ? symbols.chevron : " ", " ", symbols.arrow, " Confirm & Install"] }), _jsx(Box, { height: 1 }), _jsx(Text, { color: colors.dim, children: "Space to toggle, Enter to confirm" })] }));
184
+ }
185
+ function ConfirmButton({ onConfirm }) {
186
+ useInput((_input, key) => {
187
+ if (key.return)
188
+ onConfirm();
189
+ });
190
+ return _jsx(Text, { color: colors.muted, children: "Press Enter to save config..." });
191
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Matrix Rain Intro — green digital rain before chat starts
3
+ */
4
+ export declare function matrixIntro(durationMs?: number): Promise<void>;