@phnx-labs/agents-cli 1.22.11 → 1.22.12
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/CHANGELOG.md +38 -0
- package/dist/bin/agents +0 -0
- package/dist/commands/cli.js +12 -12
- package/dist/commands/events.d.ts +20 -1
- package/dist/commands/events.js +71 -55
- package/dist/commands/logs.js +14 -138
- package/dist/commands/repo.d.ts +1 -1
- package/dist/commands/repo.js +3 -3
- package/dist/commands/view.d.ts +1 -1
- package/dist/commands/view.js +7 -7
- package/dist/lib/cli-resources.js +2 -2
- package/dist/lib/event-stream.d.ts +1 -1
- package/dist/lib/event-stream.js +1 -1
- package/dist/lib/events.d.ts +21 -12
- package/dist/lib/events.js +312 -65
- package/dist/lib/exec.js +1 -2
- package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/migrate.d.ts +8 -0
- package/dist/lib/migrate.js +26 -62
- package/dist/lib/resources.d.ts +1 -1
- package/dist/lib/runner.js +1 -2
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/audit.js +1 -1
- package/dist/lib/startup/command-registry.js +1 -1
- package/dist/lib/state.d.ts +1 -1
- package/dist/lib/state.js +60 -2
- package/dist/lib/watchdog/runner.d.ts +13 -0
- package/dist/lib/watchdog/runner.js +75 -6
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.22.12
|
|
4
|
+
|
|
5
|
+
- Store operational events in daily history directories, retain 7 days and at most 50 MiB automatically, and make `agents logs audit` use the `agents events --audit` reader.
|
|
6
|
+
|
|
7
|
+
- **`agents cli` renamed to `agents clis`; resource directory `cli/` renamed to `clis/`.** The CLI resource kind and its subdirectory are now plural throughout: `ResourceKind` changes from `'cli'` to `'clis'`, manifests live at `clis/<name>.yaml`, `agents clis` is the only command surface (no `agents cli` alias), and `agents view --clis` replaces `--cli`. A startup migration renames any existing `cli/` directory to `clis/` in the user, system, and project `.agents/` layers; if both `cli/` and `clis/` are present the migration fails with a clear error rather than silently merging. Source: `apps/cli/src/lib/resources.ts`, `apps/cli/src/lib/cli-resources.ts`, `apps/cli/src/commands/cli.ts`, `apps/cli/src/lib/startup/command-registry.ts`, `apps/cli/src/commands/repo.ts`, `apps/cli/src/commands/view.ts`, `apps/cli/src/lib/migrate.ts`.
|
|
8
|
+
|
|
9
|
+
- **`agents.yaml` no longer silently loses top-level keys across a version-skewed
|
|
10
|
+
fleet.** `serializeCentral` (`lib/state.ts`) rewrote the synced `agents.yaml`
|
|
11
|
+
with a delete-any-key-not-in-the-in-memory-object pass. An **older CLI version
|
|
12
|
+
whose `Meta` type predated a key** (`beta:`, `notify.owner`, `feed:`, imported
|
|
13
|
+
`projects`) would parse the file, never surface that key, delete it on the next
|
|
14
|
+
write, and sync the deletion to every machine — the recurring "my config
|
|
15
|
+
vanished" data-loss (see the restore in commit `04295e3`). The delete pass now
|
|
16
|
+
consults a `Record<keyof Meta, 'central' | 'device'>` scope map (compile-time
|
|
17
|
+
exhaustive — a new `Meta` field that isn't classified fails the build) and
|
|
18
|
+
deletes **only keys this version knows** (a cleared central key, or a device
|
|
19
|
+
key that is legacy cruft in the synced file); a key it doesn't know is
|
|
20
|
+
preserved verbatim. Once a machine runs a CLI carrying this fix, it can never
|
|
21
|
+
drop a newer version's key again. Source: `apps/cli/src/lib/state.ts`,
|
|
22
|
+
`apps/cli/src/lib/__tests__/state.test.ts`.
|
|
23
|
+
|
|
24
|
+
- **Watchdog files a feed block only when a session genuinely needs the human.**
|
|
25
|
+
When the smart brain concludes a stalled session must be left for the human
|
|
26
|
+
(`needsHuman`), the watchdog now surfaces that on the owner's feed instead of
|
|
27
|
+
dropping it in a menubar-only flag. Two cases: if the session is addressable it
|
|
28
|
+
injects a self-file reminder into the agent ("You appear stuck. File it: `agents
|
|
29
|
+
feed post … --blocked`") so the agent declares its own block; if it is
|
|
30
|
+
un-addressable — the case where the watchdog can't even reach the terminal to
|
|
31
|
+
remind it — the watchdog files a declared block on the agent's behalf so the owner
|
|
32
|
+
is still paged. Paging fires **only** on this confirmed-needs-human path: a plain
|
|
33
|
+
nudge-worthy drive-forward poke (un-addressable or under a hands-off policy) is
|
|
34
|
+
flagged for the tray but never texts the owner. Both paths are gated by the
|
|
35
|
+
existing cooldown ledger (at most once per `WATCHDOG_COOLDOWN_MS` window) and are
|
|
36
|
+
no-ops when a block for the session already exists, so no double-paging. Source:
|
|
37
|
+
`apps/cli/src/lib/watchdog/runner.ts` (`NudgeDecision.needsHuman`,
|
|
38
|
+
`WatchdogTickOptions.publishBlockFn`, the needs-human skip branch),
|
|
39
|
+
`apps/cli/src/lib/watchdog/runner.test.ts`.
|
|
40
|
+
|
|
3
41
|
## 1.22.11
|
|
4
42
|
|
|
5
43
|
- **`--blocked` iMessage notifications are now phone-actionable.** The forwarded message dropped the block's `--option`s, `--default`, and timeout and instead showed `agents focus <id>` — a CLI command that is useless on a phone. It now shows the choices (`Options: publish / wait`) and the safe-default fallback (`Default in 15 min: wait`) and omits the `agents focus` line, so a `--blocked` post that carries a `--default` self-resolves when the owner can't reply. Source: `apps/cli/src/lib/feed-broadcast.ts`.
|
package/dist/bin/agents
CHANGED
|
Binary file
|
package/dist/commands/cli.js
CHANGED
|
@@ -6,13 +6,13 @@ import { listCliManifests, listCliStatus, resolveCliManifest, installCli, descri
|
|
|
6
6
|
import { getUserAgentsDir } from '../lib/state.js';
|
|
7
7
|
import { isPromptCancelled } from './utils.js';
|
|
8
8
|
function userCliDir() {
|
|
9
|
-
return path.join(getUserAgentsDir(), '
|
|
9
|
+
return path.join(getUserAgentsDir(), 'clis');
|
|
10
10
|
}
|
|
11
11
|
/** Render the status table — one row per declared CLI. */
|
|
12
12
|
function printStatus(rows) {
|
|
13
13
|
if (rows.length === 0) {
|
|
14
14
|
console.log(chalk.gray('No CLIs declared.'));
|
|
15
|
-
console.log(chalk.gray(`Create one with: agents
|
|
15
|
+
console.log(chalk.gray(`Create one with: agents clis add <name>`));
|
|
16
16
|
return;
|
|
17
17
|
}
|
|
18
18
|
const nameWidth = Math.max(4, ...rows.map((r) => r.manifest.name.length));
|
|
@@ -28,32 +28,32 @@ function printStatus(rows) {
|
|
|
28
28
|
}
|
|
29
29
|
export function registerCliCommands(program) {
|
|
30
30
|
const cliCmd = program
|
|
31
|
-
.command('
|
|
31
|
+
.command('clis')
|
|
32
32
|
.description('Declare and install host CLI binaries (gh, higgsfield, glab, ...)')
|
|
33
33
|
.addHelpText('after', `
|
|
34
|
-
CLI manifests live in <repo>/
|
|
35
|
-
binary on the host. On a fresh machine, 'agents
|
|
34
|
+
CLI manifests live in <repo>/clis/<name>.yaml and declare how to install a
|
|
35
|
+
binary on the host. On a fresh machine, 'agents clis install' runs the first
|
|
36
36
|
compatible method (npm > brew > script > binary) for every declared entry.
|
|
37
37
|
|
|
38
38
|
Examples:
|
|
39
39
|
# See which declared CLIs are installed on this host
|
|
40
|
-
agents
|
|
40
|
+
agents clis list
|
|
41
41
|
|
|
42
42
|
# Install everything that's missing
|
|
43
|
-
agents
|
|
43
|
+
agents clis install
|
|
44
44
|
|
|
45
45
|
# Install one
|
|
46
|
-
agents
|
|
46
|
+
agents clis install higgsfield
|
|
47
47
|
|
|
48
48
|
# Show the manifest detail
|
|
49
|
-
agents
|
|
49
|
+
agents clis view higgsfield
|
|
50
50
|
|
|
51
51
|
# Exit 0 if all declared CLIs are installed (use in CI / setup scripts)
|
|
52
|
-
agents
|
|
52
|
+
agents clis check
|
|
53
53
|
|
|
54
54
|
When to use:
|
|
55
55
|
- After 'agents sync' on a new machine, to materialize host binaries
|
|
56
|
-
- In a team setup: commit
|
|
56
|
+
- In a team setup: commit clis/ entries so teammates get the same toolchain
|
|
57
57
|
`);
|
|
58
58
|
cliCmd
|
|
59
59
|
.command('list')
|
|
@@ -206,7 +206,7 @@ When to use:
|
|
|
206
206
|
});
|
|
207
207
|
cliCmd
|
|
208
208
|
.command('add <name>')
|
|
209
|
-
.description('Scaffold a new manifest at ~/.agents/
|
|
209
|
+
.description('Scaffold a new manifest at ~/.agents/clis/<name>.yaml')
|
|
210
210
|
.option('--npm <pkg>', 'declare an npm install method')
|
|
211
211
|
.option('--brew <formula>', 'declare a brew install method')
|
|
212
212
|
.option('--script <url>', 'declare a curl|sh install method')
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `agents events` — read the unified event stream.
|
|
3
3
|
*
|
|
4
|
-
* One stream over BOTH operational events (`~/.agents/.history/events/
|
|
4
|
+
* One stream over BOTH operational events (`~/.agents/.history/events/YYYY-MM-DD/`: every
|
|
5
5
|
* `agents <module> <cmd>` invocation plus typed events like secrets access,
|
|
6
6
|
* version installs) AND agent-semantic events (the per-session activity logs:
|
|
7
7
|
* plans, PRs, worktrees, sub-agents, artifacts). Each is stamped with who ran
|
|
@@ -30,4 +30,23 @@ export declare function capRecords<T>(fetched: T[], limit: number | undefined):
|
|
|
30
30
|
records: T[];
|
|
31
31
|
truncated: boolean;
|
|
32
32
|
};
|
|
33
|
+
export interface EventsOptions {
|
|
34
|
+
module?: string;
|
|
35
|
+
command?: string;
|
|
36
|
+
event?: string[];
|
|
37
|
+
agent?: string;
|
|
38
|
+
caller?: string;
|
|
39
|
+
level?: string;
|
|
40
|
+
session?: string;
|
|
41
|
+
bundle?: string;
|
|
42
|
+
since?: string;
|
|
43
|
+
limit?: string;
|
|
44
|
+
json?: boolean;
|
|
45
|
+
follow?: boolean;
|
|
46
|
+
audit?: boolean;
|
|
47
|
+
}
|
|
48
|
+
/** Add the one canonical event-reader option surface to a command or alias. */
|
|
49
|
+
export declare function addEventsReadOptions(command: Command, includeAuditFlag?: boolean): Command;
|
|
50
|
+
/** Canonical reader used by both `agents events` and `agents logs audit`. */
|
|
51
|
+
export declare function runEventsCommand(options: EventsOptions, forceAudit?: boolean): Promise<void>;
|
|
33
52
|
export declare function registerEventsCommand(program: Command): void;
|
package/dist/commands/events.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `agents events` — read the unified event stream.
|
|
3
3
|
*
|
|
4
|
-
* One stream over BOTH operational events (`~/.agents/.history/events/
|
|
4
|
+
* One stream over BOTH operational events (`~/.agents/.history/events/YYYY-MM-DD/`: every
|
|
5
5
|
* `agents <module> <cmd>` invocation plus typed events like secrets access,
|
|
6
6
|
* version installs) AND agent-semantic events (the per-session activity logs:
|
|
7
7
|
* plans, PRs, worktrees, sub-agents, artifacts). Each is stamped with who ran
|
|
@@ -184,21 +184,77 @@ function registerEmitSubcommand(events) {
|
|
|
184
184
|
`,
|
|
185
185
|
});
|
|
186
186
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
.description('Read the unified event stream (operational + agent activity)')
|
|
187
|
+
/** Add the one canonical event-reader option surface to a command or alias. */
|
|
188
|
+
export function addEventsReadOptions(command, includeAuditFlag = true) {
|
|
189
|
+
command
|
|
191
190
|
.option('--module <name>', 'Only events from this group (e.g. teams, secrets, activity)')
|
|
192
191
|
.option('--command <path>', 'Only this command path — prefix match (e.g. "teams create")')
|
|
193
192
|
.option('--event <type>', 'Only this typed event (repeatable, e.g. secrets.get, secrets.unlocked, pr.opened)', collect, [])
|
|
194
193
|
.option('--agent <name>', 'Only events tagged with this agent')
|
|
194
|
+
.option('--caller <kind>', 'Only this caller kind (claude-code, codex, gemini, cursor, terminal, script)')
|
|
195
|
+
.option('--level <level>', 'Only this level: audit, warn, info, debug')
|
|
195
196
|
.option('--session <id>', 'Only events from this session (the provenance sessionId) — e.g. trace which session read a secret')
|
|
196
197
|
.option('--bundle <name>', 'Only events carrying this bundle in their payload — e.g. `--module secrets --bundle share` for every read of the share bundle')
|
|
197
198
|
.option('--since <time>', 'Only events newer than this (e.g. 2h, 7d, or ISO date)')
|
|
198
|
-
.option('--audit', 'Operational events only (skip agent activity)')
|
|
199
199
|
.option('--limit <n>', 'Max records to show; 0 for no cap (default 50)', '50')
|
|
200
200
|
.option('--json', 'Output raw records as JSON')
|
|
201
|
-
.option('-f, --follow', "Tail today's operational log live")
|
|
201
|
+
.option('-f, --follow', "Tail today's operational log live");
|
|
202
|
+
if (includeAuditFlag)
|
|
203
|
+
command.option('--audit', 'Operational events only (skip agent activity)');
|
|
204
|
+
return command;
|
|
205
|
+
}
|
|
206
|
+
/** Canonical reader used by both `agents events` and `agents logs audit`. */
|
|
207
|
+
export async function runEventsCommand(options, forceAudit = false) {
|
|
208
|
+
if (options.follow) {
|
|
209
|
+
await followLog();
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
let limit;
|
|
213
|
+
let startDate;
|
|
214
|
+
try {
|
|
215
|
+
limit = resolveEventsLimit(options.limit);
|
|
216
|
+
startDate = options.since ? parseSince(options.since) : undefined;
|
|
217
|
+
}
|
|
218
|
+
catch (err) {
|
|
219
|
+
console.error(chalk.red(err.message));
|
|
220
|
+
process.exitCode = 2;
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
const fetched = readUnifiedEvents({
|
|
224
|
+
startDate,
|
|
225
|
+
eventTypes: options.event && options.event.length ? options.event : undefined,
|
|
226
|
+
level: options.level,
|
|
227
|
+
agent: options.agent,
|
|
228
|
+
sessionId: options.session,
|
|
229
|
+
bundle: options.bundle,
|
|
230
|
+
caller: options.caller,
|
|
231
|
+
command: options.command,
|
|
232
|
+
module: options.module,
|
|
233
|
+
limit: limit === undefined ? undefined : limit + 1,
|
|
234
|
+
includeActivity: !(forceAudit || options.audit),
|
|
235
|
+
});
|
|
236
|
+
const { records, truncated } = capRecords(fetched, limit);
|
|
237
|
+
const capNote = `Showing the newest ${limit} — more events matched. Pass --limit 0 for all.`;
|
|
238
|
+
if (options.json) {
|
|
239
|
+
if (truncated)
|
|
240
|
+
console.error(chalk.yellow(capNote));
|
|
241
|
+
console.log(JSON.stringify(records, null, 2));
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
if (records.length === 0) {
|
|
245
|
+
console.log(chalk.gray('No matching events.'));
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
for (const r of records.slice().reverse())
|
|
249
|
+
console.log(renderRow(r));
|
|
250
|
+
console.log(chalk.gray(`\n${records.length} event(s). Log: ${getLogsPath()}`));
|
|
251
|
+
if (truncated)
|
|
252
|
+
console.log(chalk.yellow(capNote));
|
|
253
|
+
}
|
|
254
|
+
export function registerEventsCommand(program) {
|
|
255
|
+
const events = addEventsReadOptions(program
|
|
256
|
+
.command('events')
|
|
257
|
+
.description('Read the unified event stream (operational + agent activity)'))
|
|
202
258
|
.addHelpText('after', `
|
|
203
259
|
Examples:
|
|
204
260
|
agents events Everything — ops + agent activity
|
|
@@ -214,53 +270,7 @@ Examples:
|
|
|
214
270
|
agents events --event pr.opened --since 30d --limit 0 --json
|
|
215
271
|
Every match — use --limit 0 whenever you
|
|
216
272
|
aggregate, or you rank only the newest 50`)
|
|
217
|
-
.action(
|
|
218
|
-
if (options.follow) {
|
|
219
|
-
await followLog();
|
|
220
|
-
return;
|
|
221
|
-
}
|
|
222
|
-
let limit;
|
|
223
|
-
let startDate;
|
|
224
|
-
try {
|
|
225
|
-
limit = resolveEventsLimit(options.limit);
|
|
226
|
-
startDate = options.since ? parseSince(options.since) : undefined;
|
|
227
|
-
}
|
|
228
|
-
catch (err) {
|
|
229
|
-
console.error(chalk.red(err.message));
|
|
230
|
-
process.exit(2);
|
|
231
|
-
}
|
|
232
|
-
// Read one past the cap so we can tell a full result from a clipped one.
|
|
233
|
-
const fetched = readUnifiedEvents({
|
|
234
|
-
startDate,
|
|
235
|
-
eventTypes: options.event && options.event.length ? options.event : undefined,
|
|
236
|
-
agent: options.agent,
|
|
237
|
-
sessionId: options.session,
|
|
238
|
-
bundle: options.bundle,
|
|
239
|
-
command: options.command,
|
|
240
|
-
module: options.module,
|
|
241
|
-
limit: limit === undefined ? undefined : limit + 1,
|
|
242
|
-
includeActivity: !options.audit,
|
|
243
|
-
});
|
|
244
|
-
const { records, truncated } = capRecords(fetched, limit);
|
|
245
|
-
const capNote = `Showing the newest ${limit} — more events matched. Pass --limit 0 for all.`;
|
|
246
|
-
if (options.json) {
|
|
247
|
-
// Notice goes to stderr so `--json | jq` still receives clean JSON.
|
|
248
|
-
if (truncated)
|
|
249
|
-
console.error(chalk.yellow(capNote));
|
|
250
|
-
console.log(JSON.stringify(records, null, 2));
|
|
251
|
-
return;
|
|
252
|
-
}
|
|
253
|
-
if (records.length === 0) {
|
|
254
|
-
console.log(chalk.gray('No matching events.'));
|
|
255
|
-
return;
|
|
256
|
-
}
|
|
257
|
-
// query() returns newest-first; print oldest-first so a tail reads naturally.
|
|
258
|
-
for (const r of records.slice().reverse())
|
|
259
|
-
console.log(renderRow(r));
|
|
260
|
-
console.log(chalk.gray(`\n${records.length} event(s). Log: ${getLogsPath()}`));
|
|
261
|
-
if (truncated)
|
|
262
|
-
console.log(chalk.yellow(capNote));
|
|
263
|
-
});
|
|
273
|
+
.action((_options, command) => runEventsCommand(command.optsWithGlobals()));
|
|
264
274
|
// `events` both reads (its own action, above) and writes (this subcommand) —
|
|
265
275
|
// the same shape as `feed` / `feed post`.
|
|
266
276
|
registerEmitSubcommand(events);
|
|
@@ -271,7 +281,7 @@ function collect(value, previous) {
|
|
|
271
281
|
}
|
|
272
282
|
/** Tail today's event file, printing new lines as they land. */
|
|
273
283
|
async function followLog() {
|
|
274
|
-
|
|
284
|
+
let file = getLogsPath();
|
|
275
285
|
let offset = 0;
|
|
276
286
|
try {
|
|
277
287
|
offset = fs.statSync(file).size;
|
|
@@ -281,6 +291,12 @@ async function followLog() {
|
|
|
281
291
|
}
|
|
282
292
|
console.log(chalk.gray(`Tailing ${file} — Ctrl-C to stop`));
|
|
283
293
|
const drain = () => {
|
|
294
|
+
const nextFile = getLogsPath();
|
|
295
|
+
if (nextFile !== file) {
|
|
296
|
+
file = nextFile;
|
|
297
|
+
offset = 0;
|
|
298
|
+
console.log(chalk.gray(`Tailing ${file}`));
|
|
299
|
+
}
|
|
284
300
|
let size = 0;
|
|
285
301
|
try {
|
|
286
302
|
size = fs.statSync(file).size;
|
package/dist/commands/logs.js
CHANGED
|
@@ -21,14 +21,14 @@
|
|
|
21
21
|
* the same underlying helpers (showHostTaskLog / streamSessionTail).
|
|
22
22
|
*/
|
|
23
23
|
import chalk from 'chalk';
|
|
24
|
-
import * as fs from 'fs';
|
|
25
24
|
import { discoverSessions, resolveSessionById } from '../lib/session/discover.js';
|
|
26
25
|
import { parseAgentFilter, renderSessionLog, renderSessionLogJson } from './sessions.js';
|
|
27
26
|
import { streamSessionTail, isTailable } from './sessions-tail.js';
|
|
28
27
|
import { showHostTaskLog, hostTaskLogJson } from '../lib/hosts/logs.js';
|
|
29
28
|
import { listTasks } from '../lib/hosts/tasks.js';
|
|
30
29
|
import { itemPicker } from '../lib/picker.js';
|
|
31
|
-
import {
|
|
30
|
+
import { stats, getLogsPath, rotate, } from '../lib/events.js';
|
|
31
|
+
import { addEventsReadOptions, runEventsCommand } from './events.js';
|
|
32
32
|
/** Compact one-line label used by both the picker and the non-TTY list. */
|
|
33
33
|
function candidateLabel(c) {
|
|
34
34
|
if (c.kind === 'task') {
|
|
@@ -178,32 +178,6 @@ function parseSince(s) {
|
|
|
178
178
|
throw new Error(`Invalid --since value: ${s} (use e.g. 2h, 7d, or an ISO date)`);
|
|
179
179
|
return new Date(ms);
|
|
180
180
|
}
|
|
181
|
-
function originLabel(r) {
|
|
182
|
-
if (r.transport === 'ssh') {
|
|
183
|
-
return chalk.yellow(`ssh${r.sshClientIp ? ' ' + r.sshClientIp : ''}`);
|
|
184
|
-
}
|
|
185
|
-
return chalk.gray('local');
|
|
186
|
-
}
|
|
187
|
-
function auditDetailFor(r) {
|
|
188
|
-
if (r.command)
|
|
189
|
-
return r.command;
|
|
190
|
-
const bits = [];
|
|
191
|
-
if (typeof r.team === 'string')
|
|
192
|
-
bits.push(`team=${r.team}`);
|
|
193
|
-
if (typeof r.bundle === 'string')
|
|
194
|
-
bits.push(`bundle=${r.bundle}`);
|
|
195
|
-
if (typeof r.skill === 'string')
|
|
196
|
-
bits.push(`skill=${r.skill}`);
|
|
197
|
-
if (typeof r.version === 'string')
|
|
198
|
-
bits.push(`v=${r.version}`);
|
|
199
|
-
if (typeof r.profile === 'string')
|
|
200
|
-
bits.push(`profile=${r.profile}`);
|
|
201
|
-
if (typeof r.server === 'string')
|
|
202
|
-
bits.push(`server=${r.server}`);
|
|
203
|
-
if (typeof r.error === 'string')
|
|
204
|
-
bits.push(chalk.red(r.error));
|
|
205
|
-
return bits.join(' ');
|
|
206
|
-
}
|
|
207
181
|
function levelColor(level) {
|
|
208
182
|
if (level === 'audit')
|
|
209
183
|
return chalk.magenta(level);
|
|
@@ -213,99 +187,6 @@ function levelColor(level) {
|
|
|
213
187
|
return chalk.gray(level);
|
|
214
188
|
return chalk.blue(level);
|
|
215
189
|
}
|
|
216
|
-
function renderAuditRow(r) {
|
|
217
|
-
const time = chalk.gray(r.ts.slice(0, 19).replace('T', ' '));
|
|
218
|
-
const user = `${r.osUser ?? '?'}@${r.hostname}`;
|
|
219
|
-
const ev = r.event.startsWith('error') ? chalk.red(r.event) : chalk.cyan(r.event);
|
|
220
|
-
const lvl = levelColor(r.level ?? levelFor(r.event));
|
|
221
|
-
const agent = r.agent ? chalk.gray(` ${r.agent}`) : '';
|
|
222
|
-
const caller = chalk.gray(`via ${r.caller ?? 'unknown'}${r.session ? ` ${r.session}` : ''}`);
|
|
223
|
-
return `${time} ${lvl.padEnd(14)} ${originLabel(r).padEnd(24)} ${user.padEnd(22)} ${caller.padEnd(28)} ${ev.padEnd(26)}${agent} ${auditDetailFor(r)}`;
|
|
224
|
-
}
|
|
225
|
-
function collect(value, previous) {
|
|
226
|
-
return previous.concat([value]);
|
|
227
|
-
}
|
|
228
|
-
async function runAudit(opts) {
|
|
229
|
-
if (opts.follow) {
|
|
230
|
-
await followAuditLog();
|
|
231
|
-
return;
|
|
232
|
-
}
|
|
233
|
-
const limit = Math.max(1, parseInt(opts.limit ?? '50', 10) || 50);
|
|
234
|
-
let startDate;
|
|
235
|
-
try {
|
|
236
|
-
startDate = opts.since ? parseSince(opts.since) : undefined;
|
|
237
|
-
}
|
|
238
|
-
catch (err) {
|
|
239
|
-
console.error(chalk.red(err.message));
|
|
240
|
-
process.exit(2);
|
|
241
|
-
}
|
|
242
|
-
const records = query({
|
|
243
|
-
startDate,
|
|
244
|
-
eventTypes: opts.event?.length ? opts.event : undefined,
|
|
245
|
-
level: opts.level,
|
|
246
|
-
agent: opts.agent,
|
|
247
|
-
caller: opts.caller,
|
|
248
|
-
command: opts.command,
|
|
249
|
-
module: opts.module,
|
|
250
|
-
limit,
|
|
251
|
-
});
|
|
252
|
-
if (opts.json) {
|
|
253
|
-
console.log(JSON.stringify(records, null, 2));
|
|
254
|
-
return;
|
|
255
|
-
}
|
|
256
|
-
if (records.length === 0) {
|
|
257
|
-
console.log(chalk.gray('No matching events.'));
|
|
258
|
-
return;
|
|
259
|
-
}
|
|
260
|
-
for (const r of records.slice().reverse())
|
|
261
|
-
console.log(renderAuditRow(r));
|
|
262
|
-
console.log(chalk.gray(`\n${records.length} event(s). Log: ${getLogsPath()}`));
|
|
263
|
-
}
|
|
264
|
-
async function followAuditLog() {
|
|
265
|
-
const file = getLogsPath();
|
|
266
|
-
let offset = 0;
|
|
267
|
-
try {
|
|
268
|
-
offset = fs.statSync(file).size;
|
|
269
|
-
}
|
|
270
|
-
catch {
|
|
271
|
-
// File may not exist yet — start at 0.
|
|
272
|
-
}
|
|
273
|
-
console.log(chalk.gray(`Tailing ${file} — Ctrl-C to stop`));
|
|
274
|
-
const drain = () => {
|
|
275
|
-
let size = 0;
|
|
276
|
-
try {
|
|
277
|
-
size = fs.statSync(file).size;
|
|
278
|
-
}
|
|
279
|
-
catch {
|
|
280
|
-
return;
|
|
281
|
-
}
|
|
282
|
-
if (size <= offset) {
|
|
283
|
-
if (size < offset)
|
|
284
|
-
offset = 0;
|
|
285
|
-
return;
|
|
286
|
-
}
|
|
287
|
-
const fd = fs.openSync(file, 'r');
|
|
288
|
-
try {
|
|
289
|
-
const buf = Buffer.alloc(size - offset);
|
|
290
|
-
fs.readSync(fd, buf, 0, buf.length, offset);
|
|
291
|
-
offset = size;
|
|
292
|
-
for (const line of buf.toString('utf-8').split('\n').filter(Boolean)) {
|
|
293
|
-
try {
|
|
294
|
-
console.log(renderAuditRow(JSON.parse(line)));
|
|
295
|
-
}
|
|
296
|
-
catch {
|
|
297
|
-
// Skip malformed lines.
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
finally {
|
|
302
|
-
fs.closeSync(fd);
|
|
303
|
-
}
|
|
304
|
-
};
|
|
305
|
-
await new Promise(() => {
|
|
306
|
-
setInterval(drain, 500);
|
|
307
|
-
});
|
|
308
|
-
}
|
|
309
190
|
function humanBytes(bytes) {
|
|
310
191
|
if (bytes < 1024)
|
|
311
192
|
return `${bytes} B`;
|
|
@@ -379,19 +260,9 @@ export function registerLogsCommand(program) {
|
|
|
379
260
|
.option('-m, --full', 'Show the full raw transcript / stdout instead of the concise summary')
|
|
380
261
|
.option('--json', 'Machine-readable JSON: a host task as { kind, task, log }, a session as the redacted { session, events } (same shape as `sessions <id> --json`)')
|
|
381
262
|
.action((id, opts) => runLogs(id, opts));
|
|
382
|
-
logsCmd
|
|
263
|
+
addEventsReadOptions(logsCmd
|
|
383
264
|
.command('audit')
|
|
384
|
-
.description('
|
|
385
|
-
.option('--module <name>', 'Only events from this command group (e.g. teams, secrets)')
|
|
386
|
-
.option('--command <path>', 'Only this command path — prefix match (e.g. "teams create")')
|
|
387
|
-
.option('--event <type>', 'Only this typed event (repeatable)', collect, [])
|
|
388
|
-
.option('--agent <name>', 'Only events tagged with this agent')
|
|
389
|
-
.option('--caller <kind>', 'Only this caller kind (claude-code, codex, gemini, cursor, terminal, script)')
|
|
390
|
-
.option('--level <level>', 'Only this level: audit, warn, info, debug')
|
|
391
|
-
.option('--since <time>', 'Only events newer than this (e.g. 2h, 7d, or ISO date)')
|
|
392
|
-
.option('--limit <n>', 'Max records to show (default 50)', '50')
|
|
393
|
-
.option('--json', 'Output raw records as JSON')
|
|
394
|
-
.option('-f, --follow', "Tail today's log live")
|
|
265
|
+
.description('Alias for `agents events --audit`'), false)
|
|
395
266
|
.addHelpText('after', `
|
|
396
267
|
Examples:
|
|
397
268
|
agents logs audit Recent activity across everything
|
|
@@ -401,7 +272,7 @@ Examples:
|
|
|
401
272
|
agents logs audit --command "teams create" Just team creations
|
|
402
273
|
agents logs audit --event secrets.get --since 7d --json
|
|
403
274
|
agents logs audit -f Live tail`)
|
|
404
|
-
.action(
|
|
275
|
+
.action((_options, command) => runEventsCommand(command.optsWithGlobals(), true));
|
|
405
276
|
logsCmd
|
|
406
277
|
.command('stats')
|
|
407
278
|
.description('Show aggregate audit statistics')
|
|
@@ -410,16 +281,21 @@ Examples:
|
|
|
410
281
|
.action(async (opts) => runStats(opts));
|
|
411
282
|
logsCmd
|
|
412
283
|
.command('rotate')
|
|
413
|
-
.description('
|
|
284
|
+
.description('Apply event retention and the storage ceiling immediately')
|
|
414
285
|
.option('--days <n>', 'Retention period in days (default 7)', '7')
|
|
286
|
+
.option('--max-mb <n>', 'Total event storage ceiling in MiB (default 50)', '50')
|
|
415
287
|
.action((opts) => {
|
|
416
288
|
const days = Math.max(1, parseInt(opts.days ?? '7', 10) || 7);
|
|
417
|
-
const
|
|
289
|
+
const maxMb = Math.max(1, parseInt(opts.maxMb ?? '50', 10) || 50);
|
|
290
|
+
const result = rotate(days, maxMb * 1024 * 1024);
|
|
291
|
+
const removed = result.removedByAge + result.removedBySize;
|
|
418
292
|
if (removed > 0) {
|
|
419
|
-
console.log(`Removed ${removed}
|
|
293
|
+
console.log(`Removed ${removed} event file${removed === 1 ? '' : 's'} ` +
|
|
294
|
+
`(${result.removedByAge} by age, ${result.removedBySize} by size); ` +
|
|
295
|
+
`reclaimed ${humanBytes(result.bytesReclaimed)}.`);
|
|
420
296
|
}
|
|
421
297
|
else {
|
|
422
|
-
console.log(chalk.gray(
|
|
298
|
+
console.log(chalk.gray(`No event files removed (retention ${days} days, ceiling ${maxMb} MiB).`));
|
|
423
299
|
}
|
|
424
300
|
});
|
|
425
301
|
}
|
package/dist/commands/repo.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ import type { Command } from 'commander';
|
|
|
17
17
|
* unit a user reasons about: one skill, one command, one plugin — even if it
|
|
18
18
|
* spans several files on disk.
|
|
19
19
|
*/
|
|
20
|
-
type RepoResourceKind = 'skill' | 'command' | 'plugin' | 'hook' | 'mcp' | 'subagent' | 'rule' | 'workflow' | 'routine' | 'profile' | 'permission' | '
|
|
20
|
+
type RepoResourceKind = 'skill' | 'command' | 'plugin' | 'hook' | 'mcp' | 'subagent' | 'rule' | 'workflow' | 'routine' | 'profile' | 'permission' | 'clis' | 'config' | 'other';
|
|
21
21
|
export type ChangeAction = 'new' | 'changed' | 'removed';
|
|
22
22
|
/**
|
|
23
23
|
* Map a repo-relative path to the resource unit it belongs to. Directory-based
|
package/dist/commands/repo.js
CHANGED
|
@@ -78,7 +78,7 @@ const RESOURCE_DIRS = {
|
|
|
78
78
|
skills: 'skill', commands: 'command', prompts: 'command', plugins: 'plugin',
|
|
79
79
|
hooks: 'hook', mcp: 'mcp', subagents: 'subagent', rules: 'rule',
|
|
80
80
|
workflows: 'workflow', routines: 'routine', profiles: 'profile',
|
|
81
|
-
permissions: 'permission',
|
|
81
|
+
permissions: 'permission', clis: 'clis',
|
|
82
82
|
};
|
|
83
83
|
/** [singular, plural] display labels per kind. */
|
|
84
84
|
const RESOURCE_LABELS = {
|
|
@@ -87,13 +87,13 @@ const RESOURCE_LABELS = {
|
|
|
87
87
|
subagent: ['subagent', 'subagents'], rule: ['rule', 'rules'],
|
|
88
88
|
workflow: ['workflow', 'workflows'], routine: ['routine', 'routines'],
|
|
89
89
|
profile: ['profile', 'profiles'], permission: ['permission', 'permissions'],
|
|
90
|
-
|
|
90
|
+
clis: ['CLI', 'CLIs'], config: ['config file', 'config files'],
|
|
91
91
|
other: ['other file', 'other files'],
|
|
92
92
|
};
|
|
93
93
|
/** Display order — the resources a user cares about most come first. */
|
|
94
94
|
const RESOURCE_ORDER = [
|
|
95
95
|
'skill', 'command', 'plugin', 'hook', 'mcp', 'subagent', 'rule',
|
|
96
|
-
'workflow', 'routine', 'profile', 'permission', '
|
|
96
|
+
'workflow', 'routine', 'profile', 'permission', 'clis', 'config', 'other',
|
|
97
97
|
];
|
|
98
98
|
/**
|
|
99
99
|
* Map a repo-relative path to the resource unit it belongs to. Directory-based
|
package/dist/commands/view.d.ts
CHANGED
|
@@ -32,7 +32,7 @@ export interface ViewSectionFilter {
|
|
|
32
32
|
rules?: boolean;
|
|
33
33
|
hooks?: boolean;
|
|
34
34
|
promptcuts?: boolean;
|
|
35
|
-
|
|
35
|
+
clis?: boolean;
|
|
36
36
|
}
|
|
37
37
|
/** Trim a description to a column-friendly snippet. Strips newlines, collapses whitespace. */
|
|
38
38
|
export declare function summarizeDescription(desc: string | undefined, maxLen?: number): string;
|
package/dist/commands/view.js
CHANGED
|
@@ -154,7 +154,7 @@ function getProjectVersionFromCwd(agent) {
|
|
|
154
154
|
return null;
|
|
155
155
|
}
|
|
156
156
|
}
|
|
157
|
-
const SECTION_KEYS = ['commands', 'skills', 'mcp', 'workflows', 'plugins', 'rules', 'hooks', 'promptcuts', '
|
|
157
|
+
const SECTION_KEYS = ['commands', 'skills', 'mcp', 'workflows', 'plugins', 'rules', 'hooks', 'promptcuts', 'clis'];
|
|
158
158
|
/**
|
|
159
159
|
* Decide whether a section should render given the filter. If no flags are set,
|
|
160
160
|
* everything renders (current behavior). If any flag is set, only those sections
|
|
@@ -231,7 +231,7 @@ function hostCliSourceTag(source) {
|
|
|
231
231
|
}
|
|
232
232
|
/**
|
|
233
233
|
* Render the host-CLI section. Host CLIs are host-global: declared in any
|
|
234
|
-
* DotAgents repo's `
|
|
234
|
+
* DotAgents repo's `clis/` (project > user > system > extras), installed to PATH
|
|
235
235
|
* rather than copied into a version home. They render identically in the overview
|
|
236
236
|
* and in a per-agent detail view because every agent on the host shares them.
|
|
237
237
|
* The source tag shows which repo layer declared each — so user-level and
|
|
@@ -241,7 +241,7 @@ function renderHostClisSection(cwd) {
|
|
|
241
241
|
const { statuses, errors } = listCliStatus(cwd);
|
|
242
242
|
console.log(chalk.bold('\nHost CLIs\n'));
|
|
243
243
|
if (statuses.length === 0) {
|
|
244
|
-
console.log(` ${chalk.gray('none declared')} ${chalk.gray('— add one with `agents
|
|
244
|
+
console.log(` ${chalk.gray('none declared')} ${chalk.gray('— add one with `agents clis add <name>`')}`);
|
|
245
245
|
}
|
|
246
246
|
else {
|
|
247
247
|
const nameWidth = Math.max(...statuses.map((s) => s.manifest.name.length));
|
|
@@ -258,7 +258,7 @@ function renderHostClisSection(cwd) {
|
|
|
258
258
|
console.log(prefix + desc);
|
|
259
259
|
}
|
|
260
260
|
if (anyMissing) {
|
|
261
|
-
console.log(chalk.gray(' Install missing with `agents
|
|
261
|
+
console.log(chalk.gray(' Install missing with `agents clis install`'));
|
|
262
262
|
}
|
|
263
263
|
}
|
|
264
264
|
for (const err of errors) {
|
|
@@ -1100,7 +1100,7 @@ async function showAgentResources(agentId, requestedVersion, filter) {
|
|
|
1100
1100
|
if (shouldRenderSection('promptcuts', filter)) {
|
|
1101
1101
|
renderPromptcuts();
|
|
1102
1102
|
}
|
|
1103
|
-
if (shouldRenderSection('
|
|
1103
|
+
if (shouldRenderSection('clis', filter)) {
|
|
1104
1104
|
renderHostClisSection(cwd);
|
|
1105
1105
|
}
|
|
1106
1106
|
// Show legend at the end if git repo exists and we showed all sections.
|
|
@@ -1565,7 +1565,7 @@ export async function viewAction(agentArg, options) {
|
|
|
1565
1565
|
rules: options?.rules,
|
|
1566
1566
|
hooks: options?.hooks,
|
|
1567
1567
|
promptcuts: options?.promptcuts,
|
|
1568
|
-
|
|
1568
|
+
clis: options?.clis,
|
|
1569
1569
|
};
|
|
1570
1570
|
const filterIsSet = SECTION_KEYS.some((k) => filter[k]);
|
|
1571
1571
|
// RUSH-1320: fold any stale literal `latest` version-home into its concrete
|
|
@@ -1684,7 +1684,7 @@ export function registerViewCommand(program) {
|
|
|
1684
1684
|
.option('--rules', 'Show only rules in the detail view.')
|
|
1685
1685
|
.option('--hooks', 'Show only hooks in the detail view.')
|
|
1686
1686
|
.option('--promptcuts', 'Show only promptcuts in the detail view.')
|
|
1687
|
-
.option('--
|
|
1687
|
+
.option('--clis', 'Show only host CLIs (declared in clis/, installed to PATH).')
|
|
1688
1688
|
.option('--merged', 'Show the merged, first-wins resource surface across all layers (project, user, extras, system) in one table with the winning layer per row.')
|
|
1689
1689
|
.addHelpText('after', `
|
|
1690
1690
|
Examples:
|
|
@@ -217,7 +217,7 @@ export function parseCliManifest(contents, opts) {
|
|
|
217
217
|
* manifests and any parse errors separately so the CLI can show both.
|
|
218
218
|
*/
|
|
219
219
|
export function listCliManifests(cwd) {
|
|
220
|
-
const resolved = listResources('
|
|
220
|
+
const resolved = listResources('clis', cwd);
|
|
221
221
|
const manifests = [];
|
|
222
222
|
const errors = [];
|
|
223
223
|
for (const entry of resolved) {
|
|
@@ -240,7 +240,7 @@ export function listCliManifests(cwd) {
|
|
|
240
240
|
}
|
|
241
241
|
/** Resolve a single CLI manifest by name. Returns null when not declared. */
|
|
242
242
|
export function resolveCliManifest(name, cwd) {
|
|
243
|
-
const resolved = resolveResource('
|
|
243
|
+
const resolved = resolveResource('clis', name, cwd);
|
|
244
244
|
if (!resolved)
|
|
245
245
|
return null;
|
|
246
246
|
if (!resolved.path.endsWith('.yaml') && !resolved.path.endsWith('.yml'))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The unified event reader -- one stream over BOTH operational events
|
|
3
|
-
* (`~/.agents/.history/events/
|
|
3
|
+
* (`~/.agents/.history/events/YYYY-MM-DD/` via events.ts: secrets, commands, teams, ...) and
|
|
4
4
|
* agent-semantic events (the per-session activity logs via activity.ts: plans,
|
|
5
5
|
* PRs, worktrees, sub-agents, artifacts). They share one {@link EventType}
|
|
6
6
|
* vocabulary and one {@link EventRecord} shape, so `agents events` and any
|