@plosson/agentio 0.7.2 → 0.7.4
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/package.json +1 -1
- package/src/commands/config-import.test.ts +330 -0
- package/src/commands/config.ts +21 -2
- package/src/commands/mcp.ts +5 -0
- package/src/commands/server-tokens.test.ts +269 -0
- package/src/commands/server.ts +514 -0
- package/src/commands/teleport.test.ts +1462 -0
- package/src/commands/teleport.ts +882 -0
- package/src/index.ts +2 -0
- package/src/mcp/server.test.ts +89 -0
- package/src/mcp/server.ts +51 -30
- package/src/server/daemon.test.ts +637 -0
- package/src/server/daemon.ts +177 -0
- package/src/server/dockerfile-gen.test.ts +218 -0
- package/src/server/dockerfile-gen.ts +108 -0
- package/src/server/dockerfile-teleport.test.ts +184 -0
- package/src/server/http.test.ts +256 -0
- package/src/server/http.ts +54 -0
- package/src/server/mcp-adversarial.test.ts +643 -0
- package/src/server/mcp-e2e.test.ts +397 -0
- package/src/server/mcp-http.test.ts +364 -0
- package/src/server/mcp-http.ts +339 -0
- package/src/server/oauth-e2e.test.ts +466 -0
- package/src/server/oauth-store.test.ts +423 -0
- package/src/server/oauth-store.ts +216 -0
- package/src/server/oauth.test.ts +1502 -0
- package/src/server/oauth.ts +800 -0
- package/src/server/siteio-runner.test.ts +766 -0
- package/src/server/siteio-runner.ts +352 -0
- package/src/server/test-helpers.ts +201 -0
- package/src/types/config.ts +3 -0
- package/src/types/server.ts +61 -0
|
@@ -0,0 +1,514 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { existsSync, writeFileSync } from 'fs';
|
|
3
|
+
import { spawnSync } from 'bun';
|
|
4
|
+
|
|
5
|
+
import { handleError, CliError } from '../utils/errors';
|
|
6
|
+
import { loadConfig, saveConfig } from '../config/config-manager';
|
|
7
|
+
import { startServer } from '../server/daemon';
|
|
8
|
+
import type { Config } from '../types/config';
|
|
9
|
+
import type { ServerToken } from '../types/server';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Validate a CLI-supplied port string. Must parse to an integer in [1, 65535].
|
|
13
|
+
* Bun.serve happily accepts NaN / out-of-range / negative numbers and falls
|
|
14
|
+
* back to a default port, which is a footgun — bail loudly instead.
|
|
15
|
+
*/
|
|
16
|
+
function parsePort(value: string): number {
|
|
17
|
+
const n = Number(value);
|
|
18
|
+
if (!Number.isInteger(n) || n < 1 || n > 65535) {
|
|
19
|
+
throw new CliError(
|
|
20
|
+
'INVALID_PARAMS',
|
|
21
|
+
`Invalid --port: must be an integer in [1, 65535], got "${value}"`
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
return n;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const SERVICE_NAME = 'agentio-server';
|
|
28
|
+
const SERVICE_FILE = `/etc/systemd/system/${SERVICE_NAME}.service`;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Find the agentio binary path. Mirrors src/commands/gateway.ts.
|
|
32
|
+
*/
|
|
33
|
+
function findBinaryPath(): string {
|
|
34
|
+
const candidates = [
|
|
35
|
+
'/usr/local/bin/agentio',
|
|
36
|
+
`${process.env.HOME || ''}/.local/bin/agentio`,
|
|
37
|
+
process.argv[1],
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
for (const path of candidates) {
|
|
41
|
+
if (path && existsSync(path)) {
|
|
42
|
+
const result = spawnSync({
|
|
43
|
+
cmd: ['realpath', path],
|
|
44
|
+
stdout: 'pipe',
|
|
45
|
+
stderr: 'pipe',
|
|
46
|
+
});
|
|
47
|
+
if (result.exitCode === 0) {
|
|
48
|
+
return result.stdout.toString().trim();
|
|
49
|
+
}
|
|
50
|
+
return path;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
throw new Error('Could not find agentio binary');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function isServiceInstalled(): boolean {
|
|
58
|
+
return existsSync(SERVICE_FILE);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function checkRoot(): { isRoot: boolean; canSudo: boolean } {
|
|
62
|
+
const whoami = spawnSync({ cmd: ['whoami'], stdout: 'pipe' });
|
|
63
|
+
const isRoot = whoami.stdout.toString().trim() === 'root';
|
|
64
|
+
if (isRoot) return { isRoot: true, canSudo: true };
|
|
65
|
+
|
|
66
|
+
const sudoCheck = spawnSync({
|
|
67
|
+
cmd: ['sudo', '-n', 'true'],
|
|
68
|
+
stdout: 'pipe',
|
|
69
|
+
stderr: 'pipe',
|
|
70
|
+
});
|
|
71
|
+
return { isRoot: false, canSudo: sudoCheck.exitCode === 0 };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function runCommand(
|
|
75
|
+
cmd: string[],
|
|
76
|
+
useSudo: boolean
|
|
77
|
+
): { success: boolean; output: string; error: string } {
|
|
78
|
+
const fullCmd = useSudo ? ['sudo', ...cmd] : cmd;
|
|
79
|
+
const result = spawnSync({ cmd: fullCmd, stdout: 'pipe', stderr: 'pipe' });
|
|
80
|
+
return {
|
|
81
|
+
success: result.exitCode === 0,
|
|
82
|
+
output: result.stdout.toString(),
|
|
83
|
+
error: result.stderr.toString(),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function generateServiceFile(binaryPath: string): string {
|
|
88
|
+
return `[Unit]
|
|
89
|
+
Description=agentio HTTP MCP server
|
|
90
|
+
After=network.target
|
|
91
|
+
|
|
92
|
+
[Service]
|
|
93
|
+
Type=simple
|
|
94
|
+
ExecStart=${binaryPath} server start --foreground
|
|
95
|
+
Restart=always
|
|
96
|
+
RestartSec=5
|
|
97
|
+
Environment=HOME=${process.env.HOME}
|
|
98
|
+
|
|
99
|
+
[Install]
|
|
100
|
+
WantedBy=multi-user.target
|
|
101
|
+
`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function registerServerCommands(program: Command): void {
|
|
105
|
+
const server = program
|
|
106
|
+
.command('server')
|
|
107
|
+
.description('HTTP MCP server daemon management');
|
|
108
|
+
|
|
109
|
+
// install — systemd integration
|
|
110
|
+
server
|
|
111
|
+
.command('install')
|
|
112
|
+
.description('Install agentio server as a systemd service')
|
|
113
|
+
.action(async () => {
|
|
114
|
+
try {
|
|
115
|
+
console.log('Installing agentio-server service...\n');
|
|
116
|
+
|
|
117
|
+
const { isRoot, canSudo } = checkRoot();
|
|
118
|
+
if (!isRoot && !canSudo) {
|
|
119
|
+
console.log('This command requires sudo privileges.');
|
|
120
|
+
console.log('Run with: sudo agentio server install');
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
const useSudo = !isRoot;
|
|
124
|
+
|
|
125
|
+
let binaryPath: string;
|
|
126
|
+
try {
|
|
127
|
+
binaryPath = findBinaryPath();
|
|
128
|
+
} catch {
|
|
129
|
+
throw new CliError('CONFIG_ERROR', 'Could not find agentio binary');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
console.log(`Binary: ${binaryPath}`);
|
|
133
|
+
|
|
134
|
+
console.log('\nCreating systemd service...');
|
|
135
|
+
const serviceContent = generateServiceFile(binaryPath);
|
|
136
|
+
const tempFile = `/tmp/${SERVICE_NAME}.service`;
|
|
137
|
+
writeFileSync(tempFile, serviceContent);
|
|
138
|
+
|
|
139
|
+
const copyResult = runCommand(
|
|
140
|
+
['cp', tempFile, SERVICE_FILE],
|
|
141
|
+
useSudo
|
|
142
|
+
);
|
|
143
|
+
if (!copyResult.success) {
|
|
144
|
+
throw new CliError(
|
|
145
|
+
'CONFIG_ERROR',
|
|
146
|
+
`Failed to create service file: ${copyResult.error}`
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
console.log('Enabling service...');
|
|
151
|
+
const commands = [
|
|
152
|
+
['systemctl', 'daemon-reload'],
|
|
153
|
+
['systemctl', 'enable', SERVICE_NAME],
|
|
154
|
+
];
|
|
155
|
+
for (const cmd of commands) {
|
|
156
|
+
const result = runCommand(cmd, useSudo);
|
|
157
|
+
if (!result.success) {
|
|
158
|
+
throw new CliError(
|
|
159
|
+
'CONFIG_ERROR',
|
|
160
|
+
`Command failed: ${cmd.join(' ')}`
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
console.log('Starting agentio server...');
|
|
166
|
+
const startResult = runCommand(
|
|
167
|
+
['systemctl', 'start', SERVICE_NAME],
|
|
168
|
+
useSudo
|
|
169
|
+
);
|
|
170
|
+
if (!startResult.success) {
|
|
171
|
+
throw new CliError(
|
|
172
|
+
'CONFIG_ERROR',
|
|
173
|
+
`Failed to start service: ${startResult.error}`
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
178
|
+
const statusResult = spawnSync({
|
|
179
|
+
cmd: ['systemctl', 'is-active', SERVICE_NAME],
|
|
180
|
+
stdout: 'pipe',
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
if (statusResult.stdout.toString().trim() !== 'active') {
|
|
184
|
+
console.log('\nService failed to start. Check logs with:');
|
|
185
|
+
console.log(' journalctl -u agentio-server -f');
|
|
186
|
+
process.exit(1);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
console.log('\nagentio-server installed and running!');
|
|
190
|
+
console.log('\nManage with:');
|
|
191
|
+
console.log(' agentio server status');
|
|
192
|
+
console.log(' agentio server stop');
|
|
193
|
+
console.log(' agentio server restart');
|
|
194
|
+
console.log(' agentio server logs');
|
|
195
|
+
} catch (error) {
|
|
196
|
+
handleError(error);
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// start — foreground or via systemd
|
|
201
|
+
server
|
|
202
|
+
.command('start')
|
|
203
|
+
.description('Start the agentio HTTP MCP server')
|
|
204
|
+
.option('--foreground', 'Run in foreground (used by systemd or for dev)')
|
|
205
|
+
.option('--port <n>', 'Port to bind (default: 9999)')
|
|
206
|
+
.option('--host <host>', 'Host to bind (default: 0.0.0.0)')
|
|
207
|
+
.option('--api-key <key>', 'Override the stored API key for this run only')
|
|
208
|
+
.action(async (options) => {
|
|
209
|
+
try {
|
|
210
|
+
const port = options.port ? parsePort(options.port) : undefined;
|
|
211
|
+
|
|
212
|
+
if (options.foreground) {
|
|
213
|
+
await startServer({
|
|
214
|
+
port,
|
|
215
|
+
host: options.host,
|
|
216
|
+
apiKey: options.apiKey,
|
|
217
|
+
});
|
|
218
|
+
} else if (isServiceInstalled()) {
|
|
219
|
+
const { isRoot } = checkRoot();
|
|
220
|
+
const result = runCommand(
|
|
221
|
+
['systemctl', 'start', SERVICE_NAME],
|
|
222
|
+
!isRoot
|
|
223
|
+
);
|
|
224
|
+
if (!result.success) {
|
|
225
|
+
throw new CliError(
|
|
226
|
+
'CONFIG_ERROR',
|
|
227
|
+
`Failed to start: ${result.error}`
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
console.log('Server started');
|
|
231
|
+
} else {
|
|
232
|
+
await startServer({
|
|
233
|
+
port,
|
|
234
|
+
host: options.host,
|
|
235
|
+
apiKey: options.apiKey,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
} catch (error) {
|
|
239
|
+
handleError(error);
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
// stop
|
|
244
|
+
server
|
|
245
|
+
.command('stop')
|
|
246
|
+
.description('Stop the agentio server (systemd only)')
|
|
247
|
+
.action(async () => {
|
|
248
|
+
try {
|
|
249
|
+
if (!isServiceInstalled()) {
|
|
250
|
+
console.log('agentio-server service not installed');
|
|
251
|
+
console.log('Run: agentio server install');
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
const { isRoot } = checkRoot();
|
|
255
|
+
const result = runCommand(
|
|
256
|
+
['systemctl', 'stop', SERVICE_NAME],
|
|
257
|
+
!isRoot
|
|
258
|
+
);
|
|
259
|
+
if (!result.success) {
|
|
260
|
+
throw new CliError('CONFIG_ERROR', `Failed to stop: ${result.error}`);
|
|
261
|
+
}
|
|
262
|
+
console.log('Server stopped');
|
|
263
|
+
} catch (error) {
|
|
264
|
+
handleError(error);
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
// restart
|
|
269
|
+
server
|
|
270
|
+
.command('restart')
|
|
271
|
+
.description('Restart the agentio server (systemd only)')
|
|
272
|
+
.action(async () => {
|
|
273
|
+
try {
|
|
274
|
+
if (!isServiceInstalled()) {
|
|
275
|
+
console.log('agentio-server service not installed');
|
|
276
|
+
console.log('Run: agentio server install');
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
const { isRoot } = checkRoot();
|
|
280
|
+
const result = runCommand(
|
|
281
|
+
['systemctl', 'restart', SERVICE_NAME],
|
|
282
|
+
!isRoot
|
|
283
|
+
);
|
|
284
|
+
if (!result.success) {
|
|
285
|
+
throw new CliError(
|
|
286
|
+
'CONFIG_ERROR',
|
|
287
|
+
`Failed to restart: ${result.error}`
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
console.log('Server restarted');
|
|
291
|
+
} catch (error) {
|
|
292
|
+
handleError(error);
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
// status
|
|
297
|
+
server
|
|
298
|
+
.command('status')
|
|
299
|
+
.description('Show agentio server status')
|
|
300
|
+
.action(async () => {
|
|
301
|
+
try {
|
|
302
|
+
const config = (await loadConfig()) as Config;
|
|
303
|
+
const port = config.server?.port ?? 9999;
|
|
304
|
+
|
|
305
|
+
if (isServiceInstalled()) {
|
|
306
|
+
const statusResult = spawnSync({
|
|
307
|
+
cmd: ['systemctl', 'is-active', SERVICE_NAME],
|
|
308
|
+
stdout: 'pipe',
|
|
309
|
+
});
|
|
310
|
+
const isActive =
|
|
311
|
+
statusResult.stdout.toString().trim() === 'active';
|
|
312
|
+
console.log(`Server: ${isActive ? 'running' : 'stopped'} (systemd)`);
|
|
313
|
+
} else {
|
|
314
|
+
// Try to probe /health on the configured port.
|
|
315
|
+
try {
|
|
316
|
+
const response = await fetch(`http://127.0.0.1:${port}/health`);
|
|
317
|
+
if (response.ok) {
|
|
318
|
+
console.log(`Server: running (foreground, port ${port})`);
|
|
319
|
+
} else {
|
|
320
|
+
console.log(`Server: not running`);
|
|
321
|
+
}
|
|
322
|
+
} catch {
|
|
323
|
+
console.log('Server: not running');
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
console.log(`API Key: ${
|
|
328
|
+
config.server?.apiKey
|
|
329
|
+
? config.server.apiKey.slice(0, 12) + '...'
|
|
330
|
+
: '(not set)'
|
|
331
|
+
}`);
|
|
332
|
+
} catch (error) {
|
|
333
|
+
handleError(error);
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
// logs
|
|
338
|
+
server
|
|
339
|
+
.command('logs')
|
|
340
|
+
.description('View agentio server logs (systemd / journalctl)')
|
|
341
|
+
.option('-f, --follow', 'Follow log output')
|
|
342
|
+
.option('-n, --lines <n>', 'Number of lines to show', '50')
|
|
343
|
+
.action(async (options) => {
|
|
344
|
+
try {
|
|
345
|
+
if (!isServiceInstalled()) {
|
|
346
|
+
console.log('agentio-server service not installed');
|
|
347
|
+
console.log(
|
|
348
|
+
'When running in --foreground, logs go to the terminal directly.'
|
|
349
|
+
);
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const args = ['journalctl', '-u', SERVICE_NAME, '--no-pager'];
|
|
354
|
+
if (options.follow) {
|
|
355
|
+
args.push('-f');
|
|
356
|
+
} else {
|
|
357
|
+
args.push('-n', options.lines);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const proc = Bun.spawn(args, {
|
|
361
|
+
stdout: 'inherit',
|
|
362
|
+
stderr: 'inherit',
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
if (options.follow) {
|
|
366
|
+
process.on('SIGINT', () => {
|
|
367
|
+
proc.kill();
|
|
368
|
+
process.exit(0);
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
await proc.exited;
|
|
373
|
+
} catch (error) {
|
|
374
|
+
handleError(error);
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
// tokens — manage issued OAuth bearer tokens
|
|
379
|
+
const tokens = server
|
|
380
|
+
.command('tokens')
|
|
381
|
+
.description('Manage issued OAuth bearer tokens');
|
|
382
|
+
|
|
383
|
+
tokens
|
|
384
|
+
.command('list')
|
|
385
|
+
.description('List all issued bearer tokens')
|
|
386
|
+
.action(async () => {
|
|
387
|
+
try {
|
|
388
|
+
const config = (await loadConfig()) as Config;
|
|
389
|
+
const list = config.server?.tokens ?? [];
|
|
390
|
+
if (list.length === 0) {
|
|
391
|
+
console.log('No tokens issued yet.');
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
const fmt = (t: ServerToken) => {
|
|
395
|
+
const issued = new Date(t.issuedAt).toISOString();
|
|
396
|
+
const expires = new Date(t.expiresAt).toISOString();
|
|
397
|
+
const expired = t.expiresAt < Date.now() ? ' (EXPIRED)' : '';
|
|
398
|
+
const id = t.token.slice(0, 12);
|
|
399
|
+
return ` ${id}… client=${t.clientId} scope=${t.scope || '(none)'} issued=${issued} expires=${expires}${expired}`;
|
|
400
|
+
};
|
|
401
|
+
console.log(`${list.length} token(s) issued:`);
|
|
402
|
+
for (const t of list) {
|
|
403
|
+
console.log(fmt(t));
|
|
404
|
+
}
|
|
405
|
+
} catch (error) {
|
|
406
|
+
handleError(error);
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
tokens
|
|
411
|
+
.command('revoke')
|
|
412
|
+
.description(
|
|
413
|
+
'Revoke a token by its 12-character prefix or full opaque value'
|
|
414
|
+
)
|
|
415
|
+
.argument('<id>', 'Token id (first 12 chars) or full token value')
|
|
416
|
+
.action(async (id: string) => {
|
|
417
|
+
try {
|
|
418
|
+
const config = (await loadConfig()) as Config;
|
|
419
|
+
const list = config.server?.tokens ?? [];
|
|
420
|
+
|
|
421
|
+
// Match either the full token value or any token whose value
|
|
422
|
+
// starts with the given prefix.
|
|
423
|
+
const matches = list.filter(
|
|
424
|
+
(t) => t.token === id || t.token.startsWith(id)
|
|
425
|
+
);
|
|
426
|
+
|
|
427
|
+
if (matches.length === 0) {
|
|
428
|
+
throw new CliError(
|
|
429
|
+
'NOT_FOUND',
|
|
430
|
+
`No token found matching "${id}"`,
|
|
431
|
+
'Run `agentio server tokens list` to see issued tokens.'
|
|
432
|
+
);
|
|
433
|
+
}
|
|
434
|
+
if (matches.length > 1) {
|
|
435
|
+
throw new CliError(
|
|
436
|
+
'INVALID_PARAMS',
|
|
437
|
+
`Ambiguous prefix "${id}" matches ${matches.length} tokens`,
|
|
438
|
+
'Use a longer prefix or the full token value.'
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const target = matches[0].token;
|
|
443
|
+
const remaining = list.filter((t) => t.token !== target);
|
|
444
|
+
config.server = {
|
|
445
|
+
...config.server,
|
|
446
|
+
tokens: remaining,
|
|
447
|
+
};
|
|
448
|
+
await saveConfig(config);
|
|
449
|
+
console.log(`Revoked token ${target.slice(0, 12)}…`);
|
|
450
|
+
console.log(
|
|
451
|
+
'Note: a running daemon caches tokens in memory and will continue to honor this one until restart.'
|
|
452
|
+
);
|
|
453
|
+
} catch (error) {
|
|
454
|
+
handleError(error);
|
|
455
|
+
}
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
tokens
|
|
459
|
+
.command('clear')
|
|
460
|
+
.description('Revoke ALL issued tokens (forces every client to re-auth)')
|
|
461
|
+
.action(async () => {
|
|
462
|
+
try {
|
|
463
|
+
const config = (await loadConfig()) as Config;
|
|
464
|
+
const count = config.server?.tokens?.length ?? 0;
|
|
465
|
+
config.server = {
|
|
466
|
+
...config.server,
|
|
467
|
+
tokens: [],
|
|
468
|
+
};
|
|
469
|
+
await saveConfig(config);
|
|
470
|
+
console.log(`Cleared ${count} token(s).`);
|
|
471
|
+
if (count > 0) {
|
|
472
|
+
console.log(
|
|
473
|
+
'Note: a running daemon caches tokens in memory and will continue to honor them until restart.'
|
|
474
|
+
);
|
|
475
|
+
}
|
|
476
|
+
} catch (error) {
|
|
477
|
+
handleError(error);
|
|
478
|
+
}
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
// uninstall
|
|
482
|
+
server
|
|
483
|
+
.command('uninstall')
|
|
484
|
+
.description('Remove agentio-server systemd service')
|
|
485
|
+
.action(async () => {
|
|
486
|
+
try {
|
|
487
|
+
if (!isServiceInstalled()) {
|
|
488
|
+
console.log('agentio-server service not installed');
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
const { isRoot } = checkRoot();
|
|
493
|
+
const useSudo = !isRoot;
|
|
494
|
+
|
|
495
|
+
console.log('Stopping and removing agentio-server service...');
|
|
496
|
+
const commands = [
|
|
497
|
+
['systemctl', 'stop', SERVICE_NAME],
|
|
498
|
+
['systemctl', 'disable', SERVICE_NAME],
|
|
499
|
+
['rm', SERVICE_FILE],
|
|
500
|
+
['systemctl', 'daemon-reload'],
|
|
501
|
+
];
|
|
502
|
+
for (const cmd of commands) {
|
|
503
|
+
runCommand(cmd, useSudo);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
console.log('agentio-server service removed');
|
|
507
|
+
console.log(
|
|
508
|
+
'\nNote: Configuration is preserved in ~/.config/agentio/config.json'
|
|
509
|
+
);
|
|
510
|
+
} catch (error) {
|
|
511
|
+
handleError(error);
|
|
512
|
+
}
|
|
513
|
+
});
|
|
514
|
+
}
|