@commonlyai/cli 0.1.213 → 0.1.214
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/agent.js +66 -18
- package/src/lib/daemon-logs.js +19 -1
package/package.json
CHANGED
package/src/commands/agent.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createOwnKeyResolver, createMachineReportClient } from '../lib/own-key-launch.js';
|
|
2
2
|
import { foregroundDaemonHint } from '../lib/daemon-hint.js';
|
|
3
3
|
import { loadDaemonRecord } from '../lib/daemon-store.js';
|
|
4
|
+
import { daemonSeatLogPath, logSize, readAppended, readLogTail } from '../lib/daemon-logs.js';
|
|
4
5
|
import { WALKED_HARNESSES } from '../lib/runtime-inventory.js';
|
|
5
6
|
import { createRuntimeReporter } from '../lib/runtime-observation.js';
|
|
6
7
|
/**
|
|
@@ -4374,38 +4375,85 @@ Use --local to find the name you'd pass to 'agent run' or 'agent detach'.
|
|
|
4374
4375
|
});
|
|
4375
4376
|
|
|
4376
4377
|
// ── logs ──────────────────────────────────────────────────────────────────
|
|
4378
|
+
// Without --pod, the seat's local daemon log is read. With --pod, the owner
|
|
4379
|
+
// login reads recent events and runs from the member-gated seat-activity
|
|
4380
|
+
// route, whichever machine the seat runs on. The owner login is never sent to
|
|
4381
|
+
// the agent-runtime routes, which only accept a seat's own token.
|
|
4377
4382
|
agent
|
|
4378
4383
|
.command('logs <name>')
|
|
4379
|
-
.description('
|
|
4380
|
-
.option('--
|
|
4381
|
-
.option('--
|
|
4382
|
-
.option('--
|
|
4384
|
+
.description("Show a seat's recent log: local daemon log, or server activity with --pod")
|
|
4385
|
+
.option('--pod <podId>', 'Read server-side events and runs for a pod you belong to')
|
|
4386
|
+
.option('--instance-id <id>', 'Seat instance ID', 'default')
|
|
4387
|
+
.option('--follow', 'Continue printing new output', false)
|
|
4388
|
+
.option('--lines <n>', 'Number of trailing local lines to show', '50')
|
|
4389
|
+
.option('--instance <url>', 'Target Commonly instance (with --pod)')
|
|
4383
4390
|
.action(async (name, opts) => {
|
|
4391
|
+
if (opts.follow && opts.pod) {
|
|
4392
|
+
console.error('--follow needs a local seat log; server activity is a snapshot. Re-run without --follow.');
|
|
4393
|
+
process.exit(1);
|
|
4394
|
+
}
|
|
4395
|
+
const lines = Number(opts.lines);
|
|
4396
|
+
if (!Number.isInteger(lines) || lines < 1) {
|
|
4397
|
+
console.error(`--lines must be a positive integer, got '${opts.lines}'`);
|
|
4398
|
+
process.exit(1);
|
|
4399
|
+
}
|
|
4400
|
+
if (!opts.pod) {
|
|
4401
|
+
const path = daemonSeatLogPath(name);
|
|
4402
|
+
let output;
|
|
4403
|
+
try {
|
|
4404
|
+
output = readLogTail(path, lines);
|
|
4405
|
+
} catch (err) {
|
|
4406
|
+
console.error(`Cannot read ${path}: ${err.message}`);
|
|
4407
|
+
process.exit(1);
|
|
4408
|
+
}
|
|
4409
|
+
if (output === null) {
|
|
4410
|
+
console.error(`No local log for '${name}' at ${path}. Pass --pod <podId> to read its server-side activity.`);
|
|
4411
|
+
process.exit(1);
|
|
4412
|
+
}
|
|
4413
|
+
if (output) console.log(output);
|
|
4414
|
+
if (!opts.follow) return;
|
|
4415
|
+
// Poll rather than fs.watch: a rename-rotated log would leave a watch
|
|
4416
|
+
// on the old inode silent. Only appended bytes are printed.
|
|
4417
|
+
let offset = logSize(path);
|
|
4418
|
+
const timer = setInterval(() => {
|
|
4419
|
+
try {
|
|
4420
|
+
const next = readAppended(path, offset);
|
|
4421
|
+
offset = next.offset;
|
|
4422
|
+
if (next.text) process.stdout.write(next.text);
|
|
4423
|
+
} catch (err) {
|
|
4424
|
+
// Rotation can leave a gap with no file; wait for it to come back.
|
|
4425
|
+
if (err?.code === 'ENOENT') return;
|
|
4426
|
+
console.error(`Cannot follow ${path}: ${err.message}`);
|
|
4427
|
+
process.exit(1);
|
|
4428
|
+
}
|
|
4429
|
+
}, 1000);
|
|
4430
|
+
const close = () => { clearInterval(timer); process.exit(0); };
|
|
4431
|
+
process.once('SIGINT', close);
|
|
4432
|
+
process.once('SIGTERM', close);
|
|
4433
|
+
return;
|
|
4434
|
+
}
|
|
4435
|
+
|
|
4384
4436
|
const token = getToken(opts.instance);
|
|
4385
4437
|
if (!token) { console.error('Not logged in. Run: commonly login'); process.exit(1); }
|
|
4386
|
-
|
|
4387
4438
|
const client = createClient({ instance: resolveInstanceUrl(opts.instance), token });
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4439
|
+
const printActivity = async () => {
|
|
4440
|
+
const { events = [], runs = [] } = await client.get('/api/agents/seat-activity', {
|
|
4441
|
+
podId: opts.pod,
|
|
4391
4442
|
agentName: name,
|
|
4392
4443
|
instanceId: opts.instanceId,
|
|
4393
|
-
limit: 20,
|
|
4394
4444
|
});
|
|
4395
4445
|
events.forEach((e) => {
|
|
4396
4446
|
const ts = new Date(e.createdAt).toTimeString().slice(0, 8);
|
|
4397
|
-
|
|
4398
|
-
console.log(`[${ts}] ${e.type.padEnd(18)} → ${outcome}`);
|
|
4447
|
+
console.log(`[${ts}] ${String(e.type).padEnd(18)} → ${e.outcome || e.status}`);
|
|
4399
4448
|
});
|
|
4400
|
-
|
|
4449
|
+
runs.forEach((r) => {
|
|
4450
|
+
const ts = new Date(r.startedAt).toTimeString().slice(0, 8);
|
|
4451
|
+
console.log(`[${ts}] run ${String(r.trigger).padEnd(14)} → ${r.status} (${r.totalTokens} tokens)`);
|
|
4452
|
+
});
|
|
4453
|
+
if (events.length === 0 && runs.length === 0) console.log(`No recent activity for '${name}' in this pod.`);
|
|
4401
4454
|
};
|
|
4402
|
-
|
|
4403
4455
|
try {
|
|
4404
|
-
await
|
|
4405
|
-
if (opts.follow) {
|
|
4406
|
-
console.log('Following... (Ctrl+C to stop)');
|
|
4407
|
-
setInterval(fetchAndPrint, 5000);
|
|
4408
|
-
}
|
|
4456
|
+
await printActivity();
|
|
4409
4457
|
} catch (err) {
|
|
4410
4458
|
console.error(`Failed: ${err.message}`);
|
|
4411
4459
|
process.exit(1);
|
package/src/lib/daemon-logs.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readFileSync } from 'node:fs';
|
|
1
|
+
import { closeSync, openSync, readFileSync, readSync, statSync } from 'node:fs';
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
|
|
@@ -23,3 +23,21 @@ export const readLogTail = (path, count = 50) => {
|
|
|
23
23
|
throw error;
|
|
24
24
|
}
|
|
25
25
|
};
|
|
26
|
+
|
|
27
|
+
export const logSize = (path) => statSync(path).size;
|
|
28
|
+
|
|
29
|
+
// Bytes appended since `offset`, for `agent logs --follow`. A file that shrank
|
|
30
|
+
// (rotated or truncated) is read from the start again.
|
|
31
|
+
export const readAppended = (path, offset) => {
|
|
32
|
+
const size = logSize(path);
|
|
33
|
+
const start = size < offset ? 0 : offset;
|
|
34
|
+
if (size === start) return { text: '', offset: size };
|
|
35
|
+
const fd = openSync(path, 'r');
|
|
36
|
+
try {
|
|
37
|
+
const buffer = Buffer.alloc(size - start);
|
|
38
|
+
readSync(fd, buffer, 0, buffer.length, start);
|
|
39
|
+
return { text: buffer.toString('utf8'), offset: size };
|
|
40
|
+
} finally {
|
|
41
|
+
closeSync(fd);
|
|
42
|
+
}
|
|
43
|
+
};
|