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.
- package/README.md +95 -0
- package/bin/swag-agent.js +9 -0
- package/bin/swagmanager-mcp.js +321 -0
- package/dist/cli/app.d.ts +26 -0
- package/dist/cli/app.js +64 -0
- package/dist/cli/chat/AgentSelector.d.ts +14 -0
- package/dist/cli/chat/AgentSelector.js +14 -0
- package/dist/cli/chat/ChatApp.d.ts +9 -0
- package/dist/cli/chat/ChatApp.js +267 -0
- package/dist/cli/chat/ChatInput.d.ts +39 -0
- package/dist/cli/chat/ChatInput.js +509 -0
- package/dist/cli/chat/MarkdownText.d.ts +10 -0
- package/dist/cli/chat/MarkdownText.js +20 -0
- package/dist/cli/chat/MessageList.d.ts +37 -0
- package/dist/cli/chat/MessageList.js +80 -0
- package/dist/cli/chat/ModelSelector.d.ts +20 -0
- package/dist/cli/chat/ModelSelector.js +73 -0
- package/dist/cli/chat/RewindViewer.d.ts +26 -0
- package/dist/cli/chat/RewindViewer.js +185 -0
- package/dist/cli/chat/StoreSelector.d.ts +14 -0
- package/dist/cli/chat/StoreSelector.js +24 -0
- package/dist/cli/chat/StreamingText.d.ts +12 -0
- package/dist/cli/chat/StreamingText.js +12 -0
- package/dist/cli/chat/SubagentPanel.d.ts +45 -0
- package/dist/cli/chat/SubagentPanel.js +110 -0
- package/dist/cli/chat/TeamPanel.d.ts +21 -0
- package/dist/cli/chat/TeamPanel.js +42 -0
- package/dist/cli/chat/ToolIndicator.d.ts +25 -0
- package/dist/cli/chat/ToolIndicator.js +436 -0
- package/dist/cli/chat/hooks/useAgentLoop.d.ts +39 -0
- package/dist/cli/chat/hooks/useAgentLoop.js +382 -0
- package/dist/cli/chat/hooks/useSlashCommands.d.ts +37 -0
- package/dist/cli/chat/hooks/useSlashCommands.js +387 -0
- package/dist/cli/commands/config-cmd.d.ts +10 -0
- package/dist/cli/commands/config-cmd.js +99 -0
- package/dist/cli/commands/doctor.d.ts +14 -0
- package/dist/cli/commands/doctor.js +172 -0
- package/dist/cli/commands/init.d.ts +16 -0
- package/dist/cli/commands/init.js +278 -0
- package/dist/cli/commands/mcp.d.ts +12 -0
- package/dist/cli/commands/mcp.js +162 -0
- package/dist/cli/login/LoginApp.d.ts +7 -0
- package/dist/cli/login/LoginApp.js +157 -0
- package/dist/cli/print-mode.d.ts +31 -0
- package/dist/cli/print-mode.js +202 -0
- package/dist/cli/serve-mode.d.ts +37 -0
- package/dist/cli/serve-mode.js +636 -0
- package/dist/cli/services/agent-definitions.d.ts +25 -0
- package/dist/cli/services/agent-definitions.js +91 -0
- package/dist/cli/services/agent-events.d.ts +178 -0
- package/dist/cli/services/agent-events.js +175 -0
- package/dist/cli/services/agent-loop.d.ts +90 -0
- package/dist/cli/services/agent-loop.js +762 -0
- package/dist/cli/services/agent-worker-base.d.ts +97 -0
- package/dist/cli/services/agent-worker-base.js +220 -0
- package/dist/cli/services/auth-service.d.ts +30 -0
- package/dist/cli/services/auth-service.js +160 -0
- package/dist/cli/services/background-processes.d.ts +126 -0
- package/dist/cli/services/background-processes.js +318 -0
- package/dist/cli/services/browser-auth.d.ts +24 -0
- package/dist/cli/services/browser-auth.js +180 -0
- package/dist/cli/services/claude-md-loader.d.ts +16 -0
- package/dist/cli/services/claude-md-loader.js +58 -0
- package/dist/cli/services/config-store.d.ts +47 -0
- package/dist/cli/services/config-store.js +79 -0
- package/dist/cli/services/debug-log.d.ts +10 -0
- package/dist/cli/services/debug-log.js +52 -0
- package/dist/cli/services/error-logger.d.ts +58 -0
- package/dist/cli/services/error-logger.js +269 -0
- package/dist/cli/services/file-history.d.ts +21 -0
- package/dist/cli/services/file-history.js +83 -0
- package/dist/cli/services/format-server-response.d.ts +16 -0
- package/dist/cli/services/format-server-response.js +440 -0
- package/dist/cli/services/git-context.d.ts +11 -0
- package/dist/cli/services/git-context.js +66 -0
- package/dist/cli/services/hooks.d.ts +85 -0
- package/dist/cli/services/hooks.js +258 -0
- package/dist/cli/services/interactive-tools.d.ts +125 -0
- package/dist/cli/services/interactive-tools.js +260 -0
- package/dist/cli/services/keybinding-manager.d.ts +52 -0
- package/dist/cli/services/keybinding-manager.js +115 -0
- package/dist/cli/services/local-tools.d.ts +22 -0
- package/dist/cli/services/local-tools.js +697 -0
- package/dist/cli/services/lsp-manager.d.ts +18 -0
- package/dist/cli/services/lsp-manager.js +717 -0
- package/dist/cli/services/mcp-client.d.ts +48 -0
- package/dist/cli/services/mcp-client.js +157 -0
- package/dist/cli/services/memory-manager.d.ts +16 -0
- package/dist/cli/services/memory-manager.js +57 -0
- package/dist/cli/services/model-manager.d.ts +18 -0
- package/dist/cli/services/model-manager.js +71 -0
- package/dist/cli/services/model-router.d.ts +26 -0
- package/dist/cli/services/model-router.js +149 -0
- package/dist/cli/services/permission-modes.d.ts +13 -0
- package/dist/cli/services/permission-modes.js +43 -0
- package/dist/cli/services/rewind.d.ts +84 -0
- package/dist/cli/services/rewind.js +194 -0
- package/dist/cli/services/ripgrep.d.ts +28 -0
- package/dist/cli/services/ripgrep.js +138 -0
- package/dist/cli/services/sandbox.d.ts +29 -0
- package/dist/cli/services/sandbox.js +97 -0
- package/dist/cli/services/server-tools.d.ts +61 -0
- package/dist/cli/services/server-tools.js +543 -0
- package/dist/cli/services/session-persistence.d.ts +23 -0
- package/dist/cli/services/session-persistence.js +99 -0
- package/dist/cli/services/subagent-worker.d.ts +19 -0
- package/dist/cli/services/subagent-worker.js +41 -0
- package/dist/cli/services/subagent.d.ts +47 -0
- package/dist/cli/services/subagent.js +647 -0
- package/dist/cli/services/system-prompt.d.ts +7 -0
- package/dist/cli/services/system-prompt.js +198 -0
- package/dist/cli/services/team-lead.d.ts +73 -0
- package/dist/cli/services/team-lead.js +512 -0
- package/dist/cli/services/team-state.d.ts +77 -0
- package/dist/cli/services/team-state.js +398 -0
- package/dist/cli/services/teammate.d.ts +31 -0
- package/dist/cli/services/teammate.js +689 -0
- package/dist/cli/services/telemetry.d.ts +61 -0
- package/dist/cli/services/telemetry.js +209 -0
- package/dist/cli/services/tools/agent-tools.d.ts +14 -0
- package/dist/cli/services/tools/agent-tools.js +347 -0
- package/dist/cli/services/tools/file-ops.d.ts +15 -0
- package/dist/cli/services/tools/file-ops.js +487 -0
- package/dist/cli/services/tools/search-tools.d.ts +8 -0
- package/dist/cli/services/tools/search-tools.js +186 -0
- package/dist/cli/services/tools/shell-exec.d.ts +10 -0
- package/dist/cli/services/tools/shell-exec.js +168 -0
- package/dist/cli/services/tools/task-manager.d.ts +28 -0
- package/dist/cli/services/tools/task-manager.js +209 -0
- package/dist/cli/services/tools/web-tools.d.ts +11 -0
- package/dist/cli/services/tools/web-tools.js +395 -0
- package/dist/cli/setup/SetupApp.d.ts +9 -0
- package/dist/cli/setup/SetupApp.js +191 -0
- package/dist/cli/shared/MatrixIntro.d.ts +4 -0
- package/dist/cli/shared/MatrixIntro.js +83 -0
- package/dist/cli/shared/Theme.d.ts +74 -0
- package/dist/cli/shared/Theme.js +127 -0
- package/dist/cli/shared/WhaleBanner.d.ts +10 -0
- package/dist/cli/shared/WhaleBanner.js +12 -0
- package/dist/cli/shared/markdown.d.ts +21 -0
- package/dist/cli/shared/markdown.js +756 -0
- package/dist/cli/status/StatusApp.d.ts +4 -0
- package/dist/cli/status/StatusApp.js +105 -0
- package/dist/cli/stores/StoreApp.d.ts +7 -0
- package/dist/cli/stores/StoreApp.js +81 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +538 -0
- package/dist/local-agent/connection.d.ts +48 -0
- package/dist/local-agent/connection.js +332 -0
- package/dist/local-agent/discovery.d.ts +18 -0
- package/dist/local-agent/discovery.js +146 -0
- package/dist/local-agent/executor.d.ts +34 -0
- package/dist/local-agent/executor.js +241 -0
- package/dist/local-agent/index.d.ts +14 -0
- package/dist/local-agent/index.js +198 -0
- package/dist/node/adapters/base.d.ts +35 -0
- package/dist/node/adapters/base.js +10 -0
- package/dist/node/adapters/discord.d.ts +29 -0
- package/dist/node/adapters/discord.js +299 -0
- package/dist/node/adapters/email.d.ts +23 -0
- package/dist/node/adapters/email.js +218 -0
- package/dist/node/adapters/imessage.d.ts +17 -0
- package/dist/node/adapters/imessage.js +118 -0
- package/dist/node/adapters/slack.d.ts +26 -0
- package/dist/node/adapters/slack.js +259 -0
- package/dist/node/adapters/sms.d.ts +23 -0
- package/dist/node/adapters/sms.js +161 -0
- package/dist/node/adapters/telegram.d.ts +17 -0
- package/dist/node/adapters/telegram.js +101 -0
- package/dist/node/adapters/webchat.d.ts +27 -0
- package/dist/node/adapters/webchat.js +160 -0
- package/dist/node/adapters/whatsapp.d.ts +28 -0
- package/dist/node/adapters/whatsapp.js +230 -0
- package/dist/node/cli.d.ts +2 -0
- package/dist/node/cli.js +325 -0
- package/dist/node/config.d.ts +17 -0
- package/dist/node/config.js +31 -0
- package/dist/node/runtime.d.ts +50 -0
- package/dist/node/runtime.js +351 -0
- package/dist/server/handlers/__test-utils__/mock-supabase.d.ts +11 -0
- package/dist/server/handlers/__test-utils__/mock-supabase.js +393 -0
- package/dist/server/handlers/analytics.d.ts +17 -0
- package/dist/server/handlers/analytics.js +266 -0
- package/dist/server/handlers/api-keys.d.ts +6 -0
- package/dist/server/handlers/api-keys.js +221 -0
- package/dist/server/handlers/billing.d.ts +33 -0
- package/dist/server/handlers/billing.js +272 -0
- package/dist/server/handlers/browser.d.ts +10 -0
- package/dist/server/handlers/browser.js +517 -0
- package/dist/server/handlers/catalog.d.ts +99 -0
- package/dist/server/handlers/catalog.js +976 -0
- package/dist/server/handlers/comms.d.ts +254 -0
- package/dist/server/handlers/comms.js +588 -0
- package/dist/server/handlers/creations.d.ts +6 -0
- package/dist/server/handlers/creations.js +479 -0
- package/dist/server/handlers/crm.d.ts +89 -0
- package/dist/server/handlers/crm.js +538 -0
- package/dist/server/handlers/discovery.d.ts +6 -0
- package/dist/server/handlers/discovery.js +288 -0
- package/dist/server/handlers/embeddings.d.ts +92 -0
- package/dist/server/handlers/embeddings.js +197 -0
- package/dist/server/handlers/enrichment.d.ts +8 -0
- package/dist/server/handlers/enrichment.js +768 -0
- package/dist/server/handlers/image-gen.d.ts +6 -0
- package/dist/server/handlers/image-gen.js +409 -0
- package/dist/server/handlers/inventory.d.ts +319 -0
- package/dist/server/handlers/inventory.js +447 -0
- package/dist/server/handlers/kali.d.ts +10 -0
- package/dist/server/handlers/kali.js +210 -0
- package/dist/server/handlers/llm-providers.d.ts +6 -0
- package/dist/server/handlers/llm-providers.js +673 -0
- package/dist/server/handlers/local-agent.d.ts +6 -0
- package/dist/server/handlers/local-agent.js +118 -0
- package/dist/server/handlers/meta-ads.d.ts +111 -0
- package/dist/server/handlers/meta-ads.js +2279 -0
- package/dist/server/handlers/nodes.d.ts +33 -0
- package/dist/server/handlers/nodes.js +699 -0
- package/dist/server/handlers/operations.d.ts +138 -0
- package/dist/server/handlers/operations.js +131 -0
- package/dist/server/handlers/platform.d.ts +23 -0
- package/dist/server/handlers/platform.js +227 -0
- package/dist/server/handlers/supply-chain.d.ts +19 -0
- package/dist/server/handlers/supply-chain.js +327 -0
- package/dist/server/handlers/transcription.d.ts +17 -0
- package/dist/server/handlers/transcription.js +121 -0
- package/dist/server/handlers/video-gen.d.ts +6 -0
- package/dist/server/handlers/video-gen.js +466 -0
- package/dist/server/handlers/voice.d.ts +8 -0
- package/dist/server/handlers/voice.js +1146 -0
- package/dist/server/handlers/workflow-steps.d.ts +86 -0
- package/dist/server/handlers/workflow-steps.js +2349 -0
- package/dist/server/handlers/workflows.d.ts +7 -0
- package/dist/server/handlers/workflows.js +989 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +2427 -0
- package/dist/server/lib/batch-client.d.ts +80 -0
- package/dist/server/lib/batch-client.js +467 -0
- package/dist/server/lib/code-worker-pool.d.ts +31 -0
- package/dist/server/lib/code-worker-pool.js +224 -0
- package/dist/server/lib/code-worker.d.ts +1 -0
- package/dist/server/lib/code-worker.js +188 -0
- package/dist/server/lib/compaction-service.d.ts +32 -0
- package/dist/server/lib/compaction-service.js +162 -0
- package/dist/server/lib/logger.d.ts +19 -0
- package/dist/server/lib/logger.js +46 -0
- package/dist/server/lib/otel.d.ts +38 -0
- package/dist/server/lib/otel.js +126 -0
- package/dist/server/lib/pg-rate-limiter.d.ts +21 -0
- package/dist/server/lib/pg-rate-limiter.js +86 -0
- package/dist/server/lib/prompt-sanitizer.d.ts +37 -0
- package/dist/server/lib/prompt-sanitizer.js +177 -0
- package/dist/server/lib/provider-capabilities.d.ts +85 -0
- package/dist/server/lib/provider-capabilities.js +190 -0
- package/dist/server/lib/provider-failover.d.ts +74 -0
- package/dist/server/lib/provider-failover.js +210 -0
- package/dist/server/lib/rate-limiter.d.ts +39 -0
- package/dist/server/lib/rate-limiter.js +147 -0
- package/dist/server/lib/server-agent-loop.d.ts +107 -0
- package/dist/server/lib/server-agent-loop.js +667 -0
- package/dist/server/lib/server-subagent.d.ts +78 -0
- package/dist/server/lib/server-subagent.js +203 -0
- package/dist/server/lib/session-checkpoint.d.ts +51 -0
- package/dist/server/lib/session-checkpoint.js +145 -0
- package/dist/server/lib/ssrf-guard.d.ts +13 -0
- package/dist/server/lib/ssrf-guard.js +240 -0
- package/dist/server/lib/supabase-client.d.ts +7 -0
- package/dist/server/lib/supabase-client.js +78 -0
- package/dist/server/lib/template-resolver.d.ts +31 -0
- package/dist/server/lib/template-resolver.js +215 -0
- package/dist/server/lib/utils.d.ts +16 -0
- package/dist/server/lib/utils.js +147 -0
- package/dist/server/local-agent-gateway.d.ts +82 -0
- package/dist/server/local-agent-gateway.js +426 -0
- package/dist/server/providers/anthropic.d.ts +20 -0
- package/dist/server/providers/anthropic.js +199 -0
- package/dist/server/providers/bedrock.d.ts +20 -0
- package/dist/server/providers/bedrock.js +194 -0
- package/dist/server/providers/gemini.d.ts +24 -0
- package/dist/server/providers/gemini.js +486 -0
- package/dist/server/providers/openai.d.ts +24 -0
- package/dist/server/providers/openai.js +522 -0
- package/dist/server/providers/registry.d.ts +32 -0
- package/dist/server/providers/registry.js +58 -0
- package/dist/server/providers/shared.d.ts +32 -0
- package/dist/server/providers/shared.js +124 -0
- package/dist/server/providers/types.d.ts +92 -0
- package/dist/server/providers/types.js +12 -0
- package/dist/server/proxy-handlers.d.ts +6 -0
- package/dist/server/proxy-handlers.js +89 -0
- package/dist/server/tool-router.d.ts +149 -0
- package/dist/server/tool-router.js +803 -0
- package/dist/server/validation.d.ts +24 -0
- package/dist/server/validation.js +301 -0
- package/dist/server/worker.d.ts +19 -0
- package/dist/server/worker.js +201 -0
- package/dist/setup.d.ts +8 -0
- package/dist/setup.js +181 -0
- package/dist/shared/agent-core.d.ts +157 -0
- package/dist/shared/agent-core.js +534 -0
- package/dist/shared/anthropic-types.d.ts +105 -0
- package/dist/shared/anthropic-types.js +7 -0
- package/dist/shared/api-client.d.ts +90 -0
- package/dist/shared/api-client.js +379 -0
- package/dist/shared/constants.d.ts +33 -0
- package/dist/shared/constants.js +80 -0
- package/dist/shared/sse-parser.d.ts +26 -0
- package/dist/shared/sse-parser.js +259 -0
- package/dist/shared/tool-dispatch.d.ts +52 -0
- package/dist/shared/tool-dispatch.js +191 -0
- package/dist/shared/types.d.ts +72 -0
- package/dist/shared/types.js +7 -0
- package/dist/updater.d.ts +25 -0
- package/dist/updater.js +140 -0
- package/dist/webchat/widget.d.ts +0 -0
- package/dist/webchat/widget.js +397 -0
- package/package.json +95 -0
- package/src/cli/services/builtin-skills/commit.md +19 -0
- package/src/cli/services/builtin-skills/review-pr.md +21 -0
- 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(/ /g, " ")
|
|
198
|
+
.replace(/&/g, "&")
|
|
199
|
+
.replace(/</g, "<")
|
|
200
|
+
.replace(/>/g, ">")
|
|
201
|
+
.replace(/"/g, '"')
|
|
202
|
+
.replace(/'/g, "'")
|
|
203
|
+
.replace(/'/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
|
+
}
|