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,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">✓</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;
|