whale-code 6.4.0 → 6.5.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/bin/swagmanager-mcp.js +7 -0
- package/dist/cli/app.js +30 -2
- package/dist/cli/chat/ChatApp.d.ts +4 -4
- package/dist/cli/chat/ChatApp.js +114 -44
- package/dist/cli/chat/ChatInput.d.ts +13 -6
- package/dist/cli/chat/ChatInput.js +433 -89
- package/dist/cli/chat/MemoryManager.d.ts +15 -0
- package/dist/cli/chat/MemoryManager.js +61 -0
- package/dist/cli/chat/MessageList.d.ts +8 -0
- package/dist/cli/chat/MessageList.js +1 -1
- package/dist/cli/chat/NodeManager.d.ts +30 -0
- package/dist/cli/chat/NodeManager.js +89 -0
- package/dist/cli/chat/NodeSelector.d.ts +19 -0
- package/dist/cli/chat/NodeSelector.js +37 -0
- package/dist/cli/chat/PlanApproval.d.ts +17 -0
- package/dist/cli/chat/PlanApproval.js +82 -0
- package/dist/cli/chat/SessionManager.d.ts +16 -0
- package/dist/cli/chat/SessionManager.js +43 -0
- package/dist/cli/chat/SlashMenu.d.ts +38 -0
- package/dist/cli/chat/SlashMenu.js +208 -0
- package/dist/cli/chat/StatusBar.d.ts +16 -0
- package/dist/cli/chat/StatusBar.js +22 -0
- package/dist/cli/chat/ThemeSelector.d.ts +14 -0
- package/dist/cli/chat/ThemeSelector.js +29 -0
- package/dist/cli/chat/ToolIndicator.d.ts +8 -0
- package/dist/cli/chat/ToolIndicator.js +33 -9
- package/dist/cli/chat/hooks/useAgentLoop.d.ts +2 -1
- package/dist/cli/chat/hooks/useAgentLoop.js +22 -17
- package/dist/cli/chat/hooks/useSlashCommands.d.ts +19 -0
- package/dist/cli/chat/hooks/useSlashCommands.js +254 -15
- package/dist/cli/commands/config-cmd.js +4 -25
- package/dist/cli/commands/db.d.ts +13 -0
- package/dist/cli/commands/db.js +243 -0
- package/dist/cli/commands/doctor.js +6 -9
- package/dist/cli/commands/mcp.js +1 -20
- package/dist/cli/services/agent-events.d.ts +22 -1
- package/dist/cli/services/agent-events.js +9 -0
- package/dist/cli/services/agent-loop.js +66 -2
- package/dist/cli/services/agent-worker-base.js +21 -6
- package/dist/cli/services/api-retry.d.ts +25 -0
- package/dist/cli/services/api-retry.js +91 -0
- package/dist/cli/services/auth-service.d.ts +1 -1
- package/dist/cli/services/auth-service.js +40 -19
- package/dist/cli/services/background-processes.js +26 -2
- package/dist/cli/services/config-store.d.ts +13 -1
- package/dist/cli/services/config-store.js +116 -13
- package/dist/cli/services/format-server-response.js +12 -6
- package/dist/cli/services/ink-resize-fix.d.ts +18 -0
- package/dist/cli/services/ink-resize-fix.js +66 -0
- package/dist/cli/services/interactive-tools.d.ts +14 -0
- package/dist/cli/services/interactive-tools.js +47 -2
- package/dist/cli/services/keybinding-manager.js +1 -1
- package/dist/cli/services/local-tools.js +35 -2
- package/dist/cli/services/server-tools.js +175 -3
- package/dist/cli/services/subagent.js +15 -3
- package/dist/cli/services/system-prompt.js +5 -3
- package/dist/cli/services/task-decomposer.d.ts +35 -0
- package/dist/cli/services/task-decomposer.js +199 -0
- package/dist/cli/services/team-lead.d.ts +18 -0
- package/dist/cli/services/team-lead.js +80 -0
- package/dist/cli/services/teammate.js +5 -5
- package/dist/cli/services/telemetry.d.ts +8 -2
- package/dist/cli/services/telemetry.js +116 -92
- package/dist/cli/services/tools/agent-tools.d.ts +1 -0
- package/dist/cli/services/tools/agent-tools.js +50 -4
- package/dist/cli/services/tools/file-ops.d.ts +2 -0
- package/dist/cli/services/tools/file-ops.js +71 -19
- package/dist/cli/services/tools/shell-exec.js +22 -12
- package/dist/cli/shared/Theme.d.ts +1 -2
- package/dist/cli/shared/Theme.js +1 -1
- package/dist/cli/shared/WhaleBanner.d.ts +4 -1
- package/dist/cli/shared/WhaleBanner.js +12 -8
- package/dist/cli/shared/markdown.d.ts +5 -4
- package/dist/cli/shared/markdown.js +376 -334
- package/dist/cli/shared/theme-manager.d.ts +27 -0
- package/dist/cli/shared/theme-manager.js +178 -0
- package/dist/cli/shared/theme-presets.d.ts +16 -0
- package/dist/cli/shared/theme-presets.js +265 -0
- package/dist/index.js +0 -51
- package/dist/node/adapters/imessage.d.ts +10 -0
- package/dist/node/adapters/imessage.js +45 -6
- package/dist/node/cli.js +459 -8
- package/dist/node/config.d.ts +17 -0
- package/dist/node/gateway-client.d.ts +55 -0
- package/dist/node/gateway-client.js +201 -0
- package/dist/node/portal/clipboard.d.ts +28 -0
- package/dist/node/portal/clipboard.js +183 -0
- package/dist/node/portal/discovery.d.ts +29 -0
- package/dist/node/portal/discovery.js +61 -0
- package/dist/node/portal/forward.d.ts +30 -0
- package/dist/node/portal/forward.js +90 -0
- package/dist/node/portal/index.d.ts +47 -0
- package/dist/node/portal/index.js +250 -0
- package/dist/node/portal/multiplexer.d.ts +48 -0
- package/dist/node/portal/multiplexer.js +207 -0
- package/dist/node/portal/permissions.d.ts +36 -0
- package/dist/node/portal/permissions.js +131 -0
- package/dist/node/portal/protocol.d.ts +140 -0
- package/dist/node/portal/protocol.js +193 -0
- package/dist/node/portal/screen.d.ts +18 -0
- package/dist/node/portal/screen.js +93 -0
- package/dist/node/portal/session.d.ts +68 -0
- package/dist/node/portal/session.js +127 -0
- package/dist/node/portal/shell.d.ts +26 -0
- package/dist/node/portal/shell.js +142 -0
- package/dist/node/portal/stream.d.ts +43 -0
- package/dist/node/portal/stream.js +90 -0
- package/dist/node/portal/transfer.d.ts +33 -0
- package/dist/node/portal/transfer.js +231 -0
- package/dist/node/portal/ui.d.ts +16 -0
- package/dist/node/portal/ui.js +148 -0
- package/dist/node/remote-desktop/compile-helper.d.ts +13 -0
- package/dist/node/remote-desktop/compile-helper.js +73 -0
- package/dist/node/remote-desktop/index.d.ts +67 -0
- package/dist/node/remote-desktop/index.js +220 -0
- package/dist/node/remote-desktop/protocol.d.ts +96 -0
- package/dist/node/remote-desktop/protocol.js +67 -0
- package/dist/node/runtime.d.ts +8 -1
- package/dist/node/runtime.js +117 -9
- package/dist/server/handlers/__test-utils__/test-db.d.ts +25 -0
- package/dist/server/handlers/__test-utils__/test-db.js +128 -0
- package/dist/server/handlers/api-keys.js +26 -2
- package/dist/server/handlers/browser.d.ts +0 -4
- package/dist/server/handlers/browser.js +0 -46
- package/dist/server/handlers/catalog.js +37 -14
- package/dist/server/handlers/clickhouse.d.ts +10 -0
- package/dist/server/handlers/clickhouse.js +215 -0
- package/dist/server/handlers/comms.d.ts +308 -4
- package/dist/server/handlers/comms.js +444 -11
- package/dist/server/handlers/creations.js +1 -1
- package/dist/server/handlers/crm.d.ts +54 -8
- package/dist/server/handlers/crm.js +353 -68
- package/dist/server/handlers/embeddings.js +3 -3
- package/dist/server/handlers/enrichment.js +39 -55
- package/dist/server/handlers/inventory.js +1 -1
- package/dist/server/handlers/kali.d.ts +9 -1
- package/dist/server/handlers/kali.js +50 -1
- package/dist/server/handlers/media.d.ts +8 -0
- package/dist/server/handlers/media.js +902 -0
- package/dist/server/handlers/meta-ads.js +6 -3
- package/dist/server/handlers/nodes.d.ts +2 -0
- package/dist/server/handlers/nodes.js +331 -40
- package/dist/server/handlers/operations.d.ts +4 -6
- package/dist/server/handlers/operations.js +99 -38
- package/dist/server/handlers/platform.js +224 -107
- package/dist/server/handlers/remove-bg.d.ts +6 -0
- package/dist/server/handlers/remove-bg.js +96 -0
- package/dist/server/handlers/storefront.d.ts +6 -0
- package/dist/server/handlers/storefront.js +477 -0
- package/dist/server/handlers/supply-chain.js +21 -3
- package/dist/server/handlers/workflow-steps.js +87 -31
- package/dist/server/handlers/workflows.js +4 -1
- package/dist/server/index.js +334 -88
- package/dist/server/lib/clickhouse-buffer.d.ts +48 -0
- package/dist/server/lib/clickhouse-buffer.js +175 -0
- package/dist/server/lib/clickhouse-client.d.ts +112 -0
- package/dist/server/lib/clickhouse-client.js +141 -0
- package/dist/server/lib/coa-renderer.d.ts +91 -0
- package/dist/server/lib/coa-renderer.js +411 -0
- package/dist/server/lib/compaction-service.js +45 -1
- package/dist/server/lib/pdf-renderer.d.ts +143 -0
- package/dist/server/lib/pdf-renderer.js +867 -0
- package/dist/server/lib/react-pdf-layout.d.ts +40 -0
- package/dist/server/lib/react-pdf-layout.js +437 -0
- package/dist/server/lib/server-agent-loop.d.ts +2 -0
- package/dist/server/lib/server-agent-loop.js +61 -15
- package/dist/server/lib/server-subagent.d.ts +3 -0
- package/dist/server/lib/server-subagent.js +7 -4
- package/dist/server/lib/supabase-client.js +51 -3
- package/dist/server/lib/template-resolver.js +14 -4
- package/dist/server/lib/utils.js +15 -0
- package/dist/server/local-agent-gateway.d.ts +44 -0
- package/dist/server/local-agent-gateway.js +389 -49
- package/dist/server/providers/anthropic.js +12 -2
- package/dist/server/providers/gemini.js +17 -2
- package/dist/server/proxy-handlers.js +151 -0
- package/dist/server/tool-router.d.ts +2 -2
- package/dist/server/tool-router.js +25 -35
- package/dist/shared/agent-core.d.ts +5 -2
- package/dist/shared/agent-core.js +30 -4
- package/dist/shared/api-client.js +54 -3
- package/dist/shared/sse-parser.d.ts +1 -1
- package/dist/shared/sse-parser.js +5 -2
- package/dist/shared/tool-dispatch.js +1 -1
- package/package.json +16 -10
- package/dist/server/handlers/__test-utils__/mock-supabase.d.ts +0 -11
- package/dist/server/handlers/__test-utils__/mock-supabase.js +0 -393
|
@@ -4,7 +4,11 @@
|
|
|
4
4
|
* All consumers should import from ChatApp (re-export facade).
|
|
5
5
|
*/
|
|
6
6
|
import { useCallback } from "react";
|
|
7
|
-
import {
|
|
7
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
8
|
+
import { join } from "path";
|
|
9
|
+
import { homedir } from "os";
|
|
10
|
+
import { execSync, spawn } from "child_process";
|
|
11
|
+
import { createConnection } from "net";
|
|
8
12
|
import { setModel, getModel, getModelShortName, loadClaudeMd, getSessionTokens, resetSessionState, saveSession, loadSession, listSessions, addMemory, removeMemory, listMemories, setPermissionMode, getPermissionMode, getServerStatus, mcpClientManager, } from "../../services/agent-loop.js";
|
|
9
13
|
import { setConversationId } from "../../services/telemetry.js";
|
|
10
14
|
import { getAllServerToolDefinitions, resetServerToolClient } from "../../services/server-tools.js";
|
|
@@ -13,12 +17,58 @@ import { loadAgentDefinitions } from "../../services/agent-definitions.js";
|
|
|
13
17
|
import { SLASH_COMMANDS } from "../ChatInput.js";
|
|
14
18
|
import { MODEL_OPTIONS } from "../ModelSelector.js";
|
|
15
19
|
import { symbols } from "../../shared/Theme.js";
|
|
20
|
+
import { switchTheme, getThemePresets } from "../../shared/theme-manager.js";
|
|
16
21
|
import { loadKeybindings } from "../../services/keybinding-manager.js";
|
|
17
22
|
import { loadConfig, updateConfig } from "../../services/config-store.js";
|
|
18
|
-
import { getStoresForUser, getValidToken, selectStore } from "../../services/auth-service.js";
|
|
23
|
+
import { getStoresForUser, getValidToken, selectStore, signOut } from "../../services/auth-service.js";
|
|
19
24
|
import { getServerToolCount } from "../../services/agent-loop.js";
|
|
25
|
+
// ── Module-level helpers ──
|
|
26
|
+
function readNodeConfig() {
|
|
27
|
+
const p = join(homedir(), ".whaletools", "config.json");
|
|
28
|
+
if (!existsSync(p))
|
|
29
|
+
return { registered: false };
|
|
30
|
+
try {
|
|
31
|
+
const cfg = JSON.parse(readFileSync(p, "utf-8"));
|
|
32
|
+
return { registered: true, nodeId: cfg.node_id };
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return { registered: false };
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// ── Port helpers for node auto-start ──
|
|
39
|
+
function checkPort(port) {
|
|
40
|
+
return new Promise((resolve) => {
|
|
41
|
+
const sock = createConnection({ port, host: "127.0.0.1" }, () => {
|
|
42
|
+
sock.destroy();
|
|
43
|
+
resolve(true);
|
|
44
|
+
});
|
|
45
|
+
sock.on("error", () => resolve(false));
|
|
46
|
+
sock.setTimeout(1000, () => {
|
|
47
|
+
sock.destroy();
|
|
48
|
+
resolve(false);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
function waitForPort(port, timeout) {
|
|
53
|
+
const start = Date.now();
|
|
54
|
+
return new Promise((resolve) => {
|
|
55
|
+
const poll = () => {
|
|
56
|
+
if (Date.now() - start > timeout) {
|
|
57
|
+
resolve(false);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
checkPort(port).then((up) => {
|
|
61
|
+
if (up)
|
|
62
|
+
resolve(true);
|
|
63
|
+
else
|
|
64
|
+
setTimeout(poll, 500);
|
|
65
|
+
});
|
|
66
|
+
};
|
|
67
|
+
poll();
|
|
68
|
+
});
|
|
69
|
+
}
|
|
20
70
|
export function useSlashCommands(deps) {
|
|
21
|
-
const { exit, toolsExpanded, serverToolsAvailable, sessionId, thinkingEnabled, conversationRef, setMessages, setStreamingText, setActiveTools, setTeamState, setStoreList, setStoreSelectMode, setModelSelectMode, setCurrentModel, setSessionId, setThinkingEnabled, setUserLabel, setServerToolsAvailable, setShowRewind, rewindCheckpointCount, PKG_NAME, PKG_VERSION, } = deps;
|
|
71
|
+
const { exit, toolsExpanded, serverToolsAvailable, sessionId, thinkingEnabled, conversationRef, setMessages, setStreamingText, setActiveTools, setTeamState, setStoreList, setStoreSelectMode, setModelSelectMode, setCurrentModel, setSessionId, setThinkingEnabled, setUserLabel, setServerToolsAvailable, setShowRewind, rewindCheckpointCount, setNodeSelectMode, setNodeList, setSessionManagerMode, setSessionList, setMemoryManagerMode, setMemoryList, setThemeSelectMode, setNodeManagerMode, setNodeManagerStatus, PKG_NAME, PKG_VERSION, } = deps;
|
|
22
72
|
const handleCommand = useCallback(async (command) => {
|
|
23
73
|
const parts = command.trim().split(/\s+/);
|
|
24
74
|
const cmd = parts[0];
|
|
@@ -70,7 +120,7 @@ export function useSlashCommands(deps) {
|
|
|
70
120
|
` mode ${getPermissionMode()}`,
|
|
71
121
|
` thinking ${thinkingEnabled ? "on" : "off"} (^T)`,
|
|
72
122
|
` context ${conversationRef.current.length} messages`,
|
|
73
|
-
` expand ${toolsExpanded ? "on" : "off"} (^
|
|
123
|
+
` expand ${toolsExpanded ? "on" : "off"} (^O)`,
|
|
74
124
|
];
|
|
75
125
|
setMessages((prev) => [...prev, { role: "assistant", text: lines.join("\n") }]);
|
|
76
126
|
break;
|
|
@@ -192,11 +242,8 @@ export function useSlashCommands(deps) {
|
|
|
192
242
|
setMessages((prev) => [...prev, { role: "assistant", text: " No saved sessions." }]);
|
|
193
243
|
}
|
|
194
244
|
else {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
role: "assistant",
|
|
198
|
-
text: ` Saved sessions:\n${lines.join("\n")}\n\n Use /resume to load a session.`,
|
|
199
|
-
}]);
|
|
245
|
+
setSessionList(sessions);
|
|
246
|
+
setSessionManagerMode(true);
|
|
200
247
|
}
|
|
201
248
|
break;
|
|
202
249
|
}
|
|
@@ -280,11 +327,8 @@ export function useSlashCommands(deps) {
|
|
|
280
327
|
setMessages((prev) => [...prev, { role: "assistant", text: " No memories stored. Use /remember <fact> to add one." }]);
|
|
281
328
|
}
|
|
282
329
|
else {
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
role: "assistant",
|
|
286
|
-
text: ` ${memories.length} remembered fact${memories.length !== 1 ? "s" : ""}:\n${lines.join("\n")}`,
|
|
287
|
-
}]);
|
|
330
|
+
setMemoryList(memories);
|
|
331
|
+
setMemoryManagerMode(true);
|
|
288
332
|
}
|
|
289
333
|
break;
|
|
290
334
|
}
|
|
@@ -344,6 +388,15 @@ export function useSlashCommands(deps) {
|
|
|
344
388
|
}
|
|
345
389
|
break;
|
|
346
390
|
}
|
|
391
|
+
case "/logout": {
|
|
392
|
+
signOut();
|
|
393
|
+
setMessages((prev) => [...prev, {
|
|
394
|
+
role: "assistant",
|
|
395
|
+
text: ` ${symbols.check} Logged out. Goodbye!`,
|
|
396
|
+
}]);
|
|
397
|
+
setTimeout(() => exit(), 500);
|
|
398
|
+
break;
|
|
399
|
+
}
|
|
347
400
|
case "/tools": {
|
|
348
401
|
const lines = [];
|
|
349
402
|
lines.push(` Local (${LOCAL_TOOL_DEFINITIONS.length})`);
|
|
@@ -364,6 +417,46 @@ export function useSlashCommands(deps) {
|
|
|
364
417
|
setMessages((prev) => [...prev, { role: "assistant", text: lines.join("\n") }]);
|
|
365
418
|
break;
|
|
366
419
|
}
|
|
420
|
+
case "/theme": {
|
|
421
|
+
if (args) {
|
|
422
|
+
const success = switchTheme(args);
|
|
423
|
+
if (success) {
|
|
424
|
+
const preset = getThemePresets().find((t) => t.id === args);
|
|
425
|
+
setMessages((prev) => [...prev, {
|
|
426
|
+
role: "assistant",
|
|
427
|
+
text: ` ${symbols.check} Theme: ${preset?.label || args}`,
|
|
428
|
+
}]);
|
|
429
|
+
}
|
|
430
|
+
else {
|
|
431
|
+
const available = getThemePresets().map((t) => t.id).join(", ");
|
|
432
|
+
setMessages((prev) => [...prev, {
|
|
433
|
+
role: "assistant",
|
|
434
|
+
text: ` ${symbols.cross} Unknown theme: ${args}\n Available: ${available}`,
|
|
435
|
+
}]);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
else {
|
|
439
|
+
setThemeSelectMode(true);
|
|
440
|
+
}
|
|
441
|
+
break;
|
|
442
|
+
}
|
|
443
|
+
// ── Node Commands ──
|
|
444
|
+
case "/nodes": {
|
|
445
|
+
const nodeConfigPath = join(homedir(), ".whaletools", "config.json");
|
|
446
|
+
const registered = existsSync(nodeConfigPath);
|
|
447
|
+
let nodeId;
|
|
448
|
+
if (registered) {
|
|
449
|
+
try {
|
|
450
|
+
const cfg = JSON.parse(readFileSync(nodeConfigPath, "utf-8"));
|
|
451
|
+
nodeId = cfg.node_id;
|
|
452
|
+
}
|
|
453
|
+
catch { /* ignore parse errors */ }
|
|
454
|
+
}
|
|
455
|
+
const running = registered ? await checkPort(7890) : false;
|
|
456
|
+
setNodeManagerStatus({ registered, running, nodeId, port: 7890 });
|
|
457
|
+
setNodeManagerMode(true);
|
|
458
|
+
break;
|
|
459
|
+
}
|
|
367
460
|
}
|
|
368
461
|
}, [exit, toolsExpanded, serverToolsAvailable, sessionId, thinkingEnabled, rewindCheckpointCount]);
|
|
369
462
|
// Store select handlers
|
|
@@ -383,5 +476,151 @@ export function useSlashCommands(deps) {
|
|
|
383
476
|
setStoreSelectMode(false);
|
|
384
477
|
setStoreList([]);
|
|
385
478
|
}, []);
|
|
386
|
-
|
|
479
|
+
// Node select handlers
|
|
480
|
+
const handleNodeSelect = useCallback((node) => {
|
|
481
|
+
setNodeSelectMode(false);
|
|
482
|
+
setNodeList([]);
|
|
483
|
+
}, []);
|
|
484
|
+
const handleNodeCancel = useCallback(() => {
|
|
485
|
+
setNodeSelectMode(false);
|
|
486
|
+
setNodeList([]);
|
|
487
|
+
}, []);
|
|
488
|
+
// Session manager handler
|
|
489
|
+
const handleSessionSelect = useCallback((session) => {
|
|
490
|
+
setSessionManagerMode(false);
|
|
491
|
+
const loaded = loadSession(session.id);
|
|
492
|
+
if (loaded) {
|
|
493
|
+
conversationRef.current = loaded.messages;
|
|
494
|
+
setSessionId(session.id);
|
|
495
|
+
setConversationId(session.id);
|
|
496
|
+
setTodoSessionId(session.id);
|
|
497
|
+
loadTodos(session.id);
|
|
498
|
+
if (loaded.meta.model)
|
|
499
|
+
setModel(loaded.meta.model);
|
|
500
|
+
setMessages((prev) => [...prev, {
|
|
501
|
+
role: "assistant",
|
|
502
|
+
text: ` ${symbols.check} Resumed: ${session.title}\n ${session.messageCount} messages, model: ${getModelShortName()}`,
|
|
503
|
+
}]);
|
|
504
|
+
}
|
|
505
|
+
else {
|
|
506
|
+
setMessages((prev) => [...prev, { role: "assistant", text: " Failed to load session." }]);
|
|
507
|
+
}
|
|
508
|
+
}, []);
|
|
509
|
+
// Memory manager handler
|
|
510
|
+
const handleMemoryDelete = useCallback((index) => {
|
|
511
|
+
setMemoryList((prev) => {
|
|
512
|
+
const memory = prev[index];
|
|
513
|
+
if (memory) {
|
|
514
|
+
removeMemory(memory);
|
|
515
|
+
setMessages((msgs) => [...msgs, { role: "assistant", text: ` ${symbols.check} Forgot: ${memory}` }]);
|
|
516
|
+
}
|
|
517
|
+
const next = prev.filter((_, i) => i !== index);
|
|
518
|
+
if (next.length === 0) {
|
|
519
|
+
setMemoryManagerMode(false);
|
|
520
|
+
}
|
|
521
|
+
return next;
|
|
522
|
+
});
|
|
523
|
+
}, []);
|
|
524
|
+
// ── Node manager helpers ──
|
|
525
|
+
// Node toggle: start or stop — returns result for NodeManager to display
|
|
526
|
+
const handleNodeToggle = useCallback(async () => {
|
|
527
|
+
const { nodeId } = readNodeConfig();
|
|
528
|
+
const running = await checkPort(7890);
|
|
529
|
+
if (running) {
|
|
530
|
+
// Stop
|
|
531
|
+
try {
|
|
532
|
+
execSync("lsof -ti :7890 | xargs kill", { encoding: "utf-8" });
|
|
533
|
+
return {
|
|
534
|
+
success: true,
|
|
535
|
+
message: "Node stopped",
|
|
536
|
+
newStatus: { registered: true, running: false, nodeId, port: 7890 },
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
catch {
|
|
540
|
+
return {
|
|
541
|
+
success: false,
|
|
542
|
+
message: "Failed to stop node",
|
|
543
|
+
newStatus: { registered: true, running: true, nodeId, port: 7890 },
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
else {
|
|
548
|
+
// Start
|
|
549
|
+
spawn("whalenode", ["node", "start"], {
|
|
550
|
+
detached: true,
|
|
551
|
+
stdio: "ignore",
|
|
552
|
+
cwd: homedir(),
|
|
553
|
+
}).unref();
|
|
554
|
+
const ready = await waitForPort(7890, 8000);
|
|
555
|
+
return {
|
|
556
|
+
success: ready,
|
|
557
|
+
message: ready ? "Node started" : "Node failed to start",
|
|
558
|
+
newStatus: { registered: true, running: ready, nodeId, port: 7890 },
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
}, []);
|
|
562
|
+
// Node register: register this machine, returns result for NodeManager
|
|
563
|
+
const handleNodeRegister = useCallback(async () => {
|
|
564
|
+
const token = await getValidToken();
|
|
565
|
+
const config = loadConfig();
|
|
566
|
+
if (!token || !config.store_id) {
|
|
567
|
+
return {
|
|
568
|
+
success: false,
|
|
569
|
+
message: "Not logged in — run: whale login",
|
|
570
|
+
newStatus: { registered: false, running: false },
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
try {
|
|
574
|
+
const hostname = (await import("os")).hostname();
|
|
575
|
+
const res = await fetch(`https://whale-agent.fly.dev/nodes/register`, {
|
|
576
|
+
method: "POST",
|
|
577
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
|
|
578
|
+
body: JSON.stringify({ name: hostname, store_id: config.store_id }),
|
|
579
|
+
});
|
|
580
|
+
const data = await res.json();
|
|
581
|
+
if (!data.success) {
|
|
582
|
+
return {
|
|
583
|
+
success: false,
|
|
584
|
+
message: `Registration failed: ${data.error || "unknown"}`,
|
|
585
|
+
newStatus: { registered: false, running: false },
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
const nodeConfig = {
|
|
589
|
+
node_id: data.node.id,
|
|
590
|
+
api_key: data.api_key,
|
|
591
|
+
store_id: config.store_id,
|
|
592
|
+
server_url: "https://whale-agent.fly.dev",
|
|
593
|
+
channels: [],
|
|
594
|
+
};
|
|
595
|
+
const configDir = join(homedir(), ".whaletools");
|
|
596
|
+
if (!existsSync(configDir))
|
|
597
|
+
mkdirSync(configDir, { recursive: true });
|
|
598
|
+
writeFileSync(join(configDir, "config.json"), JSON.stringify(nodeConfig, null, 2));
|
|
599
|
+
return {
|
|
600
|
+
success: true,
|
|
601
|
+
message: `Registered: ${data.node.id}`,
|
|
602
|
+
newStatus: { registered: true, running: false, nodeId: data.node.id, port: 7890 },
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
catch {
|
|
606
|
+
return {
|
|
607
|
+
success: false,
|
|
608
|
+
message: "Registration failed",
|
|
609
|
+
newStatus: { registered: false, running: false },
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
}, []);
|
|
613
|
+
// Node manager close: dismiss panel + optionally show result in chat
|
|
614
|
+
const handleNodeClose = useCallback((resultMessage) => {
|
|
615
|
+
setNodeManagerMode(false);
|
|
616
|
+
if (resultMessage) {
|
|
617
|
+
setMessages((prev) => [...prev, { role: "assistant", text: ` ${resultMessage}` }]);
|
|
618
|
+
}
|
|
619
|
+
}, []);
|
|
620
|
+
return {
|
|
621
|
+
handleCommand, handleStoreSelect, handleStoreCancel,
|
|
622
|
+
handleNodeSelect, handleNodeCancel,
|
|
623
|
+
handleSessionSelect, handleMemoryDelete,
|
|
624
|
+
handleNodeToggle, handleNodeRegister, handleNodeClose,
|
|
625
|
+
};
|
|
387
626
|
}
|
|
@@ -7,29 +7,10 @@
|
|
|
7
7
|
* whale config <key> <value> Write single key
|
|
8
8
|
* whale config --reset Reset to defaults
|
|
9
9
|
*/
|
|
10
|
-
import {
|
|
11
|
-
import { join } from "path";
|
|
12
|
-
import { homedir } from "os";
|
|
13
|
-
const CONFIG_PATH = join(homedir(), ".swagmanager", "config.json");
|
|
10
|
+
import { loadConfig, saveConfig, clearConfig, getConfigPath } from "../services/config-store.js";
|
|
14
11
|
const DIM = "\x1b[2m";
|
|
15
12
|
const BOLD = "\x1b[1m";
|
|
16
13
|
const RESET = "\x1b[0m";
|
|
17
|
-
function loadConfig() {
|
|
18
|
-
if (!existsSync(CONFIG_PATH))
|
|
19
|
-
return {};
|
|
20
|
-
try {
|
|
21
|
-
return JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
|
|
22
|
-
}
|
|
23
|
-
catch {
|
|
24
|
-
return {};
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
function saveConfig(config) {
|
|
28
|
-
const dir = join(homedir(), ".swagmanager");
|
|
29
|
-
if (!existsSync(dir))
|
|
30
|
-
mkdirSync(dir, { recursive: true });
|
|
31
|
-
writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), { mode: 0o600 });
|
|
32
|
-
}
|
|
33
14
|
function maskSecret(key, value) {
|
|
34
15
|
if (key.includes("key") || key.includes("token") || key.includes("secret")) {
|
|
35
16
|
if (value.length > 8) {
|
|
@@ -41,19 +22,17 @@ function maskSecret(key, value) {
|
|
|
41
22
|
}
|
|
42
23
|
export async function runConfigCommand(args, flags) {
|
|
43
24
|
if (flags.reset) {
|
|
44
|
-
|
|
45
|
-
if (!existsSync(dir))
|
|
46
|
-
mkdirSync(dir, { recursive: true });
|
|
47
|
-
writeFileSync(CONFIG_PATH, JSON.stringify({}, null, 2), { mode: 0o600 });
|
|
25
|
+
clearConfig();
|
|
48
26
|
console.log("Configuration reset to defaults.");
|
|
49
27
|
return;
|
|
50
28
|
}
|
|
29
|
+
const configPath = getConfigPath();
|
|
51
30
|
const key = args[0];
|
|
52
31
|
const value = args[1];
|
|
53
32
|
if (!key) {
|
|
54
33
|
// Show all settings
|
|
55
34
|
const config = loadConfig();
|
|
56
|
-
console.log(`\n ${BOLD}whale config${RESET} ${DIM}(${
|
|
35
|
+
console.log(`\n ${BOLD}whale config${RESET} ${DIM}(${configPath})${RESET}\n`);
|
|
57
36
|
if (Object.keys(config).length === 0) {
|
|
58
37
|
console.log(" No configuration set.");
|
|
59
38
|
console.log(` ${DIM}Set a value: whale config <key> <value>${RESET}`);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* whale db — Local Supabase database management
|
|
3
|
+
*
|
|
4
|
+
* Subcommands:
|
|
5
|
+
* whale db status Check if local Supabase is running
|
|
6
|
+
* whale db start Start local Supabase
|
|
7
|
+
* whale db stop Stop local Supabase
|
|
8
|
+
* whale db reset Drop + recreate + seed local DB
|
|
9
|
+
* whale db sync Pull latest production schema, apply locally
|
|
10
|
+
* whale db seed Re-run seed.sql without full reset
|
|
11
|
+
* whale db test Run full test suite against local DB
|
|
12
|
+
*/
|
|
13
|
+
export declare function runDbCommand(args: string[]): Promise<void>;
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* whale db — Local Supabase database management
|
|
3
|
+
*
|
|
4
|
+
* Subcommands:
|
|
5
|
+
* whale db status Check if local Supabase is running
|
|
6
|
+
* whale db start Start local Supabase
|
|
7
|
+
* whale db stop Stop local Supabase
|
|
8
|
+
* whale db reset Drop + recreate + seed local DB
|
|
9
|
+
* whale db sync Pull latest production schema, apply locally
|
|
10
|
+
* whale db seed Re-run seed.sql without full reset
|
|
11
|
+
* whale db test Run full test suite against local DB
|
|
12
|
+
*/
|
|
13
|
+
import { execSync, spawn } from "child_process";
|
|
14
|
+
import { existsSync, readFileSync } from "fs";
|
|
15
|
+
import { join, resolve, dirname } from "path";
|
|
16
|
+
import { fileURLToPath } from "url";
|
|
17
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
18
|
+
const __dirname = dirname(__filename);
|
|
19
|
+
// ── ANSI colors ──
|
|
20
|
+
const GREEN = "\x1b[32m";
|
|
21
|
+
const RED = "\x1b[31m";
|
|
22
|
+
const YELLOW = "\x1b[33m";
|
|
23
|
+
const CYAN = "\x1b[36m";
|
|
24
|
+
const BOLD = "\x1b[1m";
|
|
25
|
+
const DIM = "\x1b[2m";
|
|
26
|
+
const RESET = "\x1b[0m";
|
|
27
|
+
const CHECK = `${GREEN}✓${RESET}`;
|
|
28
|
+
const CROSS = `${RED}✗${RESET}`;
|
|
29
|
+
const WARN = `${YELLOW}!${RESET}`;
|
|
30
|
+
const DB_URL = "postgresql://postgres:postgres@127.0.0.1:54322/postgres";
|
|
31
|
+
function findEcosystemRoot() {
|
|
32
|
+
// Walk up from __dirname to find the directory containing supabase/config.toml
|
|
33
|
+
let dir = resolve(__dirname, "..", "..", "..");
|
|
34
|
+
for (let i = 0; i < 5; i++) {
|
|
35
|
+
if (existsSync(join(dir, "supabase", "config.toml"))) {
|
|
36
|
+
return dir;
|
|
37
|
+
}
|
|
38
|
+
dir = resolve(dir, "..");
|
|
39
|
+
}
|
|
40
|
+
// Fallback
|
|
41
|
+
return resolve(__dirname, "..", "..", "..");
|
|
42
|
+
}
|
|
43
|
+
function isLocalRunning() {
|
|
44
|
+
try {
|
|
45
|
+
execSync(`PGPASSWORD=postgres psql "${DB_URL}" -c "SELECT 1;" 2>/dev/null`, {
|
|
46
|
+
encoding: "utf-8",
|
|
47
|
+
timeout: 5000,
|
|
48
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
49
|
+
});
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function runShell(cmd, opts) {
|
|
57
|
+
return execSync(cmd, {
|
|
58
|
+
encoding: "utf-8",
|
|
59
|
+
timeout: opts?.timeout ?? 30000,
|
|
60
|
+
cwd: opts?.cwd,
|
|
61
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
62
|
+
}).trim();
|
|
63
|
+
}
|
|
64
|
+
function spawnInteractive(cmd, args, cwd) {
|
|
65
|
+
return new Promise((resolve) => {
|
|
66
|
+
const child = spawn(cmd, args, {
|
|
67
|
+
cwd,
|
|
68
|
+
stdio: "inherit",
|
|
69
|
+
shell: true,
|
|
70
|
+
});
|
|
71
|
+
child.on("close", (code) => resolve(code ?? 1));
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
// ── Subcommands ──
|
|
75
|
+
async function dbStatus(root) {
|
|
76
|
+
console.log(`\n ${BOLD}whale db status${RESET}\n`);
|
|
77
|
+
const supaDir = join(root, "supabase");
|
|
78
|
+
// Check Docker
|
|
79
|
+
let dockerOk = false;
|
|
80
|
+
try {
|
|
81
|
+
runShell("docker info 2>/dev/null", { timeout: 5000 });
|
|
82
|
+
dockerOk = true;
|
|
83
|
+
}
|
|
84
|
+
catch { /* noop */ }
|
|
85
|
+
console.log(` ${dockerOk ? CHECK : CROSS} Docker ${DIM}${dockerOk ? "running" : "not running"}${RESET}`);
|
|
86
|
+
// Check local Supabase
|
|
87
|
+
const running = isLocalRunning();
|
|
88
|
+
console.log(` ${running ? CHECK : CROSS} Local Supabase ${DIM}${running ? "running (127.0.0.1:54321)" : "not running"}${RESET}`);
|
|
89
|
+
// Table count
|
|
90
|
+
if (running) {
|
|
91
|
+
try {
|
|
92
|
+
const count = runShell(`PGPASSWORD=postgres psql "${DB_URL}" -t -c "SELECT count(*) FROM pg_tables WHERE schemaname='public';"`, { timeout: 5000 }).trim();
|
|
93
|
+
console.log(` ${CHECK} Tables ${DIM}${count} in public schema${RESET}`);
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
console.log(` ${WARN} Tables ${DIM}could not query${RESET}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// Last sync
|
|
100
|
+
const syncFile = join(supaDir, ".last-sync");
|
|
101
|
+
if (existsSync(syncFile)) {
|
|
102
|
+
const ts = readFileSync(syncFile, "utf-8").trim();
|
|
103
|
+
console.log(` ${CHECK} Last sync ${DIM}${ts}${RESET}`);
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
console.log(` ${WARN} Last sync ${DIM}never (run: whale db sync)${RESET}`);
|
|
107
|
+
}
|
|
108
|
+
// Config
|
|
109
|
+
const configOk = existsSync(join(supaDir, "config.toml"));
|
|
110
|
+
console.log(` ${configOk ? CHECK : CROSS} Config ${DIM}${configOk ? "supabase/config.toml" : "missing"}${RESET}`);
|
|
111
|
+
console.log();
|
|
112
|
+
}
|
|
113
|
+
async function dbStart(root) {
|
|
114
|
+
const supaDir = join(root, "supabase");
|
|
115
|
+
if (!existsSync(join(supaDir, "config.toml"))) {
|
|
116
|
+
console.error(`${RED}ERROR: supabase/config.toml not found in ${root}${RESET}`);
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}
|
|
119
|
+
if (isLocalRunning()) {
|
|
120
|
+
console.log(`${GREEN}Local Supabase is already running.${RESET}`);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
console.log(`${CYAN}Starting local Supabase...${RESET}`);
|
|
124
|
+
const code = await spawnInteractive("supabase", ["start", "--workdir", "."], supaDir);
|
|
125
|
+
if (code !== 0) {
|
|
126
|
+
console.error(`${RED}Failed to start Supabase (exit ${code})${RESET}`);
|
|
127
|
+
process.exit(1);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
async function dbStop(root) {
|
|
131
|
+
const supaDir = join(root, "supabase");
|
|
132
|
+
console.log(`${CYAN}Stopping local Supabase...${RESET}`);
|
|
133
|
+
const code = await spawnInteractive("supabase", ["stop", "--workdir", "."], supaDir);
|
|
134
|
+
if (code !== 0) {
|
|
135
|
+
console.error(`${RED}Failed to stop Supabase (exit ${code})${RESET}`);
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
console.log(`${GREEN}Local Supabase stopped.${RESET}`);
|
|
139
|
+
}
|
|
140
|
+
async function dbReset(root) {
|
|
141
|
+
const script = join(root, "scripts", "reset-test-db.sh");
|
|
142
|
+
if (!existsSync(script)) {
|
|
143
|
+
console.error(`${RED}ERROR: scripts/reset-test-db.sh not found${RESET}`);
|
|
144
|
+
process.exit(1);
|
|
145
|
+
}
|
|
146
|
+
console.log(`${CYAN}Resetting local database...${RESET}`);
|
|
147
|
+
const code = await spawnInteractive("bash", [script]);
|
|
148
|
+
if (code !== 0) {
|
|
149
|
+
console.error(`${RED}Reset failed (exit ${code})${RESET}`);
|
|
150
|
+
process.exit(1);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
async function dbSync(root) {
|
|
154
|
+
const script = join(root, "scripts", "sync-local-db.sh");
|
|
155
|
+
if (!existsSync(script)) {
|
|
156
|
+
console.error(`${RED}ERROR: scripts/sync-local-db.sh not found${RESET}`);
|
|
157
|
+
process.exit(1);
|
|
158
|
+
}
|
|
159
|
+
console.log(`${CYAN}Syncing production schema to local...${RESET}`);
|
|
160
|
+
const code = await spawnInteractive("bash", [script]);
|
|
161
|
+
if (code !== 0) {
|
|
162
|
+
console.error(`${RED}Sync failed (exit ${code})${RESET}`);
|
|
163
|
+
process.exit(1);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
async function dbSeed(root) {
|
|
167
|
+
const seedFile = join(root, "supabase", "seed.sql");
|
|
168
|
+
if (!existsSync(seedFile)) {
|
|
169
|
+
console.error(`${RED}ERROR: supabase/seed.sql not found${RESET}`);
|
|
170
|
+
process.exit(1);
|
|
171
|
+
}
|
|
172
|
+
if (!isLocalRunning()) {
|
|
173
|
+
console.error(`${RED}ERROR: Local Supabase is not running. Run: whale db start${RESET}`);
|
|
174
|
+
process.exit(1);
|
|
175
|
+
}
|
|
176
|
+
console.log(`${CYAN}Re-seeding local database...${RESET}`);
|
|
177
|
+
try {
|
|
178
|
+
runShell(`PGPASSWORD=postgres psql "${DB_URL}" -f "${seedFile}"`, { timeout: 15000 });
|
|
179
|
+
console.log(`${GREEN}Seed data applied.${RESET}`);
|
|
180
|
+
}
|
|
181
|
+
catch (err) {
|
|
182
|
+
console.error(`${RED}Seed failed: ${err.message?.slice(0, 200)}${RESET}`);
|
|
183
|
+
process.exit(1);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
async function dbTest(root) {
|
|
187
|
+
const script = join(root, "scripts", "run-all-tests.sh");
|
|
188
|
+
if (!existsSync(script)) {
|
|
189
|
+
console.error(`${RED}ERROR: scripts/run-all-tests.sh not found${RESET}`);
|
|
190
|
+
process.exit(1);
|
|
191
|
+
}
|
|
192
|
+
console.log(`${CYAN}Running full test suite against local DB...${RESET}\n`);
|
|
193
|
+
const code = await spawnInteractive("bash", [script]);
|
|
194
|
+
process.exit(code);
|
|
195
|
+
}
|
|
196
|
+
// ── Help ──
|
|
197
|
+
function showDbHelp() {
|
|
198
|
+
console.log(`
|
|
199
|
+
${BOLD}whale db${RESET} — Local Supabase management
|
|
200
|
+
|
|
201
|
+
${BOLD}Subcommands:${RESET}
|
|
202
|
+
whale db status${DIM} Check if local Supabase is running${RESET}
|
|
203
|
+
whale db start${DIM} Start local Supabase${RESET}
|
|
204
|
+
whale db stop${DIM} Stop local Supabase${RESET}
|
|
205
|
+
whale db reset${DIM} Drop + recreate + seed local DB${RESET}
|
|
206
|
+
whale db sync${DIM} Pull prod schema → apply locally${RESET}
|
|
207
|
+
whale db seed${DIM} Re-run seed.sql without full reset${RESET}
|
|
208
|
+
whale db test${DIM} Run full test suite against local DB${RESET}
|
|
209
|
+
`);
|
|
210
|
+
}
|
|
211
|
+
// ── Router ──
|
|
212
|
+
export async function runDbCommand(args) {
|
|
213
|
+
const sub = args[0];
|
|
214
|
+
const root = findEcosystemRoot();
|
|
215
|
+
switch (sub) {
|
|
216
|
+
case "status":
|
|
217
|
+
return dbStatus(root);
|
|
218
|
+
case "start":
|
|
219
|
+
return dbStart(root);
|
|
220
|
+
case "stop":
|
|
221
|
+
return dbStop(root);
|
|
222
|
+
case "reset":
|
|
223
|
+
return dbReset(root);
|
|
224
|
+
case "sync":
|
|
225
|
+
return dbSync(root);
|
|
226
|
+
case "seed":
|
|
227
|
+
return dbSeed(root);
|
|
228
|
+
case "test":
|
|
229
|
+
return dbTest(root);
|
|
230
|
+
case "help":
|
|
231
|
+
case "--help":
|
|
232
|
+
case "-h":
|
|
233
|
+
showDbHelp();
|
|
234
|
+
return;
|
|
235
|
+
case undefined:
|
|
236
|
+
showDbHelp();
|
|
237
|
+
return;
|
|
238
|
+
default:
|
|
239
|
+
console.error(`${RED}Unknown db subcommand: ${sub}${RESET}`);
|
|
240
|
+
showDbHelp();
|
|
241
|
+
process.exit(1);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
@@ -99,12 +99,9 @@ async function checkServerTools() {
|
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
101
|
async function checkMcpServers() {
|
|
102
|
-
const configPath = join(homedir(), ".swagmanager", "config.json");
|
|
103
|
-
if (!existsSync(configPath)) {
|
|
104
|
-
return { status: "warn", label: "MCP servers", detail: "none configured" };
|
|
105
|
-
}
|
|
106
102
|
try {
|
|
107
|
-
const
|
|
103
|
+
const { loadConfig } = await import("../services/config-store.js");
|
|
104
|
+
const config = loadConfig();
|
|
108
105
|
const servers = config.mcpServers || {};
|
|
109
106
|
const count = Object.keys(servers).length;
|
|
110
107
|
if (count === 0) {
|
|
@@ -117,18 +114,18 @@ async function checkMcpServers() {
|
|
|
117
114
|
}
|
|
118
115
|
}
|
|
119
116
|
async function checkDiskSpace() {
|
|
120
|
-
const dir = join(homedir(), ".
|
|
117
|
+
const dir = join(homedir(), ".whaletools");
|
|
121
118
|
if (!existsSync(dir)) {
|
|
122
|
-
return { status: "ok", label: "Data dir", detail: "~/.
|
|
119
|
+
return { status: "ok", label: "Data dir", detail: "~/.whaletools/ (not created yet)" };
|
|
123
120
|
}
|
|
124
121
|
try {
|
|
125
122
|
// Get total size
|
|
126
123
|
const output = execSync(`du -sh "${dir}" 2>/dev/null`, { encoding: "utf-8", timeout: 5000 }).trim();
|
|
127
124
|
const size = output.split("\t")[0];
|
|
128
|
-
return { status: "ok", label: "Data dir", detail: `~/.
|
|
125
|
+
return { status: "ok", label: "Data dir", detail: `~/.whaletools/ (${size})` };
|
|
129
126
|
}
|
|
130
127
|
catch {
|
|
131
|
-
return { status: "ok", label: "Data dir", detail: "~/.
|
|
128
|
+
return { status: "ok", label: "Data dir", detail: "~/.whaletools/" };
|
|
132
129
|
}
|
|
133
130
|
}
|
|
134
131
|
async function checkVersion() {
|