@agentgazer/cli 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/cli.js +726 -403
- package/dist/cli.js.map +1 -1
- package/dist/commands/agent.d.ts +2 -0
- package/dist/commands/agent.d.ts.map +1 -0
- package/dist/commands/agent.js +469 -0
- package/dist/commands/agent.js.map +1 -0
- package/dist/commands/agents.d.ts +2 -0
- package/dist/commands/agents.d.ts.map +1 -0
- package/dist/commands/agents.js +70 -0
- package/dist/commands/agents.js.map +1 -0
- package/dist/commands/events.d.ts +21 -0
- package/dist/commands/events.d.ts.map +1 -0
- package/dist/commands/events.js +247 -0
- package/dist/commands/events.js.map +1 -0
- package/dist/commands/overview.d.ts +2 -0
- package/dist/commands/overview.d.ts.map +1 -0
- package/dist/commands/overview.js +8 -0
- package/dist/commands/overview.js.map +1 -0
- package/dist/commands/provider.d.ts +2 -0
- package/dist/commands/provider.d.ts.map +1 -0
- package/dist/commands/provider.js +210 -0
- package/dist/commands/provider.js.map +1 -0
- package/dist/commands/providers.d.ts +2 -0
- package/dist/commands/providers.d.ts.map +1 -0
- package/dist/commands/providers.js +77 -0
- package/dist/commands/providers.js.map +1 -0
- package/dist/config.d.ts +46 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +97 -60
- package/dist/config.js.map +1 -1
- package/dist/dashboard/assets/index-5AtWzXiJ.js +143 -0
- package/dist/dashboard/assets/index-B7NvhJW9.js +143 -0
- package/dist/dashboard/assets/index-B9izZ4lr.js +139 -0
- package/dist/dashboard/assets/index-BADwu2fU.js +143 -0
- package/dist/dashboard/assets/index-BBonREkg.js +143 -0
- package/dist/dashboard/assets/index-BGdw1Ss1.js +139 -0
- package/dist/dashboard/assets/index-BLIagyHw.css +1 -0
- package/dist/dashboard/assets/index-BN2gLOCd.js +143 -0
- package/dist/dashboard/assets/index-BOcemxE3.js +143 -0
- package/dist/dashboard/assets/index-BYYSUJ-G.js +143 -0
- package/dist/dashboard/assets/index-BhGhcADW.js +143 -0
- package/dist/dashboard/assets/index-Bjr8f_u_.js +143 -0
- package/dist/dashboard/assets/index-BsaxUL45.js +143 -0
- package/dist/dashboard/assets/index-C-DguelG.css +1 -0
- package/dist/dashboard/assets/index-C-QgKsob.js +143 -0
- package/dist/dashboard/assets/index-C6t4YWSY.js +143 -0
- package/dist/dashboard/assets/index-C9EQrGpe.js +143 -0
- package/dist/dashboard/assets/index-CB6k7nz0.js +143 -0
- package/dist/dashboard/assets/index-CBCLOBvd.js +139 -0
- package/dist/dashboard/assets/index-CGwUmIVh.js +139 -0
- package/dist/dashboard/assets/index-CLMyGFxA.js +145 -0
- package/dist/dashboard/assets/index-CYnfgO3k.js +143 -0
- package/dist/dashboard/assets/index-CiGGhaUw.js +143 -0
- package/dist/dashboard/assets/index-Cj3GjdoQ.js +143 -0
- package/dist/dashboard/assets/index-CxEl0O9p.js +143 -0
- package/dist/dashboard/assets/index-D2mLKlRw.css +1 -0
- package/dist/dashboard/assets/index-D5AUmtdo.js +139 -0
- package/dist/dashboard/assets/index-DACbampq.css +1 -0
- package/dist/dashboard/assets/index-DB0fFjgr.js +139 -0
- package/dist/dashboard/assets/index-DI-JqWdh.css +1 -0
- package/dist/dashboard/assets/index-DRcLEFcl.js +143 -0
- package/dist/dashboard/assets/index-DaUEJqLn.css +1 -0
- package/dist/dashboard/assets/index-Db5ik9Wo.js +143 -0
- package/dist/dashboard/assets/index-DfV-2ewH.css +1 -0
- package/dist/dashboard/assets/index-DgEq2MBB.css +1 -0
- package/dist/dashboard/assets/index-DhmVjP8v.js +143 -0
- package/dist/dashboard/assets/index-Dipd1pd3.js +143 -0
- package/dist/dashboard/assets/index-DirRSEOl.js +143 -0
- package/dist/dashboard/assets/index-Dr9wOl47.js +143 -0
- package/dist/dashboard/assets/index-ee-BCW5i.js +143 -0
- package/dist/dashboard/assets/index-g4KzDTwu.js +139 -0
- package/dist/dashboard/assets/index-odup_N3G.css +1 -0
- package/dist/dashboard/assets/index-wHHHG0bR.css +1 -0
- package/dist/dashboard/assets/index-wNn2zMSu.js +143 -0
- package/dist/dashboard/assets/index-wvU3UdVf.js +143 -0
- package/dist/dashboard/assets/index-yHwKni1U.css +1 -0
- package/dist/dashboard/assets/index-z-YzEeVY.js +143 -0
- package/dist/dashboard/index.html +2 -2
- package/dist/index.js +1 -11
- package/dist/index.js.map +1 -1
- package/dist/secret-store.js +26 -68
- package/dist/secret-store.js.map +1 -1
- package/dist/tui/AgentTable.d.ts +15 -0
- package/dist/tui/AgentTable.d.ts.map +1 -0
- package/dist/tui/AgentTable.js +35 -0
- package/dist/tui/AgentTable.js.map +1 -0
- package/dist/tui/EventLog.d.ts +15 -0
- package/dist/tui/EventLog.d.ts.map +1 -0
- package/dist/tui/EventLog.js +15 -0
- package/dist/tui/EventLog.js.map +1 -0
- package/dist/tui/HelpOverlay.d.ts +7 -0
- package/dist/tui/HelpOverlay.d.ts.map +1 -0
- package/dist/tui/HelpOverlay.js +11 -0
- package/dist/tui/HelpOverlay.js.map +1 -0
- package/dist/tui/Overview.d.ts +7 -0
- package/dist/tui/Overview.d.ts.map +1 -0
- package/dist/tui/Overview.js +69 -0
- package/dist/tui/Overview.js.map +1 -0
- package/dist/tui/StatusBar.d.ts +11 -0
- package/dist/tui/StatusBar.d.ts.map +1 -0
- package/dist/tui/StatusBar.js +27 -0
- package/dist/tui/StatusBar.js.map +1 -0
- package/dist/utils/api.d.ts +10 -0
- package/dist/utils/api.d.ts.map +1 -0
- package/dist/utils/api.js +114 -0
- package/dist/utils/api.js.map +1 -0
- package/dist/utils/format.d.ts +7 -0
- package/dist/utils/format.d.ts.map +1 -0
- package/dist/utils/format.js +56 -0
- package/dist/utils/format.js.map +1 -0
- package/dist/utils/prompt.d.ts +4 -0
- package/dist/utils/prompt.d.ts.map +1 -0
- package/dist/utils/prompt.js +35 -0
- package/dist/utils/prompt.js.map +1 -0
- package/package.json +13 -6
package/dist/cli.js
CHANGED
|
@@ -1,47 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
var ar = [];
|
|
23
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
-
return ar;
|
|
25
|
-
};
|
|
26
|
-
return ownKeys(o);
|
|
27
|
-
};
|
|
28
|
-
return function (mod) {
|
|
29
|
-
if (mod && mod.__esModule) return mod;
|
|
30
|
-
var result = {};
|
|
31
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
-
__setModuleDefault(result, mod);
|
|
33
|
-
return result;
|
|
34
|
-
};
|
|
35
|
-
})();
|
|
36
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
-
const fs = __importStar(require("node:fs"));
|
|
38
|
-
const path = __importStar(require("node:path"));
|
|
39
|
-
const readline = __importStar(require("node:readline"));
|
|
40
|
-
const config_js_1 = require("./config.js");
|
|
41
|
-
const secret_store_js_1 = require("./secret-store.js");
|
|
42
|
-
const server_1 = require("@agentgazer/server");
|
|
43
|
-
const proxy_1 = require("@agentgazer/proxy");
|
|
44
|
-
const shared_1 = require("@agentgazer/shared");
|
|
2
|
+
import { execSync } from "node:child_process";
|
|
3
|
+
import * as fs from "node:fs";
|
|
4
|
+
import * as net from "node:net";
|
|
5
|
+
import * as path from "node:path";
|
|
6
|
+
import * as readline from "node:readline";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = path.dirname(__filename);
|
|
10
|
+
import { ensureConfig, readConfig, resetToken, getDbPath, getConfigDir, setProvider, } from "./config.js";
|
|
11
|
+
import { detectSecretStore, migrateFromPlaintextConfig, loadProviderKeys, PROVIDER_SERVICE, } from "./secret-store.js";
|
|
12
|
+
import { startServer } from "@agentgazer/server";
|
|
13
|
+
import { startProxy } from "@agentgazer/proxy";
|
|
14
|
+
import { KNOWN_PROVIDER_NAMES } from "@agentgazer/shared";
|
|
15
|
+
// New command imports
|
|
16
|
+
import { cmdAgents } from "./commands/agents.js";
|
|
17
|
+
import { cmdAgent } from "./commands/agent.js";
|
|
18
|
+
import { cmdProviders } from "./commands/providers.js";
|
|
19
|
+
import { cmdProvider } from "./commands/provider.js";
|
|
20
|
+
import { cmdEvents } from "./commands/events.js";
|
|
21
|
+
// cmdOverview is imported dynamically to avoid ESM top-level await issues
|
|
45
22
|
// ---------------------------------------------------------------------------
|
|
46
23
|
// Arg parsing
|
|
47
24
|
// ---------------------------------------------------------------------------
|
|
@@ -52,7 +29,19 @@ function parseFlags(argv) {
|
|
|
52
29
|
if (arg.startsWith("--")) {
|
|
53
30
|
const key = arg.slice(2);
|
|
54
31
|
const next = argv[i + 1];
|
|
55
|
-
if (next !== undefined && !next.startsWith("
|
|
32
|
+
if (next !== undefined && !next.startsWith("-")) {
|
|
33
|
+
flags[key] = next;
|
|
34
|
+
i++;
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
flags[key] = "";
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
else if (arg.startsWith("-") && arg.length === 2) {
|
|
41
|
+
// Short flags like -v, -d, or short flags with values like -o json
|
|
42
|
+
const key = arg.slice(1);
|
|
43
|
+
const next = argv[i + 1];
|
|
44
|
+
if (next !== undefined && !next.startsWith("-")) {
|
|
56
45
|
flags[key] = next;
|
|
57
46
|
i++;
|
|
58
47
|
}
|
|
@@ -80,6 +69,50 @@ function parsePositional(argv) {
|
|
|
80
69
|
return positional;
|
|
81
70
|
}
|
|
82
71
|
// ---------------------------------------------------------------------------
|
|
72
|
+
// Port utilities
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
function isPortAvailable(port) {
|
|
75
|
+
return new Promise((resolve) => {
|
|
76
|
+
const server = net.createServer();
|
|
77
|
+
server.once("error", () => resolve(false));
|
|
78
|
+
server.once("listening", () => {
|
|
79
|
+
server.close(() => resolve(true));
|
|
80
|
+
});
|
|
81
|
+
server.listen(port, "0.0.0.0");
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
async function findAvailablePort(startPort, maxAttempts = 10) {
|
|
85
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
86
|
+
const port = startPort + i;
|
|
87
|
+
if (await isPortAvailable(port)) {
|
|
88
|
+
return port;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
throw new Error(`No available port found in range ${startPort}-${startPort + maxAttempts - 1}`);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Find PIDs of processes listening on the given ports.
|
|
95
|
+
* Uses lsof on macOS/Linux. Returns empty array on error.
|
|
96
|
+
*/
|
|
97
|
+
function findPidsOnPorts(ports) {
|
|
98
|
+
const pids = new Set();
|
|
99
|
+
for (const port of ports) {
|
|
100
|
+
try {
|
|
101
|
+
const result = execSync(`lsof -ti:${port} 2>/dev/null`, { encoding: "utf-8" });
|
|
102
|
+
for (const line of result.trim().split("\n")) {
|
|
103
|
+
const pid = parseInt(line.trim(), 10);
|
|
104
|
+
if (!isNaN(pid) && pid > 0) {
|
|
105
|
+
pids.add(pid);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
// No process on this port or lsof not available
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return Array.from(pids);
|
|
114
|
+
}
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
83
116
|
// Help
|
|
84
117
|
// ---------------------------------------------------------------------------
|
|
85
118
|
function printUsage() {
|
|
@@ -91,70 +124,133 @@ Usage: agentgazer <command> [options]
|
|
|
91
124
|
Commands:
|
|
92
125
|
onboard First-time setup — generate token and configure providers
|
|
93
126
|
start Start the server, proxy, and dashboard
|
|
127
|
+
stop Stop the daemon process
|
|
128
|
+
logs Show daemon logs (use -f to follow)
|
|
94
129
|
status Show current configuration
|
|
95
130
|
reset-token Generate a new auth token
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
131
|
+
overview Launch real-time TUI dashboard
|
|
132
|
+
events Query and display agent events
|
|
133
|
+
|
|
134
|
+
agents List all registered agents
|
|
135
|
+
agent <name> active Activate an agent
|
|
136
|
+
agent <name> deactive Deactivate an agent
|
|
137
|
+
agent <name> killswitch on|off Toggle kill switch
|
|
138
|
+
agent <name> delete Delete agent and all data
|
|
139
|
+
agent <name> stat Show agent statistics
|
|
140
|
+
agent <name> model List model overrides
|
|
141
|
+
agent <name> model-override <model> Set model override
|
|
142
|
+
agent <name> alerts List alert rules for agent
|
|
143
|
+
agent <name> alert add <type> Add alert rule (agent_down, error_rate, budget)
|
|
144
|
+
agent <name> alert delete <id> Delete alert rule
|
|
145
|
+
agent <name> alert reset <id> Reset alert state to normal
|
|
146
|
+
|
|
147
|
+
providers List all configured providers
|
|
148
|
+
provider add [name] [key] Add provider (interactive if args omitted)
|
|
149
|
+
provider <name> active Activate a provider
|
|
150
|
+
provider <name> deactive Deactivate a provider
|
|
151
|
+
provider <name> test-connection Test API key validity
|
|
152
|
+
provider <name> delete Delete provider and key
|
|
153
|
+
provider <name> models List available models
|
|
154
|
+
provider <name> stat Show provider statistics
|
|
155
|
+
|
|
100
156
|
version Show version
|
|
101
157
|
doctor Check system health
|
|
102
|
-
|
|
103
|
-
stats [agentId] Show agent statistics (auto-selects if only one agent)
|
|
104
|
-
uninstall Remove AgentGazer (curl-installed only)
|
|
158
|
+
uninstall Remove AgentGazer data (interactive menu)
|
|
105
159
|
help Show this help message
|
|
106
160
|
|
|
107
161
|
Options (for start):
|
|
108
|
-
--port <number> Server/dashboard port (default:
|
|
109
|
-
--proxy-port <number> LLM proxy port (default:
|
|
110
|
-
--retention-days <number> Data retention
|
|
111
|
-
--no-open Don't auto-open browser
|
|
162
|
+
--port <number> Server/dashboard port (default: 18800, or config.server.port)
|
|
163
|
+
--proxy-port <number> LLM proxy port (default: 18900, or config.server.proxyPort)
|
|
164
|
+
--retention-days <number> Data retention in days (default: 30, or config.data.retentionDays)
|
|
165
|
+
--no-open Don't auto-open browser (or set config.server.autoOpen: false)
|
|
166
|
+
-v, --verbose Print verbose logs to console
|
|
167
|
+
-d, --daemon Print info and token, then run in background
|
|
168
|
+
|
|
169
|
+
Config file: ~/.agentgazer/config.json (optional settings: port, proxyPort, autoOpen, retentionDays)
|
|
170
|
+
|
|
171
|
+
Options (for agent/provider stat):
|
|
172
|
+
--range <period> Time range: 1h, 24h, 7d, 30d (default: 24h)
|
|
173
|
+
|
|
174
|
+
Options (for delete commands):
|
|
175
|
+
--yes Skip confirmation prompts
|
|
176
|
+
|
|
177
|
+
Options (for alert add):
|
|
178
|
+
--threshold <percent> Error rate threshold (error_rate only, default: 10)
|
|
179
|
+
--timeout <seconds> Timeout in seconds (agent_down only, default: 300)
|
|
180
|
+
--limit <amount> Budget limit in USD (budget only, required)
|
|
181
|
+
--period <period> Budget period: daily, weekly, monthly (budget only)
|
|
182
|
+
--repeat Enable repeat notifications (default: enabled)
|
|
183
|
+
--no-repeat Disable repeat notifications (one-time only)
|
|
184
|
+
--interval <minutes> Repeat interval in minutes (default: 15)
|
|
185
|
+
--recovery-notify Send notification when alert recovers
|
|
186
|
+
--webhook <url> Webhook URL for notifications
|
|
187
|
+
--telegram <chat_id> Telegram chat ID for notifications
|
|
112
188
|
|
|
113
|
-
Options (for
|
|
114
|
-
--
|
|
115
|
-
|
|
189
|
+
Options (for logs):
|
|
190
|
+
-f, --follow Follow log output (like tail -f)
|
|
191
|
+
-n, --lines <number> Number of lines to show (default: 50)
|
|
116
192
|
|
|
117
|
-
Options (for
|
|
118
|
-
--
|
|
193
|
+
Options (for events):
|
|
194
|
+
-a, --agent <name> Filter by agent ID
|
|
195
|
+
-t, --type <type> Filter by event type
|
|
196
|
+
-p, --provider <name> Filter by provider
|
|
197
|
+
-s, --since <duration> Time range: 1h, 24h, 7d, 30d (default: 24h)
|
|
198
|
+
-n, --limit <number> Max events (default: 50, max: 1000)
|
|
199
|
+
-o, --output <format> Output: table, json, csv (default: table)
|
|
200
|
+
--search <term> Search in model/provider/error
|
|
201
|
+
-f, --follow Poll for new events every 3s
|
|
119
202
|
|
|
120
203
|
Options (for uninstall):
|
|
204
|
+
--all Remove everything (keys, config, data)
|
|
205
|
+
--config Remove config only
|
|
206
|
+
--keys Remove provider keys only
|
|
207
|
+
--data Remove agent data only
|
|
121
208
|
--yes Skip confirmation prompts
|
|
122
209
|
|
|
123
210
|
Examples:
|
|
124
|
-
agentgazer onboard
|
|
125
|
-
agentgazer start
|
|
126
|
-
agentgazer start
|
|
127
|
-
agentgazer
|
|
128
|
-
agentgazer
|
|
129
|
-
agentgazer
|
|
130
|
-
agentgazer
|
|
211
|
+
agentgazer onboard First-time setup
|
|
212
|
+
agentgazer start Start with defaults
|
|
213
|
+
agentgazer start -d Start as daemon (background)
|
|
214
|
+
agentgazer stop Stop the daemon
|
|
215
|
+
agentgazer logs -f Follow daemon logs
|
|
216
|
+
agentgazer overview Launch TUI dashboard
|
|
217
|
+
agentgazer provider add openai Add OpenAI (prompts for key)
|
|
218
|
+
agentgazer agent my-bot stat Show stats for my-bot
|
|
219
|
+
agentgazer agent my-bot killswitch on Enable kill switch
|
|
220
|
+
agentgazer events Show recent events
|
|
221
|
+
agentgazer events -a my-bot -t error Filter by agent and type
|
|
222
|
+
agentgazer events -f Follow new events live
|
|
223
|
+
agentgazer agent my-bot alerts List alerts for my-bot
|
|
224
|
+
agentgazer agent my-bot alert add error_rate --threshold 20
|
|
225
|
+
agentgazer agent my-bot alert add budget --limit 50 --period daily
|
|
226
|
+
agentgazer agent my-bot alert reset abc Reset alert state
|
|
131
227
|
`);
|
|
132
228
|
}
|
|
133
229
|
// ---------------------------------------------------------------------------
|
|
134
230
|
// Subcommands
|
|
135
231
|
// ---------------------------------------------------------------------------
|
|
136
|
-
const KNOWN_PROVIDERS =
|
|
232
|
+
const KNOWN_PROVIDERS = KNOWN_PROVIDER_NAMES;
|
|
137
233
|
function ask(rl, question) {
|
|
138
234
|
return new Promise((resolve) => {
|
|
139
235
|
rl.question(question, (answer) => resolve(answer.trim()));
|
|
140
236
|
});
|
|
141
237
|
}
|
|
142
238
|
async function cmdOnboard() {
|
|
143
|
-
const saved =
|
|
239
|
+
const saved = ensureConfig();
|
|
144
240
|
console.log(`
|
|
145
241
|
AgentGazer — Setup
|
|
146
242
|
───────────────────────────────────────
|
|
147
243
|
|
|
148
244
|
Token: ${saved.token}
|
|
149
|
-
Config: ${
|
|
150
|
-
Database: ${
|
|
151
|
-
Server: http://localhost:
|
|
152
|
-
Proxy: http://localhost:
|
|
245
|
+
Config: ${getConfigDir()}/config.json
|
|
246
|
+
Database: ${getDbPath()}
|
|
247
|
+
Server: http://localhost:18800
|
|
248
|
+
Proxy: http://localhost:18900
|
|
153
249
|
|
|
154
250
|
───────────────────────────────────────
|
|
155
251
|
`);
|
|
156
252
|
// Initialize secret store
|
|
157
|
-
const { store, backendName } = await
|
|
253
|
+
const { store, backendName } = await detectSecretStore(getConfigDir());
|
|
158
254
|
console.log(` Secret backend: ${backendName}\n`);
|
|
159
255
|
const rl = readline.createInterface({
|
|
160
256
|
input: process.stdin,
|
|
@@ -169,10 +265,10 @@ async function cmdOnboard() {
|
|
|
169
265
|
if (!key)
|
|
170
266
|
continue;
|
|
171
267
|
// Store API key in secret store
|
|
172
|
-
await store.set(
|
|
268
|
+
await store.set(PROVIDER_SERVICE, provider, key);
|
|
173
269
|
// Store provider entry in config.json (apiKey is empty — actual key is in secret store)
|
|
174
270
|
const providerConfig = { apiKey: "" };
|
|
175
|
-
|
|
271
|
+
setProvider(provider, providerConfig);
|
|
176
272
|
providerCount++;
|
|
177
273
|
console.log(` ✓ ${provider} configured.\n`);
|
|
178
274
|
}
|
|
@@ -208,121 +304,13 @@ async function cmdOnboard() {
|
|
|
208
304
|
|
|
209
305
|
Or point your LLM client at the proxy (with auto API key injection):
|
|
210
306
|
|
|
211
|
-
export OPENAI_BASE_URL=http://localhost:
|
|
307
|
+
export OPENAI_BASE_URL=http://localhost:18900/openai/v1
|
|
212
308
|
|
|
213
309
|
Next: run "agentgazer start" to launch.
|
|
214
310
|
`);
|
|
215
311
|
}
|
|
216
|
-
async function cmdProviders(args) {
|
|
217
|
-
const action = args[0];
|
|
218
|
-
switch (action) {
|
|
219
|
-
case "list": {
|
|
220
|
-
// List reads from config.json only — no secret store access needed.
|
|
221
|
-
const providers = (0, config_js_1.listProviders)();
|
|
222
|
-
const names = Object.keys(providers);
|
|
223
|
-
if (names.length === 0) {
|
|
224
|
-
console.log("No providers configured. Use \"agentgazer providers set-key\" to add one.");
|
|
225
|
-
return;
|
|
226
|
-
}
|
|
227
|
-
console.log("\n Configured providers:");
|
|
228
|
-
console.log(" ───────────────────────────────────────");
|
|
229
|
-
for (const name of names) {
|
|
230
|
-
const p = providers[name];
|
|
231
|
-
const keyStatus = p.apiKey ? "(plaintext — run \"agentgazer start\" to migrate)" : "(secured)";
|
|
232
|
-
const rateInfo = p.rateLimit
|
|
233
|
-
? ` (rate limit: ${p.rateLimit.maxRequests} req / ${p.rateLimit.windowSeconds}s)`
|
|
234
|
-
: "";
|
|
235
|
-
console.log(` ${name}: ${keyStatus}${rateInfo}`);
|
|
236
|
-
}
|
|
237
|
-
console.log();
|
|
238
|
-
break;
|
|
239
|
-
}
|
|
240
|
-
case "set": {
|
|
241
|
-
const name = args[1];
|
|
242
|
-
const key = args[2];
|
|
243
|
-
if (!name || !key) {
|
|
244
|
-
console.error("Usage: agentgazer providers set <provider-name> <api-key>");
|
|
245
|
-
process.exit(1);
|
|
246
|
-
}
|
|
247
|
-
if (!KNOWN_PROVIDERS.includes(name)) {
|
|
248
|
-
console.warn(`Warning: "${name}" is not a known provider (${KNOWN_PROVIDERS.join(", ")}). Proceeding anyway.`);
|
|
249
|
-
}
|
|
250
|
-
const { store, backendName } = await (0, secret_store_js_1.detectSecretStore)((0, config_js_1.getConfigDir)());
|
|
251
|
-
// Store API key in secret store
|
|
252
|
-
await store.set(secret_store_js_1.PROVIDER_SERVICE, name, key);
|
|
253
|
-
// Ensure provider entry exists in config.json (for rate limits etc.)
|
|
254
|
-
const config = (0, config_js_1.ensureConfig)();
|
|
255
|
-
if (!config.providers)
|
|
256
|
-
config.providers = {};
|
|
257
|
-
if (!config.providers[name]) {
|
|
258
|
-
config.providers[name] = { apiKey: "" };
|
|
259
|
-
}
|
|
260
|
-
// Remove any plaintext apiKey that might be in config
|
|
261
|
-
config.providers[name].apiKey = "";
|
|
262
|
-
(0, config_js_1.saveConfig)(config);
|
|
263
|
-
console.log(`Provider "${name}" configured (secret stored in ${backendName}).`);
|
|
264
|
-
break;
|
|
265
|
-
}
|
|
266
|
-
case "set-key": {
|
|
267
|
-
// Interactive provider key setup
|
|
268
|
-
const rl = readline.createInterface({
|
|
269
|
-
input: process.stdin,
|
|
270
|
-
output: process.stdout,
|
|
271
|
-
});
|
|
272
|
-
console.log("\n Available providers:");
|
|
273
|
-
console.log(" ───────────────────────────────────────");
|
|
274
|
-
KNOWN_PROVIDERS.forEach((p, i) => {
|
|
275
|
-
console.log(` ${i + 1}. ${p}`);
|
|
276
|
-
});
|
|
277
|
-
console.log();
|
|
278
|
-
try {
|
|
279
|
-
const choice = await ask(rl, " Select provider (number): ");
|
|
280
|
-
const idx = parseInt(choice, 10) - 1;
|
|
281
|
-
if (isNaN(idx) || idx < 0 || idx >= KNOWN_PROVIDERS.length) {
|
|
282
|
-
console.error(" Invalid selection.");
|
|
283
|
-
process.exit(1);
|
|
284
|
-
}
|
|
285
|
-
const provider = KNOWN_PROVIDERS[idx];
|
|
286
|
-
const apiKey = await ask(rl, ` API key for ${provider}: `);
|
|
287
|
-
if (!apiKey) {
|
|
288
|
-
console.error(" API key is required.");
|
|
289
|
-
process.exit(1);
|
|
290
|
-
}
|
|
291
|
-
// Store API key in secret store
|
|
292
|
-
const { store, backendName: backend } = await (0, secret_store_js_1.detectSecretStore)((0, config_js_1.getConfigDir)());
|
|
293
|
-
await store.set(secret_store_js_1.PROVIDER_SERVICE, provider, apiKey);
|
|
294
|
-
// Store provider entry in config.json (apiKey is empty — actual key is in secret store)
|
|
295
|
-
const providerConfig = { apiKey: "" };
|
|
296
|
-
(0, config_js_1.setProvider)(provider, providerConfig);
|
|
297
|
-
console.log(`\n ✓ ${provider} configured (secret stored in ${backend}).`);
|
|
298
|
-
console.log(` Rate limits can be configured in the Dashboard.`);
|
|
299
|
-
}
|
|
300
|
-
finally {
|
|
301
|
-
rl.close();
|
|
302
|
-
}
|
|
303
|
-
break;
|
|
304
|
-
}
|
|
305
|
-
case "remove": {
|
|
306
|
-
const name = args[1];
|
|
307
|
-
if (!name) {
|
|
308
|
-
console.error("Usage: agentgazer providers remove <provider-name>");
|
|
309
|
-
process.exit(1);
|
|
310
|
-
}
|
|
311
|
-
const { store } = await (0, secret_store_js_1.detectSecretStore)((0, config_js_1.getConfigDir)());
|
|
312
|
-
// Delete from secret store
|
|
313
|
-
await store.delete(secret_store_js_1.PROVIDER_SERVICE, name);
|
|
314
|
-
// Remove from config.json
|
|
315
|
-
(0, config_js_1.removeProvider)(name);
|
|
316
|
-
console.log(`Provider "${name}" removed.`);
|
|
317
|
-
break;
|
|
318
|
-
}
|
|
319
|
-
default:
|
|
320
|
-
console.error("Usage: agentgazer providers <list|set-key|set|remove>");
|
|
321
|
-
process.exit(1);
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
312
|
function cmdStatus() {
|
|
325
|
-
const config =
|
|
313
|
+
const config = readConfig();
|
|
326
314
|
if (!config) {
|
|
327
315
|
console.log("No configuration found. Run \"agentgazer onboard\" first.");
|
|
328
316
|
process.exit(1);
|
|
@@ -332,23 +320,90 @@ function cmdStatus() {
|
|
|
332
320
|
───────────────────────────────────────
|
|
333
321
|
|
|
334
322
|
Token: ${config.token}
|
|
335
|
-
Config: ${
|
|
336
|
-
Database: ${
|
|
337
|
-
Server: http://localhost:
|
|
338
|
-
Proxy: http://localhost:
|
|
323
|
+
Config: ${getConfigDir()}/config.json
|
|
324
|
+
Database: ${getDbPath()}
|
|
325
|
+
Server: http://localhost:18800 (default)
|
|
326
|
+
Proxy: http://localhost:18900 (default)
|
|
339
327
|
`);
|
|
340
328
|
}
|
|
341
329
|
function cmdResetToken() {
|
|
342
|
-
const config =
|
|
330
|
+
const config = resetToken();
|
|
343
331
|
console.log(`Token reset. New token: ${config.token}`);
|
|
344
332
|
}
|
|
345
333
|
async function cmdStart(flags) {
|
|
346
|
-
const
|
|
347
|
-
const
|
|
334
|
+
const verbose = "v" in flags || "verbose" in flags;
|
|
335
|
+
const daemon = "d" in flags || "daemon" in flags;
|
|
336
|
+
// Set log level for verbose mode
|
|
337
|
+
if (verbose) {
|
|
338
|
+
process.env.LOG_LEVEL = "debug";
|
|
339
|
+
}
|
|
340
|
+
// Handle daemon mode: fork process and exit
|
|
341
|
+
if (daemon && !process.env.AGENTGAZER_DAEMON_CHILD) {
|
|
342
|
+
const { spawn } = await import("node:child_process");
|
|
343
|
+
const configDir = getConfigDir();
|
|
344
|
+
const pidFile = path.join(configDir, "agentgazer.pid");
|
|
345
|
+
const logFile = path.join(configDir, "agentgazer.log");
|
|
346
|
+
// Check if already running
|
|
347
|
+
if (fs.existsSync(pidFile)) {
|
|
348
|
+
const existingPid = parseInt(fs.readFileSync(pidFile, "utf-8").trim(), 10);
|
|
349
|
+
try {
|
|
350
|
+
process.kill(existingPid, 0); // Check if process exists
|
|
351
|
+
console.error(`AgentGazer is already running (PID: ${existingPid})`);
|
|
352
|
+
console.error(`Use "agentgazer stop" to stop it first.`);
|
|
353
|
+
process.exit(1);
|
|
354
|
+
}
|
|
355
|
+
catch {
|
|
356
|
+
// Process doesn't exist, clean up stale PID file
|
|
357
|
+
fs.unlinkSync(pidFile);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
// Open log file for output
|
|
361
|
+
const logFd = fs.openSync(logFile, "a");
|
|
362
|
+
const args = process.argv.slice(2).filter((a) => a !== "-d" && a !== "--daemon");
|
|
363
|
+
const child = spawn(process.execPath, [process.argv[1], ...args], {
|
|
364
|
+
detached: true,
|
|
365
|
+
stdio: ["ignore", logFd, logFd],
|
|
366
|
+
env: { ...process.env, AGENTGAZER_DAEMON_CHILD: "1" },
|
|
367
|
+
});
|
|
368
|
+
child.unref();
|
|
369
|
+
fs.closeSync(logFd);
|
|
370
|
+
// Write PID file
|
|
371
|
+
fs.writeFileSync(pidFile, String(child.pid));
|
|
372
|
+
const config = ensureConfig();
|
|
373
|
+
// Use config values as defaults for daemon mode display
|
|
374
|
+
const serverPort = flags["port"] ? parseInt(flags["port"], 10) : (config.server?.port ?? 18800);
|
|
375
|
+
const proxyPort = flags["proxy-port"] ? parseInt(flags["proxy-port"], 10) : (config.server?.proxyPort ?? 18900);
|
|
376
|
+
console.log(`
|
|
377
|
+
╔════════════════════════════════════════════════════╗
|
|
378
|
+
║ AgentGazer started (daemon) ║
|
|
379
|
+
╠════════════════════════════════════════════════════╣
|
|
380
|
+
║ ║
|
|
381
|
+
║ Dashboard: http://localhost:${String(serverPort).padEnd(5)} ║
|
|
382
|
+
║ Proxy: http://localhost:${String(proxyPort).padEnd(5)} ║
|
|
383
|
+
║ ║
|
|
384
|
+
║ Token: ${config.token.padEnd(32)} ║
|
|
385
|
+
║ ║
|
|
386
|
+
╚════════════════════════════════════════════════════╝
|
|
387
|
+
|
|
388
|
+
Process running in background (PID: ${child.pid})
|
|
389
|
+
Logs: ${logFile}
|
|
390
|
+
|
|
391
|
+
To stop: agentgazer stop
|
|
392
|
+
To logs: agentgazer logs -f
|
|
393
|
+
`);
|
|
394
|
+
process.exit(0);
|
|
395
|
+
}
|
|
396
|
+
const config = ensureConfig();
|
|
397
|
+
// Use config values as defaults, CLI flags override
|
|
398
|
+
const defaultPort = config.server?.port ?? 18800;
|
|
399
|
+
const defaultProxyPort = config.server?.proxyPort ?? 18900;
|
|
400
|
+
const defaultRetentionDays = config.data?.retentionDays ?? 30;
|
|
401
|
+
const defaultAutoOpen = config.server?.autoOpen ?? true;
|
|
402
|
+
const requestedServerPort = flags["port"] ? parseInt(flags["port"], 10) : defaultPort;
|
|
348
403
|
const proxyPort = flags["proxy-port"]
|
|
349
404
|
? parseInt(flags["proxy-port"], 10)
|
|
350
|
-
:
|
|
351
|
-
if (isNaN(
|
|
405
|
+
: defaultProxyPort;
|
|
406
|
+
if (isNaN(requestedServerPort) || requestedServerPort < 1 || requestedServerPort > 65535) {
|
|
352
407
|
console.error("Error: --port must be a valid port number (1-65535)");
|
|
353
408
|
process.exit(1);
|
|
354
409
|
}
|
|
@@ -356,15 +411,35 @@ async function cmdStart(flags) {
|
|
|
356
411
|
console.error("Error: --proxy-port must be a valid port number (1-65535)");
|
|
357
412
|
process.exit(1);
|
|
358
413
|
}
|
|
359
|
-
if (
|
|
360
|
-
console.warn("Warning: port %d may require elevated privileges on Unix systems.",
|
|
414
|
+
if (requestedServerPort < 1024) {
|
|
415
|
+
console.warn("Warning: port %d may require elevated privileges on Unix systems.", requestedServerPort);
|
|
361
416
|
}
|
|
362
417
|
if (proxyPort < 1024) {
|
|
363
418
|
console.warn("Warning: proxy port %d may require elevated privileges on Unix systems.", proxyPort);
|
|
364
419
|
}
|
|
420
|
+
// Check if proxy port is available (no auto-switching for proxy)
|
|
421
|
+
if (!(await isPortAvailable(proxyPort))) {
|
|
422
|
+
console.error(`Error: Proxy port ${proxyPort} is already in use.`);
|
|
423
|
+
console.error(" The proxy port must be fixed for OpenClaw configuration to work.");
|
|
424
|
+
console.error(" Please stop the process using this port or specify a different port with --proxy-port <number>");
|
|
425
|
+
process.exit(1);
|
|
426
|
+
}
|
|
427
|
+
// Find available port for dashboard (auto-increment if in use)
|
|
428
|
+
let serverPort;
|
|
429
|
+
try {
|
|
430
|
+
serverPort = await findAvailablePort(requestedServerPort);
|
|
431
|
+
if (serverPort !== requestedServerPort) {
|
|
432
|
+
console.log(` Dashboard port ${requestedServerPort} is in use, using ${serverPort} instead.`);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
catch {
|
|
436
|
+
console.error(`Error: Could not find available port starting from ${requestedServerPort}`);
|
|
437
|
+
console.error(" Try specifying a different port with --port <number>");
|
|
438
|
+
process.exit(1);
|
|
439
|
+
}
|
|
365
440
|
const retentionDays = flags["retention-days"]
|
|
366
441
|
? parseInt(flags["retention-days"], 10)
|
|
367
|
-
:
|
|
442
|
+
: defaultRetentionDays;
|
|
368
443
|
if (isNaN(retentionDays) || retentionDays < 1) {
|
|
369
444
|
console.error("Error: --retention-days must be a positive integer");
|
|
370
445
|
process.exit(1);
|
|
@@ -388,38 +463,42 @@ async function cmdStart(flags) {
|
|
|
388
463
|
// ignore
|
|
389
464
|
}
|
|
390
465
|
}
|
|
466
|
+
// Initialize secret store first (needed for both server and proxy)
|
|
467
|
+
const configDir = getConfigDir();
|
|
468
|
+
const configPath = path.join(configDir, "config.json");
|
|
469
|
+
const { store, backendName } = await detectSecretStore(configDir);
|
|
391
470
|
// Start the local server
|
|
392
|
-
const { db, shutdown: shutdownServer } = await
|
|
471
|
+
const { db, shutdown: shutdownServer } = await startServer({
|
|
393
472
|
port: serverPort,
|
|
394
473
|
token: config.token,
|
|
395
|
-
dbPath:
|
|
474
|
+
dbPath: getDbPath(),
|
|
396
475
|
dashboardDir,
|
|
397
476
|
retentionDays,
|
|
477
|
+
secretStore: store,
|
|
478
|
+
configPath,
|
|
398
479
|
});
|
|
399
|
-
// Initialize secret store
|
|
400
|
-
const configDir = (0, config_js_1.getConfigDir)();
|
|
401
|
-
const configPath = path.join(configDir, "config.json");
|
|
402
|
-
const { store, backendName } = await (0, secret_store_js_1.detectSecretStore)(configDir);
|
|
403
480
|
console.log(` Secret backend: ${backendName}`);
|
|
404
481
|
// Auto-migrate plaintext keys from config.json to secret store
|
|
405
|
-
const migratedCount = await
|
|
482
|
+
const migratedCount = await migrateFromPlaintextConfig(configPath, store);
|
|
406
483
|
if (migratedCount > 0) {
|
|
407
484
|
console.log(` Migrated ${migratedCount} provider key(s) from config.json to secret store.`);
|
|
408
485
|
}
|
|
409
486
|
// Load provider keys from secret store
|
|
410
|
-
const providerKeys = await
|
|
487
|
+
const providerKeys = await loadProviderKeys(store);
|
|
411
488
|
// Start the LLM proxy (with db for policy enforcement and rate limits)
|
|
412
|
-
const { shutdown: shutdownProxy } =
|
|
489
|
+
const { shutdown: shutdownProxy } = startProxy({
|
|
413
490
|
apiKey: config.token,
|
|
414
491
|
agentId: "proxy",
|
|
415
492
|
port: proxyPort,
|
|
416
493
|
endpoint: `http://localhost:${serverPort}/api/events`,
|
|
417
494
|
providerKeys,
|
|
418
495
|
db, // Rate limits are loaded from db
|
|
496
|
+
secretStore: store, // For hot-reloading provider keys
|
|
419
497
|
});
|
|
498
|
+
const modeLabel = verbose ? "running (verbose)" : "running";
|
|
420
499
|
console.log(`
|
|
421
500
|
╔════════════════════════════════════════════════════╗
|
|
422
|
-
║ AgentGazer
|
|
501
|
+
║ AgentGazer ${modeLabel.padEnd(21)} ║
|
|
423
502
|
╠════════════════════════════════════════════════════╣
|
|
424
503
|
║ ║
|
|
425
504
|
║ Dashboard: http://localhost:${String(serverPort).padEnd(5)} ║
|
|
@@ -430,10 +509,11 @@ async function cmdStart(flags) {
|
|
|
430
509
|
╚════════════════════════════════════════════════════╝
|
|
431
510
|
|
|
432
511
|
Proxy routes: http://localhost:${proxyPort}/{provider}/...
|
|
433
|
-
Providers: ${
|
|
512
|
+
Providers: ${KNOWN_PROVIDER_NAMES.join(", ")}
|
|
434
513
|
`);
|
|
435
|
-
// Auto-open browser unless --no-open
|
|
436
|
-
|
|
514
|
+
// Auto-open browser unless --no-open flag or autoOpen=false in config
|
|
515
|
+
const shouldAutoOpen = !("no-open" in flags) && defaultAutoOpen;
|
|
516
|
+
if (shouldAutoOpen) {
|
|
437
517
|
try {
|
|
438
518
|
const open = await import("open");
|
|
439
519
|
await open.default(`http://localhost:${serverPort}`);
|
|
@@ -444,17 +524,36 @@ async function cmdStart(flags) {
|
|
|
444
524
|
}
|
|
445
525
|
// Graceful shutdown
|
|
446
526
|
let shuttingDown = false;
|
|
527
|
+
let forceExitTimeout = null;
|
|
528
|
+
const pidFile = path.join(configDir, "agentgazer.pid");
|
|
447
529
|
function handleShutdown() {
|
|
448
|
-
if (shuttingDown)
|
|
449
|
-
|
|
530
|
+
if (shuttingDown) {
|
|
531
|
+
// Second Ctrl+C: force exit immediately
|
|
532
|
+
console.log("\nForce exiting...");
|
|
533
|
+
process.exit(1);
|
|
534
|
+
}
|
|
450
535
|
shuttingDown = true;
|
|
451
|
-
console.log("\nShutting down...");
|
|
536
|
+
console.log("\nShutting down... (press Ctrl+C again to force exit)");
|
|
537
|
+
// Force exit after 5 seconds if graceful shutdown hangs
|
|
538
|
+
forceExitTimeout = setTimeout(() => {
|
|
539
|
+
console.error("Shutdown timed out, forcing exit.");
|
|
540
|
+
process.exit(1);
|
|
541
|
+
}, 5000);
|
|
542
|
+
forceExitTimeout.unref();
|
|
452
543
|
Promise.all([shutdownProxy(), shutdownServer()])
|
|
453
544
|
.then(() => {
|
|
545
|
+
if (forceExitTimeout)
|
|
546
|
+
clearTimeout(forceExitTimeout);
|
|
547
|
+
// Clean up PID file if we're a daemon child
|
|
548
|
+
if (process.env.AGENTGAZER_DAEMON_CHILD && fs.existsSync(pidFile)) {
|
|
549
|
+
fs.unlinkSync(pidFile);
|
|
550
|
+
}
|
|
454
551
|
console.log("Shutdown complete.");
|
|
455
552
|
process.exit(0);
|
|
456
553
|
})
|
|
457
554
|
.catch((err) => {
|
|
555
|
+
if (forceExitTimeout)
|
|
556
|
+
clearTimeout(forceExitTimeout);
|
|
458
557
|
console.error("Error during shutdown:", err);
|
|
459
558
|
process.exit(1);
|
|
460
559
|
});
|
|
@@ -468,69 +567,18 @@ async function cmdStart(flags) {
|
|
|
468
567
|
});
|
|
469
568
|
}
|
|
470
569
|
// ---------------------------------------------------------------------------
|
|
471
|
-
//
|
|
472
|
-
// ---------------------------------------------------------------------------
|
|
473
|
-
function formatNumber(n) {
|
|
474
|
-
return n.toLocaleString("en-US");
|
|
475
|
-
}
|
|
476
|
-
function timeAgo(iso) {
|
|
477
|
-
if (!iso)
|
|
478
|
-
return "—";
|
|
479
|
-
const diff = Date.now() - new Date(iso).getTime();
|
|
480
|
-
if (diff < 0)
|
|
481
|
-
return "just now";
|
|
482
|
-
const seconds = Math.floor(diff / 1000);
|
|
483
|
-
if (seconds < 60)
|
|
484
|
-
return `${seconds} seconds ago`;
|
|
485
|
-
const minutes = Math.floor(seconds / 60);
|
|
486
|
-
if (minutes < 60)
|
|
487
|
-
return `${minutes} minute${minutes === 1 ? "" : "s"} ago`;
|
|
488
|
-
const hours = Math.floor(minutes / 60);
|
|
489
|
-
if (hours < 24)
|
|
490
|
-
return `${hours} hour${hours === 1 ? "" : "s"} ago`;
|
|
491
|
-
const days = Math.floor(hours / 24);
|
|
492
|
-
return `${days} day${days === 1 ? "" : "s"} ago`;
|
|
493
|
-
}
|
|
494
|
-
// ---------------------------------------------------------------------------
|
|
495
|
-
// API helper
|
|
496
|
-
// ---------------------------------------------------------------------------
|
|
497
|
-
async function apiGet(urlPath, port) {
|
|
498
|
-
const config = (0, config_js_1.readConfig)();
|
|
499
|
-
const token = config?.token ?? "";
|
|
500
|
-
try {
|
|
501
|
-
const res = await fetch(`http://localhost:${port}${urlPath}`, {
|
|
502
|
-
headers: { Authorization: `Bearer ${token}` },
|
|
503
|
-
signal: AbortSignal.timeout(5000),
|
|
504
|
-
});
|
|
505
|
-
if (!res.ok) {
|
|
506
|
-
console.error(`Server responded with ${res.status} ${res.statusText}`);
|
|
507
|
-
process.exit(1);
|
|
508
|
-
}
|
|
509
|
-
return await res.json();
|
|
510
|
-
}
|
|
511
|
-
catch (err) {
|
|
512
|
-
if (err instanceof TypeError &&
|
|
513
|
-
err.cause
|
|
514
|
-
?.code === "ECONNREFUSED") {
|
|
515
|
-
console.error('Server not running. Run "agentgazer start" first.');
|
|
516
|
-
process.exit(1);
|
|
517
|
-
}
|
|
518
|
-
throw err;
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
// ---------------------------------------------------------------------------
|
|
522
|
-
// New subcommands
|
|
570
|
+
// Subcommands
|
|
523
571
|
// ---------------------------------------------------------------------------
|
|
524
|
-
function cmdVersion() {
|
|
525
|
-
|
|
526
|
-
const pkg =
|
|
572
|
+
async function cmdVersion() {
|
|
573
|
+
const pkgPath = path.resolve(__dirname, "../package.json");
|
|
574
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
527
575
|
console.log(`agentgazer ${pkg.version}`);
|
|
528
576
|
}
|
|
529
577
|
async function cmdDoctor(flags) {
|
|
530
|
-
const port = flags["port"] ? parseInt(flags["port"], 10) :
|
|
578
|
+
const port = flags["port"] ? parseInt(flags["port"], 10) : 18800;
|
|
531
579
|
const proxyPort = flags["proxy-port"]
|
|
532
580
|
? parseInt(flags["proxy-port"], 10)
|
|
533
|
-
:
|
|
581
|
+
: 18900;
|
|
534
582
|
if (isNaN(port) || port < 1 || port > 65535) {
|
|
535
583
|
console.error("Error: --port must be a valid port number (1-65535)");
|
|
536
584
|
process.exit(1);
|
|
@@ -546,7 +594,7 @@ async function cmdDoctor(flags) {
|
|
|
546
594
|
let passed = 0;
|
|
547
595
|
const total = 6;
|
|
548
596
|
// 1. Config file exists
|
|
549
|
-
const config =
|
|
597
|
+
const config = readConfig();
|
|
550
598
|
if (config) {
|
|
551
599
|
console.log(" ✓ Config file exists");
|
|
552
600
|
passed++;
|
|
@@ -563,7 +611,7 @@ async function cmdDoctor(flags) {
|
|
|
563
611
|
console.log(" ✗ Auth token not set");
|
|
564
612
|
}
|
|
565
613
|
// 3. Database file exists
|
|
566
|
-
if (fs.existsSync(
|
|
614
|
+
if (fs.existsSync(getDbPath())) {
|
|
567
615
|
console.log(" ✓ Database file exists");
|
|
568
616
|
passed++;
|
|
569
617
|
}
|
|
@@ -572,7 +620,7 @@ async function cmdDoctor(flags) {
|
|
|
572
620
|
}
|
|
573
621
|
// 4. Secret store accessible
|
|
574
622
|
try {
|
|
575
|
-
const { store } = await
|
|
623
|
+
const { store } = await detectSecretStore(getConfigDir());
|
|
576
624
|
if (await store.isAvailable()) {
|
|
577
625
|
console.log(" ✓ Secret store accessible");
|
|
578
626
|
passed++;
|
|
@@ -614,161 +662,417 @@ async function cmdDoctor(flags) {
|
|
|
614
662
|
}
|
|
615
663
|
console.log(`\n ${passed}/${total} checks passed.`);
|
|
616
664
|
}
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
665
|
+
// ---------------------------------------------------------------------------
|
|
666
|
+
// Uninstall
|
|
667
|
+
// ---------------------------------------------------------------------------
|
|
668
|
+
async function confirmPrompt(message) {
|
|
669
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
670
|
+
const answer = await ask(rl, message);
|
|
671
|
+
rl.close();
|
|
672
|
+
return /^y(es)?$/i.test(answer);
|
|
673
|
+
}
|
|
674
|
+
async function stopDaemonIfRunning() {
|
|
675
|
+
const configDir = getConfigDir();
|
|
676
|
+
const pidFile = path.join(configDir, "agentgazer.pid");
|
|
677
|
+
if (!fs.existsSync(pidFile))
|
|
627
678
|
return;
|
|
679
|
+
const pid = parseInt(fs.readFileSync(pidFile, "utf-8").trim(), 10);
|
|
680
|
+
try {
|
|
681
|
+
process.kill(pid, 0); // Check if process exists
|
|
628
682
|
}
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
const id = (a.agent_id ?? "").padEnd(18);
|
|
634
|
-
const status = (a.status ?? "unknown").padEnd(11);
|
|
635
|
-
const events = formatNumber(a.total_events ?? 0).padStart(8);
|
|
636
|
-
const heartbeat = timeAgo(a.last_heartbeat);
|
|
637
|
-
console.log(` ${id}${status}${events} ${heartbeat}`);
|
|
683
|
+
catch {
|
|
684
|
+
// Process doesn't exist, clean up stale PID file
|
|
685
|
+
fs.unlinkSync(pidFile);
|
|
686
|
+
return;
|
|
638
687
|
}
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
if (!agentId) {
|
|
647
|
-
const resp = (await apiGet("/api/agents", port));
|
|
648
|
-
const agents = resp.agents;
|
|
649
|
-
if (!agents || agents.length === 0) {
|
|
650
|
-
console.log("No agents registered yet.");
|
|
651
|
-
return;
|
|
652
|
-
}
|
|
653
|
-
if (agents.length === 1) {
|
|
654
|
-
agentId = agents[0].agent_id;
|
|
688
|
+
console.log(" Stopping AgentGazer daemon...");
|
|
689
|
+
process.kill(pid, "SIGTERM");
|
|
690
|
+
// Wait for process to exit (poll for up to 3 seconds)
|
|
691
|
+
for (let i = 0; i < 30; i++) {
|
|
692
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
693
|
+
try {
|
|
694
|
+
process.kill(pid, 0);
|
|
655
695
|
}
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
console.log();
|
|
662
|
-
process.exit(1);
|
|
696
|
+
catch {
|
|
697
|
+
if (fs.existsSync(pidFile))
|
|
698
|
+
fs.unlinkSync(pidFile);
|
|
699
|
+
console.log(" ✓ Daemon stopped");
|
|
700
|
+
return;
|
|
663
701
|
}
|
|
664
702
|
}
|
|
665
|
-
|
|
703
|
+
// Force kill
|
|
666
704
|
try {
|
|
667
|
-
|
|
705
|
+
process.kill(pid, "SIGKILL");
|
|
668
706
|
}
|
|
669
|
-
catch
|
|
670
|
-
|
|
671
|
-
|
|
707
|
+
catch {
|
|
708
|
+
// Already dead
|
|
709
|
+
}
|
|
710
|
+
if (fs.existsSync(pidFile))
|
|
711
|
+
fs.unlinkSync(pidFile);
|
|
712
|
+
console.log(" ✓ Daemon stopped (forced)");
|
|
713
|
+
}
|
|
714
|
+
async function removeProviderKeys() {
|
|
715
|
+
const configDir = getConfigDir();
|
|
716
|
+
const { store } = await detectSecretStore(configDir);
|
|
717
|
+
const providers = await store.list(PROVIDER_SERVICE);
|
|
718
|
+
if (providers.length === 0) {
|
|
719
|
+
console.log(" No provider keys found.");
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
console.log(" Removing provider keys...");
|
|
723
|
+
for (const provider of providers) {
|
|
724
|
+
try {
|
|
725
|
+
await store.delete(PROVIDER_SERVICE, provider);
|
|
726
|
+
console.log(` ✓ ${provider}`);
|
|
672
727
|
}
|
|
673
|
-
|
|
674
|
-
console.
|
|
728
|
+
catch (err) {
|
|
729
|
+
console.log(` ✗ ${provider}: ${err}`);
|
|
675
730
|
}
|
|
676
|
-
process.exit(1);
|
|
677
731
|
}
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
732
|
+
}
|
|
733
|
+
function removeConfig() {
|
|
734
|
+
const configDir = getConfigDir();
|
|
735
|
+
const configPath = path.join(configDir, "config.json");
|
|
736
|
+
if (fs.existsSync(configPath)) {
|
|
737
|
+
fs.unlinkSync(configPath);
|
|
738
|
+
console.log(` ✓ Removed config (${configPath})`);
|
|
739
|
+
}
|
|
740
|
+
else {
|
|
741
|
+
console.log(" Config file not found.");
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
function removeAgentData() {
|
|
745
|
+
const configDir = getConfigDir();
|
|
746
|
+
const dbPath = path.join(configDir, "data.db");
|
|
747
|
+
if (fs.existsSync(dbPath)) {
|
|
748
|
+
const stats = fs.statSync(dbPath);
|
|
749
|
+
const sizeMB = (stats.size / 1024 / 1024).toFixed(1);
|
|
750
|
+
fs.unlinkSync(dbPath);
|
|
751
|
+
console.log(` ✓ Removed database (${sizeMB} MB)`);
|
|
752
|
+
}
|
|
753
|
+
else {
|
|
754
|
+
console.log(" Database file not found.");
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
function removeLogFiles() {
|
|
758
|
+
const configDir = getConfigDir();
|
|
759
|
+
const logFile = path.join(configDir, "agentgazer.log");
|
|
760
|
+
const pidFile = path.join(configDir, "agentgazer.pid");
|
|
761
|
+
let removed = 0;
|
|
762
|
+
if (fs.existsSync(logFile)) {
|
|
763
|
+
fs.unlinkSync(logFile);
|
|
764
|
+
removed++;
|
|
765
|
+
}
|
|
766
|
+
if (fs.existsSync(pidFile)) {
|
|
767
|
+
fs.unlinkSync(pidFile);
|
|
768
|
+
removed++;
|
|
769
|
+
}
|
|
770
|
+
if (removed > 0) {
|
|
771
|
+
console.log(` ✓ Removed log files`);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
function showBinaryRemovalCommands() {
|
|
681
775
|
console.log(`
|
|
682
|
-
|
|
683
|
-
───────────────────────────────────────
|
|
776
|
+
To remove the agentgazer binary:
|
|
684
777
|
|
|
685
|
-
|
|
686
|
-
Errors: ${formatNumber(data.total_errors)} (${errorPct}%)
|
|
687
|
-
Cost: $${data.total_cost.toFixed(2)}
|
|
688
|
-
Tokens: ${formatNumber(data.total_tokens)}
|
|
778
|
+
npm uninstall -g @agentgazer/cli
|
|
689
779
|
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
const model = m.model.padEnd(16);
|
|
695
|
-
const cost = `$${m.cost.toFixed(2)}`;
|
|
696
|
-
console.log(` ${model}${cost} (${formatNumber(m.count)} calls)`);
|
|
697
|
-
}
|
|
698
|
-
}
|
|
699
|
-
console.log();
|
|
780
|
+
Or if installed via Homebrew:
|
|
781
|
+
|
|
782
|
+
brew uninstall agentgazer
|
|
783
|
+
`);
|
|
700
784
|
}
|
|
701
|
-
// ---------------------------------------------------------------------------
|
|
702
|
-
// Uninstall
|
|
703
|
-
// ---------------------------------------------------------------------------
|
|
704
785
|
async function cmdUninstall(flags) {
|
|
705
|
-
const
|
|
706
|
-
const
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
786
|
+
const configDir = getConfigDir();
|
|
787
|
+
const skipPrompt = "yes" in flags;
|
|
788
|
+
// Handle flags for scripting
|
|
789
|
+
if ("all" in flags) {
|
|
790
|
+
if (!skipPrompt) {
|
|
791
|
+
const confirmed = await confirmPrompt("\n This will remove ALL AgentGazer data. Continue? [y/N] ");
|
|
792
|
+
if (!confirmed) {
|
|
793
|
+
console.log(" Cancelled.");
|
|
794
|
+
return;
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
await stopDaemonIfRunning();
|
|
798
|
+
await removeProviderKeys();
|
|
799
|
+
removeConfig();
|
|
800
|
+
removeAgentData();
|
|
801
|
+
removeLogFiles();
|
|
802
|
+
showBinaryRemovalCommands();
|
|
719
803
|
return;
|
|
720
804
|
}
|
|
721
|
-
|
|
805
|
+
if ("config" in flags) {
|
|
806
|
+
if (!skipPrompt) {
|
|
807
|
+
const confirmed = await confirmPrompt("\n Remove config file? [y/N] ");
|
|
808
|
+
if (!confirmed) {
|
|
809
|
+
console.log(" Cancelled.");
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
await stopDaemonIfRunning();
|
|
814
|
+
removeConfig();
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
817
|
+
if ("keys" in flags) {
|
|
818
|
+
if (!skipPrompt) {
|
|
819
|
+
const { store } = await detectSecretStore(configDir);
|
|
820
|
+
const providers = await store.list(PROVIDER_SERVICE);
|
|
821
|
+
if (providers.length === 0) {
|
|
822
|
+
console.log(" No provider keys to remove.");
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
825
|
+
console.log(`\n Provider keys to remove: ${providers.join(", ")}`);
|
|
826
|
+
const confirmed = await confirmPrompt(" Continue? [y/N] ");
|
|
827
|
+
if (!confirmed) {
|
|
828
|
+
console.log(" Cancelled.");
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
await removeProviderKeys();
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
835
|
+
if ("data" in flags) {
|
|
836
|
+
if (!skipPrompt) {
|
|
837
|
+
const dbPath = path.join(configDir, "data.db");
|
|
838
|
+
if (!fs.existsSync(dbPath)) {
|
|
839
|
+
console.log(" No database to remove.");
|
|
840
|
+
return;
|
|
841
|
+
}
|
|
842
|
+
const confirmed = await confirmPrompt("\n Remove agent data (database)? [y/N] ");
|
|
843
|
+
if (!confirmed) {
|
|
844
|
+
console.log(" Cancelled.");
|
|
845
|
+
return;
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
await stopDaemonIfRunning();
|
|
849
|
+
removeAgentData();
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
// Interactive menu
|
|
722
853
|
console.log(`
|
|
723
854
|
AgentGazer — Uninstall
|
|
724
855
|
───────────────────────────────────────
|
|
856
|
+
|
|
857
|
+
What would you like to remove?
|
|
858
|
+
|
|
859
|
+
1. Complete uninstall (everything)
|
|
860
|
+
2. Binary only (show npm/brew command)
|
|
861
|
+
3. Config only (~/.agentgazer/config.json)
|
|
862
|
+
4. Provider keys only (from secret store)
|
|
863
|
+
5. Agent data only (~/.agentgazer/data.db)
|
|
864
|
+
|
|
725
865
|
`);
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
866
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
867
|
+
const choice = await ask(rl, " Select [1-5]: ");
|
|
868
|
+
rl.close();
|
|
869
|
+
console.log("");
|
|
870
|
+
switch (choice) {
|
|
871
|
+
case "1": {
|
|
872
|
+
// Complete uninstall
|
|
873
|
+
const confirmed = await confirmPrompt(" This will remove ALL AgentGazer data. Continue? [y/N] ");
|
|
874
|
+
if (!confirmed) {
|
|
875
|
+
console.log(" Cancelled.");
|
|
876
|
+
return;
|
|
877
|
+
}
|
|
878
|
+
await stopDaemonIfRunning();
|
|
879
|
+
await removeProviderKeys();
|
|
880
|
+
removeConfig();
|
|
881
|
+
removeAgentData();
|
|
882
|
+
removeLogFiles();
|
|
883
|
+
showBinaryRemovalCommands();
|
|
884
|
+
break;
|
|
739
885
|
}
|
|
740
|
-
|
|
741
|
-
|
|
886
|
+
case "2":
|
|
887
|
+
// Binary only
|
|
888
|
+
showBinaryRemovalCommands();
|
|
889
|
+
break;
|
|
890
|
+
case "3": {
|
|
891
|
+
// Config only
|
|
892
|
+
const confirmed = await confirmPrompt(" Remove config file? [y/N] ");
|
|
893
|
+
if (!confirmed) {
|
|
894
|
+
console.log(" Cancelled.");
|
|
895
|
+
return;
|
|
896
|
+
}
|
|
897
|
+
await stopDaemonIfRunning();
|
|
898
|
+
removeConfig();
|
|
899
|
+
break;
|
|
742
900
|
}
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
const
|
|
753
|
-
|
|
754
|
-
|
|
901
|
+
case "4": {
|
|
902
|
+
// Provider keys only
|
|
903
|
+
const { store } = await detectSecretStore(configDir);
|
|
904
|
+
const providers = await store.list(PROVIDER_SERVICE);
|
|
905
|
+
if (providers.length === 0) {
|
|
906
|
+
console.log(" No provider keys to remove.");
|
|
907
|
+
return;
|
|
908
|
+
}
|
|
909
|
+
console.log(` Provider keys to remove: ${providers.join(", ")}`);
|
|
910
|
+
const confirmed = await confirmPrompt(" Continue? [y/N] ");
|
|
911
|
+
if (!confirmed) {
|
|
912
|
+
console.log(" Cancelled.");
|
|
913
|
+
return;
|
|
914
|
+
}
|
|
915
|
+
await removeProviderKeys();
|
|
916
|
+
break;
|
|
755
917
|
}
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
918
|
+
case "5": {
|
|
919
|
+
// Agent data only
|
|
920
|
+
const dbPath = path.join(configDir, "data.db");
|
|
921
|
+
if (!fs.existsSync(dbPath)) {
|
|
922
|
+
console.log(" No database to remove.");
|
|
923
|
+
return;
|
|
924
|
+
}
|
|
925
|
+
const confirmed = await confirmPrompt(" Remove agent data (database)? [y/N] ");
|
|
926
|
+
if (!confirmed) {
|
|
927
|
+
console.log(" Cancelled.");
|
|
928
|
+
return;
|
|
929
|
+
}
|
|
930
|
+
await stopDaemonIfRunning();
|
|
931
|
+
removeAgentData();
|
|
932
|
+
break;
|
|
759
933
|
}
|
|
760
|
-
|
|
761
|
-
console.log(
|
|
934
|
+
default:
|
|
935
|
+
console.log(" Invalid option.");
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
// ---------------------------------------------------------------------------
|
|
939
|
+
// Stop command
|
|
940
|
+
// ---------------------------------------------------------------------------
|
|
941
|
+
function cmdStop() {
|
|
942
|
+
const configDir = getConfigDir();
|
|
943
|
+
const pidFile = path.join(configDir, "agentgazer.pid");
|
|
944
|
+
const config = readConfig();
|
|
945
|
+
const port = config?.server?.port ?? 18800;
|
|
946
|
+
const proxyPort = config?.server?.proxyPort ?? 18900;
|
|
947
|
+
if (!fs.existsSync(pidFile)) {
|
|
948
|
+
// No PID file, but check if processes are running on ports
|
|
949
|
+
const pidsOnPorts = findPidsOnPorts([port, proxyPort]);
|
|
950
|
+
if (pidsOnPorts.length > 0) {
|
|
951
|
+
console.log("AgentGazer PID file not found, but processes detected on ports.");
|
|
952
|
+
console.log(`Killing processes: ${pidsOnPorts.join(", ")}...`);
|
|
953
|
+
for (const pid of pidsOnPorts) {
|
|
954
|
+
try {
|
|
955
|
+
process.kill(pid, "SIGTERM");
|
|
956
|
+
}
|
|
957
|
+
catch {
|
|
958
|
+
// Ignore errors
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
// Wait a bit and force kill if needed
|
|
962
|
+
setTimeout(() => {
|
|
963
|
+
for (const pid of pidsOnPorts) {
|
|
964
|
+
try {
|
|
965
|
+
process.kill(pid, 0);
|
|
966
|
+
process.kill(pid, "SIGKILL");
|
|
967
|
+
}
|
|
968
|
+
catch {
|
|
969
|
+
// Already dead
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
console.log("AgentGazer stopped.");
|
|
973
|
+
}, 1000);
|
|
974
|
+
return;
|
|
762
975
|
}
|
|
976
|
+
console.log("AgentGazer is not running.");
|
|
977
|
+
return;
|
|
978
|
+
}
|
|
979
|
+
const pid = parseInt(fs.readFileSync(pidFile, "utf-8").trim(), 10);
|
|
980
|
+
try {
|
|
981
|
+
process.kill(pid, 0); // Check if process exists
|
|
982
|
+
}
|
|
983
|
+
catch {
|
|
984
|
+
// Process doesn't exist
|
|
985
|
+
fs.unlinkSync(pidFile);
|
|
986
|
+
console.log("AgentGazer is not running (stale PID file removed).");
|
|
987
|
+
return;
|
|
988
|
+
}
|
|
989
|
+
// Send SIGTERM
|
|
990
|
+
try {
|
|
991
|
+
process.kill(pid, "SIGTERM");
|
|
992
|
+
console.log(`Stopping AgentGazer (PID: ${pid})...`);
|
|
993
|
+
// Wait for process to exit (poll for up to 5 seconds)
|
|
994
|
+
let attempts = 0;
|
|
995
|
+
const maxAttempts = 50;
|
|
996
|
+
const checkInterval = 100;
|
|
997
|
+
const waitForExit = () => {
|
|
998
|
+
try {
|
|
999
|
+
process.kill(pid, 0);
|
|
1000
|
+
attempts++;
|
|
1001
|
+
if (attempts < maxAttempts) {
|
|
1002
|
+
setTimeout(waitForExit, checkInterval);
|
|
1003
|
+
}
|
|
1004
|
+
else {
|
|
1005
|
+
console.log("Process did not exit gracefully, sending SIGKILL...");
|
|
1006
|
+
try {
|
|
1007
|
+
process.kill(pid, "SIGKILL");
|
|
1008
|
+
}
|
|
1009
|
+
catch {
|
|
1010
|
+
// Already dead
|
|
1011
|
+
}
|
|
1012
|
+
if (fs.existsSync(pidFile))
|
|
1013
|
+
fs.unlinkSync(pidFile);
|
|
1014
|
+
console.log("AgentGazer stopped.");
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
catch {
|
|
1018
|
+
// Process exited
|
|
1019
|
+
if (fs.existsSync(pidFile))
|
|
1020
|
+
fs.unlinkSync(pidFile);
|
|
1021
|
+
console.log("AgentGazer stopped.");
|
|
1022
|
+
}
|
|
1023
|
+
};
|
|
1024
|
+
waitForExit();
|
|
1025
|
+
}
|
|
1026
|
+
catch (err) {
|
|
1027
|
+
console.error(`Failed to stop AgentGazer: ${err}`);
|
|
1028
|
+
process.exit(1);
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
// ---------------------------------------------------------------------------
|
|
1032
|
+
// Logs command
|
|
1033
|
+
// ---------------------------------------------------------------------------
|
|
1034
|
+
async function cmdLogs(flags) {
|
|
1035
|
+
const configDir = getConfigDir();
|
|
1036
|
+
const logFile = path.join(configDir, "agentgazer.log");
|
|
1037
|
+
if (!fs.existsSync(logFile)) {
|
|
1038
|
+
console.log("No log file found. Start AgentGazer with -d flag first.");
|
|
1039
|
+
console.log(` agentgazer start -d`);
|
|
1040
|
+
return;
|
|
1041
|
+
}
|
|
1042
|
+
const follow = "f" in flags || "follow" in flags;
|
|
1043
|
+
const lines = flags["n"] ? parseInt(flags["n"], 10) : (flags["lines"] ? parseInt(flags["lines"], 10) : 50);
|
|
1044
|
+
if (follow) {
|
|
1045
|
+
// Follow mode: tail -f equivalent
|
|
1046
|
+
const { spawn } = await import("node:child_process");
|
|
1047
|
+
const tail = spawn("tail", ["-f", "-n", String(lines), logFile], {
|
|
1048
|
+
stdio: "inherit",
|
|
1049
|
+
});
|
|
1050
|
+
// Handle Ctrl+C gracefully
|
|
1051
|
+
process.on("SIGINT", () => {
|
|
1052
|
+
tail.kill();
|
|
1053
|
+
process.exit(0);
|
|
1054
|
+
});
|
|
1055
|
+
await new Promise((resolve) => {
|
|
1056
|
+
tail.on("close", () => resolve());
|
|
1057
|
+
});
|
|
1058
|
+
}
|
|
1059
|
+
else {
|
|
1060
|
+
// Just show last N lines
|
|
1061
|
+
const content = fs.readFileSync(logFile, "utf-8");
|
|
1062
|
+
const allLines = content.split("\n");
|
|
1063
|
+
const lastLines = allLines.slice(-lines).join("\n");
|
|
1064
|
+
console.log(lastLines);
|
|
763
1065
|
}
|
|
764
|
-
console.log("\n ✓ AgentGazer uninstalled.\n");
|
|
765
1066
|
}
|
|
766
1067
|
// ---------------------------------------------------------------------------
|
|
767
1068
|
// Main
|
|
768
1069
|
// ---------------------------------------------------------------------------
|
|
769
1070
|
async function main() {
|
|
770
1071
|
const subcommand = process.argv[2];
|
|
771
|
-
const
|
|
1072
|
+
const allArgs = process.argv.slice(3);
|
|
1073
|
+
const flags = parseFlags(allArgs);
|
|
1074
|
+
const positional = parsePositional(allArgs);
|
|
1075
|
+
const port = flags["port"] ? parseInt(flags["port"], 10) : 18800;
|
|
772
1076
|
switch (subcommand) {
|
|
773
1077
|
case "onboard":
|
|
774
1078
|
await cmdOnboard();
|
|
@@ -776,27 +1080,46 @@ async function main() {
|
|
|
776
1080
|
case "start":
|
|
777
1081
|
await cmdStart(flags);
|
|
778
1082
|
break;
|
|
1083
|
+
case "stop":
|
|
1084
|
+
cmdStop();
|
|
1085
|
+
break;
|
|
1086
|
+
case "logs":
|
|
1087
|
+
await cmdLogs(flags);
|
|
1088
|
+
break;
|
|
779
1089
|
case "status":
|
|
780
1090
|
cmdStatus();
|
|
781
1091
|
break;
|
|
782
1092
|
case "reset-token":
|
|
783
1093
|
cmdResetToken();
|
|
784
1094
|
break;
|
|
1095
|
+
case "overview": {
|
|
1096
|
+
const { cmdOverview } = await import("./commands/overview.js");
|
|
1097
|
+
await cmdOverview(port);
|
|
1098
|
+
break;
|
|
1099
|
+
}
|
|
1100
|
+
case "agents":
|
|
1101
|
+
await cmdAgents(port);
|
|
1102
|
+
break;
|
|
1103
|
+
case "agent":
|
|
1104
|
+
await cmdAgent(positional[0], positional[1], positional.slice(2), flags);
|
|
1105
|
+
break;
|
|
785
1106
|
case "providers":
|
|
786
|
-
await cmdProviders(
|
|
1107
|
+
await cmdProviders(port);
|
|
1108
|
+
break;
|
|
1109
|
+
case "provider":
|
|
1110
|
+
await cmdProvider(positional[0], positional.slice(1), flags);
|
|
1111
|
+
break;
|
|
1112
|
+
case "events":
|
|
1113
|
+
await cmdEvents(flags);
|
|
787
1114
|
break;
|
|
788
1115
|
case "version":
|
|
789
|
-
|
|
1116
|
+
case "--version":
|
|
1117
|
+
case "-V":
|
|
1118
|
+
await cmdVersion();
|
|
790
1119
|
break;
|
|
791
1120
|
case "doctor":
|
|
792
1121
|
await cmdDoctor(flags);
|
|
793
1122
|
break;
|
|
794
|
-
case "agents":
|
|
795
|
-
await cmdAgents(flags);
|
|
796
|
-
break;
|
|
797
|
-
case "stats":
|
|
798
|
-
await cmdStats(flags);
|
|
799
|
-
break;
|
|
800
1123
|
case "uninstall":
|
|
801
1124
|
await cmdUninstall(flags);
|
|
802
1125
|
break;
|