@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.
- package/README.md +14 -14
- package/dist/app.d.ts +4 -4
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +145 -130
- package/dist/app.js.map +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +463 -333
- package/dist/cli.js.map +1 -1
- package/dist/db/schema.d.ts +15 -15
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/db/schema.js +65 -62
- package/dist/db/schema.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +18 -18
- package/dist/index.js.map +1 -1
- package/dist/middleware/ModuleAuth.d.ts +2 -2
- package/dist/middleware/ModuleAuth.d.ts.map +1 -1
- package/dist/middleware/ModuleAuth.js +32 -29
- package/dist/middleware/ModuleAuth.js.map +1 -1
- package/dist/middleware/auth.d.ts +1 -1
- package/dist/middleware/auth.d.ts.map +1 -1
- package/dist/middleware/auth.js +4 -4
- package/dist/middleware/auth.js.map +1 -1
- package/dist/migrations/remove-notes-prompts.d.ts.map +1 -1
- package/dist/migrations/remove-notes-prompts.js +26 -26
- package/dist/migrations/remove-notes-prompts.js.map +1 -1
- package/dist/routes/bookmarks.d.ts +1 -1
- package/dist/routes/bookmarks.d.ts.map +1 -1
- package/dist/routes/bookmarks.js +53 -44
- package/dist/routes/bookmarks.js.map +1 -1
- package/dist/routes/config.d.ts +1 -1
- package/dist/routes/config.d.ts.map +1 -1
- package/dist/routes/config.js +29 -27
- package/dist/routes/config.js.map +1 -1
- package/dist/routes/files.d.ts +1 -1
- package/dist/routes/files.d.ts.map +1 -1
- package/dist/routes/files.js +175 -134
- package/dist/routes/files.js.map +1 -1
- package/dist/routes/git.d.ts +1 -1
- package/dist/routes/git.d.ts.map +1 -1
- package/dist/routes/git.js +183 -169
- package/dist/routes/git.js.map +1 -1
- package/dist/routes/moduleRegistry.d.ts +3 -3
- package/dist/routes/moduleRegistry.d.ts.map +1 -1
- package/dist/routes/moduleRegistry.js +58 -58
- package/dist/routes/moduleRegistry.js.map +1 -1
- package/dist/routes/notifications.d.ts +1 -1
- package/dist/routes/notifications.d.ts.map +1 -1
- package/dist/routes/notifications.js +69 -64
- package/dist/routes/notifications.js.map +1 -1
- package/dist/routes/port-forward.d.ts +1 -1
- package/dist/routes/port-forward.d.ts.map +1 -1
- package/dist/routes/port-forward.js +59 -50
- package/dist/routes/port-forward.js.map +1 -1
- package/dist/routes/projects.d.ts +1 -1
- package/dist/routes/projects.d.ts.map +1 -1
- package/dist/routes/projects.js +134 -120
- package/dist/routes/projects.js.map +1 -1
- package/dist/routes/ssh.d.ts +1 -1
- package/dist/routes/ssh.d.ts.map +1 -1
- package/dist/routes/ssh.js +47 -47
- package/dist/routes/ssh.js.map +1 -1
- package/dist/routes/tasks.d.ts +1 -1
- package/dist/routes/tasks.d.ts.map +1 -1
- package/dist/routes/tasks.js +53 -49
- package/dist/routes/tasks.js.map +1 -1
- package/dist/routes/tmux.d.ts +1 -1
- package/dist/routes/tmux.d.ts.map +1 -1
- package/dist/routes/tmux.js +337 -241
- package/dist/routes/tmux.js.map +1 -1
- package/dist/routes/tunnel.d.ts +2 -2
- package/dist/routes/tunnel.d.ts.map +1 -1
- package/dist/routes/tunnel.js +115 -74
- package/dist/routes/tunnel.js.map +1 -1
- package/dist/services/ModulePermissions.d.ts +2 -2
- package/dist/services/ModulePermissions.d.ts.map +1 -1
- package/dist/services/ModulePermissions.js +50 -40
- package/dist/services/ModulePermissions.js.map +1 -1
- package/dist/services/ModuleRegistryService.d.ts +10 -10
- package/dist/services/ModuleRegistryService.d.ts.map +1 -1
- package/dist/services/ModuleRegistryService.js +156 -131
- package/dist/services/ModuleRegistryService.js.map +1 -1
- package/dist/services/agent.service.d.ts.map +1 -1
- package/dist/services/agent.service.js +24 -21
- package/dist/services/agent.service.js.map +1 -1
- package/dist/services/bootstrap.d.ts +1 -1
- package/dist/services/bootstrap.d.ts.map +1 -1
- package/dist/services/bootstrap.js +146 -69
- package/dist/services/bootstrap.js.map +1 -1
- package/dist/services/service-manager.d.ts +2 -2
- package/dist/services/service-manager.d.ts.map +1 -1
- package/dist/services/service-manager.js +75 -63
- package/dist/services/service-manager.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,34 +1,63 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
3
|
-
import { Command } from
|
|
4
|
-
import { AgentService } from
|
|
5
|
-
import { ServiceManager } from
|
|
6
|
-
import { readFileSync } from
|
|
7
|
-
import { join, dirname } from
|
|
8
|
-
import { fileURLToPath } from
|
|
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 =
|
|
12
|
+
let packageVersion = "1.0.0";
|
|
13
13
|
try {
|
|
14
|
-
const packageJsonPath = join(__dirname,
|
|
15
|
-
const packageJson = JSON.parse(readFileSync(packageJsonPath,
|
|
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 =
|
|
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 =
|
|
53
|
+
const apiKey = await resolveApiKey(agentUrl);
|
|
25
54
|
const headers = {
|
|
26
|
-
...(options.body ? {
|
|
27
|
-
...(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(
|
|
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(
|
|
64
|
-
.description(
|
|
65
|
-
.version(packageVersion,
|
|
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(
|
|
71
|
-
.description(
|
|
72
|
-
.option(
|
|
73
|
-
.option(
|
|
74
|
-
.option(
|
|
75
|
-
.option(
|
|
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(
|
|
97
|
-
.description(
|
|
98
|
-
.option(
|
|
99
|
-
.option(
|
|
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(
|
|
115
|
-
.description(
|
|
116
|
-
.option(
|
|
117
|
-
.option(
|
|
118
|
-
.option(
|
|
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(
|
|
134
|
-
.description(
|
|
135
|
-
.option(
|
|
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 ===
|
|
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(
|
|
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(
|
|
173
|
-
.description(
|
|
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(
|
|
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(
|
|
196
|
-
.description(
|
|
197
|
-
.option(
|
|
198
|
-
.option(
|
|
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(
|
|
214
|
-
.description(
|
|
215
|
-
.option(
|
|
216
|
-
.option(
|
|
217
|
-
.option(
|
|
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(
|
|
232
|
-
.description(
|
|
233
|
-
.option(
|
|
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(
|
|
251
|
-
.description(
|
|
252
|
-
.option(
|
|
253
|
-
.option(
|
|
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(
|
|
280
|
-
.description(
|
|
281
|
-
.option(
|
|
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 ||
|
|
292
|
-
console.log(` Uptime: ${data.uptime ? `${Math.floor(data.uptime / 60)}m` :
|
|
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(
|
|
303
|
-
.description(
|
|
304
|
-
.option(
|
|
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(
|
|
427
|
+
const { bootstrap, checkDependencies } = await import("./services/bootstrap.js");
|
|
308
428
|
if (options.check) {
|
|
309
|
-
console.log(
|
|
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 ?
|
|
313
|
-
console.log(` ${icon} ${name.padEnd(14)} ${info.available ? info.version :
|
|
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(
|
|
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(
|
|
442
|
+
console.log("\n All dependencies are installed.\n");
|
|
323
443
|
}
|
|
324
444
|
}
|
|
325
445
|
else {
|
|
326
|
-
console.log(
|
|
446
|
+
console.log("\n Installing system dependencies...\n");
|
|
327
447
|
const results = await bootstrap({ verbose: true });
|
|
328
|
-
const failed = results.filter(r => r.status ===
|
|
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(
|
|
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(
|
|
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(
|
|
346
|
-
.description(
|
|
347
|
-
.option(
|
|
348
|
-
.option(
|
|
349
|
-
.option(
|
|
350
|
-
.option(
|
|
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(
|
|
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:
|
|
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,
|
|
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(
|
|
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(
|
|
511
|
+
fail("Failed to list config");
|
|
392
512
|
}
|
|
393
513
|
}
|
|
394
514
|
else {
|
|
395
|
-
console.log(
|
|
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(
|
|
407
|
-
.description(
|
|
526
|
+
.command("tunnel")
|
|
527
|
+
.description("Manage cloudflared tunnels");
|
|
408
528
|
tunnelCmd
|
|
409
|
-
.command(
|
|
410
|
-
.description(
|
|
411
|
-
.option(
|
|
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,
|
|
534
|
+
const { ok, data } = await agentFetch(options.agentUrl, "/api/tunnel");
|
|
415
535
|
if (!ok)
|
|
416
|
-
fail(
|
|
536
|
+
fail("Failed to list tunnels");
|
|
417
537
|
if (data.tunnels.length === 0) {
|
|
418
|
-
console.log(
|
|
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
|
-
|
|
544
|
+
"Public URL": t.publicUrl || "(none)",
|
|
425
545
|
Status: t.status,
|
|
426
|
-
PID: t.pid ||
|
|
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(
|
|
436
|
-
.description(
|
|
437
|
-
.requiredOption(
|
|
438
|
-
.option(
|
|
439
|
-
.option(
|
|
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,
|
|
444
|
-
method:
|
|
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 ||
|
|
456
|
-
console.log(` PID: ${data.pid ||
|
|
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(
|
|
465
|
-
.description(
|
|
466
|
-
.requiredOption(
|
|
467
|
-
.option(
|
|
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:
|
|
591
|
+
method: "POST",
|
|
472
592
|
});
|
|
473
593
|
if (!ok)
|
|
474
|
-
fail(data.error ||
|
|
475
|
-
console.log(
|
|
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(
|
|
483
|
-
.description(
|
|
484
|
-
.requiredOption(
|
|
485
|
-
.option(
|
|
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:
|
|
609
|
+
method: "DELETE",
|
|
490
610
|
});
|
|
491
611
|
if (!ok)
|
|
492
|
-
fail(data.error ||
|
|
493
|
-
console.log(
|
|
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(
|
|
501
|
-
.description(
|
|
502
|
-
.option(
|
|
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,
|
|
625
|
+
const { ok, data } = await agentFetch(options.agentUrl, "/api/tunnel/status");
|
|
506
626
|
if (!ok)
|
|
507
|
-
fail(
|
|
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(
|
|
520
|
-
.description(
|
|
521
|
-
.option(
|
|
522
|
-
.option(
|
|
523
|
-
.option(
|
|
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,
|
|
647
|
+
const { ok, data } = await agentFetch(options.agentUrl, "/api/agent-tunnel/start", { method: "POST" });
|
|
528
648
|
if (!ok)
|
|
529
|
-
fail(
|
|
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,
|
|
534
|
-
|
|
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,
|
|
659
|
+
const { ok, data } = await agentFetch(options.agentUrl, "/api/agent-tunnel");
|
|
538
660
|
if (!ok)
|
|
539
|
-
fail(
|
|
540
|
-
console.log(`\n Tunnel URL: ${data.tunnelUrl ||
|
|
541
|
-
console.log(` Status: ${data.status ||
|
|
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(
|
|
553
|
-
.description(
|
|
674
|
+
.command("session")
|
|
675
|
+
.description("Manage tmux terminal sessions");
|
|
554
676
|
sessionCmd
|
|
555
|
-
.command(
|
|
556
|
-
.description(
|
|
557
|
-
.option(
|
|
558
|
-
.option(
|
|
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 ?
|
|
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(
|
|
565
|
-
const sessions = Array.isArray(data)
|
|
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(
|
|
691
|
+
console.log(" No sessions found.");
|
|
568
692
|
}
|
|
569
693
|
else {
|
|
570
|
-
formatTable(sessions.map(s => ({
|
|
571
|
-
ID: (s.id || s.sessionId ||
|
|
572
|
-
Name: s.sessionName || s.name ||
|
|
573
|
-
Status: s.status ||
|
|
574
|
-
Port: s.ttydPort ||
|
|
575
|
-
Project: s.projectId ||
|
|
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(
|
|
585
|
-
.description(
|
|
586
|
-
.requiredOption(
|
|
587
|
-
.option(
|
|
588
|
-
.option(
|
|
589
|
-
.option(
|
|
590
|
-
.option(
|
|
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,
|
|
594
|
-
method:
|
|
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 ||
|
|
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(
|
|
613
|
-
.description(
|
|
614
|
-
.requiredOption(
|
|
615
|
-
.option(
|
|
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:
|
|
743
|
+
method: "DELETE",
|
|
620
744
|
});
|
|
621
745
|
if (!ok)
|
|
622
|
-
fail(data.error ||
|
|
623
|
-
console.log(
|
|
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(
|
|
631
|
-
.description(
|
|
632
|
-
.requiredOption(
|
|
633
|
-
.requiredOption(
|
|
634
|
-
.option(
|
|
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:
|
|
762
|
+
method: "POST",
|
|
639
763
|
body: JSON.stringify({ command: options.command }),
|
|
640
764
|
});
|
|
641
765
|
if (!ok)
|
|
642
|
-
fail(data.error ||
|
|
643
|
-
console.log(
|
|
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(
|
|
651
|
-
.description(
|
|
652
|
-
.requiredOption(
|
|
653
|
-
.option(
|
|
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(
|
|
659
|
-
console.log(data.output ||
|
|
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(
|
|
673
|
-
.description(
|
|
674
|
-
.option(
|
|
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,
|
|
799
|
+
const { ok, data } = await agentFetch(options.agentUrl, "/api/ssh/connections");
|
|
678
800
|
if (!ok)
|
|
679
|
-
fail(
|
|
680
|
-
const conns = Array.isArray(data)
|
|
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(
|
|
806
|
+
console.log(" No SSH connections saved.");
|
|
683
807
|
}
|
|
684
808
|
else {
|
|
685
|
-
formatTable(conns.map(c => ({
|
|
686
|
-
ID: (c.id ||
|
|
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(
|
|
700
|
-
.description(
|
|
701
|
-
.requiredOption(
|
|
702
|
-
.requiredOption(
|
|
703
|
-
.requiredOption(
|
|
704
|
-
.option(
|
|
705
|
-
.option(
|
|
706
|
-
.option(
|
|
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,
|
|
710
|
-
method:
|
|
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 ||
|
|
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(
|
|
729
|
-
.description(
|
|
730
|
-
.requiredOption(
|
|
731
|
-
.option(
|
|
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:
|
|
859
|
+
method: "DELETE",
|
|
736
860
|
});
|
|
737
861
|
if (!ok)
|
|
738
|
-
fail(
|
|
739
|
-
console.log(
|
|
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(
|
|
747
|
-
.description(
|
|
748
|
-
.requiredOption(
|
|
749
|
-
.option(
|
|
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:
|
|
876
|
+
const { ok, data } = await agentFetch(options.agentUrl, `/api/ssh/test/${options.id}`, { method: "POST" });
|
|
753
877
|
if (!ok)
|
|
754
|
-
fail(data.error ||
|
|
755
|
-
console.log(
|
|
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(
|
|
763
|
-
.description(
|
|
764
|
-
.requiredOption(
|
|
765
|
-
.requiredOption(
|
|
766
|
-
.option(
|
|
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,
|
|
770
|
-
method:
|
|
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 ||
|
|
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(
|
|
792
|
-
.description(
|
|
915
|
+
.command("forward")
|
|
916
|
+
.description("Manage SSH port forwards");
|
|
793
917
|
forwardCmd
|
|
794
|
-
.command(
|
|
795
|
-
.description(
|
|
796
|
-
.option(
|
|
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,
|
|
923
|
+
const { ok, data } = await agentFetch(options.agentUrl, "/api/port-forward/");
|
|
800
924
|
if (!ok)
|
|
801
|
-
fail(
|
|
802
|
-
const fwds = Array.isArray(data)
|
|
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(
|
|
930
|
+
console.log(" No port forwards found.");
|
|
805
931
|
}
|
|
806
932
|
else {
|
|
807
|
-
formatTable(fwds.map(f => ({
|
|
808
|
-
ID: (f.id ||
|
|
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(
|
|
822
|
-
.description(
|
|
823
|
-
.requiredOption(
|
|
824
|
-
.requiredOption(
|
|
825
|
-
.requiredOption(
|
|
826
|
-
.requiredOption(
|
|
827
|
-
.option(
|
|
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,
|
|
831
|
-
method:
|
|
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 ||
|
|
841
|
-
console.log(
|
|
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(
|
|
849
|
-
.description(
|
|
850
|
-
.requiredOption(
|
|
851
|
-
.option(
|
|
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:
|
|
981
|
+
method: "POST",
|
|
856
982
|
});
|
|
857
983
|
if (!ok)
|
|
858
|
-
fail(data.error ||
|
|
859
|
-
console.log(
|
|
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(
|
|
867
|
-
.description(
|
|
868
|
-
.requiredOption(
|
|
869
|
-
.option(
|
|
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:
|
|
999
|
+
method: "POST",
|
|
874
1000
|
});
|
|
875
1001
|
if (!ok)
|
|
876
|
-
fail(data.error ||
|
|
877
|
-
console.log(
|
|
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(
|
|
885
|
-
.description(
|
|
886
|
-
.requiredOption(
|
|
887
|
-
.option(
|
|
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:
|
|
1017
|
+
method: "DELETE",
|
|
892
1018
|
});
|
|
893
1019
|
if (!ok)
|
|
894
|
-
fail(
|
|
895
|
-
console.log(
|
|
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(
|
|
909
|
-
.description(
|
|
910
|
-
.option(
|
|
911
|
-
.option(
|
|
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(
|
|
1041
|
+
fail("Failed to list tasks");
|
|
918
1042
|
const tasks = Array.isArray(data) ? data : [];
|
|
919
1043
|
if (tasks.length === 0) {
|
|
920
|
-
console.log(
|
|
1044
|
+
console.log(" No tasks found.");
|
|
921
1045
|
}
|
|
922
1046
|
else {
|
|
923
|
-
formatTable(tasks.map(t => ({
|
|
924
|
-
ID: (t.id ||
|
|
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) :
|
|
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(
|
|
937
|
-
.description(
|
|
938
|
-
.requiredOption(
|
|
939
|
-
.option(
|
|
940
|
-
.option(
|
|
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,
|
|
944
|
-
method:
|
|
1067
|
+
const { ok, data } = await agentFetch(options.agentUrl, "/api/tasks/", {
|
|
1068
|
+
method: "POST",
|
|
945
1069
|
body: JSON.stringify({
|
|
946
|
-
type:
|
|
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 ||
|
|
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(
|
|
969
|
-
.description(
|
|
970
|
-
.option(
|
|
971
|
-
.option(
|
|
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
|
|
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(
|
|
978
|
-
const notifications = Array.isArray(data)
|
|
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(
|
|
1106
|
+
console.log(" No notifications.");
|
|
981
1107
|
}
|
|
982
1108
|
else {
|
|
983
1109
|
for (const n of notifications) {
|
|
984
|
-
const icon = n.type ===
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
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(
|
|
1002
|
-
.description(
|
|
1003
|
-
.option(
|
|
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,
|
|
1007
|
-
method:
|
|
1136
|
+
const { ok } = await agentFetch(options.agentUrl, "/api/notifications/read-all", {
|
|
1137
|
+
method: "PUT",
|
|
1008
1138
|
});
|
|
1009
1139
|
if (!ok)
|
|
1010
|
-
fail(
|
|
1011
|
-
console.log(
|
|
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(
|
|
1022
|
-
.description(
|
|
1023
|
-
.option(
|
|
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,
|
|
1156
|
+
const { ok, data } = await agentFetch(options.agentUrl, "/api/config/system/info");
|
|
1027
1157
|
if (!ok)
|
|
1028
|
-
fail(
|
|
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}`);
|