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
package/dist/node/cli.js
CHANGED
|
@@ -26,9 +26,16 @@ async function main() {
|
|
|
26
26
|
case "node":
|
|
27
27
|
await handleNodeCommand(subcommand, args.slice(2));
|
|
28
28
|
break;
|
|
29
|
+
case "remote-desktop":
|
|
30
|
+
await handleRemoteDesktopCommand(subcommand, args.slice(2));
|
|
31
|
+
break;
|
|
29
32
|
case "channel":
|
|
30
33
|
await handleChannelCommand(subcommand, args.slice(2));
|
|
31
34
|
break;
|
|
35
|
+
case "portal":
|
|
36
|
+
case "p":
|
|
37
|
+
await handlePortalCommand(subcommand, args.slice(2));
|
|
38
|
+
break;
|
|
32
39
|
case "status":
|
|
33
40
|
await handleStatus();
|
|
34
41
|
break;
|
|
@@ -91,6 +98,10 @@ async function handleNodeCommand(sub, args) {
|
|
|
91
98
|
}
|
|
92
99
|
case "start": {
|
|
93
100
|
const config = requireConfig();
|
|
101
|
+
// Inject API keys from config into env so decomposer/reviewer can find them
|
|
102
|
+
if (config.anthropic_api_key && !process.env.ANTHROPIC_API_KEY) {
|
|
103
|
+
process.env.ANTHROPIC_API_KEY = config.anthropic_api_key;
|
|
104
|
+
}
|
|
94
105
|
const runtime = new NodeRuntime(config);
|
|
95
106
|
await runtime.start();
|
|
96
107
|
// Keep process alive
|
|
@@ -278,6 +289,37 @@ async function handleChannelCommand(sub, args) {
|
|
|
278
289
|
console.log("Usage: whale channel [add|list]");
|
|
279
290
|
}
|
|
280
291
|
}
|
|
292
|
+
async function handleRemoteDesktopCommand(sub, _args) {
|
|
293
|
+
const config = requireConfig();
|
|
294
|
+
switch (sub) {
|
|
295
|
+
case "enable": {
|
|
296
|
+
config.remote_desktop = { ...config.remote_desktop, enabled: true };
|
|
297
|
+
saveConfig(config);
|
|
298
|
+
console.log("Remote desktop enabled. Takes effect on next `whale node start`.");
|
|
299
|
+
break;
|
|
300
|
+
}
|
|
301
|
+
case "disable": {
|
|
302
|
+
config.remote_desktop = { ...config.remote_desktop, enabled: false };
|
|
303
|
+
saveConfig(config);
|
|
304
|
+
console.log("Remote desktop disabled. Takes effect on next `whale node start`.");
|
|
305
|
+
break;
|
|
306
|
+
}
|
|
307
|
+
case "status": {
|
|
308
|
+
const rd = config.remote_desktop;
|
|
309
|
+
console.log("Remote Desktop:");
|
|
310
|
+
console.log(` Enabled: ${rd?.enabled !== false ? "yes" : "no"}`);
|
|
311
|
+
console.log(` Port: ${rd?.port || 5900}`);
|
|
312
|
+
console.log(` Max FPS: ${rd?.max_fps || 60}`);
|
|
313
|
+
console.log(` Quality: ${rd?.quality || 0.6}`);
|
|
314
|
+
if (process.platform !== "darwin") {
|
|
315
|
+
console.log(` Platform: ${process.platform} (macOS required)`);
|
|
316
|
+
}
|
|
317
|
+
break;
|
|
318
|
+
}
|
|
319
|
+
default:
|
|
320
|
+
console.log("Usage: whale remote-desktop [enable|disable|status]");
|
|
321
|
+
}
|
|
322
|
+
}
|
|
281
323
|
async function handleStatus() {
|
|
282
324
|
const config = loadConfig();
|
|
283
325
|
if (!config) {
|
|
@@ -291,21 +333,430 @@ async function handleStatus() {
|
|
|
291
333
|
console.log(` Channels: ${config.channels.length}`);
|
|
292
334
|
console.log(` Config: ${getConfigPath()}`);
|
|
293
335
|
}
|
|
336
|
+
// ============================================================================
|
|
337
|
+
// PORTAL COMMANDS
|
|
338
|
+
// ============================================================================
|
|
339
|
+
async function handlePortalCommand(sub, args) {
|
|
340
|
+
const config = requireConfig();
|
|
341
|
+
// Lazy imports — only load portal code when needed
|
|
342
|
+
const { discoverNodes, findNode } = await import("./portal/discovery.js");
|
|
343
|
+
const { printNodeList, printConnecting, printConnected, printDisconnected, printError, printProgress, showPortalMenu } = await import("./portal/ui.js");
|
|
344
|
+
const discoveryOpts = {
|
|
345
|
+
serverUrl: config.server_url,
|
|
346
|
+
apiKey: config.api_key,
|
|
347
|
+
storeId: config.store_id,
|
|
348
|
+
};
|
|
349
|
+
switch (sub) {
|
|
350
|
+
case "ls":
|
|
351
|
+
case "list": {
|
|
352
|
+
const { values } = parseArgs({
|
|
353
|
+
args,
|
|
354
|
+
options: { all: { type: "boolean", short: "a", default: false } },
|
|
355
|
+
});
|
|
356
|
+
const nodes = await discoverNodes({ ...discoveryOpts, includeOffline: values.all });
|
|
357
|
+
printNodeList(nodes, values.all);
|
|
358
|
+
break;
|
|
359
|
+
}
|
|
360
|
+
case "sh":
|
|
361
|
+
case "shell": {
|
|
362
|
+
const nodeName = args[0];
|
|
363
|
+
if (!nodeName) {
|
|
364
|
+
console.error("Usage: whale portal sh <node> [--dir PATH] [-c COMMAND]");
|
|
365
|
+
process.exit(1);
|
|
366
|
+
}
|
|
367
|
+
const { values } = parseArgs({
|
|
368
|
+
args: args.slice(1),
|
|
369
|
+
options: {
|
|
370
|
+
dir: { type: "string" },
|
|
371
|
+
c: { type: "string" },
|
|
372
|
+
},
|
|
373
|
+
});
|
|
374
|
+
const node = await findNode(nodeName, discoveryOpts);
|
|
375
|
+
if (!node) {
|
|
376
|
+
printError(`Node "${nodeName}" not found or offline`);
|
|
377
|
+
process.exit(1);
|
|
378
|
+
}
|
|
379
|
+
printConnecting(node.name);
|
|
380
|
+
const { GatewayClient } = await import("./gateway-client.js");
|
|
381
|
+
const { PortalManager } = await import("./portal/index.js");
|
|
382
|
+
const { openShell } = await import("./portal/shell.js");
|
|
383
|
+
const gateway = new GatewayClient({
|
|
384
|
+
serverUrl: config.server_url,
|
|
385
|
+
apiKey: config.api_key,
|
|
386
|
+
capabilities: ["portal"],
|
|
387
|
+
version: "1.1.0",
|
|
388
|
+
});
|
|
389
|
+
gateway.start();
|
|
390
|
+
await waitForGateway(gateway);
|
|
391
|
+
const portal = new PortalManager({ nodeConfig: config, gateway });
|
|
392
|
+
const session = await portal.connect(node.id, ["shell"]);
|
|
393
|
+
printConnected(node.hostname);
|
|
394
|
+
const { cleanup } = await openShell(session, {
|
|
395
|
+
cwd: values.dir,
|
|
396
|
+
command: values.c,
|
|
397
|
+
});
|
|
398
|
+
// If single command mode, we're already done
|
|
399
|
+
if (values.c) {
|
|
400
|
+
cleanup();
|
|
401
|
+
session.close();
|
|
402
|
+
gateway.stop();
|
|
403
|
+
break;
|
|
404
|
+
}
|
|
405
|
+
// Wait for shell to close
|
|
406
|
+
await new Promise((resolve) => {
|
|
407
|
+
session.on("close", () => {
|
|
408
|
+
cleanup();
|
|
409
|
+
printDisconnected();
|
|
410
|
+
resolve();
|
|
411
|
+
});
|
|
412
|
+
});
|
|
413
|
+
gateway.stop();
|
|
414
|
+
break;
|
|
415
|
+
}
|
|
416
|
+
case "push": {
|
|
417
|
+
const localPath = args[0];
|
|
418
|
+
const target = args[1];
|
|
419
|
+
if (!localPath || !target) {
|
|
420
|
+
console.error("Usage: whale portal push <local-path> <node>[:/remote/path]");
|
|
421
|
+
process.exit(1);
|
|
422
|
+
}
|
|
423
|
+
const [nodeName, remotePath] = target.includes(":") ? target.split(":", 2) : [target, undefined];
|
|
424
|
+
const node = await findNode(nodeName, discoveryOpts);
|
|
425
|
+
if (!node) {
|
|
426
|
+
printError(`Node "${nodeName}" not found or offline`);
|
|
427
|
+
process.exit(1);
|
|
428
|
+
}
|
|
429
|
+
printConnecting(node.name);
|
|
430
|
+
const { GatewayClient } = await import("./gateway-client.js");
|
|
431
|
+
const { PortalManager } = await import("./portal/index.js");
|
|
432
|
+
const { pushFile } = await import("./portal/transfer.js");
|
|
433
|
+
const gateway = new GatewayClient({
|
|
434
|
+
serverUrl: config.server_url,
|
|
435
|
+
apiKey: config.api_key,
|
|
436
|
+
capabilities: ["portal"],
|
|
437
|
+
version: "1.1.0",
|
|
438
|
+
});
|
|
439
|
+
gateway.start();
|
|
440
|
+
await waitForGateway(gateway);
|
|
441
|
+
const portal = new PortalManager({ nodeConfig: config, gateway });
|
|
442
|
+
const session = await portal.connect(node.id, ["file"]);
|
|
443
|
+
printConnected(node.hostname);
|
|
444
|
+
await pushFile(session, localPath, remotePath, printProgress);
|
|
445
|
+
console.log(`\nFile sent to ${node.name}`);
|
|
446
|
+
session.close();
|
|
447
|
+
gateway.stop();
|
|
448
|
+
break;
|
|
449
|
+
}
|
|
450
|
+
case "pull": {
|
|
451
|
+
const source = args[0];
|
|
452
|
+
const localDir = args[1];
|
|
453
|
+
if (!source || !source.includes(":")) {
|
|
454
|
+
console.error("Usage: whale portal pull <node>:/path/to/file [local-dir]");
|
|
455
|
+
process.exit(1);
|
|
456
|
+
}
|
|
457
|
+
const [nodeName, remotePath] = source.split(":", 2);
|
|
458
|
+
const node = await findNode(nodeName, discoveryOpts);
|
|
459
|
+
if (!node) {
|
|
460
|
+
printError(`Node "${nodeName}" not found or offline`);
|
|
461
|
+
process.exit(1);
|
|
462
|
+
}
|
|
463
|
+
printConnecting(node.name);
|
|
464
|
+
const { GatewayClient } = await import("./gateway-client.js");
|
|
465
|
+
const { PortalManager } = await import("./portal/index.js");
|
|
466
|
+
const { pullFile } = await import("./portal/transfer.js");
|
|
467
|
+
const gateway = new GatewayClient({
|
|
468
|
+
serverUrl: config.server_url,
|
|
469
|
+
apiKey: config.api_key,
|
|
470
|
+
capabilities: ["portal"],
|
|
471
|
+
version: "1.1.0",
|
|
472
|
+
});
|
|
473
|
+
gateway.start();
|
|
474
|
+
await waitForGateway(gateway);
|
|
475
|
+
const portal = new PortalManager({ nodeConfig: config, gateway });
|
|
476
|
+
const session = await portal.connect(node.id, ["file"]);
|
|
477
|
+
printConnected(node.hostname);
|
|
478
|
+
const savedPath = await pullFile(session, remotePath, localDir, printProgress);
|
|
479
|
+
console.log(`\nFile saved to ${savedPath}`);
|
|
480
|
+
session.close();
|
|
481
|
+
gateway.stop();
|
|
482
|
+
break;
|
|
483
|
+
}
|
|
484
|
+
case "forward": {
|
|
485
|
+
// Format: 8080:<node>:3000
|
|
486
|
+
const spec = args[0];
|
|
487
|
+
if (!spec) {
|
|
488
|
+
console.error("Usage: whale portal forward <local-port>:<node>:<remote-port>");
|
|
489
|
+
process.exit(1);
|
|
490
|
+
}
|
|
491
|
+
const parts = spec.split(":");
|
|
492
|
+
if (parts.length !== 3) {
|
|
493
|
+
console.error("Format: <local-port>:<node>:<remote-port>");
|
|
494
|
+
process.exit(1);
|
|
495
|
+
}
|
|
496
|
+
const localPort = parseInt(parts[0], 10);
|
|
497
|
+
const nodeName = parts[1];
|
|
498
|
+
const remotePort = parseInt(parts[2], 10);
|
|
499
|
+
const node = await findNode(nodeName, discoveryOpts);
|
|
500
|
+
if (!node) {
|
|
501
|
+
printError(`Node "${nodeName}" not found or offline`);
|
|
502
|
+
process.exit(1);
|
|
503
|
+
}
|
|
504
|
+
printConnecting(node.name);
|
|
505
|
+
const { GatewayClient } = await import("./gateway-client.js");
|
|
506
|
+
const { PortalManager } = await import("./portal/index.js");
|
|
507
|
+
const { startForward } = await import("./portal/forward.js");
|
|
508
|
+
const gateway = new GatewayClient({
|
|
509
|
+
serverUrl: config.server_url,
|
|
510
|
+
apiKey: config.api_key,
|
|
511
|
+
capabilities: ["portal"],
|
|
512
|
+
version: "1.1.0",
|
|
513
|
+
});
|
|
514
|
+
gateway.start();
|
|
515
|
+
await waitForGateway(gateway);
|
|
516
|
+
const portal = new PortalManager({ nodeConfig: config, gateway });
|
|
517
|
+
const session = await portal.connect(node.id, ["forward"]);
|
|
518
|
+
printConnected(node.hostname);
|
|
519
|
+
const info = await startForward(session, localPort, remotePort);
|
|
520
|
+
console.log(`Forwarding localhost:${info.localPort} → ${node.name}:${info.remotePort}`);
|
|
521
|
+
console.log("Press Ctrl+C to stop");
|
|
522
|
+
await new Promise((resolve) => {
|
|
523
|
+
process.on("SIGINT", () => {
|
|
524
|
+
info.server.close();
|
|
525
|
+
session.close();
|
|
526
|
+
gateway.stop();
|
|
527
|
+
resolve();
|
|
528
|
+
});
|
|
529
|
+
});
|
|
530
|
+
break;
|
|
531
|
+
}
|
|
532
|
+
case "screen": {
|
|
533
|
+
const nodeName = args[0];
|
|
534
|
+
if (!nodeName) {
|
|
535
|
+
console.error("Usage: whale portal screen <node> [--control]");
|
|
536
|
+
process.exit(1);
|
|
537
|
+
}
|
|
538
|
+
const { values } = parseArgs({
|
|
539
|
+
args: args.slice(1),
|
|
540
|
+
options: { control: { type: "boolean", default: false } },
|
|
541
|
+
});
|
|
542
|
+
const node = await findNode(nodeName, discoveryOpts);
|
|
543
|
+
if (!node) {
|
|
544
|
+
printError(`Node "${nodeName}" not found or offline`);
|
|
545
|
+
process.exit(1);
|
|
546
|
+
}
|
|
547
|
+
printConnecting(node.name);
|
|
548
|
+
const { GatewayClient } = await import("./gateway-client.js");
|
|
549
|
+
const { PortalManager } = await import("./portal/index.js");
|
|
550
|
+
const { openScreen } = await import("./portal/screen.js");
|
|
551
|
+
const gateway = new GatewayClient({
|
|
552
|
+
serverUrl: config.server_url,
|
|
553
|
+
apiKey: config.api_key,
|
|
554
|
+
capabilities: ["portal"],
|
|
555
|
+
version: "1.1.0",
|
|
556
|
+
});
|
|
557
|
+
gateway.start();
|
|
558
|
+
await waitForGateway(gateway);
|
|
559
|
+
const portal = new PortalManager({ nodeConfig: config, gateway });
|
|
560
|
+
const session = await portal.connect(node.id, ["screen"]);
|
|
561
|
+
printConnected(node.hostname);
|
|
562
|
+
const { stream, cleanup } = await openScreen(session, values.control ?? false);
|
|
563
|
+
console.log(`Screen share active${values.control ? " (control enabled)" : " (view only)"}`);
|
|
564
|
+
console.log("Press Ctrl+C to stop");
|
|
565
|
+
let frameCount = 0;
|
|
566
|
+
stream.on("data", () => {
|
|
567
|
+
frameCount++;
|
|
568
|
+
if (frameCount % 60 === 0) {
|
|
569
|
+
process.stderr.write(`\r Frames: ${frameCount}`);
|
|
570
|
+
}
|
|
571
|
+
});
|
|
572
|
+
await new Promise((resolve) => {
|
|
573
|
+
process.on("SIGINT", () => {
|
|
574
|
+
cleanup();
|
|
575
|
+
session.close();
|
|
576
|
+
gateway.stop();
|
|
577
|
+
console.log(`\n Total frames: ${frameCount}`);
|
|
578
|
+
resolve();
|
|
579
|
+
});
|
|
580
|
+
});
|
|
581
|
+
break;
|
|
582
|
+
}
|
|
583
|
+
case "clip":
|
|
584
|
+
case "clipboard": {
|
|
585
|
+
const { values: clipValues } = parseArgs({
|
|
586
|
+
args,
|
|
587
|
+
options: {
|
|
588
|
+
from: { type: "string" },
|
|
589
|
+
sync: { type: "string" },
|
|
590
|
+
},
|
|
591
|
+
allowPositionals: true,
|
|
592
|
+
});
|
|
593
|
+
const positionals = args.filter(a => !a.startsWith("--"));
|
|
594
|
+
const nodeName = clipValues.from || clipValues.sync || positionals[0];
|
|
595
|
+
if (!nodeName) {
|
|
596
|
+
console.error("Usage: whale portal clip <node> | --from <node> | --sync <node>");
|
|
597
|
+
process.exit(1);
|
|
598
|
+
}
|
|
599
|
+
const node = await findNode(nodeName, discoveryOpts);
|
|
600
|
+
if (!node) {
|
|
601
|
+
printError(`Node "${nodeName}" not found or offline`);
|
|
602
|
+
process.exit(1);
|
|
603
|
+
}
|
|
604
|
+
const { GatewayClient } = await import("./gateway-client.js");
|
|
605
|
+
const { PortalManager } = await import("./portal/index.js");
|
|
606
|
+
const { pushClipboard, pullClipboard, syncClipboard } = await import("./portal/clipboard.js");
|
|
607
|
+
const gateway = new GatewayClient({
|
|
608
|
+
serverUrl: config.server_url,
|
|
609
|
+
apiKey: config.api_key,
|
|
610
|
+
capabilities: ["portal"],
|
|
611
|
+
version: "1.1.0",
|
|
612
|
+
});
|
|
613
|
+
gateway.start();
|
|
614
|
+
await waitForGateway(gateway);
|
|
615
|
+
const portal = new PortalManager({ nodeConfig: config, gateway });
|
|
616
|
+
const session = await portal.connect(node.id, ["clipboard"]);
|
|
617
|
+
if (clipValues.sync) {
|
|
618
|
+
const { cleanup } = await syncClipboard(session);
|
|
619
|
+
console.log(`Clipboard sync active with ${node.name}. Press Ctrl+C to stop.`);
|
|
620
|
+
await new Promise((resolve) => {
|
|
621
|
+
process.on("SIGINT", () => {
|
|
622
|
+
cleanup();
|
|
623
|
+
session.close();
|
|
624
|
+
gateway.stop();
|
|
625
|
+
resolve();
|
|
626
|
+
});
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
else if (clipValues.from) {
|
|
630
|
+
const content = await pullClipboard(session);
|
|
631
|
+
console.log(`Clipboard received from ${node.name} (${content.length} chars)`);
|
|
632
|
+
session.close();
|
|
633
|
+
gateway.stop();
|
|
634
|
+
}
|
|
635
|
+
else {
|
|
636
|
+
await pushClipboard(session);
|
|
637
|
+
console.log(`Clipboard sent to ${node.name}`);
|
|
638
|
+
session.close();
|
|
639
|
+
gateway.stop();
|
|
640
|
+
}
|
|
641
|
+
break;
|
|
642
|
+
}
|
|
643
|
+
case "status": {
|
|
644
|
+
// Just show local session info from config
|
|
645
|
+
console.log("Use `whale portal ls` to see online nodes.");
|
|
646
|
+
console.log("Use `whale portal close <session-id>` to close a session.");
|
|
647
|
+
break;
|
|
648
|
+
}
|
|
649
|
+
case "close": {
|
|
650
|
+
const sessionId = args[0];
|
|
651
|
+
if (!sessionId) {
|
|
652
|
+
console.error("Usage: whale portal close <session-id>");
|
|
653
|
+
process.exit(1);
|
|
654
|
+
}
|
|
655
|
+
console.log(`Session ${sessionId} closed.`);
|
|
656
|
+
break;
|
|
657
|
+
}
|
|
658
|
+
default: {
|
|
659
|
+
// If sub looks like a node name, open interactive menu
|
|
660
|
+
if (sub && !sub.startsWith("-")) {
|
|
661
|
+
const node = await findNode(sub, discoveryOpts);
|
|
662
|
+
if (node) {
|
|
663
|
+
const choice = await showPortalMenu(node.name);
|
|
664
|
+
if (choice === "quit")
|
|
665
|
+
break;
|
|
666
|
+
// Re-dispatch based on menu choice
|
|
667
|
+
switch (choice) {
|
|
668
|
+
case "shell":
|
|
669
|
+
await handlePortalCommand("sh", [sub]);
|
|
670
|
+
break;
|
|
671
|
+
case "push":
|
|
672
|
+
console.log("Enter local path to push:");
|
|
673
|
+
// Fall through to push command with readline
|
|
674
|
+
break;
|
|
675
|
+
case "pull":
|
|
676
|
+
console.log("Enter remote path to pull:");
|
|
677
|
+
break;
|
|
678
|
+
case "forward":
|
|
679
|
+
console.log("Enter forward spec (local:remote):");
|
|
680
|
+
break;
|
|
681
|
+
case "screen":
|
|
682
|
+
await handlePortalCommand("screen", [sub]);
|
|
683
|
+
break;
|
|
684
|
+
case "clipboard":
|
|
685
|
+
await handlePortalCommand("clip", [sub]);
|
|
686
|
+
break;
|
|
687
|
+
default:
|
|
688
|
+
printError("Unknown menu option");
|
|
689
|
+
}
|
|
690
|
+
break;
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
printPortalHelp();
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
function printPortalHelp() {
|
|
698
|
+
console.log(`
|
|
699
|
+
Whale Portal — node-to-node connectivity
|
|
700
|
+
|
|
701
|
+
Commands:
|
|
702
|
+
whale portal ls [--all] List online nodes
|
|
703
|
+
whale portal sh <node> [--dir PATH] [-c CMD] Remote shell
|
|
704
|
+
whale portal push <path> <node>[:/dest] Send file to node
|
|
705
|
+
whale portal pull <node>:/path [local-dir] Get file from node
|
|
706
|
+
whale portal forward <local>:<node>:<remote> Port forwarding
|
|
707
|
+
whale portal screen <node> [--control] Screen share
|
|
708
|
+
whale portal clip <node> Send clipboard to node
|
|
709
|
+
whale portal clip --from <node> Get clipboard from node
|
|
710
|
+
whale portal clip --sync <node> Bidirectional clipboard sync
|
|
711
|
+
whale portal <node> Interactive portal menu
|
|
712
|
+
|
|
713
|
+
Alias: whale p <anything> = whale portal <anything>
|
|
714
|
+
`);
|
|
715
|
+
}
|
|
716
|
+
async function waitForGateway(gateway) {
|
|
717
|
+
if (gateway.isConnected)
|
|
718
|
+
return;
|
|
719
|
+
return new Promise((resolve, reject) => {
|
|
720
|
+
const timeout = setTimeout(() => reject(new Error("Gateway connection timed out")), 15_000);
|
|
721
|
+
gateway.on("connected", () => {
|
|
722
|
+
clearTimeout(timeout);
|
|
723
|
+
resolve();
|
|
724
|
+
});
|
|
725
|
+
gateway.on("error", (err) => {
|
|
726
|
+
clearTimeout(timeout);
|
|
727
|
+
reject(new Error(`Gateway error: ${err}`));
|
|
728
|
+
});
|
|
729
|
+
});
|
|
730
|
+
}
|
|
294
731
|
function printHelp() {
|
|
295
732
|
console.log(`
|
|
296
733
|
WhaleNode CLI v1.1.0 — Bridge local channels to WhaleTools AI agents
|
|
297
734
|
|
|
298
735
|
Commands:
|
|
299
|
-
whale node register
|
|
300
|
-
whale node start
|
|
301
|
-
whale node status
|
|
736
|
+
whale node register Register this machine as a WhaleNode
|
|
737
|
+
whale node start Start the node (heartbeat + channel adapters)
|
|
738
|
+
whale node status Show node status
|
|
739
|
+
|
|
740
|
+
whale channel add Add a channel adapter
|
|
741
|
+
whale channel list List configured channels
|
|
742
|
+
|
|
743
|
+
whale remote-desktop enable Enable remote desktop (macOS)
|
|
744
|
+
whale remote-desktop disable Disable remote desktop
|
|
745
|
+
whale remote-desktop status Show remote desktop status
|
|
302
746
|
|
|
303
|
-
whale
|
|
304
|
-
whale
|
|
747
|
+
whale portal ls List online nodes in your store
|
|
748
|
+
whale portal sh <node> Remote shell on a node
|
|
749
|
+
whale portal push <f> <n> Send file to a node
|
|
750
|
+
whale portal pull <n>:/f Get file from a node
|
|
751
|
+
whale portal forward L:N:R Forward local port L to remote port R via node N
|
|
752
|
+
whale portal screen <node> Screen share a node
|
|
753
|
+
whale portal clip <node> Clipboard sync
|
|
754
|
+
whale portal <node> Interactive portal menu
|
|
755
|
+
whale p <...> Alias for whale portal
|
|
305
756
|
|
|
306
|
-
whale status
|
|
307
|
-
whale version
|
|
308
|
-
whale help
|
|
757
|
+
whale status Quick status check
|
|
758
|
+
whale version Show version
|
|
759
|
+
whale help Show this help
|
|
309
760
|
|
|
310
761
|
Channel Types:
|
|
311
762
|
imessage --groups 109,110 --mention @whale
|
package/dist/node/config.d.ts
CHANGED
|
@@ -4,12 +4,29 @@ export interface ChannelConfig {
|
|
|
4
4
|
name: string;
|
|
5
5
|
config: Record<string, unknown>;
|
|
6
6
|
}
|
|
7
|
+
export interface RemoteDesktopConfig {
|
|
8
|
+
enabled?: boolean;
|
|
9
|
+
port?: number;
|
|
10
|
+
max_fps?: number;
|
|
11
|
+
quality?: number;
|
|
12
|
+
}
|
|
13
|
+
export interface PortalConfig {
|
|
14
|
+
receive_dir?: string;
|
|
15
|
+
auto_accept_admins?: boolean;
|
|
16
|
+
approval_ttl_minutes?: number;
|
|
17
|
+
max_sessions?: number;
|
|
18
|
+
}
|
|
7
19
|
export interface NodeConfig {
|
|
8
20
|
node_id: string;
|
|
9
21
|
api_key: string;
|
|
10
22
|
store_id: string;
|
|
11
23
|
server_url: string;
|
|
12
24
|
channels: ChannelConfig[];
|
|
25
|
+
remote_desktop?: RemoteDesktopConfig;
|
|
26
|
+
portal?: PortalConfig;
|
|
27
|
+
supabase_url?: string;
|
|
28
|
+
supabase_service_key?: string;
|
|
29
|
+
anthropic_api_key?: string;
|
|
13
30
|
}
|
|
14
31
|
export declare function getConfigPath(): string;
|
|
15
32
|
export declare function loadConfig(): NodeConfig | null;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gateway Client — connects whale-node to the server's local-agent-gateway WebSocket.
|
|
3
|
+
*
|
|
4
|
+
* Authenticates with the node's API key, handles ping/pong keepalive,
|
|
5
|
+
* routes incoming exec/cluster_command messages, responds with results.
|
|
6
|
+
* Auto-reconnects on disconnect with exponential backoff.
|
|
7
|
+
*/
|
|
8
|
+
import { EventEmitter } from "node:events";
|
|
9
|
+
export interface GatewayClientConfig {
|
|
10
|
+
serverUrl: string;
|
|
11
|
+
apiKey: string;
|
|
12
|
+
capabilities?: string[];
|
|
13
|
+
version?: string;
|
|
14
|
+
}
|
|
15
|
+
export type CommandHandler = (msg: any) => Promise<any>;
|
|
16
|
+
export type BinaryHandler = (data: Buffer) => void;
|
|
17
|
+
export declare class GatewayClient extends EventEmitter {
|
|
18
|
+
private ws;
|
|
19
|
+
private config;
|
|
20
|
+
private reconnectDelay;
|
|
21
|
+
private maxReconnectDelay;
|
|
22
|
+
private running;
|
|
23
|
+
private agentId;
|
|
24
|
+
private commandHandlers;
|
|
25
|
+
private binaryHandler;
|
|
26
|
+
constructor(config: GatewayClientConfig);
|
|
27
|
+
/**
|
|
28
|
+
* Register a handler for a specific command type (e.g. "exec", "cluster_command").
|
|
29
|
+
*/
|
|
30
|
+
onCommand(type: string, handler: CommandHandler): void;
|
|
31
|
+
/**
|
|
32
|
+
* Connect to the server's WebSocket gateway.
|
|
33
|
+
*/
|
|
34
|
+
start(): void;
|
|
35
|
+
/**
|
|
36
|
+
* Disconnect and stop reconnecting.
|
|
37
|
+
*/
|
|
38
|
+
stop(): void;
|
|
39
|
+
/**
|
|
40
|
+
* Register a handler for binary messages (portal frames).
|
|
41
|
+
*/
|
|
42
|
+
onBinary(handler: BinaryHandler): void;
|
|
43
|
+
/**
|
|
44
|
+
* Send raw binary data over the WebSocket (for portal frames).
|
|
45
|
+
*/
|
|
46
|
+
sendBinary(data: Buffer): void;
|
|
47
|
+
/**
|
|
48
|
+
* Send a JSON message over the WebSocket.
|
|
49
|
+
*/
|
|
50
|
+
sendJson(msg: Record<string, unknown>): void;
|
|
51
|
+
get isConnected(): boolean;
|
|
52
|
+
private connect;
|
|
53
|
+
private scheduleReconnect;
|
|
54
|
+
private send;
|
|
55
|
+
}
|