@phnx-labs/agents-cli 1.20.92 → 1.20.93
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 +121 -0
- package/dist/bin/agents +0 -0
- package/dist/commands/events.js +91 -1
- package/dist/commands/projects.d.ts +10 -0
- package/dist/commands/projects.js +189 -8
- package/dist/commands/secrets.d.ts +17 -0
- package/dist/commands/secrets.js +198 -7
- package/dist/commands/send.d.ts +14 -12
- package/dist/commands/send.js +105 -35
- package/dist/commands/sync.js +9 -3
- package/dist/commands/view.js +4 -0
- package/dist/index.js +16 -0
- package/dist/lib/activity.d.ts +8 -0
- package/dist/lib/activity.js +7 -0
- package/dist/lib/channels/send.d.ts +83 -0
- package/dist/lib/channels/send.js +112 -0
- package/dist/lib/events-ingest.d.ts +46 -0
- package/dist/lib/events-ingest.js +182 -0
- package/dist/lib/events.d.ts +15 -3
- package/dist/lib/events.js +55 -3
- package/dist/lib/linear-project-counts.d.ts +62 -0
- package/dist/lib/linear-project-counts.js +122 -0
- package/dist/lib/linear-projects.d.ts +50 -0
- package/dist/lib/linear-projects.js +114 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/menubar/notify-desktop.d.ts +17 -2
- package/dist/lib/menubar/notify-desktop.js +8 -2
- package/dist/lib/project-probe.d.ts +75 -0
- package/dist/lib/project-probe.js +160 -0
- package/dist/lib/project-resources.d.ts +8 -0
- package/dist/lib/project-resources.js +31 -3
- package/dist/lib/project-status.d.ts +32 -1
- package/dist/lib/project-status.js +82 -1
- package/dist/lib/projects.d.ts +6 -0
- package/dist/lib/projects.js +12 -0
- package/dist/lib/routine-notify.d.ts +11 -0
- package/dist/lib/routine-notify.js +22 -0
- package/dist/lib/run-notify.js +3 -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/audit.d.ts +1 -1
- package/dist/lib/secrets/audit.js +53 -10
- package/dist/lib/secrets/list-filter.d.ts +20 -5
- package/dist/lib/secrets/list-filter.js +22 -6
- package/dist/lib/secrets/usage-db.d.ts +106 -0
- package/dist/lib/secrets/usage-db.js +236 -0
- package/dist/lib/session/remote-active.d.ts +5 -1
- package/dist/lib/session/remote-active.js +4 -1
- package/dist/lib/sqlite.js +28 -1
- package/dist/lib/state.d.ts +12 -0
- package/dist/lib/state.js +14 -0
- package/dist/lib/types.d.ts +5 -4
- package/dist/lib/versions.d.ts +6 -0
- package/dist/lib/versions.js +6 -4
- package/package.json +1 -1
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SQLite-backed usage read-model for `agents secrets`.
|
|
3
|
+
*
|
|
4
|
+
* A small local database at ~/.agents/secrets/secrets.db that records one
|
|
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).
|
|
22
|
+
*/
|
|
23
|
+
import * as fs from 'fs';
|
|
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. */
|
|
28
|
+
export const SECRET_USAGE_EVENTS = [
|
|
29
|
+
'access',
|
|
30
|
+
'unlock',
|
|
31
|
+
'import',
|
|
32
|
+
'export',
|
|
33
|
+
'create',
|
|
34
|
+
'view',
|
|
35
|
+
];
|
|
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
|
+
function emptyEvents() {
|
|
59
|
+
return {
|
|
60
|
+
access: { count: 0, last: null },
|
|
61
|
+
unlock: { count: 0, last: null },
|
|
62
|
+
import: { count: 0, last: null },
|
|
63
|
+
export: { count: 0, last: null },
|
|
64
|
+
create: { count: 0, last: null },
|
|
65
|
+
view: { count: 0, last: null },
|
|
66
|
+
};
|
|
67
|
+
}
|
|
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
|
+
function toSummary(bundle, rows, byAgent) {
|
|
131
|
+
const events = emptyEvents();
|
|
132
|
+
let total = 0;
|
|
133
|
+
let lastUsedAt = null;
|
|
134
|
+
let firstUsedAt = null;
|
|
135
|
+
for (const r of rows) {
|
|
136
|
+
if (r.event in events) {
|
|
137
|
+
const stat = events[r.event];
|
|
138
|
+
stat.count = r.n;
|
|
139
|
+
stat.last = r.last;
|
|
140
|
+
}
|
|
141
|
+
total += r.n;
|
|
142
|
+
if (r.last && (!lastUsedAt || r.last > lastUsedAt))
|
|
143
|
+
lastUsedAt = r.last;
|
|
144
|
+
if (r.first && (!firstUsedAt || r.first < firstUsedAt))
|
|
145
|
+
firstUsedAt = r.first;
|
|
146
|
+
}
|
|
147
|
+
return { bundle, total, events, lastUsedAt, firstUsedAt, byAgent };
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Usage summary for one bundle, or undefined when nothing has ever been
|
|
151
|
+
* recorded (or the DB is unavailable). Never throws.
|
|
152
|
+
*/
|
|
153
|
+
export function getBundleUsage(bundle) {
|
|
154
|
+
const db = open();
|
|
155
|
+
if (!db)
|
|
156
|
+
return undefined;
|
|
157
|
+
try {
|
|
158
|
+
const rows = db
|
|
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
|
+
}
|
|
173
|
+
}
|
|
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
|
+
export function getAllBundleUsage() {
|
|
180
|
+
const out = new Map();
|
|
181
|
+
const db = open();
|
|
182
|
+
if (!db)
|
|
183
|
+
return out;
|
|
184
|
+
try {
|
|
185
|
+
const rows = db
|
|
186
|
+
.prepare(`SELECT bundle, event, COUNT(*) AS n, MAX(ts) AS last, MIN(ts) AS first
|
|
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;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
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
|
+
export function getUsageHistory(bundle, limit = 20) {
|
|
209
|
+
const db = open();
|
|
210
|
+
if (!db)
|
|
211
|
+
return [];
|
|
212
|
+
try {
|
|
213
|
+
const sql = bundle
|
|
214
|
+
? `SELECT ts, bundle, event, agent, host, source, status, key_count AS keyCount
|
|
215
|
+
FROM usage_events WHERE bundle = ? ORDER BY ts DESC, id DESC LIMIT ?`
|
|
216
|
+
: `SELECT ts, bundle, event, agent, host, source, status, key_count AS keyCount
|
|
217
|
+
FROM usage_events ORDER BY ts DESC, id DESC LIMIT ?`;
|
|
218
|
+
const rows = bundle
|
|
219
|
+
? db.prepare(sql).all(bundle, limit)
|
|
220
|
+
: db.prepare(sql).all(limit);
|
|
221
|
+
return rows;
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
return [];
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
/** Close the cached handle. Used by tests between temp-db swaps. */
|
|
228
|
+
export function closeSecretsUsageDb() {
|
|
229
|
+
if (cached) {
|
|
230
|
+
try {
|
|
231
|
+
cached.db.close();
|
|
232
|
+
}
|
|
233
|
+
catch { /* ignore */ }
|
|
234
|
+
cached = null;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
@@ -25,5 +25,9 @@ export interface RemoteActiveResult {
|
|
|
25
25
|
* (from `--host`), fan out to exactly those. Otherwise sweep the registered,
|
|
26
26
|
* online devices from `ag devices`, excluding this machine and any without an
|
|
27
27
|
* address. Results from all peers run in parallel and are flattened.
|
|
28
|
+
* `opts.quiet` suppresses the per-device stderr line for callers that report
|
|
29
|
+
* skipped peers once, compactly, themselves.
|
|
28
30
|
*/
|
|
29
|
-
export declare function gatherRemoteActive(hosts?: string[]
|
|
31
|
+
export declare function gatherRemoteActive(hosts?: string[], opts?: {
|
|
32
|
+
quiet?: boolean;
|
|
33
|
+
}): Promise<RemoteActiveResult>;
|
|
@@ -56,13 +56,16 @@ export function parseRemoteActive(stdout, machine) {
|
|
|
56
56
|
* (from `--host`), fan out to exactly those. Otherwise sweep the registered,
|
|
57
57
|
* online devices from `ag devices`, excluding this machine and any without an
|
|
58
58
|
* address. Results from all peers run in parallel and are flattened.
|
|
59
|
+
* `opts.quiet` suppresses the per-device stderr line for callers that report
|
|
60
|
+
* skipped peers once, compactly, themselves.
|
|
59
61
|
*/
|
|
60
|
-
export async function gatherRemoteActive(hosts) {
|
|
62
|
+
export async function gatherRemoteActive(hosts, opts) {
|
|
61
63
|
const result = await gatherRemoteAgentsJson({
|
|
62
64
|
args: ['sessions', '--active', '--json'],
|
|
63
65
|
noFanoutEnv: NO_FANOUT_ENV,
|
|
64
66
|
hosts,
|
|
65
67
|
parse: parseRemoteActive,
|
|
68
|
+
quiet: opts?.quiet,
|
|
66
69
|
});
|
|
67
70
|
return { sessions: result.items, deviceCount: result.deviceCount };
|
|
68
71
|
}
|
package/dist/lib/sqlite.js
CHANGED
|
@@ -14,11 +14,38 @@
|
|
|
14
14
|
import { createRequire } from 'module';
|
|
15
15
|
const isBun = typeof globalThis.Bun !== 'undefined';
|
|
16
16
|
const require = createRequire(import.meta.url);
|
|
17
|
+
// node:sqlite emits a process-level ExperimentalWarning the first time it loads.
|
|
18
|
+
// The packaged CLI launches Node with --no-warnings=ExperimentalWarning, but a
|
|
19
|
+
// direct `node dist/...` run (and vitest's subprocesses) does not, so the warning
|
|
20
|
+
// would leak onto stderr and break any command whose --json output is asserted to
|
|
21
|
+
// be clean. Suppress only that single warning for the duration of the load; every
|
|
22
|
+
// other warning passes through untouched.
|
|
23
|
+
function loadNodeSqlite() {
|
|
24
|
+
const original = process.emitWarning;
|
|
25
|
+
const filtered = ((warning, ...rest) => {
|
|
26
|
+
const name = warning instanceof Error
|
|
27
|
+
? warning.name
|
|
28
|
+
: typeof rest[0] === 'string'
|
|
29
|
+
? rest[0]
|
|
30
|
+
: rest[0]?.type;
|
|
31
|
+
const message = warning instanceof Error ? warning.message : warning;
|
|
32
|
+
if (name === 'ExperimentalWarning' && /SQLite/i.test(String(message ?? '')))
|
|
33
|
+
return;
|
|
34
|
+
original.call(process, warning, ...rest);
|
|
35
|
+
});
|
|
36
|
+
process.emitWarning = filtered;
|
|
37
|
+
try {
|
|
38
|
+
return require('node:sqlite');
|
|
39
|
+
}
|
|
40
|
+
finally {
|
|
41
|
+
process.emitWarning = original;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
17
44
|
// Keep Node on createRequire() so Vitest doesn't try to prebundle the built-in
|
|
18
45
|
// sqlite module as a userland package during test collection.
|
|
19
46
|
const sqliteMod = isBun
|
|
20
47
|
? await import('bun:sqlite')
|
|
21
|
-
:
|
|
48
|
+
: loadNodeSqlite();
|
|
22
49
|
// bun:sqlite exports `Database`; node:sqlite exports `DatabaseSync`.
|
|
23
50
|
const NativeDatabase = sqliteMod.Database
|
|
24
51
|
?? sqliteMod.DatabaseSync;
|
package/dist/lib/state.d.ts
CHANGED
|
@@ -98,6 +98,18 @@ export declare function getUserSubagentsDir(): string;
|
|
|
98
98
|
export declare function getSystemWorkflowsDir(): string;
|
|
99
99
|
export declare function getUserWorkflowsDir(): string;
|
|
100
100
|
export declare function getUserSecretsDir(): string;
|
|
101
|
+
/**
|
|
102
|
+
* Path to the secrets usage read-model database (~/.agents/secrets/secrets.db).
|
|
103
|
+
* Read at CALL time so a test can redirect it to a temp file via
|
|
104
|
+
* AGENTS_SECRETS_DB without racing the module-load capture of USER_SECRETS_DIR —
|
|
105
|
+
* mirrors the AGENTS_EVENTS_PATH / AGENTS_DEVICES_DIR escape hatches. Holds only
|
|
106
|
+
* value-free usage telemetry (which bundle was created/imported/exported/viewed/
|
|
107
|
+
* accessed/unlocked, when, by whom), never a secret value. It is a derived index
|
|
108
|
+
* fed FROM the emitSecretAudit chokepoint alongside the append-only
|
|
109
|
+
* ~/.agents/events.jsonl audit log — the same way sessions.db indexes session
|
|
110
|
+
* metadata off the real session flow — not a second write path.
|
|
111
|
+
*/
|
|
112
|
+
export declare function getSecretsDbPath(): string;
|
|
101
113
|
export declare function getUserPromptcutsPath(): string;
|
|
102
114
|
/** Canonical home anchor (HOME env override or os.homedir()). */
|
|
103
115
|
export declare function getHomeDir(): string;
|
package/dist/lib/state.js
CHANGED
|
@@ -299,6 +299,20 @@ export function getUserSubagentsDir() { return USER_SUBAGENTS_DIR; }
|
|
|
299
299
|
export function getSystemWorkflowsDir() { return SYSTEM_WORKFLOWS_DIR; }
|
|
300
300
|
export function getUserWorkflowsDir() { return USER_WORKFLOWS_DIR; }
|
|
301
301
|
export function getUserSecretsDir() { return USER_SECRETS_DIR; }
|
|
302
|
+
/**
|
|
303
|
+
* Path to the secrets usage read-model database (~/.agents/secrets/secrets.db).
|
|
304
|
+
* Read at CALL time so a test can redirect it to a temp file via
|
|
305
|
+
* AGENTS_SECRETS_DB without racing the module-load capture of USER_SECRETS_DIR —
|
|
306
|
+
* mirrors the AGENTS_EVENTS_PATH / AGENTS_DEVICES_DIR escape hatches. Holds only
|
|
307
|
+
* value-free usage telemetry (which bundle was created/imported/exported/viewed/
|
|
308
|
+
* accessed/unlocked, when, by whom), never a secret value. It is a derived index
|
|
309
|
+
* fed FROM the emitSecretAudit chokepoint alongside the append-only
|
|
310
|
+
* ~/.agents/events.jsonl audit log — the same way sessions.db indexes session
|
|
311
|
+
* metadata off the real session flow — not a second write path.
|
|
312
|
+
*/
|
|
313
|
+
export function getSecretsDbPath() {
|
|
314
|
+
return process.env.AGENTS_SECRETS_DB ?? path.join(USER_SECRETS_DIR, 'secrets.db');
|
|
315
|
+
}
|
|
302
316
|
export function getUserPromptcutsPath() { return USER_PROMPTCUTS_FILE; }
|
|
303
317
|
// ─── User operational path getters ────────────────────────────────────────────
|
|
304
318
|
//
|
package/dist/lib/types.d.ts
CHANGED
|
@@ -873,10 +873,11 @@ export interface Meta {
|
|
|
873
873
|
};
|
|
874
874
|
/**
|
|
875
875
|
* Owner/channel notification config for `agents send` / `agents notify`.
|
|
876
|
-
* `owner` is the
|
|
877
|
-
* `transports` maps a user-facing channel name to the
|
|
878
|
-
* delivers it — explicit, one provider per channel,
|
|
879
|
-
* default to name-identity (channel `slack` ->
|
|
876
|
+
* `owner` is the address expanded by `--to owner` and by `agents notify`
|
|
877
|
+
* (channel + target). `transports` maps a user-facing channel name to the
|
|
878
|
+
* provider that actually delivers it — explicit, one provider per channel,
|
|
879
|
+
* no fallback. Omitted keys default to name-identity (channel `slack` ->
|
|
880
|
+
* provider `slack`).
|
|
880
881
|
*/
|
|
881
882
|
notify?: {
|
|
882
883
|
owner?: {
|
package/dist/lib/versions.d.ts
CHANGED
|
@@ -464,6 +464,12 @@ export interface SyncResult {
|
|
|
464
464
|
subagents: string[];
|
|
465
465
|
plugins: string[];
|
|
466
466
|
workflows: string[];
|
|
467
|
+
/**
|
|
468
|
+
* Project files the sync left alone because the workspace already has them
|
|
469
|
+
* (repo-relative). Reported once, grouped, by the command that rendered the
|
|
470
|
+
* sync — never one line per file from down here.
|
|
471
|
+
*/
|
|
472
|
+
projectSkipped: string[];
|
|
467
473
|
}
|
|
468
474
|
/**
|
|
469
475
|
* Enumerate the DotAgent repo names that resources can be scoped to:
|
package/dist/lib/versions.js
CHANGED
|
@@ -2420,7 +2420,7 @@ export function mergeRepoScopedSelections(repos, cwd = process.cwd()) {
|
|
|
2420
2420
|
*/
|
|
2421
2421
|
export function syncResourcesToVersion(agent, version, selection, options = {}) {
|
|
2422
2422
|
if (isAgentHardDeprecated(agent)) {
|
|
2423
|
-
return { commands: false, skills: false, hooks: false, memory: [], permissions: false, mcp: [], subagents: [], plugins: [], workflows: [] };
|
|
2423
|
+
return { commands: false, skills: false, hooks: false, memory: [], permissions: false, mcp: [], subagents: [], plugins: [], workflows: [], projectSkipped: [] };
|
|
2424
2424
|
}
|
|
2425
2425
|
const agentConfig = AGENTS[agent];
|
|
2426
2426
|
const versionHome = getVersionHomePath(agent, version);
|
|
@@ -2431,7 +2431,7 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
|
|
|
2431
2431
|
// care about the ORIGINAL intent: a caller passing no selection means
|
|
2432
2432
|
// "full sync; persist the staleness manifest after."
|
|
2433
2433
|
const userPassedSelection = selection !== undefined;
|
|
2434
|
-
const result = { commands: false, skills: false, hooks: false, memory: [], permissions: false, mcp: [], subagents: [], plugins: [], workflows: [] };
|
|
2434
|
+
const result = { commands: false, skills: false, hooks: false, memory: [], permissions: false, mcp: [], subagents: [], plugins: [], workflows: [], projectSkipped: [] };
|
|
2435
2435
|
const cwd = options.cwd || process.cwd();
|
|
2436
2436
|
const projectAgentsDir = options.projectDir || getProjectAgentsDir(cwd);
|
|
2437
2437
|
const userAgentsDir = getUserAgentsDir();
|
|
@@ -2499,7 +2499,7 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
|
|
|
2499
2499
|
}
|
|
2500
2500
|
}
|
|
2501
2501
|
if (projectAgentsDir) {
|
|
2502
|
-
syncProjectResourcesToAgent(agent, version, projectAgentsDir);
|
|
2502
|
+
result.projectSkipped = syncProjectResourcesToAgent(agent, version, projectAgentsDir).skipped;
|
|
2503
2503
|
}
|
|
2504
2504
|
// Fast guard: skip the entire sync when the caller requested a full sync and
|
|
2505
2505
|
// nothing has changed since the last full sync. Pattern-derived selections
|
|
@@ -2508,7 +2508,9 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
|
|
|
2508
2508
|
if (!userPassedSelection && !options.force) {
|
|
2509
2509
|
const manifest = loadManifest(agent, version);
|
|
2510
2510
|
if (manifest && !isStale(manifest, agent, version, cwd)) {
|
|
2511
|
-
|
|
2511
|
+
// Nothing synced, but the project sync above already ran — carry its
|
|
2512
|
+
// skipped files out so the caller can still report them.
|
|
2513
|
+
return { ...result };
|
|
2512
2514
|
}
|
|
2513
2515
|
}
|
|
2514
2516
|
// Helper: remove a path (symlink or real) if it exists
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@phnx-labs/agents-cli",
|
|
3
|
-
"version": "1.20.
|
|
3
|
+
"version": "1.20.93",
|
|
4
4
|
"description": "One CLI for all your AI coding agents - versions, config, cloud dispatch, sessions, and teams (now with first-class Grok Build CLI support)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|