@phnx-labs/agents-cli 1.20.34 → 1.20.35
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 +22 -0
- package/README.md +5 -1
- package/dist/commands/computer-actions.d.ts +15 -0
- package/dist/commands/computer-actions.js +120 -22
- package/dist/commands/cost.js +17 -12
- package/dist/commands/doctor.d.ts +1 -0
- package/dist/commands/doctor.js +59 -14
- package/dist/commands/events.d.ts +16 -0
- package/dist/commands/events.js +173 -0
- package/dist/commands/exec.js +6 -1
- package/dist/commands/hosts.js +28 -24
- package/dist/commands/inspect.d.ts +2 -0
- package/dist/commands/inspect.js +54 -7
- package/dist/commands/logs.d.ts +17 -0
- package/dist/commands/logs.js +139 -0
- package/dist/commands/models.d.ts +3 -0
- package/dist/commands/models.js +27 -8
- package/dist/commands/permissions.js +9 -2
- package/dist/commands/repo.d.ts +34 -0
- package/dist/commands/repo.js +243 -65
- package/dist/commands/resource-view.d.ts +20 -0
- package/dist/commands/resource-view.js +90 -28
- package/dist/commands/secrets.js +40 -16
- package/dist/commands/sessions-resume.d.ts +2 -0
- package/dist/commands/sessions-resume.js +209 -0
- package/dist/commands/sessions-tail.d.ts +10 -0
- package/dist/commands/sessions-tail.js +11 -0
- package/dist/commands/sessions.d.ts +73 -1
- package/dist/commands/sessions.js +273 -53
- package/dist/commands/view.d.ts +3 -0
- package/dist/commands/view.js +19 -8
- package/dist/commands/wallet.d.ts +6 -0
- package/dist/commands/wallet.js +22 -5
- package/dist/index.js +58 -31
- package/dist/lib/agents.js +30 -9
- package/dist/lib/browser/cdp.js +5 -1
- package/dist/lib/browser/drivers/ssh.d.ts +8 -0
- package/dist/lib/browser/drivers/ssh.js +76 -16
- package/dist/lib/browser/ipc.d.ts +3 -0
- package/dist/lib/browser/ipc.js +13 -9
- package/dist/lib/browser/service.js +71 -15
- package/dist/lib/daemon.d.ts +35 -0
- package/dist/lib/daemon.js +33 -5
- package/dist/lib/devices/connect.d.ts +3 -2
- package/dist/lib/devices/connect.js +5 -3
- package/dist/lib/events.d.ts +9 -2
- package/dist/lib/events.js +70 -11
- package/dist/lib/exec.js +22 -0
- package/dist/lib/hooks.js +8 -2
- package/dist/lib/hosts/dispatch.js +6 -7
- package/dist/lib/hosts/logs.d.ts +16 -0
- package/dist/lib/hosts/logs.js +45 -0
- package/dist/lib/hosts/progress.d.ts +66 -0
- package/dist/lib/hosts/progress.js +125 -17
- package/dist/lib/hosts/ready.d.ts +23 -2
- package/dist/lib/hosts/ready.js +35 -13
- package/dist/lib/hosts/reconcile.d.ts +53 -0
- package/dist/lib/hosts/reconcile.js +81 -0
- package/dist/lib/hosts/tasks.d.ts +8 -0
- package/dist/lib/hosts/tasks.js +14 -0
- package/dist/lib/permissions.d.ts +4 -0
- package/dist/lib/permissions.js +35 -0
- package/dist/lib/picker.d.ts +24 -0
- package/dist/lib/picker.js +124 -0
- package/dist/lib/plugin-marketplace.d.ts +30 -0
- package/dist/lib/plugin-marketplace.js +215 -2
- package/dist/lib/plugins.d.ts +5 -0
- package/dist/lib/plugins.js +45 -4
- package/dist/lib/session/active.d.ts +7 -0
- package/dist/lib/session/active.js +11 -3
- package/dist/lib/session/discover.js +94 -0
- package/dist/lib/session/parse.d.ts +6 -0
- package/dist/lib/session/parse.js +283 -2
- package/dist/lib/session/pid-registry.d.ts +21 -0
- package/dist/lib/session/pid-registry.js +80 -0
- package/dist/lib/session/remote-active.d.ts +26 -0
- package/dist/lib/session/remote-active.js +141 -0
- package/dist/lib/session/remote.js +2 -6
- package/dist/lib/session/render.js +1 -1
- package/dist/lib/session/sync/config.d.ts +8 -0
- package/dist/lib/session/sync/config.js +11 -2
- package/dist/lib/session/types.d.ts +1 -1
- package/dist/lib/session/types.js +1 -1
- package/dist/lib/shims.d.ts +11 -3
- package/dist/lib/shims.js +54 -30
- package/dist/lib/ssh-exec.d.ts +26 -3
- package/dist/lib/ssh-exec.js +45 -3
- package/dist/lib/ssh-tunnel.d.ts +24 -5
- package/dist/lib/ssh-tunnel.js +60 -62
- package/dist/lib/staleness/writers/hooks.js +1 -1
- package/dist/lib/startup/command-registry.d.ts +2 -0
- package/dist/lib/startup/command-registry.js +4 -0
- package/dist/lib/teams/registry.js +25 -9
- package/dist/lib/terminal/backends/ghostty.d.ts +6 -0
- package/dist/lib/terminal/backends/ghostty.js +69 -0
- package/dist/lib/terminal/backends/index.d.ts +18 -0
- package/dist/lib/terminal/backends/index.js +29 -0
- package/dist/lib/terminal/backends/iterm.d.ts +6 -0
- package/dist/lib/terminal/backends/iterm.js +62 -0
- package/dist/lib/terminal/backends/tmux.d.ts +14 -0
- package/dist/lib/terminal/backends/tmux.js +23 -0
- package/dist/lib/terminal/engine.d.ts +39 -0
- package/dist/lib/terminal/engine.js +54 -0
- package/dist/lib/terminal/index.d.ts +14 -0
- package/dist/lib/terminal/index.js +5 -0
- package/dist/lib/terminal/policy.d.ts +11 -0
- package/dist/lib/terminal/policy.js +11 -0
- package/dist/lib/terminal/quote.d.ts +11 -0
- package/dist/lib/terminal/quote.js +13 -0
- package/dist/lib/terminal/shell.d.ts +6 -0
- package/dist/lib/terminal/shell.js +23 -0
- package/dist/lib/terminal/transport.d.ts +15 -0
- package/dist/lib/terminal/transport.js +40 -0
- package/dist/lib/terminal/types.d.ts +59 -0
- package/dist/lib/terminal/types.js +13 -0
- package/dist/lib/types.d.ts +7 -0
- package/dist/lib/versions.d.ts +7 -3
- package/dist/lib/versions.js +10 -5
- package/dist/lib/whats-new.d.ts +9 -0
- package/dist/lib/whats-new.js +35 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 1.20.35
|
|
6
|
+
|
|
7
|
+
**CI: build node-pty's native binary on macOS/Windows so the release matrix is green cross-platform**
|
|
8
|
+
|
|
9
|
+
- The cross-platform matrix (`ci.yml`, runs only on `release/**` + `v*`) installed deps with `bun install --ignore-scripts`, so `pty.node` from `@homebridge/node-pty-prebuilt-multiarch` was never fetched/built. That package ships prebuilt binaries only for Linux; macOS/Windows obtain `pty.node` via its own install script (prebuild-install download, else a node-gyp compile). With that script skipped the native module was absent, so the daemon-liveness integration test added in #568 — which spawns the real daemon (it loads node-pty) and asserts the browser IPC socket stays up — crashed on macOS/Windows while passing on Linux, and had been red on every release since. The matrix runs only on release branches, so it never surfaced on normal PRs (bun does not run that install script even without `--ignore-scripts` in bun 1.3.x). CI now runs a dedicated step that invokes the package's own install script (`npm run install`), which prefers a prebuilt download and falls back to a node-gyp compile, so it self-heals across platforms and node ABIs. Production (`npm install`) already built the native module, so end users were unaffected. A second macOS/Windows-only failure in the same #568 daemon-liveness test was also fixed: the test rooted its fake `HOME` under `os.tmpdir()`, which on macOS is the long `/var/folders/…/T/…`, pushing the daemon's AF_UNIX socket path to ~116 bytes — past macOS's 104-byte `sun_path` limit — so `bind()` failed with `EADDRINUSE`. The test now roots `HOME` at a short base on POSIX (Windows uses length-unlimited named pipes); real users with a normal `HOME` were never affected. Source: `.github/workflows/ci.yml`, `src/lib/daemon.test.ts`.
|
|
10
|
+
|
|
11
|
+
**`agents logs`: a top-level, unified run-log viewer (#575)**
|
|
12
|
+
|
|
13
|
+
- Viewing a dispatched run's output used to be nested and undiscoverable — only `agents hosts logs <id>` and `agents daemon logs` existed, and `agents hosts` wasn't even in `--help`. `agents logs [id]` is now a discoverable top-level command that resolves a run across **two substrates** — host-dispatch task stdout (`agents run --host`) and the local session index — and shows or (`-f`) follows it. `[id]`/`--session` load directly (host task tried first, then session); with no id, `--host`/`--agent`/`--version` filter a merged candidate list (one match shows, several open a fuzzy picker, non-TTY prints the list). Additive: `agents hosts logs` and `agents sessions tail` are unchanged and share the same helpers. Source: `src/commands/logs.ts`, `src/lib/hosts/logs.ts`.
|
|
14
|
+
|
|
15
|
+
**Host-follow log tailer: no self-corruption on localhost, byte-accurate offsets (#586, #589)**
|
|
16
|
+
|
|
17
|
+
- Following a run dispatched to **localhost** tripled the on-disk log and triple-printed the output, because the local mirror file and the remote log were the same file and the tailer appended its own reads back into it; it now detects that aliasing by file identity (`dev:ino`) and echoes only. Separately, the offset tracker advanced by a re-encoded string length, so a multibyte UTF-8 char split at a poll boundary drifted the offset and corrupted the stream on non-ASCII output; the tail is now byte-exact (raw `Buffer` via `sshExecRaw`). Source: `src/lib/hosts/progress.ts`, `src/lib/ssh-exec.ts`.
|
|
18
|
+
|
|
19
|
+
**`agents upgrade`: the "What's new" changelog is now a compact heading list (#562)**
|
|
20
|
+
|
|
21
|
+
- The post-upgrade changelog dumped every heading *and* every verbose sub-bullet for each version in the range — a screenful across a multi-version jump. It now prints one bullet per feature/fix heading and links to the full CHANGELOG for the details. The parser was extracted to a pure, unit-tested `renderWhatsNew` so it can be exercised without the CLI's import-time side effects. Source: `src/lib/whats-new.ts`, `src/index.ts`.
|
|
22
|
+
|
|
23
|
+
**`agents sessions --active`: a per-pid registry de-collapses co-located agents (#546)**
|
|
24
|
+
|
|
25
|
+
- On a host with no terminal extension (bare SSH/tmux — e.g. any Linux box), `--active` could only map a discovered agent process to a session by guessing the newest `.jsonl` in its cwd, so several agents in the same repo collapsed onto one session row (observed live: a single id listed 28 times), and `/restore` couldn't tell them apart. `agents run` now records each launch to `~/.agents/.cache/terminals/by-pid/<pid>.json` (`{agent, cwd, tmuxPane, sessionId, startedAtMs}`) — the headless equivalent of the terminal extension's `live-terminals.json` — so `--active` and `/restore` attribute each co-located agent correctly. Source: `src/lib/session/pid-registry.ts`, `src/lib/session/active.ts`, `src/lib/exec.ts`.
|
|
26
|
+
|
|
5
27
|
## 1.20.34
|
|
6
28
|
|
|
7
29
|
**Test suite runs remotely on a crabbox VM (#525, #540)**
|
package/README.md
CHANGED
|
@@ -240,7 +240,9 @@ agents hosts add gpu-box
|
|
|
240
240
|
agents hosts check gpu-box # reachable? which agents-cli version?
|
|
241
241
|
|
|
242
242
|
# Run there instead of locally
|
|
243
|
-
agents run claude --host gpu-box "profile this build"
|
|
243
|
+
agents run claude --host gpu-box "profile this build" # follows live by default
|
|
244
|
+
agents logs --host gpu-box # pick a dispatched run and view its log
|
|
245
|
+
agents logs <id> -f # re-attach to a running one and follow
|
|
244
246
|
agents view claude --host gpu-box # inspect the remote install
|
|
245
247
|
agents sync --host gpu-box # make the remote machine current
|
|
246
248
|
|
|
@@ -252,6 +254,8 @@ agents ssh mac-mini # hardened SSH: fails fast if offline,
|
|
|
252
254
|
|
|
253
255
|
**Hosts** (`agents hosts`) are git-synced dispatch targets in `agents.yaml`; **devices** (`agents devices`) are your Tailscale machines in a local registry. Both ride SSH. See [docs/00-concepts.md](docs/00-concepts.md#devices--hosts).
|
|
254
256
|
|
|
257
|
+
Every `--host` command rides one multiplexed SSH engine, tuned for driving a fleet from a small laptop: the first call to a machine opens a control socket and every later call reuses it (no repeat TCP+auth handshake), connections carry keepalive so a dropped link dies in ~45 s instead of zombying, and following a remote run polls in a single round-trip per cycle. Measured against a Tailscale-relayed host: repeated calls **~6–7× faster**, dispatch readiness **~2×**, and the follow loop **~21× faster with 50% fewer local ssh spawns**. Design: [docs/09-ssh-transport.md](docs/09-ssh-transport.md) · reproduce: `node scripts/bench-ssh.mjs <host>`.
|
|
258
|
+
|
|
255
259
|
---
|
|
256
260
|
|
|
257
261
|
## Teams
|
|
@@ -55,4 +55,19 @@ export declare function buildWaitParams(opts: {
|
|
|
55
55
|
};
|
|
56
56
|
export declare function withClient<T>(fn: (client: ComputerClient) => Promise<T>): Promise<T>;
|
|
57
57
|
export declare function unwrap(r: RPCResponse): Record<string, unknown>;
|
|
58
|
+
export declare function resolveTargetPidDecision(client: ComputerClient, opts: {
|
|
59
|
+
pid?: number;
|
|
60
|
+
bundle?: string;
|
|
61
|
+
}, gate?: {
|
|
62
|
+
verb?: string;
|
|
63
|
+
env?: NodeJS.ProcessEnv;
|
|
64
|
+
nowMs?: number;
|
|
65
|
+
}): Promise<{
|
|
66
|
+
ok: true;
|
|
67
|
+
pid: number;
|
|
68
|
+
source: 'pid' | 'list_apps' | 'session_admission';
|
|
69
|
+
} | {
|
|
70
|
+
ok: false;
|
|
71
|
+
error: string;
|
|
72
|
+
}>;
|
|
58
73
|
export declare function registerActionCommands(program: Command): void;
|
|
@@ -6,7 +6,88 @@
|
|
|
6
6
|
// The daemon already implements every method; this file is the thin, typed
|
|
7
7
|
// CLI skin over it plus a shared target resolver so callers stay in bundle-id
|
|
8
8
|
// space and never hand-manage pids.
|
|
9
|
-
import
|
|
9
|
+
import * as fs from 'fs';
|
|
10
|
+
import * as path from 'path';
|
|
11
|
+
import { openComputerClient, describeTransport, resolvePolicyPath, } from '../lib/computer-rpc.js';
|
|
12
|
+
import { COMPUTER_INPUT_GATED_VERBS, formatComputerPermissionGrantHint, } from '../lib/permissions.js';
|
|
13
|
+
function isComputerInputVerb(verb) {
|
|
14
|
+
return typeof verb === 'string' && COMPUTER_INPUT_GATED_VERBS.includes(verb);
|
|
15
|
+
}
|
|
16
|
+
function computerSessionId(env = process.env) {
|
|
17
|
+
return env.CODEX_THREAD_ID
|
|
18
|
+
|| env.CLAUDE_CODE_SESSION_ID
|
|
19
|
+
|| env.CLAUDE_SESSION_ID
|
|
20
|
+
|| env.AGENTS_SESSION_ID
|
|
21
|
+
|| env.AGENTS_RUN_ID
|
|
22
|
+
|| null;
|
|
23
|
+
}
|
|
24
|
+
function admissionCachePath(env = process.env) {
|
|
25
|
+
if (env.AGENTS_COMPUTER_ADMISSION_CACHE)
|
|
26
|
+
return env.AGENTS_COMPUTER_ADMISSION_CACHE;
|
|
27
|
+
return path.join(path.dirname(resolvePolicyPath()), 'computer-target-admissions.json');
|
|
28
|
+
}
|
|
29
|
+
function targetSelector(opts) {
|
|
30
|
+
return opts.bundle ? `bundle:${opts.bundle}` : 'frontmost';
|
|
31
|
+
}
|
|
32
|
+
function readAdmissionCache(filePath) {
|
|
33
|
+
try {
|
|
34
|
+
const parsed = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
35
|
+
return Array.isArray(parsed.admissions) ? parsed.admissions.filter(isAdmission) : [];
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return [];
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function isAdmission(v) {
|
|
42
|
+
if (!v || typeof v !== 'object')
|
|
43
|
+
return false;
|
|
44
|
+
const r = v;
|
|
45
|
+
return r.sessionId !== undefined
|
|
46
|
+
&& typeof r.sessionId === 'string'
|
|
47
|
+
&& typeof r.selector === 'string'
|
|
48
|
+
&& r.gateClass === 'input'
|
|
49
|
+
&& typeof r.pid === 'number'
|
|
50
|
+
&& typeof r.bundle_id === 'string'
|
|
51
|
+
&& typeof r.name === 'string'
|
|
52
|
+
&& typeof r.admittedAtMs === 'number'
|
|
53
|
+
&& isComputerInputVerb(r.admittedByVerb);
|
|
54
|
+
}
|
|
55
|
+
function rememberAdmission(app, opts) {
|
|
56
|
+
const sessionId = computerSessionId(opts.env);
|
|
57
|
+
if (!sessionId)
|
|
58
|
+
return;
|
|
59
|
+
const filePath = admissionCachePath(opts.env);
|
|
60
|
+
const admissions = readAdmissionCache(filePath);
|
|
61
|
+
const next = {
|
|
62
|
+
sessionId,
|
|
63
|
+
selector: opts.selector,
|
|
64
|
+
gateClass: 'input',
|
|
65
|
+
pid: app.pid,
|
|
66
|
+
bundle_id: app.bundle_id,
|
|
67
|
+
name: app.name,
|
|
68
|
+
admittedAtMs: opts.nowMs ?? Date.now(),
|
|
69
|
+
admittedByVerb: opts.verb,
|
|
70
|
+
};
|
|
71
|
+
const filtered = admissions.filter((a) => !(a.sessionId === sessionId && a.selector === opts.selector && a.gateClass === 'input'));
|
|
72
|
+
filtered.push(next);
|
|
73
|
+
try {
|
|
74
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
75
|
+
fs.writeFileSync(filePath, JSON.stringify({ admissions: filtered }, null, 2), { mode: 0o600 });
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
// Cache persistence is best-effort; the daemon remains the final gate.
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function findAdmission(opts) {
|
|
82
|
+
const sessionId = computerSessionId(opts.env);
|
|
83
|
+
if (!sessionId)
|
|
84
|
+
return null;
|
|
85
|
+
const admissions = readAdmissionCache(admissionCachePath(opts.env));
|
|
86
|
+
const matches = admissions
|
|
87
|
+
.filter((a) => a.sessionId === sessionId && a.selector === opts.selector && a.gateClass === 'input')
|
|
88
|
+
.sort((a, b) => b.admittedAtMs - a.admittedAtMs);
|
|
89
|
+
return matches[0] ?? null;
|
|
90
|
+
}
|
|
10
91
|
// Pure target picker — exercised by unit tests. Precedence: explicit --pid,
|
|
11
92
|
// then --bundle, then the frontmost active allow-listed app (the same default
|
|
12
93
|
// `screenshot` uses). Kept side-effect-free so the resolution rules are
|
|
@@ -24,7 +105,7 @@ export function pickTarget(list, opts) {
|
|
|
24
105
|
if (!app) {
|
|
25
106
|
return {
|
|
26
107
|
ok: false,
|
|
27
|
-
error: `bundle not in allow list (or not running): ${opts.bundle}\
|
|
108
|
+
error: `bundle not in allow list (or not running): ${opts.bundle}\n${formatComputerPermissionGrantHint(opts.bundle)}`,
|
|
28
109
|
};
|
|
29
110
|
}
|
|
30
111
|
return { ok: true, app };
|
|
@@ -33,7 +114,7 @@ export function pickTarget(list, opts) {
|
|
|
33
114
|
if (!active) {
|
|
34
115
|
return {
|
|
35
116
|
ok: false,
|
|
36
|
-
error:
|
|
117
|
+
error: `no active app found in allow list\n${formatComputerPermissionGrantHint()}`,
|
|
37
118
|
};
|
|
38
119
|
}
|
|
39
120
|
return { ok: true, app: active };
|
|
@@ -141,20 +222,37 @@ export function unwrap(r) {
|
|
|
141
222
|
}
|
|
142
223
|
return r.result ?? {};
|
|
143
224
|
}
|
|
144
|
-
|
|
145
|
-
// and exiting when no target matches.
|
|
146
|
-
async function resolveTargetPid(client, opts) {
|
|
225
|
+
export async function resolveTargetPidDecision(client, opts, gate) {
|
|
147
226
|
// A directly-supplied pid skips the list_apps roundtrip — the daemon gates.
|
|
148
227
|
if (opts.pid != null)
|
|
149
|
-
return opts.pid;
|
|
228
|
+
return { ok: true, pid: opts.pid, source: 'pid' };
|
|
150
229
|
const apps = unwrap(await client.call('list_apps'));
|
|
151
230
|
const list = apps.apps || [];
|
|
152
231
|
const picked = pickTarget(list, opts);
|
|
232
|
+
const verb = gate?.verb;
|
|
233
|
+
const selector = targetSelector(opts);
|
|
234
|
+
if (picked.ok && isComputerInputVerb(verb)) {
|
|
235
|
+
rememberAdmission(picked.app, { selector, verb, env: gate?.env, nowMs: gate?.nowMs });
|
|
236
|
+
}
|
|
153
237
|
if (!picked.ok) {
|
|
154
|
-
|
|
238
|
+
if (isComputerInputVerb(verb)) {
|
|
239
|
+
const admitted = findAdmission({ selector, env: gate?.env });
|
|
240
|
+
if (admitted)
|
|
241
|
+
return { ok: true, pid: admitted.pid, source: 'session_admission' };
|
|
242
|
+
}
|
|
243
|
+
return { ok: false, error: picked.error };
|
|
244
|
+
}
|
|
245
|
+
return { ok: true, pid: picked.app.pid, source: 'list_apps' };
|
|
246
|
+
}
|
|
247
|
+
// Resolve the target pid via list_apps + pickTarget, printing a precise error
|
|
248
|
+
// and exiting when no target matches.
|
|
249
|
+
async function resolveTargetPid(client, opts, gate) {
|
|
250
|
+
const resolved = await resolveTargetPidDecision(client, opts, gate);
|
|
251
|
+
if (!resolved.ok) {
|
|
252
|
+
console.error(resolved.error);
|
|
155
253
|
process.exit(1);
|
|
156
254
|
}
|
|
157
|
-
return
|
|
255
|
+
return resolved.pid;
|
|
158
256
|
}
|
|
159
257
|
// --raise flag: app-level focus_window before the main action so coordinate
|
|
160
258
|
// clicks and keystrokes land on a visible, key window.
|
|
@@ -210,7 +308,7 @@ export function registerActionCommands(program) {
|
|
|
210
308
|
.option('--depth <n>', 'Max tree depth', (v) => parseInt(v, 10))
|
|
211
309
|
.option('--json', 'Emit compact JSON (default: pretty)')).action(async (opts) => {
|
|
212
310
|
await withClient(async (client) => {
|
|
213
|
-
const pid = await resolveTargetPid(client, opts);
|
|
311
|
+
const pid = await resolveTargetPid(client, opts, { verb: 'describe' });
|
|
214
312
|
const params = { pid };
|
|
215
313
|
if (opts.depth != null)
|
|
216
314
|
params.max_depth = opts.depth;
|
|
@@ -228,7 +326,7 @@ export function registerActionCommands(program) {
|
|
|
228
326
|
.option('--raise', 'Bring the target app to the front first')
|
|
229
327
|
.option('--json', 'Emit JSON'))).action(async (opts) => {
|
|
230
328
|
await withClient(async (client) => {
|
|
231
|
-
const pid = await resolveTargetPid(client, opts);
|
|
329
|
+
const pid = await resolveTargetPid(client, opts, { verb: 'click' });
|
|
232
330
|
const spec = buildElementOrCoords(opts);
|
|
233
331
|
if (!spec.ok) {
|
|
234
332
|
console.error(spec.error);
|
|
@@ -250,7 +348,7 @@ export function registerActionCommands(program) {
|
|
|
250
348
|
.description('Right-click (context menu) an element or coordinate')
|
|
251
349
|
.option('--json', 'Emit JSON'))).action(async (opts) => {
|
|
252
350
|
await withClient(async (client) => {
|
|
253
|
-
const pid = await resolveTargetPid(client, opts);
|
|
351
|
+
const pid = await resolveTargetPid(client, opts, { verb: 'right-click' });
|
|
254
352
|
const spec = buildElementOrCoords(opts);
|
|
255
353
|
if (!spec.ok) {
|
|
256
354
|
console.error(spec.error);
|
|
@@ -269,7 +367,7 @@ export function registerActionCommands(program) {
|
|
|
269
367
|
.option('--allow-secure-field', 'Permit typing into a password field')
|
|
270
368
|
.option('--json', 'Emit JSON'))).action(async (opts) => {
|
|
271
369
|
await withClient(async (client) => {
|
|
272
|
-
const pid = await resolveTargetPid(client, opts);
|
|
370
|
+
const pid = await resolveTargetPid(client, opts, { verb: 'type' });
|
|
273
371
|
const spec = buildElementOrCoords(opts);
|
|
274
372
|
if (!spec.ok) {
|
|
275
373
|
console.error(spec.error);
|
|
@@ -295,7 +393,7 @@ export function registerActionCommands(program) {
|
|
|
295
393
|
.option('--char-delay <ms>', 'Inter-character delay in ms (default 4; raise for lossy keyboard relays like VM guests, e.g. 25). Clamped to [1, 250].', (v) => parseInt(v, 10))
|
|
296
394
|
.option('--json', 'Emit JSON')).action(async (opts) => {
|
|
297
395
|
await withClient(async (client) => {
|
|
298
|
-
const pid = await resolveTargetPid(client, opts);
|
|
396
|
+
const pid = await resolveTargetPid(client, opts, { verb: 'type-text' });
|
|
299
397
|
await raiseIfRequested(client, pid, opts.raise);
|
|
300
398
|
const params = { pid, text: opts.text };
|
|
301
399
|
if (opts.commit)
|
|
@@ -319,7 +417,7 @@ export function registerActionCommands(program) {
|
|
|
319
417
|
.option('--require-frontmost', 'Fail (not warn) if the target is not the frontmost app')
|
|
320
418
|
.option('--json', 'Emit JSON')).action(async (opts) => {
|
|
321
419
|
await withClient(async (client) => {
|
|
322
|
-
const pid = await resolveTargetPid(client, opts);
|
|
420
|
+
const pid = await resolveTargetPid(client, opts, { verb: 'key' });
|
|
323
421
|
await raiseIfRequested(client, pid, opts.raise);
|
|
324
422
|
const params = { pid, keys: opts.keys };
|
|
325
423
|
if (opts.requireFrontmost)
|
|
@@ -350,7 +448,7 @@ export function registerActionCommands(program) {
|
|
|
350
448
|
process.exit(1);
|
|
351
449
|
}
|
|
352
450
|
await withClient(async (client) => {
|
|
353
|
-
const pid = await resolveTargetPid(client, opts);
|
|
451
|
+
const pid = await resolveTargetPid(client, opts, { verb: 'drag' });
|
|
354
452
|
await raiseIfRequested(client, pid, opts.raise);
|
|
355
453
|
const params = {
|
|
356
454
|
pid,
|
|
@@ -373,7 +471,7 @@ export function registerActionCommands(program) {
|
|
|
373
471
|
.option('--raise', 'Bring the target app to the front first')
|
|
374
472
|
.option('--json', 'Emit JSON'))).action(async (opts) => {
|
|
375
473
|
await withClient(async (client) => {
|
|
376
|
-
const pid = await resolveTargetPid(client, opts);
|
|
474
|
+
const pid = await resolveTargetPid(client, opts, { verb: 'scroll' });
|
|
377
475
|
await raiseIfRequested(client, pid, opts.raise);
|
|
378
476
|
const params = { pid };
|
|
379
477
|
if (opts.id)
|
|
@@ -398,7 +496,7 @@ export function registerActionCommands(program) {
|
|
|
398
496
|
.requiredOption('--action <name>', 'AX action name')
|
|
399
497
|
.option('--json', 'Emit JSON')).action(async (opts) => {
|
|
400
498
|
await withClient(async (client) => {
|
|
401
|
-
const pid = await resolveTargetPid(client, opts);
|
|
499
|
+
const pid = await resolveTargetPid(client, opts, { verb: 'ax-action' });
|
|
402
500
|
const res = unwrap(await client.call('ax_action', { pid, element_id: opts.id, action: opts.action }));
|
|
403
501
|
emit(res, Boolean(opts.json), () => `performed ${opts.action}`);
|
|
404
502
|
});
|
|
@@ -410,7 +508,7 @@ export function registerActionCommands(program) {
|
|
|
410
508
|
.requiredOption('--id <@eN>', 'Element id from `describe`')
|
|
411
509
|
.option('--json', 'Emit JSON')).action(async (opts) => {
|
|
412
510
|
await withClient(async (client) => {
|
|
413
|
-
const pid = await resolveTargetPid(client, opts);
|
|
511
|
+
const pid = await resolveTargetPid(client, opts, { verb: 'focus' });
|
|
414
512
|
const res = unwrap(await client.call('set_focus', { pid, element_id: opts.id }));
|
|
415
513
|
emit(res, Boolean(opts.json), () => `focused ${opts.id}`);
|
|
416
514
|
});
|
|
@@ -426,7 +524,7 @@ export function registerActionCommands(program) {
|
|
|
426
524
|
.option('--title <s>', 'Raise the window whose title contains this string')
|
|
427
525
|
.option('--json', 'Emit JSON')).action(async (opts) => {
|
|
428
526
|
await withClient(async (client) => {
|
|
429
|
-
const pid = await resolveTargetPid(client, opts);
|
|
527
|
+
const pid = await resolveTargetPid(client, opts, { verb: 'raise' });
|
|
430
528
|
const res = unwrap(await client.call('focus_window', { pid, ...buildRaiseParams(opts) }));
|
|
431
529
|
emit(res, Boolean(opts.json), () => {
|
|
432
530
|
const scope = res.raised_window ? `window ${res.title ?? res.window_id ?? ''}`.trim() : 'app';
|
|
@@ -455,7 +553,7 @@ export function registerActionCommands(program) {
|
|
|
455
553
|
const params = { ...spec.params };
|
|
456
554
|
// duration-only waits don't need a target pid
|
|
457
555
|
if (params.duration_ms == null)
|
|
458
|
-
params.pid = await resolveTargetPid(client, opts);
|
|
556
|
+
params.pid = await resolveTargetPid(client, opts, { verb: 'wait' });
|
|
459
557
|
const res = unwrap(await client.call('wait', params));
|
|
460
558
|
emit(res, Boolean(opts.json), () => res.satisfied ? `satisfied (${res.waited_ms}ms)` : `timed out (${res.waited_ms}ms)`);
|
|
461
559
|
});
|
|
@@ -468,7 +566,7 @@ export function registerActionCommands(program) {
|
|
|
468
566
|
.option('--max-chars <n>', 'Cap the extracted text length', (v) => parseInt(v, 10))
|
|
469
567
|
.option('--json', 'Emit JSON')).action(async (opts) => {
|
|
470
568
|
await withClient(async (client) => {
|
|
471
|
-
const pid = await resolveTargetPid(client, opts);
|
|
569
|
+
const pid = await resolveTargetPid(client, opts, { verb: 'get-text' });
|
|
472
570
|
const params = { pid };
|
|
473
571
|
if (opts.id)
|
|
474
572
|
params.element_id = opts.id;
|
package/dist/commands/cost.js
CHANGED
|
@@ -4,6 +4,7 @@ import { discoverSessions, parseTimeFilter } from '../lib/session/discover.js';
|
|
|
4
4
|
import { queryUsageRollup, topSessionsByCost, } from '../lib/session/db.js';
|
|
5
5
|
import { formatUsd, PRICING_VERSION } from '../lib/pricing/index.js';
|
|
6
6
|
import { formatDuration } from '../lib/session/render.js';
|
|
7
|
+
import { terminalWidth, truncateToWidth, stringWidth, padToWidth } from '../lib/session/width.js';
|
|
7
8
|
export function registerCostCommand(program) {
|
|
8
9
|
addHostOption(program.command('cost'))
|
|
9
10
|
.description('Roll up $ cost and duration across local agent sessions')
|
|
@@ -84,27 +85,36 @@ async function costAction(options) {
|
|
|
84
85
|
// Top sessions by cost.
|
|
85
86
|
if (top.length > 0) {
|
|
86
87
|
out.push(chalk.bold('Top sessions by cost'));
|
|
88
|
+
const cols = terminalWidth();
|
|
89
|
+
const showProject = cols >= 100;
|
|
87
90
|
const costW = Math.max(...top.map(t => formatUsd(t.costUsd).length), 4);
|
|
88
91
|
for (const t of top) {
|
|
89
92
|
const cost = formatUsd(t.costUsd).padStart(costW);
|
|
90
93
|
const dur = t.durationMs > 0 ? formatDuration(t.durationMs) : '—';
|
|
91
|
-
const label = t.meta.label || t.meta.topic || '(untitled)';
|
|
92
|
-
const proj = t.meta.project ? chalk.gray(` ${t.meta.project}`) : '';
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
94
|
+
const label = (t.meta.label || t.meta.topic || '(untitled)').replace(/\s+/g, ' ').trim();
|
|
95
|
+
const proj = showProject && t.meta.project ? chalk.gray(` ${t.meta.project}`) : '';
|
|
96
|
+
const prefix = ` ${chalk.green(cost)} ${chalk.gray(t.meta.shortId)} ${chalk.cyan(t.meta.agent.padEnd(7))} `;
|
|
97
|
+
const suffix = proj + chalk.gray(` ${dur}`);
|
|
98
|
+
const topicW = Math.max(12, cols - stringWidth(prefix) - stringWidth(suffix));
|
|
99
|
+
out.push(prefix + truncateToWidth(label, topicW) + suffix);
|
|
96
100
|
}
|
|
97
101
|
out.push('');
|
|
98
102
|
}
|
|
99
103
|
// Per-agent / per-project / per-day breakdown.
|
|
100
104
|
const groupLabel = groupBy === 'agent' ? 'agent' : groupBy === 'project' ? 'project' : 'day';
|
|
101
105
|
out.push(chalk.bold(`By ${groupLabel}`));
|
|
102
|
-
const
|
|
106
|
+
const cols = terminalWidth();
|
|
103
107
|
const costW2 = Math.max(...breakdown.map(r => formatUsd(r.costUsd).length), 4);
|
|
108
|
+
const countW = Math.max(...breakdown.map(r => String(r.sessionCount).length), 1);
|
|
109
|
+
const sessionW = Math.max(...breakdown.map(r => `${String(r.sessionCount).padStart(countW)} session${r.sessionCount !== 1 ? 's' : ''}`.length));
|
|
110
|
+
const durationW = Math.max(...breakdown.map(r => r.durationMs > 0 ? stringWidth(formatDuration(r.durationMs)) : 1), 1);
|
|
111
|
+
const fixedW = 2 + 2 + costW2 + 2 + sessionW + 2 + durationW;
|
|
112
|
+
const keyW = Math.max(8, Math.min(Math.max(...breakdown.map(r => stringWidth(r.key)), groupLabel.length), cols - fixedW));
|
|
104
113
|
for (const r of breakdown) {
|
|
105
114
|
const cost = formatUsd(r.costUsd).padStart(costW2);
|
|
106
115
|
const dur = r.durationMs > 0 ? formatDuration(r.durationMs) : '—';
|
|
107
|
-
|
|
116
|
+
const sessions = `${String(r.sessionCount).padStart(countW)} session${r.sessionCount !== 1 ? 's' : ''}`;
|
|
117
|
+
out.push(` ${padToWidth(truncateToWidth(r.key, keyW), keyW)} ${chalk.green(cost)} ${chalk.gray(padToWidth(sessions, sessionW))} ${chalk.gray(padToWidth(dur, durationW))}`);
|
|
108
118
|
}
|
|
109
119
|
console.log(out.join('\n'));
|
|
110
120
|
}
|
|
@@ -132,8 +142,3 @@ function renderDailyHistogram(daily) {
|
|
|
132
142
|
}
|
|
133
143
|
return lines.join('\n');
|
|
134
144
|
}
|
|
135
|
-
/** Truncate a string to n chars with an ellipsis. */
|
|
136
|
-
function truncate(s, n) {
|
|
137
|
-
const oneLine = s.replace(/\s+/g, ' ').trim();
|
|
138
|
-
return oneLine.length > n ? oneLine.slice(0, n - 1) + '…' : oneLine;
|
|
139
|
-
}
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
* apply pending sync.
|
|
23
23
|
*/
|
|
24
24
|
import type { Command } from 'commander';
|
|
25
|
+
export declare function wrapLine(prefix: string, text: string, width?: number): string[];
|
|
25
26
|
/**
|
|
26
27
|
* Windows-only advisory lines. When the effective PowerShell execution policy
|
|
27
28
|
* blocks unsigned local `.ps1` scripts (`Restricted`/`AllSigned`), the generated
|
package/dist/commands/doctor.js
CHANGED
|
@@ -13,6 +13,7 @@ import { listCliStatus } from '../lib/cli-resources.js';
|
|
|
13
13
|
import { setHelpSections } from '../lib/help.js';
|
|
14
14
|
import { heal, healChangedAnything } from '../lib/heal.js';
|
|
15
15
|
import { blocksLocalScripts, getEffectiveExecutionPolicy } from '../lib/platform/winpath.js';
|
|
16
|
+
import { terminalWidth, truncateToWidth, stringWidth, padToWidth } from '../lib/session/width.js';
|
|
16
17
|
import * as fs from 'fs';
|
|
17
18
|
const AGENT_NAMES = Object.fromEntries(ALL_AGENT_IDS.map((id) => [id, AGENTS[id].name]));
|
|
18
19
|
// ─── overview mode (no target) ────────────────────────────────────────────────
|
|
@@ -29,7 +30,6 @@ function divergenceLines(report) {
|
|
|
29
30
|
else if (p.status === 'diff')
|
|
30
31
|
lines.push(`plugin ${p.name} — ${p.detail ?? 'mirror drifted'}`);
|
|
31
32
|
}
|
|
32
|
-
const counts = [];
|
|
33
33
|
for (const kind of ['commands', 'skills', 'hooks', 'rules', 'mcp', 'permissions', 'subagents']) {
|
|
34
34
|
const rows = report.kinds[kind];
|
|
35
35
|
const miss = rows.filter((r) => r.status === 'missing').length;
|
|
@@ -40,12 +40,44 @@ function divergenceLines(report) {
|
|
|
40
40
|
if (dif)
|
|
41
41
|
bits.push(`${dif} drifted`);
|
|
42
42
|
if (bits.length)
|
|
43
|
-
|
|
43
|
+
lines.push(`${kind.padEnd(11)} ${bits.join(' · ')}`);
|
|
44
44
|
}
|
|
45
|
-
if (counts.length)
|
|
46
|
-
lines.push(counts.join(' · '));
|
|
47
45
|
return lines;
|
|
48
46
|
}
|
|
47
|
+
function collapseWhitespace(s) {
|
|
48
|
+
return s.replace(/\s+/g, ' ').trim();
|
|
49
|
+
}
|
|
50
|
+
export function wrapLine(prefix, text, width = terminalWidth()) {
|
|
51
|
+
const words = collapseWhitespace(text).split(' ').filter(Boolean);
|
|
52
|
+
if (words.length === 0)
|
|
53
|
+
return [prefix.trimEnd()];
|
|
54
|
+
const continuation = ' '.repeat(stringWidth(prefix));
|
|
55
|
+
const lines = [];
|
|
56
|
+
let linePrefix = prefix;
|
|
57
|
+
let line = prefix;
|
|
58
|
+
let hasWord = false;
|
|
59
|
+
for (const word of words) {
|
|
60
|
+
const room = Math.max(1, width - stringWidth(linePrefix));
|
|
61
|
+
const piece = stringWidth(word) > room ? truncateToWidth(word, room) : word;
|
|
62
|
+
const candidate = hasWord ? `${line} ${piece}` : `${line}${piece}`;
|
|
63
|
+
if (hasWord && stringWidth(candidate) > width) {
|
|
64
|
+
lines.push(line);
|
|
65
|
+
linePrefix = continuation;
|
|
66
|
+
line = continuation + piece;
|
|
67
|
+
hasWord = true;
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
line = candidate;
|
|
71
|
+
hasWord = true;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
lines.push(line);
|
|
75
|
+
return lines;
|
|
76
|
+
}
|
|
77
|
+
function printWrappedLine(prefix, text) {
|
|
78
|
+
for (const line of wrapLine(prefix, text))
|
|
79
|
+
console.log(chalk.gray(line));
|
|
80
|
+
}
|
|
49
81
|
function checkSyncStatus(cwd) {
|
|
50
82
|
const rows = [];
|
|
51
83
|
// Every installed version, not just the default — a stale NON-default version
|
|
@@ -130,7 +162,7 @@ function renderOverviewText(clis, syncRows, orphanRows, hostClis) {
|
|
|
130
162
|
}
|
|
131
163
|
}
|
|
132
164
|
if (hidden.length > 0) {
|
|
133
|
-
|
|
165
|
+
printWrappedLine(' ', `+${hidden.length} more supported (${hidden.join(', ')}) — \`agents add <name>\` to manage`);
|
|
134
166
|
}
|
|
135
167
|
console.log();
|
|
136
168
|
console.log(chalk.bold('Sync status (installed versions)'));
|
|
@@ -162,7 +194,7 @@ function renderOverviewText(clis, syncRows, orphanRows, hostClis) {
|
|
|
162
194
|
// Version homes are reconciled only by management commands, so point at one
|
|
163
195
|
// rather than promising an auto-sync that never happens.
|
|
164
196
|
if (anyOutOfSync) {
|
|
165
|
-
|
|
197
|
+
printWrappedLine(' ', 'Reconcile with `agents doctor <agent>@<version> --fix` or `agents sync <agent>@<version>` (not applied on launch).');
|
|
166
198
|
}
|
|
167
199
|
}
|
|
168
200
|
console.log();
|
|
@@ -199,10 +231,17 @@ function renderOverviewText(clis, syncRows, orphanRows, hostClis) {
|
|
|
199
231
|
const label = manifest.name.padEnd(nameWidth);
|
|
200
232
|
const src = chalk.gray(`[${manifest.source}]`);
|
|
201
233
|
if (installed) {
|
|
202
|
-
|
|
234
|
+
const prefix = ` ${chalk.green('ready')} ${label} ${src}`;
|
|
235
|
+
const desc = manifest.description
|
|
236
|
+
? ` ${truncateToWidth(collapseWhitespace(manifest.description), Math.max(1, terminalWidth() - stringWidth(prefix) - 2))}`
|
|
237
|
+
: '';
|
|
238
|
+
console.log(prefix + chalk.gray(desc));
|
|
203
239
|
}
|
|
204
240
|
else {
|
|
205
|
-
|
|
241
|
+
const prefix = ` ${chalk.red('miss ')} ${label} ${src}`;
|
|
242
|
+
const msg = `not installed — run \`agents cli install ${manifest.name}\``;
|
|
243
|
+
const budget = Math.max(1, terminalWidth() - stringWidth(prefix) - 2);
|
|
244
|
+
console.log(prefix + chalk.gray(` ${truncateToWidth(msg, budget)}`));
|
|
206
245
|
}
|
|
207
246
|
}
|
|
208
247
|
}
|
|
@@ -357,8 +396,12 @@ function renderKindSection(kind, rows, layers, options) {
|
|
|
357
396
|
}
|
|
358
397
|
for (const r of visible) {
|
|
359
398
|
const src = sourceLabel(r, layers);
|
|
360
|
-
const
|
|
361
|
-
|
|
399
|
+
const name = padToWidth(truncateToWidth(r.name, 28), 28);
|
|
400
|
+
const prefix = ` ${statusLabel(r.status)} ${name} ${src}`;
|
|
401
|
+
const detail = r.detail
|
|
402
|
+
? chalk.gray(` ${truncateToWidth(collapseWhitespace(r.detail), Math.max(1, terminalWidth() - stringWidth(prefix) - 2))}`)
|
|
403
|
+
: '';
|
|
404
|
+
console.log(prefix + detail);
|
|
362
405
|
if (options.showDiff && r.status === 'diff' && r.sourcePath && r.homePath) {
|
|
363
406
|
const expected = readExpectedForDiff(kind, r);
|
|
364
407
|
const actual = safeRead(r.homePath);
|
|
@@ -395,8 +438,10 @@ function readExpectedForDiff(kind, row) {
|
|
|
395
438
|
function renderTargetText(report, options) {
|
|
396
439
|
const label = `${AGENT_NAMES[report.agent] || report.agent}@${report.version}`;
|
|
397
440
|
console.log(chalk.bold(label));
|
|
398
|
-
|
|
399
|
-
|
|
441
|
+
const homePrefix = ' home: ';
|
|
442
|
+
const cwdPrefix = ' cwd: ';
|
|
443
|
+
console.log(chalk.gray(homePrefix + truncateToWidth(report.home, Math.max(1, terminalWidth() - stringWidth(homePrefix)))));
|
|
444
|
+
console.log(chalk.gray(cwdPrefix + truncateToWidth(report.cwd, Math.max(1, terminalWidth() - stringWidth(cwdPrefix)))));
|
|
400
445
|
const layerStr = [
|
|
401
446
|
report.layers.project ? `project=${report.layers.project}` : null,
|
|
402
447
|
`user=${report.layers.user}`,
|
|
@@ -405,7 +450,7 @@ function renderTargetText(report, options) {
|
|
|
405
450
|
? `extras=[${report.layers.extras.map((e) => e.alias).join(',')}]`
|
|
406
451
|
: null,
|
|
407
452
|
].filter(Boolean).join(' ');
|
|
408
|
-
|
|
453
|
+
printWrappedLine(' layers: ', layerStr);
|
|
409
454
|
// Staleness manifest verdict — single-line summary from the staleness
|
|
410
455
|
// library, sitting alongside the detailed per-resource diff below.
|
|
411
456
|
const manifest = loadManifest(report.agent, report.version);
|
|
@@ -446,7 +491,7 @@ function renderTargetText(report, options) {
|
|
|
446
491
|
}
|
|
447
492
|
else {
|
|
448
493
|
console.log(` Verdict: ${verdictParts.join(', ')}.`);
|
|
449
|
-
|
|
494
|
+
printWrappedLine(' ', `Run \`agents doctor ${report.agent}@${report.version} --fix\` to heal, or \`agents prune cleanup\` to drop extras.`);
|
|
450
495
|
}
|
|
451
496
|
}
|
|
452
497
|
// ─── fix / heal mode ───────────────────────────────────────────────────────────
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `agents events` — read the structured audit/event log.
|
|
3
|
+
*
|
|
4
|
+
* The event log (`~/.agents/.cache/logs/events-YYYY-MM-DD.jsonl`) records every
|
|
5
|
+
* `agents <module> <cmd>` invocation plus richer typed events (secrets access,
|
|
6
|
+
* version installs, ...), each stamped with who ran it and from where (OS user,
|
|
7
|
+
* local vs SSH, remote client IP). This command reads it back — the audit trail
|
|
8
|
+
* for "who accessed a secret / created a team / started an agent, and from which
|
|
9
|
+
* host?".
|
|
10
|
+
*
|
|
11
|
+
* Filter by `--module` (top-level group, e.g. teams), `--command` (path prefix,
|
|
12
|
+
* e.g. "teams create"), `--event` (typed event), `--agent`, and `--since`.
|
|
13
|
+
* `--follow` tails today's log live.
|
|
14
|
+
*/
|
|
15
|
+
import type { Command } from 'commander';
|
|
16
|
+
export declare function registerEventsCommand(program: Command): void;
|