@burdenoff/vibe-agent 1.0.1 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/README.md +14 -14
  2. package/dist/app.d.ts +4 -4
  3. package/dist/app.d.ts.map +1 -1
  4. package/dist/app.js +145 -130
  5. package/dist/app.js.map +1 -1
  6. package/dist/cli.d.ts +1 -1
  7. package/dist/cli.js +463 -333
  8. package/dist/cli.js.map +1 -1
  9. package/dist/db/schema.d.ts +15 -15
  10. package/dist/db/schema.d.ts.map +1 -1
  11. package/dist/db/schema.js +65 -62
  12. package/dist/db/schema.js.map +1 -1
  13. package/dist/index.d.ts +1 -1
  14. package/dist/index.js +18 -18
  15. package/dist/index.js.map +1 -1
  16. package/dist/middleware/ModuleAuth.d.ts +2 -2
  17. package/dist/middleware/ModuleAuth.d.ts.map +1 -1
  18. package/dist/middleware/ModuleAuth.js +32 -29
  19. package/dist/middleware/ModuleAuth.js.map +1 -1
  20. package/dist/middleware/auth.d.ts +1 -1
  21. package/dist/middleware/auth.d.ts.map +1 -1
  22. package/dist/middleware/auth.js +4 -4
  23. package/dist/middleware/auth.js.map +1 -1
  24. package/dist/migrations/remove-notes-prompts.d.ts.map +1 -1
  25. package/dist/migrations/remove-notes-prompts.js +26 -26
  26. package/dist/migrations/remove-notes-prompts.js.map +1 -1
  27. package/dist/routes/bookmarks.d.ts +1 -1
  28. package/dist/routes/bookmarks.d.ts.map +1 -1
  29. package/dist/routes/bookmarks.js +53 -44
  30. package/dist/routes/bookmarks.js.map +1 -1
  31. package/dist/routes/config.d.ts +1 -1
  32. package/dist/routes/config.d.ts.map +1 -1
  33. package/dist/routes/config.js +29 -27
  34. package/dist/routes/config.js.map +1 -1
  35. package/dist/routes/files.d.ts +1 -1
  36. package/dist/routes/files.d.ts.map +1 -1
  37. package/dist/routes/files.js +175 -134
  38. package/dist/routes/files.js.map +1 -1
  39. package/dist/routes/git.d.ts +1 -1
  40. package/dist/routes/git.d.ts.map +1 -1
  41. package/dist/routes/git.js +183 -169
  42. package/dist/routes/git.js.map +1 -1
  43. package/dist/routes/moduleRegistry.d.ts +3 -3
  44. package/dist/routes/moduleRegistry.d.ts.map +1 -1
  45. package/dist/routes/moduleRegistry.js +58 -58
  46. package/dist/routes/moduleRegistry.js.map +1 -1
  47. package/dist/routes/notifications.d.ts +1 -1
  48. package/dist/routes/notifications.d.ts.map +1 -1
  49. package/dist/routes/notifications.js +69 -64
  50. package/dist/routes/notifications.js.map +1 -1
  51. package/dist/routes/port-forward.d.ts +1 -1
  52. package/dist/routes/port-forward.d.ts.map +1 -1
  53. package/dist/routes/port-forward.js +59 -50
  54. package/dist/routes/port-forward.js.map +1 -1
  55. package/dist/routes/projects.d.ts +1 -1
  56. package/dist/routes/projects.d.ts.map +1 -1
  57. package/dist/routes/projects.js +134 -120
  58. package/dist/routes/projects.js.map +1 -1
  59. package/dist/routes/ssh.d.ts +1 -1
  60. package/dist/routes/ssh.d.ts.map +1 -1
  61. package/dist/routes/ssh.js +47 -47
  62. package/dist/routes/ssh.js.map +1 -1
  63. package/dist/routes/tasks.d.ts +1 -1
  64. package/dist/routes/tasks.d.ts.map +1 -1
  65. package/dist/routes/tasks.js +53 -49
  66. package/dist/routes/tasks.js.map +1 -1
  67. package/dist/routes/tmux.d.ts +1 -1
  68. package/dist/routes/tmux.d.ts.map +1 -1
  69. package/dist/routes/tmux.js +337 -241
  70. package/dist/routes/tmux.js.map +1 -1
  71. package/dist/routes/tunnel.d.ts +2 -2
  72. package/dist/routes/tunnel.d.ts.map +1 -1
  73. package/dist/routes/tunnel.js +115 -74
  74. package/dist/routes/tunnel.js.map +1 -1
  75. package/dist/services/ModulePermissions.d.ts +2 -2
  76. package/dist/services/ModulePermissions.d.ts.map +1 -1
  77. package/dist/services/ModulePermissions.js +50 -40
  78. package/dist/services/ModulePermissions.js.map +1 -1
  79. package/dist/services/ModuleRegistryService.d.ts +10 -10
  80. package/dist/services/ModuleRegistryService.d.ts.map +1 -1
  81. package/dist/services/ModuleRegistryService.js +156 -131
  82. package/dist/services/ModuleRegistryService.js.map +1 -1
  83. package/dist/services/agent.service.d.ts.map +1 -1
  84. package/dist/services/agent.service.js +24 -21
  85. package/dist/services/agent.service.js.map +1 -1
  86. package/dist/services/bootstrap.d.ts +1 -1
  87. package/dist/services/bootstrap.d.ts.map +1 -1
  88. package/dist/services/bootstrap.js +146 -69
  89. package/dist/services/bootstrap.js.map +1 -1
  90. package/dist/services/service-manager.d.ts +2 -2
  91. package/dist/services/service-manager.d.ts.map +1 -1
  92. package/dist/services/service-manager.js +75 -63
  93. package/dist/services/service-manager.js.map +1 -1
  94. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -1,34 +1,63 @@
1
1
  #!/usr/bin/env node
2
- import 'dotenv/config';
3
- import { Command } from 'commander';
4
- import { AgentService } from './services/agent.service.js';
5
- import { ServiceManager } from './services/service-manager.js';
6
- import { readFileSync } from 'fs';
7
- import { join, dirname } from 'path';
8
- import { fileURLToPath } from 'url';
2
+ import "dotenv/config";
3
+ import { Command } from "commander";
4
+ import { AgentService } from "./services/agent.service.js";
5
+ import { ServiceManager } from "./services/service-manager.js";
6
+ import { readFileSync } from "fs";
7
+ import { join, dirname } from "path";
8
+ import { fileURLToPath } from "url";
9
9
  const __filename = fileURLToPath(import.meta.url);
10
10
  const __dirname = dirname(__filename);
11
11
  // Read package.json for version
12
- let packageVersion = '1.0.0';
12
+ let packageVersion = "1.0.0";
13
13
  try {
14
- const packageJsonPath = join(__dirname, '..', 'package.json');
15
- const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
14
+ const packageJsonPath = join(__dirname, "..", "package.json");
15
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
16
16
  packageVersion = packageJson.version;
17
17
  }
18
18
  catch {
19
19
  // fallback
20
20
  }
21
- const DEFAULT_AGENT_URL = 'http://localhost:3005';
21
+ const DEFAULT_AGENT_URL = "http://localhost:3005";
22
22
  // ─── Helpers ──────────────────────────────────────────────────────────────────
23
+ // Cache for auto-fetched API key (per agent URL)
24
+ const _apiKeyCache = new Map();
25
+ /**
26
+ * Resolve the agent API key. Uses AGENT_API_KEY env if set, otherwise
27
+ * auto-fetches from the agent's auth-exempt /api/agent-api-key endpoint.
28
+ */
29
+ async function resolveApiKey(agentUrl) {
30
+ // Prefer explicit env variable
31
+ if (process.env.AGENT_API_KEY)
32
+ return process.env.AGENT_API_KEY;
33
+ // Check cache
34
+ if (_apiKeyCache.has(agentUrl))
35
+ return _apiKeyCache.get(agentUrl);
36
+ // Auto-fetch from agent (this endpoint is auth-exempt)
37
+ try {
38
+ const res = await fetch(`${agentUrl}/api/agent-api-key`);
39
+ if (res.ok) {
40
+ const data = (await res.json());
41
+ if (data.apiKey) {
42
+ _apiKeyCache.set(agentUrl, data.apiKey);
43
+ return data.apiKey;
44
+ }
45
+ }
46
+ }
47
+ catch {
48
+ // Agent not reachable — return undefined, caller will fail with a clear message
49
+ }
50
+ return undefined;
51
+ }
23
52
  async function agentFetch(agentUrl, path, options = {}) {
24
- const apiKey = process.env.AGENT_API_KEY;
53
+ const apiKey = await resolveApiKey(agentUrl);
25
54
  const headers = {
26
- ...(options.body ? { 'Content-Type': 'application/json' } : {}),
27
- ...(apiKey ? { 'x-agent-api-key': apiKey } : {}),
55
+ ...(options.body ? { "Content-Type": "application/json" } : {}),
56
+ ...(apiKey ? { "x-agent-api-key": apiKey } : {}),
28
57
  ...(options.headers ?? {}),
29
58
  };
30
59
  const res = await fetch(`${agentUrl}${path}`, { ...options, headers });
31
- const data = await res.json().catch(() => ({}));
60
+ const data = (await res.json().catch(() => ({})));
32
61
  return { ok: res.ok, status: res.status, data };
33
62
  }
34
63
  function fail(msg) {
@@ -37,11 +66,97 @@ function fail(msg) {
37
66
  }
38
67
  function formatTable(rows) {
39
68
  if (rows.length === 0) {
40
- console.log(' (none)');
69
+ console.log(" (none)");
41
70
  return;
42
71
  }
43
72
  console.table(rows);
44
73
  }
74
+ /**
75
+ * After the agent starts, poll until healthy and print connection details
76
+ * (agent URL, API key, tunnel URL). Used by both foreground and daemon start.
77
+ */
78
+ async function printAgentDetails(agentUrl, maxWaitMs = 15000) {
79
+ const startTime = Date.now();
80
+ let healthy = false;
81
+ // Poll until the agent is healthy
82
+ while (Date.now() - startTime < maxWaitMs) {
83
+ try {
84
+ const res = await fetch(`${agentUrl}/health`);
85
+ if (res.ok) {
86
+ healthy = true;
87
+ break;
88
+ }
89
+ }
90
+ catch {
91
+ // Not ready yet
92
+ }
93
+ await new Promise((r) => setTimeout(r, 500));
94
+ }
95
+ if (!healthy) {
96
+ console.log(`\n \x1b[33m⚠\x1b[0m Agent not yet responding at ${agentUrl}. Check \x1b[1mvibe logs\x1b[0m for details.\n`);
97
+ return;
98
+ }
99
+ // Fetch API key (auth-exempt endpoint)
100
+ let apiKey = "(unavailable)";
101
+ try {
102
+ const res = await fetch(`${agentUrl}/api/agent-api-key`);
103
+ if (res.ok) {
104
+ const data = (await res.json());
105
+ apiKey = data.apiKey;
106
+ }
107
+ }
108
+ catch {
109
+ /* ignore */
110
+ }
111
+ // Fetch tunnel URL (auth-exempt endpoint)
112
+ let tunnelUrl = "(not running)";
113
+ let tunnelWaiting = false;
114
+ try {
115
+ const res = await fetch(`${agentUrl}/api/agent-tunnel`);
116
+ if (res.ok) {
117
+ const data = (await res.json());
118
+ if (data.tunnelUrl) {
119
+ tunnelUrl = data.tunnelUrl;
120
+ }
121
+ else if (data.status !== "inactive") {
122
+ tunnelWaiting = true;
123
+ }
124
+ }
125
+ }
126
+ catch {
127
+ /* ignore */
128
+ }
129
+ // If tunnel is still starting, wait a bit longer for it
130
+ if (tunnelWaiting || tunnelUrl === "(not running)") {
131
+ const tunnelWaitMs = 20000;
132
+ const tunnelStart = Date.now();
133
+ while (Date.now() - tunnelStart < tunnelWaitMs) {
134
+ try {
135
+ const res = await fetch(`${agentUrl}/api/agent-tunnel`);
136
+ if (res.ok) {
137
+ const data = (await res.json());
138
+ if (data.tunnelUrl) {
139
+ tunnelUrl = data.tunnelUrl;
140
+ break;
141
+ }
142
+ }
143
+ }
144
+ catch {
145
+ /* ignore */
146
+ }
147
+ await new Promise((r) => setTimeout(r, 1000));
148
+ }
149
+ }
150
+ console.log(`\n \x1b[1m── Agent Connection Details ──\x1b[0m\n`);
151
+ console.log(` \x1b[1mAgent URL:\x1b[0m ${agentUrl}`);
152
+ console.log(` \x1b[1mAPI Key:\x1b[0m ${apiKey}`);
153
+ console.log(` \x1b[1mTunnel URL:\x1b[0m ${tunnelUrl === "(not running)" ? `\x1b[33m${tunnelUrl}\x1b[0m` : `\x1b[32m${tunnelUrl}\x1b[0m`}`);
154
+ console.log(`\n Copy the API Key and Tunnel URL into the VibeControls UI agent configuration.`);
155
+ if (tunnelUrl === "(not running)") {
156
+ console.log(` Start a tunnel manually: \x1b[1mvibe tunnel agent --start --agent-url ${agentUrl}\x1b[0m`);
157
+ }
158
+ console.log();
159
+ }
45
160
  function timeAgo(dateStr) {
46
161
  const diff = Date.now() - new Date(dateStr).getTime();
47
162
  const s = Math.floor(diff / 1000);
@@ -60,19 +175,19 @@ const program = new Command();
60
175
  const agentService = new AgentService();
61
176
  const serviceManager = new ServiceManager();
62
177
  program
63
- .name('vibe')
64
- .description('VibeControls Agent CLI — Remote development environment management')
65
- .version(packageVersion, '-v, --version');
178
+ .name("vibe")
179
+ .description("VibeControls Agent CLI — Remote development environment management")
180
+ .version(packageVersion, "-v, --version");
66
181
  // ═══════════════════════════════════════════════════════════════════════════════
67
182
  // Core Commands
68
183
  // ═══════════════════════════════════════════════════════════════════════════════
69
184
  program
70
- .command('start')
71
- .description('Start the agent server')
72
- .option('-p, --port <port>', 'Port to run on', '3005')
73
- .option('-n, --name <name>', 'Instance name', 'default')
74
- .option('-d, --daemon', 'Run as background daemon', false)
75
- .option('--db-path <path>', 'SQLite database path', './vibecontrols-agent.db')
185
+ .command("start")
186
+ .description("Start the agent server")
187
+ .option("-p, --port <port>", "Port to run on", "3005")
188
+ .option("-n, --name <name>", "Instance name", "default")
189
+ .option("-d, --daemon", "Run as background daemon", false)
190
+ .option("--db-path <path>", "SQLite database path", "./vibecontrols-agent.db")
76
191
  .action(async (options) => {
77
192
  try {
78
193
  const config = {
@@ -83,8 +198,13 @@ program
83
198
  };
84
199
  if (options.daemon) {
85
200
  await serviceManager.startDaemon(config);
201
+ // Print connection details (API key, tunnel URL, etc.)
202
+ const agentUrl = `http://localhost:${config.port}`;
203
+ await printAgentDetails(agentUrl);
86
204
  }
87
205
  else {
206
+ // Foreground mode — agentService.start() blocks, so details are
207
+ // printed by index.ts directly to stdout. Nothing extra needed here.
88
208
  await agentService.start(config);
89
209
  }
90
210
  }
@@ -93,10 +213,10 @@ program
93
213
  }
94
214
  });
95
215
  program
96
- .command('stop')
97
- .description('Stop a running agent instance')
98
- .option('-n, --name <name>', 'Instance name', 'default')
99
- .option('--all', 'Stop all instances', false)
216
+ .command("stop")
217
+ .description("Stop a running agent instance")
218
+ .option("-n, --name <name>", "Instance name", "default")
219
+ .option("--all", "Stop all instances", false)
100
220
  .action(async (options) => {
101
221
  try {
102
222
  if (options.all) {
@@ -111,11 +231,11 @@ program
111
231
  }
112
232
  });
113
233
  program
114
- .command('restart')
115
- .description('Restart an agent instance')
116
- .option('-n, --name <name>', 'Instance name', 'default')
117
- .option('-p, --port <port>', 'Port', '3005')
118
- .option('--db-path <path>', 'Database path', './vibecontrols-agent.db')
234
+ .command("restart")
235
+ .description("Restart an agent instance")
236
+ .option("-n, --name <name>", "Instance name", "default")
237
+ .option("-p, --port <port>", "Port", "3005")
238
+ .option("--db-path <path>", "Database path", "./vibecontrols-agent.db")
119
239
  .action(async (options) => {
120
240
  try {
121
241
  await serviceManager.restart(options.name, {
@@ -130,9 +250,9 @@ program
130
250
  }
131
251
  });
132
252
  program
133
- .command('status')
134
- .description('Show status of agent instances')
135
- .option('-n, --name <name>', 'Specific instance name')
253
+ .command("status")
254
+ .description("Show status of agent instances")
255
+ .option("-n, --name <name>", "Specific instance name")
136
256
  .action(async (options) => {
137
257
  try {
138
258
  if (options.name) {
@@ -142,7 +262,7 @@ program
142
262
  }
143
263
  else {
144
264
  console.log(`\n Agent: ${status.name}`);
145
- console.log(` Status: ${status.status === 'running' ? '\x1b[32m● running\x1b[0m' : '\x1b[31m○ stopped\x1b[0m'}`);
265
+ console.log(` Status: ${status.status === "running" ? "\x1b[32m● running\x1b[0m" : "\x1b[31m○ stopped\x1b[0m"}`);
146
266
  console.log(` PID: ${status.pid}`);
147
267
  console.log(` Port: ${status.port}`);
148
268
  console.log(` Started: ${status.startTime}\n`);
@@ -151,10 +271,10 @@ program
151
271
  else {
152
272
  const all = await serviceManager.getStatusAll();
153
273
  if (all.length === 0) {
154
- console.log('No agent instances registered.');
274
+ console.log("No agent instances registered.");
155
275
  }
156
276
  else {
157
- formatTable(all.map(s => ({
277
+ formatTable(all.map((s) => ({
158
278
  Name: s.name,
159
279
  Status: s.status,
160
280
  PID: s.pid,
@@ -169,16 +289,16 @@ program
169
289
  }
170
290
  });
171
291
  program
172
- .command('list')
173
- .description('List all agent instances')
292
+ .command("list")
293
+ .description("List all agent instances")
174
294
  .action(async () => {
175
295
  try {
176
296
  const instances = await serviceManager.listInstances();
177
297
  if (instances.length === 0) {
178
- console.log('No agent instances found.');
298
+ console.log("No agent instances found.");
179
299
  }
180
300
  else {
181
- formatTable(instances.map(i => ({
301
+ formatTable(instances.map((i) => ({
182
302
  Name: i.name,
183
303
  Status: i.status,
184
304
  PID: i.pid,
@@ -192,10 +312,10 @@ program
192
312
  }
193
313
  });
194
314
  program
195
- .command('kill')
196
- .description('Force kill an agent instance')
197
- .option('-n, --name <name>', 'Instance name', 'default')
198
- .option('--all', 'Kill all instances', false)
315
+ .command("kill")
316
+ .description("Force kill an agent instance")
317
+ .option("-n, --name <name>", "Instance name", "default")
318
+ .option("--all", "Kill all instances", false)
199
319
  .action(async (options) => {
200
320
  try {
201
321
  if (options.all) {
@@ -210,11 +330,11 @@ program
210
330
  }
211
331
  });
212
332
  program
213
- .command('logs')
214
- .description('Show logs for an agent instance')
215
- .option('-n, --name <name>', 'Instance name', 'default')
216
- .option('-f, --follow', 'Follow log output', false)
217
- .option('--tail <lines>', 'Number of lines', '100')
333
+ .command("logs")
334
+ .description("Show logs for an agent instance")
335
+ .option("-n, --name <name>", "Instance name", "default")
336
+ .option("-f, --follow", "Follow log output", false)
337
+ .option("--tail <lines>", "Number of lines", "100")
218
338
  .action(async (options) => {
219
339
  try {
220
340
  await serviceManager.showLogs(options.name, {
@@ -228,15 +348,15 @@ program
228
348
  });
229
349
  // ─── Key ──────────────────────────────────────────────────────────────────────
230
350
  program
231
- .command('key')
232
- .description('Display the current agent API key')
233
- .option('--agent-url <url>', 'Agent URL', DEFAULT_AGENT_URL)
351
+ .command("key")
352
+ .description("Display the current agent API key")
353
+ .option("--agent-url <url>", "Agent URL", DEFAULT_AGENT_URL)
234
354
  .action(async (options) => {
235
355
  try {
236
356
  const res = await fetch(`${options.agentUrl}/api/agent-api-key`);
237
357
  if (!res.ok)
238
358
  fail(`Agent returned ${res.status}`);
239
- const data = await res.json();
359
+ const data = (await res.json());
240
360
  console.log(`\n \x1b[1mAPI Key:\x1b[0m ${data.apiKey}`);
241
361
  console.log(` Generated: ${data.generatedAt}`);
242
362
  console.log(` Note: ${data.note}\n`);
@@ -247,10 +367,10 @@ program
247
367
  });
248
368
  // ─── Health ───────────────────────────────────────────────────────────────────
249
369
  program
250
- .command('health')
251
- .description('Check health of the agent')
252
- .option('-n, --name <name>', 'Instance name (uses local registry)')
253
- .option('--agent-url <url>', 'Agent URL (direct)', DEFAULT_AGENT_URL)
370
+ .command("health")
371
+ .description("Check health of the agent")
372
+ .option("-n, --name <name>", "Instance name (uses local registry)")
373
+ .option("--agent-url <url>", "Agent URL (direct)", DEFAULT_AGENT_URL)
254
374
  .action(async (options) => {
255
375
  try {
256
376
  if (options.name) {
@@ -261,7 +381,7 @@ program
261
381
  const res = await fetch(`${options.agentUrl}/health`);
262
382
  if (!res.ok)
263
383
  fail(`Health check failed: ${res.status}`);
264
- const data = await res.json();
384
+ const data = (await res.json());
265
385
  console.log(`\n Status: \x1b[32m${data.status}\x1b[0m`);
266
386
  console.log(` Version: ${data.version}`);
267
387
  console.log(` Uptime: ${Math.floor(data.uptime / 60)}m ${Math.floor(data.uptime % 60)}s`);
@@ -276,9 +396,9 @@ program
276
396
  });
277
397
  // ─── Version (detailed) ──────────────────────────────────────────────────────
278
398
  program
279
- .command('info')
280
- .description('Show detailed version and system information')
281
- .option('--agent-url <url>', 'Agent URL', DEFAULT_AGENT_URL)
399
+ .command("info")
400
+ .description("Show detailed version and system information")
401
+ .option("--agent-url <url>", "Agent URL", DEFAULT_AGENT_URL)
282
402
  .action(async (options) => {
283
403
  console.log(`\n \x1b[1mVibeControls Agent\x1b[0m v${packageVersion}`);
284
404
  console.log(` Node.js: ${process.version}`);
@@ -287,9 +407,9 @@ program
287
407
  try {
288
408
  const res = await fetch(`${options.agentUrl}/api/version`);
289
409
  if (res.ok) {
290
- const data = await res.json();
291
- console.log(` Agent Port: ${data.port || 'N/A'}`);
292
- console.log(` Uptime: ${data.uptime ? `${Math.floor(data.uptime / 60)}m` : 'N/A'}`);
410
+ const data = (await res.json());
411
+ console.log(` Agent Port: ${data.port || "N/A"}`);
412
+ console.log(` Uptime: ${data.uptime ? `${Math.floor(data.uptime / 60)}m` : "N/A"}`);
293
413
  }
294
414
  }
295
415
  catch {
@@ -299,40 +419,40 @@ program
299
419
  });
300
420
  // ─── Setup ────────────────────────────────────────────────────────────────────
301
421
  program
302
- .command('setup')
303
- .description('Install or verify system dependencies (tmux, ttyd, cloudflared)')
304
- .option('--check', 'Only check without installing', false)
422
+ .command("setup")
423
+ .description("Install or verify system dependencies (tmux, ttyd, cloudflared)")
424
+ .option("--check", "Only check without installing", false)
305
425
  .action(async (options) => {
306
426
  try {
307
- const { bootstrap, checkDependencies } = await import('./services/bootstrap.js');
427
+ const { bootstrap, checkDependencies } = await import("./services/bootstrap.js");
308
428
  if (options.check) {
309
- console.log('\n Checking dependencies...\n');
429
+ console.log("\n Checking dependencies...\n");
310
430
  const deps = checkDependencies();
311
431
  for (const [name, info] of Object.entries(deps)) {
312
- const icon = info.available ? '\x1b[32m✓\x1b[0m' : '\x1b[31m✗\x1b[0m';
313
- console.log(` ${icon} ${name.padEnd(14)} ${info.available ? info.version : 'not installed'}`);
432
+ const icon = info.available ? "\x1b[32m✓\x1b[0m" : "\x1b[31m✗\x1b[0m";
433
+ console.log(` ${icon} ${name.padEnd(14)} ${info.available ? info.version : "not installed"}`);
314
434
  }
315
435
  const missing = Object.entries(deps).filter(([, v]) => !v.available);
316
436
  if (missing.length > 0) {
317
- console.log(`\n Missing: ${missing.map(([k]) => k).join(', ')}`);
318
- console.log(' Run `vibe setup` to install them.\n');
437
+ console.log(`\n Missing: ${missing.map(([k]) => k).join(", ")}`);
438
+ console.log(" Run `vibe setup` to install them.\n");
319
439
  process.exit(1);
320
440
  }
321
441
  else {
322
- console.log('\n All dependencies are installed.\n');
442
+ console.log("\n All dependencies are installed.\n");
323
443
  }
324
444
  }
325
445
  else {
326
- console.log('\n Installing system dependencies...\n');
446
+ console.log("\n Installing system dependencies...\n");
327
447
  const results = await bootstrap({ verbose: true });
328
- const failed = results.filter(r => r.status === 'failed');
448
+ const failed = results.filter((r) => r.status === "failed");
329
449
  if (failed.length > 0) {
330
- console.log(`\n ${failed.length} tool(s) failed: ${failed.map(f => f.tool).join(', ')}`);
331
- console.log(' You may need to install manually or use sudo.\n');
450
+ console.log(`\n ${failed.length} tool(s) failed: ${failed.map((f) => f.tool).join(", ")}`);
451
+ console.log(" You may need to install manually or use sudo.\n");
332
452
  process.exit(1);
333
453
  }
334
454
  else {
335
- console.log('\n All dependencies installed successfully.\n');
455
+ console.log("\n All dependencies installed successfully.\n");
336
456
  }
337
457
  }
338
458
  }
@@ -342,22 +462,22 @@ program
342
462
  });
343
463
  // ─── Config ───────────────────────────────────────────────────────────────────
344
464
  program
345
- .command('config')
346
- .description('Manage agent configuration')
347
- .option('--set <key=value>', 'Set a configuration value')
348
- .option('--get <key>', 'Get a configuration value')
349
- .option('--list', 'List all configuration')
350
- .option('--agent-url <url>', 'Agent URL', DEFAULT_AGENT_URL)
465
+ .command("config")
466
+ .description("Manage agent configuration")
467
+ .option("--set <key=value>", "Set a configuration value")
468
+ .option("--get <key>", "Get a configuration value")
469
+ .option("--list", "List all configuration")
470
+ .option("--agent-url <url>", "Agent URL", DEFAULT_AGENT_URL)
351
471
  .action(async (options) => {
352
472
  try {
353
473
  if (options.set) {
354
- const eq = options.set.indexOf('=');
474
+ const eq = options.set.indexOf("=");
355
475
  if (eq === -1)
356
- fail('Use --set key=value format');
476
+ fail("Use --set key=value format");
357
477
  const key = options.set.substring(0, eq);
358
478
  const value = options.set.substring(eq + 1);
359
479
  const { ok } = await agentFetch(options.agentUrl, `/api/config/${key}`, {
360
- method: 'PUT',
480
+ method: "PUT",
361
481
  body: JSON.stringify({ value }),
362
482
  });
363
483
  if (ok)
@@ -375,11 +495,11 @@ program
375
495
  }
376
496
  }
377
497
  else if (options.list) {
378
- const { ok, data } = await agentFetch(options.agentUrl, '/api/config/');
498
+ const { ok, data } = await agentFetch(options.agentUrl, "/api/config/");
379
499
  if (ok) {
380
500
  const entries = Object.entries(data);
381
501
  if (entries.length === 0) {
382
- console.log(' No configuration set.');
502
+ console.log(" No configuration set.");
383
503
  }
384
504
  else {
385
505
  for (const [k, v] of entries) {
@@ -388,11 +508,11 @@ program
388
508
  }
389
509
  }
390
510
  else {
391
- fail('Failed to list config');
511
+ fail("Failed to list config");
392
512
  }
393
513
  }
394
514
  else {
395
- console.log('Use --set key=value, --get <key>, or --list');
515
+ console.log("Use --set key=value, --get <key>, or --list");
396
516
  }
397
517
  }
398
518
  catch (error) {
@@ -403,27 +523,27 @@ program
403
523
  // Tunnel Commands
404
524
  // ═══════════════════════════════════════════════════════════════════════════════
405
525
  const tunnelCmd = program
406
- .command('tunnel')
407
- .description('Manage cloudflared tunnels');
526
+ .command("tunnel")
527
+ .description("Manage cloudflared tunnels");
408
528
  tunnelCmd
409
- .command('list')
410
- .description('List all tunnels')
411
- .option('--agent-url <url>', 'Agent URL', DEFAULT_AGENT_URL)
529
+ .command("list")
530
+ .description("List all tunnels")
531
+ .option("--agent-url <url>", "Agent URL", DEFAULT_AGENT_URL)
412
532
  .action(async (options) => {
413
533
  try {
414
- const { ok, data } = await agentFetch(options.agentUrl, '/api/tunnel');
534
+ const { ok, data } = await agentFetch(options.agentUrl, "/api/tunnel");
415
535
  if (!ok)
416
- fail('Failed to list tunnels');
536
+ fail("Failed to list tunnels");
417
537
  if (data.tunnels.length === 0) {
418
- console.log(' No tunnels found.');
538
+ console.log(" No tunnels found.");
419
539
  }
420
540
  else {
421
- formatTable(data.tunnels.map(t => ({
422
- ID: t.id.substring(0, 12) + '...',
541
+ formatTable(data.tunnels.map((t) => ({
542
+ ID: t.id.substring(0, 12) + "...",
423
543
  Port: t.localPort,
424
- 'Public URL': t.publicUrl || '(none)',
544
+ "Public URL": t.publicUrl || "(none)",
425
545
  Status: t.status,
426
- PID: t.pid || 'N/A',
546
+ PID: t.pid || "N/A",
427
547
  })));
428
548
  }
429
549
  }
@@ -432,16 +552,16 @@ tunnelCmd
432
552
  }
433
553
  });
434
554
  tunnelCmd
435
- .command('start')
436
- .description('Start a tunnel for a local port')
437
- .requiredOption('-p, --port <port>', 'Local port to expose')
438
- .option('-s, --subdomain <subdomain>', 'Preferred subdomain')
439
- .option('--agent-url <url>', 'Agent URL', DEFAULT_AGENT_URL)
555
+ .command("start")
556
+ .description("Start a tunnel for a local port")
557
+ .requiredOption("-p, --port <port>", "Local port to expose")
558
+ .option("-s, --subdomain <subdomain>", "Preferred subdomain")
559
+ .option("--agent-url <url>", "Agent URL", DEFAULT_AGENT_URL)
440
560
  .action(async (options) => {
441
561
  try {
442
562
  console.log(` Starting tunnel for port ${options.port}...`);
443
- const { ok, data } = await agentFetch(options.agentUrl, '/api/tunnel/start', {
444
- method: 'POST',
563
+ const { ok, data } = await agentFetch(options.agentUrl, "/api/tunnel/start", {
564
+ method: "POST",
445
565
  body: JSON.stringify({
446
566
  localPort: parseInt(options.port),
447
567
  subdomain: options.subdomain,
@@ -452,8 +572,8 @@ tunnelCmd
452
572
  console.log(`\n \x1b[32mTunnel started:\x1b[0m`);
453
573
  console.log(` ID: ${data.id}`);
454
574
  console.log(` Local Port: ${data.localPort}`);
455
- console.log(` Public URL: ${data.publicUrl || '(pending...)'}`);
456
- console.log(` PID: ${data.pid || 'N/A'}`);
575
+ console.log(` Public URL: ${data.publicUrl || "(pending...)"}`);
576
+ console.log(` PID: ${data.pid || "N/A"}`);
457
577
  console.log(` Status: ${data.status}\n`);
458
578
  }
459
579
  catch (error) {
@@ -461,50 +581,50 @@ tunnelCmd
461
581
  }
462
582
  });
463
583
  tunnelCmd
464
- .command('stop')
465
- .description('Stop a running tunnel')
466
- .requiredOption('-i, --id <id>', 'Tunnel ID')
467
- .option('--agent-url <url>', 'Agent URL', DEFAULT_AGENT_URL)
584
+ .command("stop")
585
+ .description("Stop a running tunnel")
586
+ .requiredOption("-i, --id <id>", "Tunnel ID")
587
+ .option("--agent-url <url>", "Agent URL", DEFAULT_AGENT_URL)
468
588
  .action(async (options) => {
469
589
  try {
470
590
  const { ok, data } = await agentFetch(options.agentUrl, `/api/tunnel/${options.id}/stop`, {
471
- method: 'POST',
591
+ method: "POST",
472
592
  });
473
593
  if (!ok)
474
- fail(data.error || 'Failed to stop tunnel');
475
- console.log(' Tunnel stopped.');
594
+ fail(data.error || "Failed to stop tunnel");
595
+ console.log(" Tunnel stopped.");
476
596
  }
477
597
  catch (error) {
478
598
  fail(`Failed: ${error instanceof Error ? error.message : error}`);
479
599
  }
480
600
  });
481
601
  tunnelCmd
482
- .command('delete')
483
- .description('Delete a tunnel')
484
- .requiredOption('-i, --id <id>', 'Tunnel ID')
485
- .option('--agent-url <url>', 'Agent URL', DEFAULT_AGENT_URL)
602
+ .command("delete")
603
+ .description("Delete a tunnel")
604
+ .requiredOption("-i, --id <id>", "Tunnel ID")
605
+ .option("--agent-url <url>", "Agent URL", DEFAULT_AGENT_URL)
486
606
  .action(async (options) => {
487
607
  try {
488
608
  const { ok, data } = await agentFetch(options.agentUrl, `/api/tunnel/${options.id}`, {
489
- method: 'DELETE',
609
+ method: "DELETE",
490
610
  });
491
611
  if (!ok)
492
- fail(data.error || 'Failed to delete tunnel');
493
- console.log(' Tunnel deleted.');
612
+ fail(data.error || "Failed to delete tunnel");
613
+ console.log(" Tunnel deleted.");
494
614
  }
495
615
  catch (error) {
496
616
  fail(`Failed: ${error instanceof Error ? error.message : error}`);
497
617
  }
498
618
  });
499
619
  tunnelCmd
500
- .command('status')
501
- .description('Get tunnel overview')
502
- .option('--agent-url <url>', 'Agent URL', DEFAULT_AGENT_URL)
620
+ .command("status")
621
+ .description("Get tunnel overview")
622
+ .option("--agent-url <url>", "Agent URL", DEFAULT_AGENT_URL)
503
623
  .action(async (options) => {
504
624
  try {
505
- const { ok, data } = await agentFetch(options.agentUrl, '/api/tunnel/status');
625
+ const { ok, data } = await agentFetch(options.agentUrl, "/api/tunnel/status");
506
626
  if (!ok)
507
- fail('Failed to get tunnel status');
627
+ fail("Failed to get tunnel status");
508
628
  console.log(`\n Total: ${data.total}`);
509
629
  console.log(` Active: \x1b[32m${data.active}\x1b[0m`);
510
630
  console.log(` Inactive: ${data.inactive}`);
@@ -516,29 +636,31 @@ tunnelCmd
516
636
  });
517
637
  // Agent tunnel (the main agent tunnel, not per-port tunnels)
518
638
  tunnelCmd
519
- .command('agent')
520
- .description('Show/manage the main agent cloudflared tunnel')
521
- .option('--start', 'Start the agent tunnel')
522
- .option('--stop', 'Stop the agent tunnel')
523
- .option('--agent-url <url>', 'Agent URL', DEFAULT_AGENT_URL)
639
+ .command("agent")
640
+ .description("Show/manage the main agent cloudflared tunnel")
641
+ .option("--start", "Start the agent tunnel")
642
+ .option("--stop", "Stop the agent tunnel")
643
+ .option("--agent-url <url>", "Agent URL", DEFAULT_AGENT_URL)
524
644
  .action(async (options) => {
525
645
  try {
526
646
  if (options.start) {
527
- const { ok, data } = await agentFetch(options.agentUrl, '/api/agent-tunnel/start', { method: 'POST' });
647
+ const { ok, data } = await agentFetch(options.agentUrl, "/api/agent-tunnel/start", { method: "POST" });
528
648
  if (!ok)
529
- fail('Failed to start agent tunnel');
649
+ fail("Failed to start agent tunnel");
530
650
  console.log(` Agent tunnel started: ${data.tunnelUrl}`);
531
651
  }
532
652
  else if (options.stop) {
533
- await agentFetch(options.agentUrl, '/api/agent-tunnel/stop', { method: 'POST' });
534
- console.log(' Agent tunnel stopped.');
653
+ await agentFetch(options.agentUrl, "/api/agent-tunnel/stop", {
654
+ method: "POST",
655
+ });
656
+ console.log(" Agent tunnel stopped.");
535
657
  }
536
658
  else {
537
- const { ok, data } = await agentFetch(options.agentUrl, '/api/agent-tunnel');
659
+ const { ok, data } = await agentFetch(options.agentUrl, "/api/agent-tunnel");
538
660
  if (!ok)
539
- fail('Failed to get agent tunnel');
540
- console.log(`\n Tunnel URL: ${data.tunnelUrl || '(not running)'}`);
541
- console.log(` Status: ${data.status || 'unknown'}\n`);
661
+ fail("Failed to get agent tunnel");
662
+ console.log(`\n Tunnel URL: ${data.tunnelUrl || "(not running)"}`);
663
+ console.log(` Status: ${data.status || "unknown"}\n`);
542
664
  }
543
665
  }
544
666
  catch (error) {
@@ -549,30 +671,32 @@ tunnelCmd
549
671
  // Session Commands (tmux)
550
672
  // ═══════════════════════════════════════════════════════════════════════════════
551
673
  const sessionCmd = program
552
- .command('session')
553
- .description('Manage tmux terminal sessions');
674
+ .command("session")
675
+ .description("Manage tmux terminal sessions");
554
676
  sessionCmd
555
- .command('list')
556
- .description('List all tmux sessions')
557
- .option('--system', 'Include system (unmanaged) sessions')
558
- .option('--agent-url <url>', 'Agent URL', DEFAULT_AGENT_URL)
677
+ .command("list")
678
+ .description("List all tmux sessions")
679
+ .option("--system", "Include system (unmanaged) sessions")
680
+ .option("--agent-url <url>", "Agent URL", DEFAULT_AGENT_URL)
559
681
  .action(async (options) => {
560
682
  try {
561
- const path = options.system ? '/api/tmux/system' : '/api/tmux/';
683
+ const path = options.system ? "/api/tmux/system" : "/api/tmux/";
562
684
  const { ok, data } = await agentFetch(options.agentUrl, path);
563
685
  if (!ok)
564
- fail('Failed to list sessions');
565
- const sessions = Array.isArray(data) ? data : (data.sessions ?? []);
686
+ fail("Failed to list sessions");
687
+ const sessions = Array.isArray(data)
688
+ ? data
689
+ : (data.sessions ?? []);
566
690
  if (sessions.length === 0) {
567
- console.log(' No sessions found.');
691
+ console.log(" No sessions found.");
568
692
  }
569
693
  else {
570
- formatTable(sessions.map(s => ({
571
- ID: (s.id || s.sessionId || '').substring(0, 12),
572
- Name: s.sessionName || s.name || 'N/A',
573
- Status: s.status || 'unknown',
574
- Port: s.ttydPort || 'N/A',
575
- Project: s.projectId || 'N/A',
694
+ formatTable(sessions.map((s) => ({
695
+ ID: (s.id || s.sessionId || "").substring(0, 12),
696
+ Name: s.sessionName || s.name || "N/A",
697
+ Status: s.status || "unknown",
698
+ Port: s.ttydPort || "N/A",
699
+ Project: s.projectId || "N/A",
576
700
  })));
577
701
  }
578
702
  }
@@ -581,17 +705,17 @@ sessionCmd
581
705
  }
582
706
  });
583
707
  sessionCmd
584
- .command('create')
585
- .description('Create a new tmux session')
586
- .requiredOption('--name <name>', 'Session name')
587
- .option('--project <id>', 'Project ID', 'default')
588
- .option('--command <cmd>', 'Initial command')
589
- .option('--cwd <dir>', 'Working directory')
590
- .option('--agent-url <url>', 'Agent URL', DEFAULT_AGENT_URL)
708
+ .command("create")
709
+ .description("Create a new tmux session")
710
+ .requiredOption("--name <name>", "Session name")
711
+ .option("--project <id>", "Project ID", "default")
712
+ .option("--command <cmd>", "Initial command")
713
+ .option("--cwd <dir>", "Working directory")
714
+ .option("--agent-url <url>", "Agent URL", DEFAULT_AGENT_URL)
591
715
  .action(async (options) => {
592
716
  try {
593
- const { ok, data } = await agentFetch(options.agentUrl, '/api/tmux/create', {
594
- method: 'POST',
717
+ const { ok, data } = await agentFetch(options.agentUrl, "/api/tmux/create", {
718
+ method: "POST",
595
719
  body: JSON.stringify({
596
720
  sessionId: `cli-${Date.now()}`,
597
721
  sessionName: options.name,
@@ -601,7 +725,7 @@ sessionCmd
601
725
  }),
602
726
  });
603
727
  if (!ok)
604
- fail(data.error || 'Failed to create session');
728
+ fail(data.error || "Failed to create session");
605
729
  console.log(` Session created: ${data.sessionName || options.name}`);
606
730
  }
607
731
  catch (error) {
@@ -609,54 +733,54 @@ sessionCmd
609
733
  }
610
734
  });
611
735
  sessionCmd
612
- .command('kill')
613
- .description('Kill a tmux session')
614
- .requiredOption('-i, --id <id>', 'Session ID')
615
- .option('--agent-url <url>', 'Agent URL', DEFAULT_AGENT_URL)
736
+ .command("kill")
737
+ .description("Kill a tmux session")
738
+ .requiredOption("-i, --id <id>", "Session ID")
739
+ .option("--agent-url <url>", "Agent URL", DEFAULT_AGENT_URL)
616
740
  .action(async (options) => {
617
741
  try {
618
742
  const { ok, data } = await agentFetch(options.agentUrl, `/api/tmux/${options.id}`, {
619
- method: 'DELETE',
743
+ method: "DELETE",
620
744
  });
621
745
  if (!ok)
622
- fail(data.error || 'Failed to kill session');
623
- console.log(' Session killed.');
746
+ fail(data.error || "Failed to kill session");
747
+ console.log(" Session killed.");
624
748
  }
625
749
  catch (error) {
626
750
  fail(`Failed: ${error instanceof Error ? error.message : error}`);
627
751
  }
628
752
  });
629
753
  sessionCmd
630
- .command('exec')
631
- .description('Execute a command in a tmux session')
632
- .requiredOption('-i, --id <id>', 'Session ID')
633
- .requiredOption('-c, --command <cmd>', 'Command to execute')
634
- .option('--agent-url <url>', 'Agent URL', DEFAULT_AGENT_URL)
754
+ .command("exec")
755
+ .description("Execute a command in a tmux session")
756
+ .requiredOption("-i, --id <id>", "Session ID")
757
+ .requiredOption("-c, --command <cmd>", "Command to execute")
758
+ .option("--agent-url <url>", "Agent URL", DEFAULT_AGENT_URL)
635
759
  .action(async (options) => {
636
760
  try {
637
761
  const { ok, data } = await agentFetch(options.agentUrl, `/api/tmux/${options.id}/command`, {
638
- method: 'POST',
762
+ method: "POST",
639
763
  body: JSON.stringify({ command: options.command }),
640
764
  });
641
765
  if (!ok)
642
- fail(data.error || 'Failed to execute command');
643
- console.log(' Command sent.');
766
+ fail(data.error || "Failed to execute command");
767
+ console.log(" Command sent.");
644
768
  }
645
769
  catch (error) {
646
770
  fail(`Failed: ${error instanceof Error ? error.message : error}`);
647
771
  }
648
772
  });
649
773
  sessionCmd
650
- .command('capture')
651
- .description('Capture output from a tmux session')
652
- .requiredOption('-i, --id <id>', 'Session ID')
653
- .option('--agent-url <url>', 'Agent URL', DEFAULT_AGENT_URL)
774
+ .command("capture")
775
+ .description("Capture output from a tmux session")
776
+ .requiredOption("-i, --id <id>", "Session ID")
777
+ .option("--agent-url <url>", "Agent URL", DEFAULT_AGENT_URL)
654
778
  .action(async (options) => {
655
779
  try {
656
780
  const { ok, data } = await agentFetch(options.agentUrl, `/api/tmux/${options.id}/capture`);
657
781
  if (!ok)
658
- fail('Failed to capture output');
659
- console.log(data.output || '(empty)');
782
+ fail("Failed to capture output");
783
+ console.log(data.output || "(empty)");
660
784
  }
661
785
  catch (error) {
662
786
  fail(`Failed: ${error instanceof Error ? error.message : error}`);
@@ -665,25 +789,25 @@ sessionCmd
665
789
  // ═══════════════════════════════════════════════════════════════════════════════
666
790
  // SSH Commands
667
791
  // ═══════════════════════════════════════════════════════════════════════════════
668
- const sshCmd = program
669
- .command('ssh')
670
- .description('Manage SSH connections');
792
+ const sshCmd = program.command("ssh").description("Manage SSH connections");
671
793
  sshCmd
672
- .command('list')
673
- .description('List saved SSH connections')
674
- .option('--agent-url <url>', 'Agent URL', DEFAULT_AGENT_URL)
794
+ .command("list")
795
+ .description("List saved SSH connections")
796
+ .option("--agent-url <url>", "Agent URL", DEFAULT_AGENT_URL)
675
797
  .action(async (options) => {
676
798
  try {
677
- const { ok, data } = await agentFetch(options.agentUrl, '/api/ssh/connections');
799
+ const { ok, data } = await agentFetch(options.agentUrl, "/api/ssh/connections");
678
800
  if (!ok)
679
- fail('Failed to list SSH connections');
680
- const conns = Array.isArray(data) ? data : (data.connections ?? []);
801
+ fail("Failed to list SSH connections");
802
+ const conns = Array.isArray(data)
803
+ ? data
804
+ : (data.connections ?? []);
681
805
  if (conns.length === 0) {
682
- console.log(' No SSH connections saved.');
806
+ console.log(" No SSH connections saved.");
683
807
  }
684
808
  else {
685
- formatTable(conns.map(c => ({
686
- ID: (c.id || '').substring(0, 12),
809
+ formatTable(conns.map((c) => ({
810
+ ID: (c.id || "").substring(0, 12),
687
811
  Name: c.serverName,
688
812
  Host: c.host,
689
813
  Port: c.port,
@@ -696,18 +820,18 @@ sshCmd
696
820
  }
697
821
  });
698
822
  sshCmd
699
- .command('add')
700
- .description('Add an SSH connection')
701
- .requiredOption('--name <name>', 'Server name')
702
- .requiredOption('--host <host>', 'Hostname or IP')
703
- .requiredOption('--user <user>', 'Username')
704
- .option('--port <port>', 'Port', '22')
705
- .option('--key <path>', 'Private key path')
706
- .option('--agent-url <url>', 'Agent URL', DEFAULT_AGENT_URL)
823
+ .command("add")
824
+ .description("Add an SSH connection")
825
+ .requiredOption("--name <name>", "Server name")
826
+ .requiredOption("--host <host>", "Hostname or IP")
827
+ .requiredOption("--user <user>", "Username")
828
+ .option("--port <port>", "Port", "22")
829
+ .option("--key <path>", "Private key path")
830
+ .option("--agent-url <url>", "Agent URL", DEFAULT_AGENT_URL)
707
831
  .action(async (options) => {
708
832
  try {
709
- const { ok, data } = await agentFetch(options.agentUrl, '/api/ssh/connections', {
710
- method: 'POST',
833
+ const { ok, data } = await agentFetch(options.agentUrl, "/api/ssh/connections", {
834
+ method: "POST",
711
835
  body: JSON.stringify({
712
836
  serverName: options.name,
713
837
  host: options.host,
@@ -717,7 +841,7 @@ sshCmd
717
841
  }),
718
842
  });
719
843
  if (!ok)
720
- fail(data.error || 'Failed to add connection');
844
+ fail(data.error || "Failed to add connection");
721
845
  console.log(` SSH connection '${options.name}' added.`);
722
846
  }
723
847
  catch (error) {
@@ -725,56 +849,56 @@ sshCmd
725
849
  }
726
850
  });
727
851
  sshCmd
728
- .command('remove')
729
- .description('Remove an SSH connection')
730
- .requiredOption('-i, --id <id>', 'Connection ID')
731
- .option('--agent-url <url>', 'Agent URL', DEFAULT_AGENT_URL)
852
+ .command("remove")
853
+ .description("Remove an SSH connection")
854
+ .requiredOption("-i, --id <id>", "Connection ID")
855
+ .option("--agent-url <url>", "Agent URL", DEFAULT_AGENT_URL)
732
856
  .action(async (options) => {
733
857
  try {
734
858
  const { ok } = await agentFetch(options.agentUrl, `/api/ssh/connections/${options.id}`, {
735
- method: 'DELETE',
859
+ method: "DELETE",
736
860
  });
737
861
  if (!ok)
738
- fail('Failed to remove connection');
739
- console.log(' SSH connection removed.');
862
+ fail("Failed to remove connection");
863
+ console.log(" SSH connection removed.");
740
864
  }
741
865
  catch (error) {
742
866
  fail(`Failed: ${error instanceof Error ? error.message : error}`);
743
867
  }
744
868
  });
745
869
  sshCmd
746
- .command('test')
747
- .description('Test an SSH connection')
748
- .requiredOption('-i, --id <id>', 'Connection ID')
749
- .option('--agent-url <url>', 'Agent URL', DEFAULT_AGENT_URL)
870
+ .command("test")
871
+ .description("Test an SSH connection")
872
+ .requiredOption("-i, --id <id>", "Connection ID")
873
+ .option("--agent-url <url>", "Agent URL", DEFAULT_AGENT_URL)
750
874
  .action(async (options) => {
751
875
  try {
752
- const { ok, data } = await agentFetch(options.agentUrl, `/api/ssh/test/${options.id}`, { method: 'POST' });
876
+ const { ok, data } = await agentFetch(options.agentUrl, `/api/ssh/test/${options.id}`, { method: "POST" });
753
877
  if (!ok)
754
- fail(data.error || 'Connection test failed');
755
- console.log(' \x1b[32mConnection successful.\x1b[0m');
878
+ fail(data.error || "Connection test failed");
879
+ console.log(" \x1b[32mConnection successful.\x1b[0m");
756
880
  }
757
881
  catch (error) {
758
882
  fail(`Failed: ${error instanceof Error ? error.message : error}`);
759
883
  }
760
884
  });
761
885
  sshCmd
762
- .command('exec')
763
- .description('Execute a command on a remote server')
764
- .requiredOption('-i, --id <id>', 'Connection ID')
765
- .requiredOption('-c, --command <cmd>', 'Command to execute')
766
- .option('--agent-url <url>', 'Agent URL', DEFAULT_AGENT_URL)
886
+ .command("exec")
887
+ .description("Execute a command on a remote server")
888
+ .requiredOption("-i, --id <id>", "Connection ID")
889
+ .requiredOption("-c, --command <cmd>", "Command to execute")
890
+ .option("--agent-url <url>", "Agent URL", DEFAULT_AGENT_URL)
767
891
  .action(async (options) => {
768
892
  try {
769
- const { ok, data } = await agentFetch(options.agentUrl, '/api/ssh/execute', {
770
- method: 'POST',
893
+ const { ok, data } = await agentFetch(options.agentUrl, "/api/ssh/execute", {
894
+ method: "POST",
771
895
  body: JSON.stringify({
772
896
  connectionId: options.id,
773
897
  command: options.command,
774
898
  }),
775
899
  });
776
900
  if (!ok)
777
- fail(data.error || 'Execution failed');
901
+ fail(data.error || "Execution failed");
778
902
  if (data.output)
779
903
  console.log(data.output);
780
904
  if (data.error)
@@ -788,24 +912,26 @@ sshCmd
788
912
  // Port Forward Commands
789
913
  // ═══════════════════════════════════════════════════════════════════════════════
790
914
  const forwardCmd = program
791
- .command('forward')
792
- .description('Manage SSH port forwards');
915
+ .command("forward")
916
+ .description("Manage SSH port forwards");
793
917
  forwardCmd
794
- .command('list')
795
- .description('List all port forwards')
796
- .option('--agent-url <url>', 'Agent URL', DEFAULT_AGENT_URL)
918
+ .command("list")
919
+ .description("List all port forwards")
920
+ .option("--agent-url <url>", "Agent URL", DEFAULT_AGENT_URL)
797
921
  .action(async (options) => {
798
922
  try {
799
- const { ok, data } = await agentFetch(options.agentUrl, '/api/port-forward/');
923
+ const { ok, data } = await agentFetch(options.agentUrl, "/api/port-forward/");
800
924
  if (!ok)
801
- fail('Failed to list forwards');
802
- const fwds = Array.isArray(data) ? data : (data.forwards ?? []);
925
+ fail("Failed to list forwards");
926
+ const fwds = Array.isArray(data)
927
+ ? data
928
+ : (data.forwards ?? []);
803
929
  if (fwds.length === 0) {
804
- console.log(' No port forwards found.');
930
+ console.log(" No port forwards found.");
805
931
  }
806
932
  else {
807
- formatTable(fwds.map(f => ({
808
- ID: (f.id || '').substring(0, 12),
933
+ formatTable(fwds.map((f) => ({
934
+ ID: (f.id || "").substring(0, 12),
809
935
  Local: f.localPort,
810
936
  Remote: `${f.remoteHost}:${f.remotePort}`,
811
937
  Server: f.serverName,
@@ -818,17 +944,17 @@ forwardCmd
818
944
  }
819
945
  });
820
946
  forwardCmd
821
- .command('create')
822
- .description('Create a port forward rule')
823
- .requiredOption('--local <port>', 'Local port')
824
- .requiredOption('--remote-host <host>', 'Remote host')
825
- .requiredOption('--remote-port <port>', 'Remote port')
826
- .requiredOption('--server <name>', 'SSH server name')
827
- .option('--agent-url <url>', 'Agent URL', DEFAULT_AGENT_URL)
947
+ .command("create")
948
+ .description("Create a port forward rule")
949
+ .requiredOption("--local <port>", "Local port")
950
+ .requiredOption("--remote-host <host>", "Remote host")
951
+ .requiredOption("--remote-port <port>", "Remote port")
952
+ .requiredOption("--server <name>", "SSH server name")
953
+ .option("--agent-url <url>", "Agent URL", DEFAULT_AGENT_URL)
828
954
  .action(async (options) => {
829
955
  try {
830
- const { ok, data } = await agentFetch(options.agentUrl, '/api/port-forward/', {
831
- method: 'POST',
956
+ const { ok, data } = await agentFetch(options.agentUrl, "/api/port-forward/", {
957
+ method: "POST",
832
958
  body: JSON.stringify({
833
959
  localPort: parseInt(options.local),
834
960
  remoteHost: options.remoteHost,
@@ -837,62 +963,62 @@ forwardCmd
837
963
  }),
838
964
  });
839
965
  if (!ok)
840
- fail(data.error || 'Failed to create forward');
841
- console.log(' Port forward created.');
966
+ fail(data.error || "Failed to create forward");
967
+ console.log(" Port forward created.");
842
968
  }
843
969
  catch (error) {
844
970
  fail(`Failed: ${error instanceof Error ? error.message : error}`);
845
971
  }
846
972
  });
847
973
  forwardCmd
848
- .command('start')
849
- .description('Start a port forward')
850
- .requiredOption('-i, --id <id>', 'Forward ID')
851
- .option('--agent-url <url>', 'Agent URL', DEFAULT_AGENT_URL)
974
+ .command("start")
975
+ .description("Start a port forward")
976
+ .requiredOption("-i, --id <id>", "Forward ID")
977
+ .option("--agent-url <url>", "Agent URL", DEFAULT_AGENT_URL)
852
978
  .action(async (options) => {
853
979
  try {
854
980
  const { ok, data } = await agentFetch(options.agentUrl, `/api/port-forward/${options.id}/start`, {
855
- method: 'POST',
981
+ method: "POST",
856
982
  });
857
983
  if (!ok)
858
- fail(data.error || 'Failed to start forward');
859
- console.log(' Port forward started.');
984
+ fail(data.error || "Failed to start forward");
985
+ console.log(" Port forward started.");
860
986
  }
861
987
  catch (error) {
862
988
  fail(`Failed: ${error instanceof Error ? error.message : error}`);
863
989
  }
864
990
  });
865
991
  forwardCmd
866
- .command('stop')
867
- .description('Stop a port forward')
868
- .requiredOption('-i, --id <id>', 'Forward ID')
869
- .option('--agent-url <url>', 'Agent URL', DEFAULT_AGENT_URL)
992
+ .command("stop")
993
+ .description("Stop a port forward")
994
+ .requiredOption("-i, --id <id>", "Forward ID")
995
+ .option("--agent-url <url>", "Agent URL", DEFAULT_AGENT_URL)
870
996
  .action(async (options) => {
871
997
  try {
872
998
  const { ok, data } = await agentFetch(options.agentUrl, `/api/port-forward/${options.id}/stop`, {
873
- method: 'POST',
999
+ method: "POST",
874
1000
  });
875
1001
  if (!ok)
876
- fail(data.error || 'Failed to stop forward');
877
- console.log(' Port forward stopped.');
1002
+ fail(data.error || "Failed to stop forward");
1003
+ console.log(" Port forward stopped.");
878
1004
  }
879
1005
  catch (error) {
880
1006
  fail(`Failed: ${error instanceof Error ? error.message : error}`);
881
1007
  }
882
1008
  });
883
1009
  forwardCmd
884
- .command('delete')
885
- .description('Delete a port forward')
886
- .requiredOption('-i, --id <id>', 'Forward ID')
887
- .option('--agent-url <url>', 'Agent URL', DEFAULT_AGENT_URL)
1010
+ .command("delete")
1011
+ .description("Delete a port forward")
1012
+ .requiredOption("-i, --id <id>", "Forward ID")
1013
+ .option("--agent-url <url>", "Agent URL", DEFAULT_AGENT_URL)
888
1014
  .action(async (options) => {
889
1015
  try {
890
1016
  const { ok } = await agentFetch(options.agentUrl, `/api/port-forward/${options.id}`, {
891
- method: 'DELETE',
1017
+ method: "DELETE",
892
1018
  });
893
1019
  if (!ok)
894
- fail('Failed to delete forward');
895
- console.log(' Port forward deleted.');
1020
+ fail("Failed to delete forward");
1021
+ console.log(" Port forward deleted.");
896
1022
  }
897
1023
  catch (error) {
898
1024
  fail(`Failed: ${error instanceof Error ? error.message : error}`);
@@ -901,30 +1027,28 @@ forwardCmd
901
1027
  // ═══════════════════════════════════════════════════════════════════════════════
902
1028
  // Task Commands
903
1029
  // ═══════════════════════════════════════════════════════════════════════════════
904
- const taskCmd = program
905
- .command('task')
906
- .description('Manage background tasks');
1030
+ const taskCmd = program.command("task").description("Manage background tasks");
907
1031
  taskCmd
908
- .command('list')
909
- .description('List tasks')
910
- .option('--status <status>', 'Filter by status (pending, running, completed, failed)')
911
- .option('--agent-url <url>', 'Agent URL', DEFAULT_AGENT_URL)
1032
+ .command("list")
1033
+ .description("List tasks")
1034
+ .option("--status <status>", "Filter by status (pending, running, completed, failed)")
1035
+ .option("--agent-url <url>", "Agent URL", DEFAULT_AGENT_URL)
912
1036
  .action(async (options) => {
913
1037
  try {
914
- const query = options.status ? `?status=${options.status}` : '';
1038
+ const query = options.status ? `?status=${options.status}` : "";
915
1039
  const { ok, data } = await agentFetch(options.agentUrl, `/api/tasks/${query}`);
916
1040
  if (!ok)
917
- fail('Failed to list tasks');
1041
+ fail("Failed to list tasks");
918
1042
  const tasks = Array.isArray(data) ? data : [];
919
1043
  if (tasks.length === 0) {
920
- console.log(' No tasks found.');
1044
+ console.log(" No tasks found.");
921
1045
  }
922
1046
  else {
923
- formatTable(tasks.map(t => ({
924
- ID: (t.id || '').substring(0, 12),
1047
+ formatTable(tasks.map((t) => ({
1048
+ ID: (t.id || "").substring(0, 12),
925
1049
  Type: t.type,
926
1050
  Status: t.status,
927
- Created: t.createdAt ? timeAgo(t.createdAt) : 'N/A',
1051
+ Created: t.createdAt ? timeAgo(t.createdAt) : "N/A",
928
1052
  })));
929
1053
  }
930
1054
  }
@@ -933,17 +1057,17 @@ taskCmd
933
1057
  }
934
1058
  });
935
1059
  taskCmd
936
- .command('run')
937
- .description('Run a command as a background task')
938
- .requiredOption('-c, --command <cmd>', 'Command to execute')
939
- .option('--cwd <dir>', 'Working directory')
940
- .option('--agent-url <url>', 'Agent URL', DEFAULT_AGENT_URL)
1060
+ .command("run")
1061
+ .description("Run a command as a background task")
1062
+ .requiredOption("-c, --command <cmd>", "Command to execute")
1063
+ .option("--cwd <dir>", "Working directory")
1064
+ .option("--agent-url <url>", "Agent URL", DEFAULT_AGENT_URL)
941
1065
  .action(async (options) => {
942
1066
  try {
943
- const { ok, data } = await agentFetch(options.agentUrl, '/api/tasks/', {
944
- method: 'POST',
1067
+ const { ok, data } = await agentFetch(options.agentUrl, "/api/tasks/", {
1068
+ method: "POST",
945
1069
  body: JSON.stringify({
946
- type: 'command',
1070
+ type: "command",
947
1071
  payload: JSON.stringify({
948
1072
  command: options.command,
949
1073
  cwd: options.cwd,
@@ -951,7 +1075,7 @@ taskCmd
951
1075
  }),
952
1076
  });
953
1077
  if (!ok)
954
- fail(data.error || 'Failed to create task');
1078
+ fail(data.error || "Failed to create task");
955
1079
  console.log(` Task created: ${data.id}`);
956
1080
  }
957
1081
  catch (error) {
@@ -961,30 +1085,36 @@ taskCmd
961
1085
  // ═══════════════════════════════════════════════════════════════════════════════
962
1086
  // Notification Commands
963
1087
  // ═══════════════════════════════════════════════════════════════════════════════
964
- const notifyCmd = program
965
- .command('notify')
966
- .description('Manage notifications');
1088
+ const notifyCmd = program.command("notify").description("Manage notifications");
967
1089
  notifyCmd
968
- .command('list')
969
- .description('List notifications')
970
- .option('--unread', 'Show only unread')
971
- .option('--agent-url <url>', 'Agent URL', DEFAULT_AGENT_URL)
1090
+ .command("list")
1091
+ .description("List notifications")
1092
+ .option("--unread", "Show only unread")
1093
+ .option("--agent-url <url>", "Agent URL", DEFAULT_AGENT_URL)
972
1094
  .action(async (options) => {
973
1095
  try {
974
- const path = options.unread ? '/api/notifications/unread' : '/api/notifications/';
1096
+ const path = options.unread
1097
+ ? "/api/notifications/unread"
1098
+ : "/api/notifications/";
975
1099
  const { ok, data } = await agentFetch(options.agentUrl, path);
976
1100
  if (!ok)
977
- fail('Failed to list notifications');
978
- const notifications = Array.isArray(data) ? data : (data.notifications ?? []);
1101
+ fail("Failed to list notifications");
1102
+ const notifications = Array.isArray(data)
1103
+ ? data
1104
+ : (data.notifications ?? []);
979
1105
  if (notifications.length === 0) {
980
- console.log(' No notifications.');
1106
+ console.log(" No notifications.");
981
1107
  }
982
1108
  else {
983
1109
  for (const n of notifications) {
984
- const icon = n.type === 'error' ? '\x1b[31m●\x1b[0m' :
985
- n.type === 'warning' ? '\x1b[33m●\x1b[0m' :
986
- n.type === 'success' ? '\x1b[32m●\x1b[0m' : '\x1b[34m●\x1b[0m';
987
- const unread = n.status === 'unread' ? ' [NEW]' : '';
1110
+ const icon = n.type === "error"
1111
+ ? "\x1b[31m●\x1b[0m"
1112
+ : n.type === "warning"
1113
+ ? "\x1b[33m●\x1b[0m"
1114
+ : n.type === "success"
1115
+ ? "\x1b[32m●\x1b[0m"
1116
+ : "\x1b[34m●\x1b[0m";
1117
+ const unread = n.status === "unread" ? " [NEW]" : "";
988
1118
  console.log(` ${icon} ${n.title}${unread}`);
989
1119
  console.log(` ${n.message}`);
990
1120
  if (n.createdAt)
@@ -998,17 +1128,17 @@ notifyCmd
998
1128
  }
999
1129
  });
1000
1130
  notifyCmd
1001
- .command('read-all')
1002
- .description('Mark all notifications as read')
1003
- .option('--agent-url <url>', 'Agent URL', DEFAULT_AGENT_URL)
1131
+ .command("read-all")
1132
+ .description("Mark all notifications as read")
1133
+ .option("--agent-url <url>", "Agent URL", DEFAULT_AGENT_URL)
1004
1134
  .action(async (options) => {
1005
1135
  try {
1006
- const { ok } = await agentFetch(options.agentUrl, '/api/notifications/read-all', {
1007
- method: 'PUT',
1136
+ const { ok } = await agentFetch(options.agentUrl, "/api/notifications/read-all", {
1137
+ method: "PUT",
1008
1138
  });
1009
1139
  if (!ok)
1010
- fail('Failed to mark as read');
1011
- console.log(' All notifications marked as read.');
1140
+ fail("Failed to mark as read");
1141
+ console.log(" All notifications marked as read.");
1012
1142
  }
1013
1143
  catch (error) {
1014
1144
  fail(`Failed: ${error instanceof Error ? error.message : error}`);
@@ -1018,14 +1148,14 @@ notifyCmd
1018
1148
  // System Info
1019
1149
  // ═══════════════════════════════════════════════════════════════════════════════
1020
1150
  program
1021
- .command('system')
1022
- .description('Show system information from the agent')
1023
- .option('--agent-url <url>', 'Agent URL', DEFAULT_AGENT_URL)
1151
+ .command("system")
1152
+ .description("Show system information from the agent")
1153
+ .option("--agent-url <url>", "Agent URL", DEFAULT_AGENT_URL)
1024
1154
  .action(async (options) => {
1025
1155
  try {
1026
- const { ok, data } = await agentFetch(options.agentUrl, '/api/config/system/info');
1156
+ const { ok, data } = await agentFetch(options.agentUrl, "/api/config/system/info");
1027
1157
  if (!ok)
1028
- fail('Failed to get system info');
1158
+ fail("Failed to get system info");
1029
1159
  console.log(`\n Hostname: ${data.hostname}`);
1030
1160
  console.log(` Platform: ${data.platform}`);
1031
1161
  console.log(` Architecture: ${data.arch}`);