@phnx-labs/agents-cli 1.21.1 → 1.21.3
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 +206 -0
- package/README.md +1 -0
- package/dist/bin/agents +0 -0
- package/dist/commands/computer-actions.d.ts +4 -0
- package/dist/commands/computer-actions.js +34 -0
- package/dist/commands/computer.js +4 -2
- package/dist/commands/doctor.js +5 -2
- package/dist/commands/feed.js +28 -19
- package/dist/commands/hooks.js +9 -45
- package/dist/commands/menubar.js +24 -24
- package/dist/commands/message.js +23 -3
- package/dist/commands/perf.d.ts +29 -0
- package/dist/commands/perf.js +90 -23
- package/dist/commands/projects.d.ts +21 -0
- package/dist/commands/projects.js +169 -23
- package/dist/commands/routines.js +46 -1
- package/dist/commands/sessions-picker.js +17 -2
- package/dist/commands/sessions.d.ts +5 -0
- package/dist/commands/sessions.js +14 -0
- package/dist/commands/ssh.js +69 -0
- package/dist/commands/trends.d.ts +2 -0
- package/dist/commands/trends.js +158 -0
- package/dist/commands/usage.d.ts +4 -4
- package/dist/commands/view.d.ts +6 -0
- package/dist/commands/view.js +90 -45
- package/dist/index.js +22 -1
- package/dist/lib/agents.js +2 -2
- package/dist/lib/analytics/dashboard.d.ts +11 -0
- package/dist/lib/analytics/dashboard.js +31 -0
- package/dist/lib/analytics/recipes.d.ts +32 -0
- package/dist/lib/analytics/recipes.js +316 -0
- package/dist/lib/analytics/usage-db.d.ts +84 -0
- package/dist/lib/analytics/usage-db.js +301 -0
- package/dist/lib/browser/service.js +31 -0
- package/dist/lib/cli-resources.d.ts +20 -0
- package/dist/lib/cli-resources.js +48 -1
- package/dist/lib/computer/dispatch.d.ts +3 -1
- package/dist/lib/computer/dispatch.js +10 -2
- package/dist/lib/daemon.js +51 -14
- package/dist/lib/devices/health-report.d.ts +5 -0
- package/dist/lib/devices/health-report.js +3 -0
- package/dist/lib/event-stream.d.ts +2 -0
- package/dist/lib/event-stream.js +3 -0
- package/dist/lib/events.d.ts +3 -1
- package/dist/lib/events.js +4 -2
- package/dist/lib/feed-broadcast.d.ts +52 -7
- package/dist/lib/feed-broadcast.js +125 -18
- package/dist/lib/fleet-cache.d.ts +37 -0
- package/dist/lib/fleet-cache.js +40 -0
- package/dist/lib/fleet-status.d.ts +53 -0
- package/dist/lib/fleet-status.js +120 -0
- package/dist/lib/friction-heuristics.d.ts +32 -0
- package/dist/lib/friction-heuristics.js +47 -0
- package/dist/lib/git.d.ts +14 -0
- package/dist/lib/git.js +36 -0
- package/dist/lib/hooks/cache.js +28 -6
- package/dist/lib/hooks/profile.d.ts +8 -0
- package/dist/lib/hooks/profile.js +15 -18
- package/dist/lib/hooks.js +72 -17
- package/dist/lib/linear-cache.d.ts +63 -0
- package/dist/lib/linear-cache.js +146 -0
- package/dist/lib/linear-project-counts.d.ts +35 -5
- package/dist/lib/linear-project-counts.js +61 -16
- package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/Info.plist +3 -1
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/menubar/install-menubar.d.ts +7 -0
- package/dist/lib/menubar/install-menubar.js +36 -6
- package/dist/lib/percentile.d.ts +12 -0
- package/dist/lib/percentile.js +24 -0
- package/dist/lib/perf/db.d.ts +7 -3
- package/dist/lib/perf/db.js +37 -19
- package/dist/lib/perf/types.d.ts +10 -0
- package/dist/lib/plugins.js +12 -1
- package/dist/lib/project-doctor.d.ts +36 -0
- package/dist/lib/project-doctor.js +45 -0
- package/dist/lib/project-import.d.ts +11 -1
- package/dist/lib/project-import.js +17 -3
- package/dist/lib/project-status.d.ts +25 -5
- package/dist/lib/project-status.js +48 -6
- package/dist/lib/resources.d.ts +16 -0
- package/dist/lib/resources.js +25 -14
- package/dist/lib/rotate.d.ts +27 -0
- package/dist/lib/rotate.js +44 -17
- package/dist/lib/routines.d.ts +16 -0
- package/dist/lib/routines.js +26 -0
- package/dist/lib/runner.js +41 -0
- 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/usage-db.d.ts +3 -63
- package/dist/lib/secrets/usage-db.js +46 -186
- package/dist/lib/session/db.d.ts +17 -1
- package/dist/lib/session/db.js +240 -6
- package/dist/lib/session/discover.d.ts +31 -0
- package/dist/lib/session/discover.js +50 -1
- package/dist/lib/session/highlights.d.ts +24 -4
- package/dist/lib/session/highlights.js +52 -7
- package/dist/lib/session/parse.js +19 -3
- package/dist/lib/session/prompt.d.ts +19 -0
- package/dist/lib/session/prompt.js +43 -0
- package/dist/lib/session/types.d.ts +43 -0
- package/dist/lib/startup/command-registry.d.ts +1 -0
- package/dist/lib/startup/command-registry.js +2 -0
- package/dist/lib/state.d.ts +31 -3
- package/dist/lib/state.js +53 -10
- package/dist/lib/types.d.ts +22 -4
- package/dist/lib/usage-refresh.d.ts +106 -0
- package/dist/lib/usage-refresh.js +238 -0
- package/dist/lib/usage.d.ts +152 -17
- package/dist/lib/usage.js +393 -79
- package/package.json +1 -1
|
@@ -1,30 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* value-free row for every secret lifecycle/access event a bundle accrues over
|
|
6
|
-
* its life — created, imported, exported, viewed, accessed (read for injection),
|
|
7
|
-
* unlocked. It is the queryable, per-bundle counterpart to the append-only
|
|
8
|
-
* ~/.agents/events.jsonl audit log: the SAME chokepoint (`emitSecretAudit`,
|
|
9
|
-
* lib/secrets/audit.ts) feeds both, and this store answers "how often / how
|
|
10
|
-
* recently / by whom was THIS bundle used?" without scanning the whole event
|
|
11
|
-
* stream. It is a DERIVED index fed off the real access flow — the way
|
|
12
|
-
* sessions.db indexes session metadata — never a second write path an operation
|
|
13
|
-
* has to remember to call.
|
|
14
|
-
*
|
|
15
|
-
* Contract, mirroring the audit log: NEVER a secret value. Only metadata — the
|
|
16
|
-
* bundle name, the event kind, key counts, the resolving agent/host, a status.
|
|
17
|
-
*
|
|
18
|
-
* Every write is best-effort: a failure here (missing runtime SQLite, a locked
|
|
19
|
-
* db, a read-only fs) is swallowed so usage telemetry can never break secret
|
|
20
|
-
* resolution. Set AGENTS_NO_USAGE_TRACK=1 to disable recording entirely (used by
|
|
21
|
-
* tests and by callers that must stay perfectly silent).
|
|
2
|
+
* Secrets usage read-model — thin adapter over the analytics usage warehouse
|
|
3
|
+
* (`kind=secret`). Kept so `secrets view` / `list` / `activity` keep their API
|
|
4
|
+
* while the durable store lives at ~/.agents/.history/analytics/usage.db.
|
|
22
5
|
*/
|
|
23
|
-
import
|
|
24
|
-
import * as path from 'path';
|
|
25
|
-
import Database from '../sqlite.js';
|
|
26
|
-
import { getSecretsDbPath } from '../state.js';
|
|
27
|
-
/** All event kinds, in the order `view` prints them. */
|
|
6
|
+
import { recordUsage, getSecretBundleRollup, getSecretBundleAgents, getAllSecretBundleRollups, getSecretHistory, closeUsageDb, } from '../analytics/usage-db.js';
|
|
28
7
|
export const SECRET_USAGE_EVENTS = [
|
|
29
8
|
'access',
|
|
30
9
|
'unlock',
|
|
@@ -33,28 +12,6 @@ export const SECRET_USAGE_EVENTS = [
|
|
|
33
12
|
'create',
|
|
34
13
|
'view',
|
|
35
14
|
];
|
|
36
|
-
/** Events older than this are pruned on open so the history table stays bounded. */
|
|
37
|
-
const EVENT_RETENTION_MS = 90 * 24 * 60 * 60 * 1000;
|
|
38
|
-
const SCHEMA = `
|
|
39
|
-
CREATE TABLE IF NOT EXISTS usage_events (
|
|
40
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
41
|
-
ts TEXT NOT NULL,
|
|
42
|
-
bundle TEXT NOT NULL,
|
|
43
|
-
event TEXT NOT NULL,
|
|
44
|
-
agent TEXT,
|
|
45
|
-
host TEXT,
|
|
46
|
-
source TEXT,
|
|
47
|
-
status TEXT,
|
|
48
|
-
key_count INTEGER
|
|
49
|
-
);
|
|
50
|
-
CREATE INDEX IF NOT EXISTS idx_usage_bundle ON usage_events(bundle);
|
|
51
|
-
CREATE INDEX IF NOT EXISTS idx_usage_bundle_event ON usage_events(bundle, event);
|
|
52
|
-
CREATE INDEX IF NOT EXISTS idx_usage_ts ON usage_events(ts DESC);
|
|
53
|
-
`;
|
|
54
|
-
// Cached handle keyed by the resolved path, so a test that redirects
|
|
55
|
-
// AGENTS_SECRETS_DB to a fresh temp file transparently reopens instead of
|
|
56
|
-
// reusing a stale handle pointed at the previous path.
|
|
57
|
-
let cached = null;
|
|
58
15
|
function emptyEvents() {
|
|
59
16
|
return {
|
|
60
17
|
access: { count: 0, last: null },
|
|
@@ -65,68 +22,6 @@ function emptyEvents() {
|
|
|
65
22
|
view: { count: 0, last: null },
|
|
66
23
|
};
|
|
67
24
|
}
|
|
68
|
-
/**
|
|
69
|
-
* Open (creating if needed) the usage DB, returning null on any failure so
|
|
70
|
-
* every caller degrades to a no-op rather than throwing into secret resolution.
|
|
71
|
-
*/
|
|
72
|
-
function open() {
|
|
73
|
-
const dbPath = getSecretsDbPath();
|
|
74
|
-
if (cached && cached.path === dbPath)
|
|
75
|
-
return cached.db;
|
|
76
|
-
if (cached) {
|
|
77
|
-
try {
|
|
78
|
-
cached.db.close();
|
|
79
|
-
}
|
|
80
|
-
catch { /* ignore */ }
|
|
81
|
-
cached = null;
|
|
82
|
-
}
|
|
83
|
-
try {
|
|
84
|
-
fs.mkdirSync(path.dirname(dbPath), { recursive: true });
|
|
85
|
-
const db = new Database(dbPath);
|
|
86
|
-
// WAL + a busy timeout so concurrent agent runs writing usage rows don't
|
|
87
|
-
// fail each other under load; every write is still best-effort besides.
|
|
88
|
-
db.pragma('journal_mode = WAL');
|
|
89
|
-
db.pragma('busy_timeout = 2000');
|
|
90
|
-
db.exec(SCHEMA);
|
|
91
|
-
// Bounded retention: this is a usage history for operators, not a compliance
|
|
92
|
-
// log (that is events.jsonl). Prune once per open on a 90-day window.
|
|
93
|
-
try {
|
|
94
|
-
db.prepare(`DELETE FROM usage_events WHERE ts < ?`).run(new Date(Date.now() - EVENT_RETENTION_MS).toISOString());
|
|
95
|
-
}
|
|
96
|
-
catch { /* prune is best-effort */ }
|
|
97
|
-
cached = { path: dbPath, db };
|
|
98
|
-
return db;
|
|
99
|
-
}
|
|
100
|
-
catch {
|
|
101
|
-
return null;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
/**
|
|
105
|
-
* Record one usage event. Best-effort and value-free — swallows every error and
|
|
106
|
-
* honors AGENTS_NO_USAGE_TRACK so telemetry never blocks or slows a read. Rows
|
|
107
|
-
* with an empty bundle name are ignored (usage is per-bundle by definition).
|
|
108
|
-
*
|
|
109
|
-
* This is called from ONE place only — `emitSecretAudit` (lib/secrets/audit.ts)
|
|
110
|
-
* — so every recorded event has already been written to the events.jsonl audit
|
|
111
|
-
* log through the same chokepoint. Do not call it from a command handler; emit
|
|
112
|
-
* the audit event instead.
|
|
113
|
-
*/
|
|
114
|
-
export function recordSecretUsage(p) {
|
|
115
|
-
if (process.env.AGENTS_NO_USAGE_TRACK)
|
|
116
|
-
return;
|
|
117
|
-
if (!p.bundle)
|
|
118
|
-
return;
|
|
119
|
-
const db = open();
|
|
120
|
-
if (!db)
|
|
121
|
-
return;
|
|
122
|
-
try {
|
|
123
|
-
db.prepare(`INSERT INTO usage_events (ts, bundle, event, agent, host, source, status, key_count)
|
|
124
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(new Date().toISOString(), p.bundle, p.event, p.agent ?? null, p.host ?? null, p.source ?? null, p.status ?? 'success', p.keyCount ?? null);
|
|
125
|
-
}
|
|
126
|
-
catch {
|
|
127
|
-
// Telemetry must never break secret resolution.
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
25
|
function toSummary(bundle, rows, byAgent) {
|
|
131
26
|
const events = emptyEvents();
|
|
132
27
|
let total = 0;
|
|
@@ -146,91 +41,56 @@ function toSummary(bundle, rows, byAgent) {
|
|
|
146
41
|
}
|
|
147
42
|
return { bundle, total, events, lastUsedAt, firstUsedAt, byAgent };
|
|
148
43
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
44
|
+
export function recordSecretUsage(p) {
|
|
45
|
+
if (!p.bundle)
|
|
46
|
+
return;
|
|
47
|
+
const meta = {};
|
|
48
|
+
if (p.keyCount != null)
|
|
49
|
+
meta.keyCount = p.keyCount;
|
|
50
|
+
if (p.host)
|
|
51
|
+
meta.host = p.host;
|
|
52
|
+
recordUsage({
|
|
53
|
+
kind: 'secret',
|
|
54
|
+
name: p.bundle,
|
|
55
|
+
event: p.event,
|
|
56
|
+
agent: p.agent,
|
|
57
|
+
source: p.source,
|
|
58
|
+
status: p.status,
|
|
59
|
+
meta: Object.keys(meta).length ? meta : undefined,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
153
62
|
export function getBundleUsage(bundle) {
|
|
154
|
-
const
|
|
155
|
-
if (
|
|
63
|
+
const rows = getSecretBundleRollup(bundle);
|
|
64
|
+
if (rows.length === 0)
|
|
156
65
|
return undefined;
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
.prepare(`SELECT event, COUNT(*) AS n, MAX(ts) AS last, MIN(ts) AS first
|
|
160
|
-
FROM usage_events WHERE bundle = ? GROUP BY event`)
|
|
161
|
-
.all(bundle);
|
|
162
|
-
if (rows.length === 0)
|
|
163
|
-
return undefined;
|
|
164
|
-
const agents = db
|
|
165
|
-
.prepare(`SELECT agent, COUNT(*) AS n FROM usage_events
|
|
166
|
-
WHERE bundle = ? AND agent IS NOT NULL GROUP BY agent ORDER BY n DESC`)
|
|
167
|
-
.all(bundle);
|
|
168
|
-
return toSummary(bundle, rows, agents.map((a) => ({ agent: a.agent, count: a.n })));
|
|
169
|
-
}
|
|
170
|
-
catch {
|
|
171
|
-
return undefined;
|
|
172
|
-
}
|
|
66
|
+
const agents = getSecretBundleAgents(bundle);
|
|
67
|
+
return toSummary(bundle, rows, agents.map((a) => ({ agent: a.agent, count: a.n })));
|
|
173
68
|
}
|
|
174
|
-
/**
|
|
175
|
-
* Usage summaries for every bundle that has any recorded event, keyed by bundle
|
|
176
|
-
* name. Powers `secrets list --sort uses|used`. Empty map when the DB is
|
|
177
|
-
* unavailable or has no rows. Never throws.
|
|
178
|
-
*/
|
|
179
69
|
export function getAllBundleUsage() {
|
|
180
70
|
const out = new Map();
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
FROM usage_events GROUP BY bundle, event`)
|
|
188
|
-
.all();
|
|
189
|
-
const byBundle = new Map();
|
|
190
|
-
for (const r of rows) {
|
|
191
|
-
const list = byBundle.get(r.bundle) ?? [];
|
|
192
|
-
list.push(r);
|
|
193
|
-
byBundle.set(r.bundle, list);
|
|
194
|
-
}
|
|
195
|
-
for (const [bundle, list] of byBundle)
|
|
196
|
-
out.set(bundle, toSummary(bundle, list, []));
|
|
197
|
-
return out;
|
|
198
|
-
}
|
|
199
|
-
catch {
|
|
200
|
-
return out;
|
|
71
|
+
const rows = getAllSecretBundleRollups();
|
|
72
|
+
const byBundle = new Map();
|
|
73
|
+
for (const r of rows) {
|
|
74
|
+
const list = byBundle.get(r.name) ?? [];
|
|
75
|
+
list.push(r);
|
|
76
|
+
byBundle.set(r.name, list);
|
|
201
77
|
}
|
|
78
|
+
for (const [bundle, list] of byBundle)
|
|
79
|
+
out.set(bundle, toSummary(bundle, list, []));
|
|
80
|
+
return out;
|
|
202
81
|
}
|
|
203
|
-
/**
|
|
204
|
-
* Recent events for the `secrets activity` timeline — one bundle when named,
|
|
205
|
-
* else across all bundles — newest first. Empty when the DB is unavailable.
|
|
206
|
-
* Never throws.
|
|
207
|
-
*/
|
|
208
82
|
export function getUsageHistory(bundle, limit = 20) {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
? db.prepare(sql).all(bundle, limit)
|
|
220
|
-
: db.prepare(sql).all(limit);
|
|
221
|
-
return rows;
|
|
222
|
-
}
|
|
223
|
-
catch {
|
|
224
|
-
return [];
|
|
225
|
-
}
|
|
83
|
+
return getSecretHistory(bundle, limit).map((r) => ({
|
|
84
|
+
ts: r.ts,
|
|
85
|
+
bundle: r.bundle,
|
|
86
|
+
event: r.event,
|
|
87
|
+
agent: r.agent,
|
|
88
|
+
host: r.host,
|
|
89
|
+
source: r.source,
|
|
90
|
+
status: r.status,
|
|
91
|
+
keyCount: r.keyCount,
|
|
92
|
+
}));
|
|
226
93
|
}
|
|
227
|
-
/** Close the cached handle. Used by tests between temp-db swaps. */
|
|
228
94
|
export function closeSecretsUsageDb() {
|
|
229
|
-
|
|
230
|
-
try {
|
|
231
|
-
cached.db.close();
|
|
232
|
-
}
|
|
233
|
-
catch { /* ignore */ }
|
|
234
|
-
cached = null;
|
|
235
|
-
}
|
|
95
|
+
closeUsageDb();
|
|
236
96
|
}
|
package/dist/lib/session/db.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ import type { SessionAgentId, SessionMeta } from './types.js';
|
|
|
11
11
|
/** Current schema version; bumped when migrations are added. Exported so tests
|
|
12
12
|
* assert against the constant instead of hardcoding a number that every bump
|
|
13
13
|
* then has to chase (docs/05-sessions.md calls the constant the source of truth). */
|
|
14
|
-
export declare const SCHEMA_VERSION =
|
|
14
|
+
export declare const SCHEMA_VERSION = 24;
|
|
15
15
|
/** Raw row shape returned from the sessions table. */
|
|
16
16
|
export interface SessionRow {
|
|
17
17
|
id: string;
|
|
@@ -35,6 +35,7 @@ export interface SessionRow {
|
|
|
35
35
|
cost_usd: number | null;
|
|
36
36
|
duration_ms: number | null;
|
|
37
37
|
model: string | null;
|
|
38
|
+
tool_call_count: number | null;
|
|
38
39
|
file_path: string;
|
|
39
40
|
file_mtime_ms: number | null;
|
|
40
41
|
file_size: number | null;
|
|
@@ -53,6 +54,9 @@ export interface SessionRow {
|
|
|
53
54
|
linear_project_url: string | null;
|
|
54
55
|
actor: string | null;
|
|
55
56
|
initiated_by: string | null;
|
|
57
|
+
/** NULL means "not yet computed" (a row scanned before this field existed) — see rowToMeta. */
|
|
58
|
+
used_browser: number | null;
|
|
59
|
+
used_computer: number | null;
|
|
56
60
|
}
|
|
57
61
|
/** File stat snapshot used to detect changes between scan runs. */
|
|
58
62
|
export interface ScanStamp {
|
|
@@ -87,6 +91,18 @@ export interface QueryOptions {
|
|
|
87
91
|
* with NULLs sorted last so unpriced rows never crowd out real data.
|
|
88
92
|
*/
|
|
89
93
|
sortBy?: 'timestamp' | 'cost' | 'duration';
|
|
94
|
+
/**
|
|
95
|
+
* Only sessions that invoked this skill (#12), joined against
|
|
96
|
+
* session_resource_usage.kind='skill'. Matches either the full stored name
|
|
97
|
+
* (bare, or `plugin:name` for a plugin skill) or just the short name after
|
|
98
|
+
* the colon — `--skill design` finds a session that used `rush:design`.
|
|
99
|
+
*/
|
|
100
|
+
skill?: string;
|
|
101
|
+
/**
|
|
102
|
+
* Only sessions that used a skill or slash-command owned by this plugin
|
|
103
|
+
* (#12), joined against session_resource_usage.plugin.
|
|
104
|
+
*/
|
|
105
|
+
plugin?: string;
|
|
90
106
|
}
|
|
91
107
|
/** Open (or return the cached) sessions database, applying migrations as needed. */
|
|
92
108
|
export declare function getDB(): Database.Database;
|