@phnx-labs/agents-cli 1.22.56 → 1.22.58
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 +70 -0
- package/README.md +4 -4
- package/dist/bootstrap.js +11 -2
- package/dist/cli/command-registry.d.ts +0 -1
- package/dist/cli/command-registry.js +0 -3
- package/dist/commands/accounts.js +7 -3
- package/dist/commands/apply.js +10 -2
- package/dist/commands/exec.js +1 -1
- package/dist/commands/fork.d.ts +23 -10
- package/dist/commands/fork.js +115 -58
- package/dist/commands/hooks.js +4 -4
- package/dist/commands/insights.d.ts +7 -5
- package/dist/commands/insights.js +16 -9
- package/dist/commands/monitors.js +11 -0
- package/dist/commands/perf.d.ts +16 -7
- package/dist/commands/perf.js +29 -20
- package/dist/commands/prune.js +5 -3
- package/dist/commands/routines.d.ts +8 -0
- package/dist/commands/routines.js +57 -3
- package/dist/commands/rules.js +1 -1
- package/dist/commands/sessions-picker.d.ts +11 -0
- package/dist/commands/sessions-picker.js +16 -0
- package/dist/commands/sessions.js +1 -0
- package/dist/commands/share.d.ts +14 -0
- package/dist/commands/share.js +43 -2
- package/dist/commands/ssh.js +24 -14
- package/dist/commands/status.js +1 -1
- package/dist/commands/sync.js +83 -7
- package/dist/commands/traces.js +7 -0
- package/dist/commands/trash.d.ts +2 -2
- package/dist/commands/trash.js +2 -6
- package/dist/commands/versions.d.ts +2 -2
- package/dist/commands/versions.js +1 -10
- package/dist/commands/view.d.ts +2 -2
- package/dist/commands/view.js +7 -6
- package/dist/index.d.ts +1 -0
- package/dist/index.js +14 -0
- package/dist/lib/account-registry.d.ts +5 -1
- package/dist/lib/account-registry.js +47 -14
- package/dist/lib/accounting/capacity.d.ts +18 -7
- package/dist/lib/accounting/capacity.js +19 -8
- package/dist/lib/accounting/usage-ingest.d.ts +1 -0
- package/dist/lib/accounting/usage-ingest.js +75 -0
- package/dist/lib/accounting/usage-sync.d.ts +97 -0
- package/dist/lib/accounting/usage-sync.js +203 -0
- package/dist/lib/accounting/usage.d.ts +48 -2
- package/dist/lib/accounting/usage.js +79 -2
- package/dist/lib/agent-spec/agents.js +1 -1
- package/dist/lib/analytics/mix-commands.d.ts +8 -7
- package/dist/lib/analytics/mix-commands.js +50 -73
- package/dist/lib/auth-mint.d.ts +11 -1
- package/dist/lib/auth-mint.js +21 -6
- package/dist/lib/browser/ipc.d.ts +8 -0
- package/dist/lib/browser/ipc.js +87 -0
- package/dist/lib/browser/service.d.ts +19 -0
- package/dist/lib/browser/service.js +96 -11
- package/dist/lib/browser/sessions-list.js +10 -1
- package/dist/lib/daemon/daemon.js +5 -0
- package/dist/lib/daemon/runner.d.ts +3 -0
- package/dist/lib/daemon/runner.js +95 -53
- package/dist/lib/daemon/usage-sync-service.d.ts +21 -0
- package/dist/lib/daemon/usage-sync-service.js +42 -0
- package/dist/lib/daemon-services.d.ts +1 -1
- package/dist/lib/daemon-services.js +5 -0
- package/dist/lib/device-config.d.ts +17 -6
- package/dist/lib/device-config.js +25 -11
- package/dist/lib/devices/connect.d.ts +17 -8
- package/dist/lib/devices/connect.js +31 -14
- package/dist/lib/devices/pool.d.ts +4 -3
- package/dist/lib/devices/pool.js +13 -5
- package/dist/lib/doctor-diff.js +77 -7
- package/dist/lib/exec.d.ts +6 -41
- package/dist/lib/exec.js +6 -41
- package/dist/lib/fleet/manifest.d.ts +17 -0
- package/dist/lib/fleet/manifest.js +26 -0
- package/dist/lib/git.d.ts +13 -1
- package/dist/lib/git.js +36 -7
- package/dist/lib/harness/adapter.d.ts +7 -7
- package/dist/lib/harness/adapters/claude.js +3 -2
- package/dist/lib/hooks/install.d.ts +27 -11
- package/dist/lib/hooks/install.js +42 -17
- package/dist/lib/hosts/reconnect.d.ts +52 -203
- package/dist/lib/hosts/reconnect.js +64 -284
- package/dist/lib/hosts/remote-cmd.d.ts +9 -0
- package/dist/lib/hosts/remote-cmd.js +22 -0
- package/dist/lib/installations/migrate.d.ts +6 -120
- package/dist/lib/installations/migrate.js +27 -259
- package/dist/lib/installations/shims.d.ts +13 -95
- package/dist/lib/installations/shims.js +22 -139
- package/dist/lib/installations/store.js +1 -1
- package/dist/lib/installations/versions.d.ts +26 -133
- package/dist/lib/installations/versions.js +41 -204
- package/dist/lib/perf/db.d.ts +1 -1
- package/dist/lib/perf/db.js +1 -1
- package/dist/lib/plugins/skills.d.ts +8 -1
- package/dist/lib/plugins/skills.js +18 -2
- package/dist/lib/refresh.d.ts +9 -0
- package/dist/lib/refresh.js +3 -1
- package/dist/lib/routine-readiness.d.ts +15 -1
- package/dist/lib/routine-readiness.js +41 -0
- package/dist/lib/sandbox.d.ts +4 -1
- package/dist/lib/sandbox.js +30 -1
- package/dist/lib/secrets/agent.d.ts +80 -225
- package/dist/lib/secrets/agent.js +139 -401
- package/dist/lib/secrets/bundles.d.ts +73 -222
- package/dist/lib/secrets/bundles.js +168 -467
- package/dist/lib/secrets/reaper.d.ts +28 -70
- package/dist/lib/secrets/reaper.js +30 -85
- package/dist/lib/secrets/remote.d.ts +42 -129
- package/dist/lib/secrets/remote.js +55 -173
- package/dist/lib/self-heal/checks/install-staging.d.ts +4 -0
- package/dist/lib/self-heal/checks/install-staging.js +96 -0
- package/dist/lib/self-heal/registry.js +2 -0
- package/dist/lib/self-heal/types.d.ts +1 -1
- package/dist/lib/self-update.d.ts +23 -0
- package/dist/lib/self-update.js +50 -0
- package/dist/lib/session/active.d.ts +16 -32
- package/dist/lib/session/active.js +10 -68
- package/dist/lib/session/db.d.ts +24 -36
- package/dist/lib/session/db.js +143 -44
- package/dist/lib/session/discover.d.ts +6 -58
- package/dist/lib/session/discover.js +5 -43
- package/dist/lib/session/fork.d.ts +45 -26
- package/dist/lib/session/fork.js +32 -95
- package/dist/lib/session/parse.d.ts +1 -19
- package/dist/lib/session/parse.js +2 -15
- package/dist/lib/session/tool-calls.d.ts +43 -1
- package/dist/lib/session/tool-calls.js +74 -44
- package/dist/lib/session/tool-store.d.ts +33 -2
- package/dist/lib/session/tool-store.js +56 -3
- package/dist/lib/staleness/writers/sources.d.ts +5 -0
- package/dist/lib/staleness/writers/sources.js +2 -1
- package/dist/lib/startup/command-registry.d.ts +8 -2
- package/dist/lib/startup/command-registry.js +12 -4
- package/dist/lib/sync-status.d.ts +22 -0
- package/dist/lib/sync-status.js +27 -0
- package/dist/lib/sync-umbrella.d.ts +9 -0
- package/dist/lib/sync-umbrella.js +21 -2
- package/dist/lib/traces/insights.d.ts +47 -14
- package/dist/lib/traces/insights.js +92 -21
- package/dist/lib/traces/phenotype.d.ts +23 -3
- package/dist/lib/traces/phenotype.js +72 -24
- package/dist/lib/traces/sync.d.ts +15 -0
- package/dist/lib/traces/sync.js +104 -19
- package/dist/lib/traces/worker-template.js +154 -1
- package/package.json +1 -1
|
@@ -1,18 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Version
|
|
3
|
-
*
|
|
4
|
-
* Handles installing, removing, listing, and switching between agent CLI versions.
|
|
5
|
-
* Each version is installed into an isolated directory under ~/.agents/.system/versions/{agent}/{version}/
|
|
6
|
-
* with its own HOME directory for config isolation. Resources (commands, skills, hooks, memory,
|
|
7
|
-
* MCP servers, permissions, subagents, plugins) from ~/.agents/ are synced into version homes
|
|
8
|
-
* via copies or conversions (not symlinks).
|
|
9
|
-
*
|
|
10
|
-
* Key responsibilities:
|
|
11
|
-
* - Version lifecycle: install, remove, list, resolve (project-level or global default)
|
|
12
|
-
* - Resource discovery: scan ~/.agents/ for available resources across all types
|
|
13
|
-
* - Resource sync: copy/convert resources into a version's isolated config directory
|
|
14
|
-
* - Diff and reconciliation: detect new/unsynced resources and prompt users to sync them
|
|
15
|
-
* - Agent/version target resolution: parse agent@version specs from CLI flags
|
|
2
|
+
* Version lifecycle, resource sync, and agent@version resolution for agents-cli.
|
|
3
|
+
* Each version lives in an isolated home under ~/.agents/.history/versions/{agent}/{version}/.
|
|
16
4
|
*/
|
|
17
5
|
import * as fs from 'fs';
|
|
18
6
|
import * as path from 'path';
|
|
@@ -121,12 +109,7 @@ function sourceMapFromWorkflows(cwd) {
|
|
|
121
109
|
.filter(d => d.isDirectory() && fs.existsSync(path.join(dir, d.name, 'WORKFLOW.md')))
|
|
122
110
|
.map(d => d.name));
|
|
123
111
|
}
|
|
124
|
-
//
|
|
125
|
-
// each to the layer it actually resolves from (system / user / project / extra) —
|
|
126
|
-
// the same first-wins resolution the plugins staleness checker uses. Hardcoding
|
|
127
|
-
// 'user' here made a `system:*` selection expand to zero plugins, so
|
|
128
|
-
// `agents sync <agent> system` silently skipped system-layer plugins like `swarm`
|
|
129
|
-
// (RUSH-3207).
|
|
112
|
+
// Attribute each plugin to the layer it resolves from so `system:*` selections include system-layer plugins.
|
|
130
113
|
function sourceMapFromPlugins(cwd) {
|
|
131
114
|
return sourceMapFromLayeredDirectory(cwd, ['plugins'], (dir) => fs.readdirSync(dir, { withFileTypes: true })
|
|
132
115
|
.filter(d => d.isDirectory() && fs.existsSync(path.join(dir, d.name, '.claude-plugin', 'plugin.json')))
|
|
@@ -155,9 +138,7 @@ function sourceMapFromPluginSkills(plugins, activePluginNames, cwd) {
|
|
|
155
138
|
}
|
|
156
139
|
return sources;
|
|
157
140
|
}
|
|
158
|
-
/**
|
|
159
|
-
* Get all available resources from ~/.agents/.
|
|
160
|
-
*/
|
|
141
|
+
/** Discover all resources available for syncing from ~/.agents/. */
|
|
161
142
|
export function getAvailableResources(cwd = process.cwd()) {
|
|
162
143
|
const result = {
|
|
163
144
|
commands: [],
|
|
@@ -201,14 +182,7 @@ export function getAvailableResources(cwd = process.cwd()) {
|
|
|
201
182
|
}
|
|
202
183
|
}
|
|
203
184
|
result.skills = filterNamesForActiveResourceProfile('skills', Array.from(skillNames), sourceMapFromResources('skills', cwd));
|
|
204
|
-
// Hooks:
|
|
205
|
-
// - top-level script files
|
|
206
|
-
// - one-level *group* dirs that contain top-level scripts (session-starts/*.sh)
|
|
207
|
-
// → each script is its own hook resource (install name = basename)
|
|
208
|
-
// - other directories (e.g. tests/ with fixtures only) → directory bundles,
|
|
209
|
-
// copied wholesale as one resource (pre-existing layout)
|
|
210
|
-
// Auxiliary content like README.md / promptcuts.yaml is not a hook. Older sync
|
|
211
|
-
// runs chmod 0o755'd everything, so an exec bit alone is not the signal.
|
|
185
|
+
// Hooks: top-level scripts, expanded one-level group dirs, or whole-dir bundles. Exec bit alone is not the signal (older syncs chmod'd everything).
|
|
212
186
|
const NON_SCRIPT_EXTS = new Set(['.md', '.markdown', '.rst', '.txt', '.yaml', '.yml', '.json', '.toml', '.ini', '.conf']);
|
|
213
187
|
const SCRIPT_EXTS = new Set(['.sh', '.bash', '.zsh', '.py', '.js', '.ts', '.mjs', '.cjs', '.rb', '.pl', '.ps1']);
|
|
214
188
|
const HOOK_GROUP_SKIP = new Set(['node_modules', '.git', '.cache']);
|
|
@@ -238,8 +212,7 @@ export function getAvailableResources(cwd = process.cwd()) {
|
|
|
238
212
|
}
|
|
239
213
|
if (!stat.isDirectory() || HOOK_GROUP_SKIP.has(name))
|
|
240
214
|
continue;
|
|
241
|
-
//
|
|
242
|
-
// them; otherwise treat the whole dir as one hook resource.
|
|
215
|
+
// Expand dirs containing top-level scripts; bundle dirs without any.
|
|
243
216
|
let nestedNames;
|
|
244
217
|
try {
|
|
245
218
|
nestedNames = fs.readdirSync(full);
|
|
@@ -273,10 +246,7 @@ export function getAvailableResources(cwd = process.cwd()) {
|
|
|
273
246
|
}
|
|
274
247
|
}
|
|
275
248
|
result.hooks = filterNamesForActiveResourceProfile('hooks', Array.from(hookNames), sourceMapFromResources('hooks', cwd));
|
|
276
|
-
// Rules — list available presets across layers
|
|
277
|
-
// The composer selects exactly one preset per sync; this list drives the
|
|
278
|
-
// resource-count display and `agents rules switch` picker. Routes through
|
|
279
|
-
// the rules-dir getters so test mocks work the same as production paths.
|
|
249
|
+
// Rules — list available presets across layers.
|
|
280
250
|
const presetNames = new Set();
|
|
281
251
|
const rulesDirs = [];
|
|
282
252
|
if (projectAgentsDir)
|
|
@@ -303,10 +273,8 @@ export function getAvailableResources(cwd = process.cwd()) {
|
|
|
303
273
|
result.memory = filterNamesForActiveResourceProfile('memory', Array.from(presetNames));
|
|
304
274
|
const scopedMcp = getScopedMcpResources(cwd);
|
|
305
275
|
result.mcp = filterNamesForActiveResourceProfile('mcp', scopedMcp.map(resource => resource.name), new Map(scopedMcp.map(resource => [resource.name, resource.scope])));
|
|
306
|
-
// Permission groups (from permissions/groups/*.yaml)
|
|
307
276
|
const permissionSources = sourceMapFromPermissionGroups(cwd);
|
|
308
277
|
result.permissions = filterNamesForActiveResourceProfile('permissions', Array.from(permissionSources.keys()), permissionSources);
|
|
309
|
-
// Subagents (directories with AGENT.md)
|
|
310
278
|
const subagentNames = new Set();
|
|
311
279
|
for (const { base } of resourceBases) {
|
|
312
280
|
const subagentsDir = path.join(base, 'subagents');
|
|
@@ -320,7 +288,6 @@ export function getAvailableResources(cwd = process.cwd()) {
|
|
|
320
288
|
}
|
|
321
289
|
}
|
|
322
290
|
result.subagents = filterNamesForActiveResourceProfile('subagents', Array.from(subagentNames), sourceMapFromResources('subagents', cwd));
|
|
323
|
-
// Workflows (directories with WORKFLOW.md)
|
|
324
291
|
const workflowSources = sourceMapFromWorkflows(cwd);
|
|
325
292
|
result.workflows = filterNamesForActiveResourceProfile('workflows', Array.from(workflowSources.keys()), workflowSources);
|
|
326
293
|
// Plugins (directories with .claude-plugin/plugin.json)
|
|
@@ -342,10 +309,7 @@ const SKILL_COPY_IGNORE = new Set(['.DS_Store', '.git', '.gitignore', '.venv', '
|
|
|
342
309
|
function shouldSkillEntryBeSkipped(name) {
|
|
343
310
|
return SKILL_COPY_IGNORE.has(name);
|
|
344
311
|
}
|
|
345
|
-
/**
|
|
346
|
-
* Recursively compare two directories: every file in src must exist in dest with identical content.
|
|
347
|
-
* Skips the same entries that copyDir skips (symlinks and SKILL_COPY_IGNORE members).
|
|
348
|
-
*/
|
|
312
|
+
/** Recursively compare two directories for identical content, skipping symlinks and ignored entries. */
|
|
349
313
|
function skillDirsMatch(src, dest) {
|
|
350
314
|
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
351
315
|
for (const entry of entries) {
|
|
@@ -362,9 +326,7 @@ function skillDirsMatch(src, dest) {
|
|
|
362
326
|
return false;
|
|
363
327
|
}
|
|
364
328
|
else {
|
|
365
|
-
//
|
|
366
|
-
// Equal mtimes across different trees are accidental — content-compare
|
|
367
|
-
// whenever sizes match.
|
|
329
|
+
// Size-first check avoids reads on mismatch; equal mtimes are unreliable across trees.
|
|
368
330
|
let srcStat;
|
|
369
331
|
let destStat;
|
|
370
332
|
try {
|
|
@@ -382,10 +344,7 @@ function skillDirsMatch(src, dest) {
|
|
|
382
344
|
}
|
|
383
345
|
return true;
|
|
384
346
|
}
|
|
385
|
-
/**
|
|
386
|
-
* Get what's ACTUALLY synced to a version by inspecting the version home.
|
|
387
|
-
* This is the source of truth - not the tracking in agents.yaml.
|
|
388
|
-
*/
|
|
347
|
+
/** Return what's actually synced to a version home (source of truth, not agents.yaml tracking). */
|
|
389
348
|
export function getActuallySyncedResources(agent, version, options = {}) {
|
|
390
349
|
const versionHome = path.join(getVersionsDir(), agent, version, 'home');
|
|
391
350
|
const cwd = options.cwd || process.cwd();
|
|
@@ -401,10 +360,7 @@ export function getActuallySyncedResources(agent, version, options = {}) {
|
|
|
401
360
|
workflows: [],
|
|
402
361
|
promptcuts: false,
|
|
403
362
|
};
|
|
404
|
-
// Dispatch
|
|
405
|
-
// exists for every supported (agent, kind) pair; unsupported pairs leave
|
|
406
|
-
// the field empty. The previous per-agent if-ladder silently dropped
|
|
407
|
-
// antigravity/gemini/grok detection — see PR description for details.
|
|
363
|
+
// Dispatch through per-kind detectors; unsupported (agent, kind) pairs leave the field empty.
|
|
408
364
|
const ctx = { version, versionHome, cwd };
|
|
409
365
|
result.commands = getDetector('commands', agent)?.list(ctx) ?? [];
|
|
410
366
|
result.skills = getDetector('skills', agent)?.list(ctx) ?? [];
|
|
@@ -417,14 +373,7 @@ export function getActuallySyncedResources(agent, version, options = {}) {
|
|
|
417
373
|
result.workflows = getDetector('workflows', agent)?.list(ctx) ?? [];
|
|
418
374
|
return result;
|
|
419
375
|
}
|
|
420
|
-
/**
|
|
421
|
-
* Names that exist ONLY in the project's `.agents/` layer (no matching entry in
|
|
422
|
-
* user/system/extra layers). Sync intentionally skips project-layer commands,
|
|
423
|
-
* skills, hooks, subagents, plugins, and workflows for security — see the
|
|
424
|
-
* defense comments above each sync branch in syncResourcesToVersion. Without
|
|
425
|
-
* this filter, those names would forever appear in the "New resources" diff
|
|
426
|
-
* because they live in `available` but never reach `actuallySynced`.
|
|
427
|
-
*/
|
|
376
|
+
/** Names that exist only in the project's `.agents/` layer. Sync skips project-layer resources for security, so filter them out of the "new resources" diff. */
|
|
428
377
|
export function getProjectOnlyResources(cwd = process.cwd()) {
|
|
429
378
|
const empty = {
|
|
430
379
|
commands: new Set(), skills: new Set(), hooks: new Set(),
|
|
@@ -522,16 +471,7 @@ export function getProjectOnlyResources(cwd = process.cwd()) {
|
|
|
522
471
|
}
|
|
523
472
|
return empty;
|
|
524
473
|
}
|
|
525
|
-
/**
|
|
526
|
-
* Compare available resources with what's ACTUALLY synced to version home.
|
|
527
|
-
* Returns only NEW resources that haven't been synced yet.
|
|
528
|
-
* Source of truth: the actual files/config, NOT agents.yaml tracking.
|
|
529
|
-
*
|
|
530
|
-
* `projectOnly` (recommended): the result of `getProjectOnlyResources(cwd)`.
|
|
531
|
-
* Names listed there are filtered out for kinds that sync intentionally
|
|
532
|
-
* excludes the project layer — otherwise they would re-appear as "new"
|
|
533
|
-
* on every run and "Yes, sync all new" would silently do nothing for them.
|
|
534
|
-
*/
|
|
474
|
+
/** Return resources in `available` that are not yet synced to the version home. `projectOnly` filters project-layer resources that sync skips for security. */
|
|
535
475
|
export function getNewResources(available, actuallySynced, projectOnly) {
|
|
536
476
|
const exclude = projectOnly || {
|
|
537
477
|
commands: new Set(), skills: new Set(), hooks: new Set(),
|
|
@@ -541,8 +481,7 @@ export function getNewResources(available, actuallySynced, projectOnly) {
|
|
|
541
481
|
commands: available.commands.filter(c => !actuallySynced.commands.includes(c) && !exclude.commands.has(c)),
|
|
542
482
|
skills: available.skills.filter(s => !actuallySynced.skills.includes(s) && !exclude.skills.has(s)),
|
|
543
483
|
hooks: available.hooks.filter(h => !actuallySynced.hooks.includes(h) && !exclude.hooks.has(h)),
|
|
544
|
-
//
|
|
545
|
-
// If any preset is synced, don't report others as "new".
|
|
484
|
+
// Only one rules preset can be active; if any is synced, don't report others as new.
|
|
546
485
|
memory: actuallySynced.memory.length > 0
|
|
547
486
|
? []
|
|
548
487
|
: available.memory.filter(m => !actuallySynced.memory.includes(m)),
|
|
@@ -551,15 +490,11 @@ export function getNewResources(available, actuallySynced, projectOnly) {
|
|
|
551
490
|
subagents: available.subagents.filter(s => !actuallySynced.subagents.includes(s) && !exclude.subagents.has(s)),
|
|
552
491
|
plugins: available.plugins.filter(p => !actuallySynced.plugins.includes(p) && !exclude.plugins.has(p)),
|
|
553
492
|
workflows: available.workflows.filter(w => !actuallySynced.workflows.includes(w) && !exclude.workflows.has(w)),
|
|
554
|
-
// Promptcuts
|
|
555
|
-
// directly, so there is never a "new" per-version state to reconcile.
|
|
493
|
+
// Promptcuts are not version-scoped; the hook reads the user/system file directly.
|
|
556
494
|
promptcuts: false,
|
|
557
495
|
};
|
|
558
496
|
}
|
|
559
|
-
/**
|
|
560
|
-
* Check if there are any new resources to sync.
|
|
561
|
-
* When version is provided, uses version-specific capability checks.
|
|
562
|
-
*/
|
|
497
|
+
/** Return true when `diff` contains any resources the agent/version actually supports. */
|
|
563
498
|
export function hasNewResources(diff, agent, version) {
|
|
564
499
|
const commandsApply = agent ? supports(agent, 'commands', version).ok : true;
|
|
565
500
|
const hooksApply = agent ? supports(agent, 'hooks', version).ok : true;
|
|
@@ -578,16 +513,11 @@ export function hasNewResources(diff, agent, version) {
|
|
|
578
513
|
(diff.plugins.length > 0 && pluginsApply) ||
|
|
579
514
|
(diff.workflows.length > 0 && workflowsApply));
|
|
580
515
|
}
|
|
581
|
-
/**
|
|
582
|
-
* Build a summary string of new resources.
|
|
583
|
-
* E.g., "2 commands, 5 permission groups"
|
|
584
|
-
*/
|
|
516
|
+
/** Build a human-readable summary of new resources, e.g. "2 commands, 5 permission groups". */
|
|
585
517
|
function buildNewResourcesSummary(newResources, agent, version) {
|
|
586
518
|
const agentConfig = AGENTS[agent];
|
|
587
519
|
const parts = [];
|
|
588
|
-
//
|
|
589
|
-
// double-count and so "16 commands" never appears in the summary when commands have
|
|
590
|
-
// already been emitted as skills in the version home.
|
|
520
|
+
// Version-aware gates avoid double-counting commands already emitted as skills (Codex >= 0.117.0).
|
|
591
521
|
const commandsApply = supports(agent, 'commands', version).ok;
|
|
592
522
|
const commandsAsSkills = version ? shouldInstallCommandAsSkill(agent, version) : false;
|
|
593
523
|
const rulesApply = supports(agent, 'rules', version).ok;
|
|
@@ -620,10 +550,7 @@ function buildNewResourcesSummary(newResources, agent, version) {
|
|
|
620
550
|
}
|
|
621
551
|
return parts.join(', ');
|
|
622
552
|
}
|
|
623
|
-
/**
|
|
624
|
-
* Prompt user to select which NEW resources to sync.
|
|
625
|
-
* Only shows resources that haven't been synced yet.
|
|
626
|
-
*/
|
|
553
|
+
/** Prompt the user to select which new resources to sync. */
|
|
627
554
|
export async function promptNewResourceSelection(agent, newResources, version) {
|
|
628
555
|
const agentConfig = AGENTS[agent];
|
|
629
556
|
const selection = {};
|
|
@@ -634,15 +561,12 @@ export async function promptNewResourceSelection(agent, newResources, version) {
|
|
|
634
561
|
const commandsAsSkills = version ? shouldInstallCommandAsSkill(agent, version) : false;
|
|
635
562
|
const commandsBranch = commandsApply || commandsAsSkills;
|
|
636
563
|
const rulesBranch = supports(agent, 'rules', version).ok;
|
|
637
|
-
// Get permission group info for display
|
|
638
564
|
const permissionGroups = discoverPermissionGroups();
|
|
639
565
|
const newPermissionGroups = permissionGroups.filter(g => newResources.permissions.includes(g.name));
|
|
640
566
|
const totalNewPermissionRules = newPermissionGroups.reduce((sum, g) => sum + g.ruleCount, 0);
|
|
641
|
-
// Build the summary
|
|
642
567
|
const summary = buildNewResourcesSummary(newResources, agent, version);
|
|
643
568
|
console.log(chalk.cyan(`\nNew resources available:`));
|
|
644
569
|
console.log(chalk.gray(` ${summary}`));
|
|
645
|
-
// Ask how to handle new resources
|
|
646
570
|
const action = await select({
|
|
647
571
|
message: 'Sync new resources?',
|
|
648
572
|
choices: [
|
|
@@ -656,7 +580,6 @@ export async function promptNewResourceSelection(agent, newResources, version) {
|
|
|
656
580
|
return null;
|
|
657
581
|
}
|
|
658
582
|
if (action === 'all') {
|
|
659
|
-
// Sync all new resources
|
|
660
583
|
if (newResources.commands.length > 0 && commandsBranch)
|
|
661
584
|
selection.commands = newResources.commands;
|
|
662
585
|
if (newResources.skills.length > 0)
|
|
@@ -677,7 +600,6 @@ export async function promptNewResourceSelection(agent, newResources, version) {
|
|
|
677
600
|
selection.workflows = newResources.workflows;
|
|
678
601
|
return selection;
|
|
679
602
|
}
|
|
680
|
-
// Select specific items for each category
|
|
681
603
|
if (newResources.commands.length > 0 && commandsBranch) {
|
|
682
604
|
const selected = await checkbox({
|
|
683
605
|
message: 'Select new commands to sync:',
|
|
@@ -762,15 +684,11 @@ export async function promptNewResourceSelection(agent, newResources, version) {
|
|
|
762
684
|
}
|
|
763
685
|
return selection;
|
|
764
686
|
}
|
|
765
|
-
/**
|
|
766
|
-
* Prompt user to select which resources to sync from ~/.agents/.
|
|
767
|
-
* Returns the selection, or null if user cancels.
|
|
768
|
-
*/
|
|
687
|
+
/** Prompt the user to select which resources to sync from ~/.agents/. */
|
|
769
688
|
export async function promptResourceSelection(agent) {
|
|
770
689
|
const available = getAvailableResources();
|
|
771
690
|
const agentConfig = AGENTS[agent];
|
|
772
691
|
const selection = {};
|
|
773
|
-
// Get permission group info for display
|
|
774
692
|
const permissionGroups = discoverPermissionGroups();
|
|
775
693
|
const totalPermissionRules = permissionGroups.reduce((sum, g) => sum + g.ruleCount, 0);
|
|
776
694
|
const categories = [
|
|
@@ -788,7 +706,6 @@ export async function promptResourceSelection(agent) {
|
|
|
788
706
|
console.log(chalk.gray('No resources available to sync.'));
|
|
789
707
|
return {};
|
|
790
708
|
}
|
|
791
|
-
// Step 1: Select categories (with "Select All" shortcut at the top)
|
|
792
709
|
console.log();
|
|
793
710
|
const SELECT_ALL_KEY = '__select_all__';
|
|
794
711
|
const selectedCategories = await checkbox({
|
|
@@ -805,7 +722,6 @@ export async function promptResourceSelection(agent) {
|
|
|
805
722
|
if (selectedCategories.length === 0) {
|
|
806
723
|
return {};
|
|
807
724
|
}
|
|
808
|
-
// If "Select All" was picked, or all individual categories are selected, sync everything without per-category prompts
|
|
809
725
|
const allCategoryKeys = availableCategories.map(c => c.key);
|
|
810
726
|
if (selectedCategories.includes(SELECT_ALL_KEY) || allCategoryKeys.every(k => selectedCategories.includes(k))) {
|
|
811
727
|
for (const c of availableCategories) {
|
|
@@ -813,10 +729,8 @@ export async function promptResourceSelection(agent) {
|
|
|
813
729
|
}
|
|
814
730
|
return selection;
|
|
815
731
|
}
|
|
816
|
-
// Step 2: For each selected category, ask all/specific/skip
|
|
817
732
|
for (const category of selectedCategories) {
|
|
818
733
|
const categoryLabel = categories.find(c => c.key === category).label;
|
|
819
|
-
// Special handling for permissions - show groups
|
|
820
734
|
if (category === 'permissions') {
|
|
821
735
|
const choice = await select({
|
|
822
736
|
message: `${categoryLabel}:`,
|
|
@@ -845,7 +759,6 @@ export async function promptResourceSelection(agent) {
|
|
|
845
759
|
}
|
|
846
760
|
}
|
|
847
761
|
else {
|
|
848
|
-
// Standard handling for other categories
|
|
849
762
|
const items = available[category];
|
|
850
763
|
const choice = await select({
|
|
851
764
|
message: `${categoryLabel}:`,
|
|
@@ -877,13 +790,7 @@ export async function promptResourceSelection(agent) {
|
|
|
877
790
|
}
|
|
878
791
|
return selection;
|
|
879
792
|
}
|
|
880
|
-
/**
|
|
881
|
-
* Parse agent@version syntax.
|
|
882
|
-
* Examples:
|
|
883
|
-
* "claude@1.5.0" -> { agent: "claude", version: "1.5.0" }
|
|
884
|
-
* "claude" -> { agent: "claude", version: "latest" }
|
|
885
|
-
* "codex@latest" -> { agent: "codex", version: "latest" }
|
|
886
|
-
*/
|
|
793
|
+
/** Parse an `agent@version` spec; bare agent means `latest`. */
|
|
887
794
|
export function parseAgentSpec(spec) {
|
|
888
795
|
const parts = spec.split('@');
|
|
889
796
|
if (parts.length > 2) {
|
|
@@ -904,9 +811,6 @@ export function parseAgentSpec(spec) {
|
|
|
904
811
|
version,
|
|
905
812
|
};
|
|
906
813
|
}
|
|
907
|
-
/**
|
|
908
|
-
* Get the latest available version from npm for an agent.
|
|
909
|
-
*/
|
|
910
814
|
export async function getLatestNpmVersion(agent) {
|
|
911
815
|
const agentConfig = AGENTS[agent];
|
|
912
816
|
if (!agentConfig.npmPackage)
|
|
@@ -919,9 +823,6 @@ export async function getLatestNpmVersion(agent) {
|
|
|
919
823
|
return null;
|
|
920
824
|
}
|
|
921
825
|
}
|
|
922
|
-
/**
|
|
923
|
-
* Get the oldest published version from npm for an agent.
|
|
924
|
-
*/
|
|
925
826
|
export async function getOldestNpmVersion(agent) {
|
|
926
827
|
const agentConfig = AGENTS[agent];
|
|
927
828
|
if (!agentConfig.npmPackage)
|
|
@@ -929,8 +830,7 @@ export async function getOldestNpmVersion(agent) {
|
|
|
929
830
|
try {
|
|
930
831
|
const { stdout } = await execFileAsync('npm', ['view', agentConfig.npmPackage, 'versions', '--json'], { shell: process.platform === 'win32' });
|
|
931
832
|
const parsed = JSON.parse(stdout.trim());
|
|
932
|
-
//
|
|
933
|
-
// bare string (single published version). Normalize to an array.
|
|
833
|
+
// npm view returns an array for multiple versions or a bare string for one.
|
|
934
834
|
const versions = Array.isArray(parsed) ? parsed : [parsed];
|
|
935
835
|
const sorted = versions.filter((v) => VERSION_RE.test(v)).sort(compareVersions);
|
|
936
836
|
return sorted[0] ?? null;
|
|
@@ -939,9 +839,7 @@ export async function getOldestNpmVersion(agent) {
|
|
|
939
839
|
return null;
|
|
940
840
|
}
|
|
941
841
|
}
|
|
942
|
-
/**
|
|
943
|
-
* Check if 'latest' version is already installed (by resolving to actual version).
|
|
944
|
-
*/
|
|
842
|
+
/** Check whether the npm `latest` version is installed. */
|
|
945
843
|
export async function isLatestInstalled(agent) {
|
|
946
844
|
const latestVersion = await getLatestNpmVersion(agent);
|
|
947
845
|
if (!latestVersion) {
|
|
@@ -949,9 +847,7 @@ export async function isLatestInstalled(agent) {
|
|
|
949
847
|
}
|
|
950
848
|
return { installed: isVersionInstalled(agent, latestVersion), version: latestVersion };
|
|
951
849
|
}
|
|
952
|
-
/**
|
|
953
|
-
* Check if 'oldest' published version is already installed (by resolving to actual version).
|
|
954
|
-
*/
|
|
850
|
+
/** Check whether the npm `oldest` version is installed. */
|
|
955
851
|
export async function isOldestInstalled(agent) {
|
|
956
852
|
const oldestVersion = await getOldestNpmVersion(agent);
|
|
957
853
|
if (!oldestVersion) {
|
|
@@ -960,12 +856,9 @@ export async function isOldestInstalled(agent) {
|
|
|
960
856
|
return { installed: isVersionInstalled(agent, oldestVersion), version: oldestVersion };
|
|
961
857
|
}
|
|
962
858
|
/**
|
|
963
|
-
* List every version directory for an agent, including
|
|
964
|
-
*
|
|
965
|
-
*
|
|
966
|
-
* Used by `agents prune cleanup` to surface stale installs that the regular
|
|
967
|
-
* `listInstalledVersions` filters out. Do NOT use elsewhere — every other
|
|
968
|
-
* call site assumes a working binary.
|
|
859
|
+
* List every version directory for an agent, including home-only leftovers, for
|
|
860
|
+
* `agents prune cleanup` only. Do NOT use elsewhere — every other call site
|
|
861
|
+
* assumes a working binary.
|
|
969
862
|
*/
|
|
970
863
|
export function listInstalledVersionDirs(agent) {
|
|
971
864
|
const agentVersionsDir = path.join(getVersionsDir(), agent);
|
|
@@ -984,15 +877,9 @@ export function listInstalledVersionDirs(agent) {
|
|
|
984
877
|
}
|
|
985
878
|
return out.sort((a, b) => compareVersions(a.version, b.version));
|
|
986
879
|
}
|
|
987
|
-
/**
|
|
988
|
-
* Set the global default version for an agent.
|
|
989
|
-
*/
|
|
880
|
+
/** Set (or clear) the global default version for an agent. */
|
|
990
881
|
export function setGlobalDefault(agent, version) {
|
|
991
|
-
//
|
|
992
|
-
// check, so recording one for an isolated-only agent is the root of the original
|
|
993
|
-
// breach. Clearing is always allowed — `removeVersion` legitimately clears a
|
|
994
|
-
// default as the last non-isolated version goes away, which is the very moment
|
|
995
|
-
// an agent BECOMES isolated-only.
|
|
882
|
+
// Setting a global default for an isolated-only agent would breach the isolation boundary; clearing is allowed.
|
|
996
883
|
if (version !== undefined) {
|
|
997
884
|
assertIsolationBoundary(agent, 'set a global default');
|
|
998
885
|
}
|
|
@@ -1009,13 +896,7 @@ export function setGlobalDefault(agent, version) {
|
|
|
1009
896
|
}
|
|
1010
897
|
writeMeta(meta);
|
|
1011
898
|
}
|
|
1012
|
-
/**
|
|
1013
|
-
* Set (or clear, with `undefined`) the preferred isolated version.
|
|
1014
|
-
*
|
|
1015
|
-
* Deliberately does NOT touch the launcher, the bare shim, the `~/.<agent>` config
|
|
1016
|
-
* symlink or the global default — the five things `setDefaultVersion` does. This is
|
|
1017
|
-
* a pointer inside the sandbox, so it stays inside the sandbox.
|
|
1018
|
-
*/
|
|
899
|
+
/** Set (or clear) the preferred isolated version without touching the launcher, shim, or global default. */
|
|
1019
900
|
export function setIsolatedDefault(agent, version) {
|
|
1020
901
|
const meta = readMeta();
|
|
1021
902
|
if (!meta.isolatedAgents) {
|
|
@@ -1147,18 +1028,14 @@ async function checkGrokAccountCollision(installedVersion) {
|
|
|
1147
1028
|
`Grok's self-updater can't distinguish two accounts that land on the same release — sign in to ${targetEmail}'s ` +
|
|
1148
1029
|
`install (agents use grok@${installedVersion}) before updating it, or wait until the releases diverge.`);
|
|
1149
1030
|
}
|
|
1150
|
-
/**
|
|
1151
|
-
* Install a specific version of an agent.
|
|
1152
|
-
*/
|
|
1031
|
+
/** Install a specific version of an agent. */
|
|
1153
1032
|
export async function installVersion(agent, version, onProgress, opts) {
|
|
1154
1033
|
const agentConfig = AGENTS[agent];
|
|
1155
1034
|
const requestedLabel = version;
|
|
1156
1035
|
if (isAgentHardDeprecated(agent)) {
|
|
1157
1036
|
return { success: false, installedVersion: version, error: hardDeprecationError(agent) };
|
|
1158
1037
|
}
|
|
1159
|
-
//
|
|
1160
|
-
// parser already enforces this for user input; this guard protects direct
|
|
1161
|
-
// callers and tests the critical install path at the source.
|
|
1038
|
+
// Also validate at the source so direct callers and tests cannot pass invalid versions.
|
|
1162
1039
|
if (!VERSION_RE.test(version)) {
|
|
1163
1040
|
throw new Error(`Invalid version: ${JSON.stringify(version)}`);
|
|
1164
1041
|
}
|
|
@@ -1166,12 +1043,7 @@ export async function installVersion(agent, version, onProgress, opts) {
|
|
|
1166
1043
|
if (!agentConfig.installScript) {
|
|
1167
1044
|
return { success: false, installedVersion: version, error: 'Agent has no npm package' };
|
|
1168
1045
|
}
|
|
1169
|
-
//
|
|
1170
|
-
// installer only ever fetches the CURRENT release — there is no semver to
|
|
1171
|
-
// pin. Rather than hard-refuse `<agent>@1.2.3` (the old behavior):
|
|
1172
|
-
// - if the binary is already installed, a pin is a no-op — it self-updates
|
|
1173
|
-
// in place, so skip the installer and just refresh our bookkeeping;
|
|
1174
|
-
// - otherwise redirect the pin to a current-release install.
|
|
1046
|
+
// Self-updating agents have no pinnable semver; an installed binary is a no-op, otherwise redirect to latest.
|
|
1175
1047
|
let runInstaller = true;
|
|
1176
1048
|
if (version !== 'latest' && isSelfUpdatingAgent(agent)) {
|
|
1177
1049
|
const liveVersion = await getLiveVersion(agent);
|
|
@@ -1624,7 +1496,7 @@ export async function reconcileStaleLatestDir(agent, installedVersion) {
|
|
|
1624
1496
|
* re-install) without collision and gives a chronological audit trail.
|
|
1625
1497
|
*
|
|
1626
1498
|
* The whole versionDir moves — including `home/` (transcripts, sessions). The
|
|
1627
|
-
* user can recover everything via `agents
|
|
1499
|
+
* user can recover everything via `agents restore <agent>@<version>`.
|
|
1628
1500
|
* Nothing is ever hard-deleted.
|
|
1629
1501
|
*/
|
|
1630
1502
|
export function softDeleteVersionDir(agent, version) {
|
|
@@ -1658,7 +1530,7 @@ export function softDeleteVersionDir(agent, version) {
|
|
|
1658
1530
|
* Remove a specific version of an agent.
|
|
1659
1531
|
*
|
|
1660
1532
|
* Soft-delete only: moves the entire version directory (including `home/`)
|
|
1661
|
-
* to ~/.agents/.system/trash/versions/. Recoverable via `agents
|
|
1533
|
+
* to ~/.agents/.system/trash/versions/. Recoverable via `agents restore`.
|
|
1662
1534
|
* Nothing is hard-deleted.
|
|
1663
1535
|
*/
|
|
1664
1536
|
export function removeVersion(agent, version) {
|
|
@@ -1814,24 +1686,7 @@ export async function healDanglingVersionPointers(agent, cwd) {
|
|
|
1814
1686
|
}
|
|
1815
1687
|
return healed;
|
|
1816
1688
|
}
|
|
1817
|
-
/**
|
|
1818
|
-
* Normalize a user-supplied @version token across CLI subcommands.
|
|
1819
|
-
*
|
|
1820
|
-
* undefined / "" / "default" / "pinned" -> undefined (caller falls back to project pin or global default)
|
|
1821
|
-
* "any" -> undefined (caller imposes no version constraint — e.g. resume across any version)
|
|
1822
|
-
* "latest" -> highest installed version (process.exit if none installed)
|
|
1823
|
-
* "oldest" -> lowest installed version (process.exit if none installed)
|
|
1824
|
-
* "x.y.z" (installed) -> "x.y.z"
|
|
1825
|
-
* "x.y.z" (not installed) -> process.exit with installed-list hint
|
|
1826
|
-
*
|
|
1827
|
-
* `pinned` is a synonym for `default`: both name the project pin / global
|
|
1828
|
-
* default, which the caller resolves.
|
|
1829
|
-
*
|
|
1830
|
-
* Use this anywhere the user can type `agents <cmd> claude@<token>` to keep the
|
|
1831
|
-
* vocabulary consistent. Subcommands with different semantics for `latest`
|
|
1832
|
-
* (install/remove/use, where `latest` means npm-latest) keep their existing
|
|
1833
|
-
* parsing.
|
|
1834
|
-
*/
|
|
1689
|
+
/** Normalize a user-supplied `@version` token. `default`/`pinned`/`any` → undefined; `latest`/`oldest` → extreme installed version; concrete versions must be installed. */
|
|
1835
1690
|
export function resolveVersionAlias(agent, raw) {
|
|
1836
1691
|
if (!raw || raw === 'default' || raw === 'pinned' || raw === 'any')
|
|
1837
1692
|
return undefined;
|
|
@@ -2911,11 +2766,7 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
|
|
|
2911
2766
|
}
|
|
2912
2767
|
return result;
|
|
2913
2768
|
}
|
|
2914
|
-
/**
|
|
2915
|
-
* Thrown when the user references an agent@version that is not installed.
|
|
2916
|
-
* Carries the parsed (agentId, version) so callers can react — e.g. prompt
|
|
2917
|
-
* to install it on demand — without having to parse the error message.
|
|
2918
|
-
*/
|
|
2769
|
+
/** Thrown when an `agent@version` target is not installed; carries the parsed ids so callers can react without parsing the message. */
|
|
2919
2770
|
export class VersionNotInstalledError extends Error {
|
|
2920
2771
|
agentId;
|
|
2921
2772
|
version;
|
|
@@ -2929,11 +2780,7 @@ export class VersionNotInstalledError extends Error {
|
|
|
2929
2780
|
this.name = 'VersionNotInstalledError';
|
|
2930
2781
|
}
|
|
2931
2782
|
}
|
|
2932
|
-
/**
|
|
2933
|
-
* Resolve a comma-separated --agents list into concrete version selections.
|
|
2934
|
-
* Bare agents target the default version, or the newest installed version when no default exists.
|
|
2935
|
-
* Explicit agent@version targets only that installed version.
|
|
2936
|
-
*/
|
|
2783
|
+
/** Resolve a comma-separated `--agents` list into concrete installed version selections. */
|
|
2937
2784
|
export function resolveAgentVersionTargets(value, availableAgents, options = {}) {
|
|
2938
2785
|
const selectedAgents = [];
|
|
2939
2786
|
const versionSelections = new Map();
|
|
@@ -3025,12 +2872,7 @@ export function resolveAgentVersionTargets(value, availableAgents, options = {})
|
|
|
3025
2872
|
}
|
|
3026
2873
|
return { selectedAgents, versionSelections };
|
|
3027
2874
|
}
|
|
3028
|
-
/**
|
|
3029
|
-
* Resolve a comma-separated --agents list into install/apply targets.
|
|
3030
|
-
* Bare agents target the default version (or newest installed version) when managed,
|
|
3031
|
-
* and fall back to the agent's effective HOME when unmanaged.
|
|
3032
|
-
* Explicit agent@version targets only that installed version.
|
|
3033
|
-
*/
|
|
2875
|
+
/** Resolve a comma-separated `--agents` list into install/apply targets, distinguishing managed versions from direct homes. */
|
|
3034
2876
|
export function resolveInstalledAgentTargets(value, availableAgents, options = {}) {
|
|
3035
2877
|
const selectedAgents = [];
|
|
3036
2878
|
const directAgents = [];
|
|
@@ -3128,9 +2970,7 @@ export function resolveInstalledAgentTargets(value, availableAgents, options = {
|
|
|
3128
2970
|
}
|
|
3129
2971
|
return { selectedAgents, directAgents, versionSelections };
|
|
3130
2972
|
}
|
|
3131
|
-
/**
|
|
3132
|
-
* Resolve configured manifest targets into direct homes and managed versions.
|
|
3133
|
-
*/
|
|
2973
|
+
/** Resolve configured manifest targets into direct homes and managed versions. */
|
|
3134
2974
|
export function resolveConfiguredAgentTargets(agents, agentVersions, availableAgents, options = {}) {
|
|
3135
2975
|
const targetSpecs = [];
|
|
3136
2976
|
const broadTargets = agents ? [...agents] : [...availableAgents];
|
|
@@ -3157,10 +2997,7 @@ export function resolveConfiguredAgentTargets(agents, agentVersions, availableAg
|
|
|
3157
2997
|
}
|
|
3158
2998
|
return resolveInstalledAgentTargets(targetSpecs.join(','), availableAgents, options);
|
|
3159
2999
|
}
|
|
3160
|
-
/**
|
|
3161
|
-
* Prompt user to select agents and versions for resource installation.
|
|
3162
|
-
* Returns selected agents and their version selections.
|
|
3163
|
-
*/
|
|
3000
|
+
/** Prompt the user to select agents and versions for resource installation. */
|
|
3164
3001
|
export async function promptAgentVersionSelection(availableAgents, options = {}) {
|
|
3165
3002
|
const versionSelections = new Map();
|
|
3166
3003
|
// Filter to installed agents (only those with versions managed by agents CLI)
|
package/dist/lib/perf/db.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Disposable performance warehouse — SQLite under ~/.agents/.cache/perf/.
|
|
3
3
|
*
|
|
4
|
-
* Opened only by `agents perf` / `hooks profile` (read path). Writers use
|
|
4
|
+
* Opened only by `agents insights perf` / `hooks profile` (read path). Writers use
|
|
5
5
|
* {@link recordSample} in `./spool.ts` (NDJSON, no SQLite).
|
|
6
6
|
*/
|
|
7
7
|
import Database from '../sqlite.js';
|
package/dist/lib/perf/db.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Disposable performance warehouse — SQLite under ~/.agents/.cache/perf/.
|
|
3
3
|
*
|
|
4
|
-
* Opened only by `agents perf` / `hooks profile` (read path). Writers use
|
|
4
|
+
* Opened only by `agents insights perf` / `hooks profile` (read path). Writers use
|
|
5
5
|
* {@link recordSample} in `./spool.ts` (NDJSON, no SQLite).
|
|
6
6
|
*/
|
|
7
7
|
import * as fs from 'fs';
|
|
@@ -79,7 +79,14 @@ export declare function skillContentMatches(agentId: AgentId, skillName: string,
|
|
|
79
79
|
export declare function listCentralSkills(): string[];
|
|
80
80
|
/**
|
|
81
81
|
* Resolve a skill name to its source directory. Searches user dir first,
|
|
82
|
-
* then system dir, then extra repos
|
|
82
|
+
* then system dir, then extra repos, then plugin-bundled skills
|
|
83
|
+
* (`plugins/<plugin>/skills/<name>` under any trusted base). Returns null if no
|
|
84
|
+
* source has a SKILL.md.
|
|
85
|
+
*
|
|
86
|
+
* Crediting plugins here is load-bearing (PHNX-3185): a skill materialized from
|
|
87
|
+
* `plugins/design/skills/design` has a real source, so it must resolve — else
|
|
88
|
+
* `versionSkillMatches` finds no source and the skill reads as drifted, and
|
|
89
|
+
* {@link diffVersionSkills} classes it an orphan `prune cleanup` would delete.
|
|
83
90
|
*/
|
|
84
91
|
export declare function resolveSkillSourcePath(skillName: string): string | null;
|
|
85
92
|
/**
|
|
@@ -16,6 +16,7 @@ import { capableAgents, isCapable } from '../capabilities.js';
|
|
|
16
16
|
import { getUserSkillsDir, getSkillsDir as getSystemSkillsDir, getProjectAgentsDir, getEnabledExtraRepos, getTrashSkillsDir } from '../state.js';
|
|
17
17
|
import { getEffectiveHome, getVersionHomePath, listInstalledVersions } from '../installations/store.js';
|
|
18
18
|
import { listCommandSkillsInVersion } from '../command-skills.js';
|
|
19
|
+
import { pluginSkillDirs, listPluginSkillNames } from '../staleness/writers/sources.js';
|
|
19
20
|
import { emit } from '../feed/events.js';
|
|
20
21
|
const HOME = os.homedir();
|
|
21
22
|
/** User-scoped skills dir (~/.agents/skills/). Used for installs. */
|
|
@@ -352,7 +353,14 @@ export function listCentralSkills() {
|
|
|
352
353
|
}
|
|
353
354
|
/**
|
|
354
355
|
* Resolve a skill name to its source directory. Searches user dir first,
|
|
355
|
-
* then system dir, then extra repos
|
|
356
|
+
* then system dir, then extra repos, then plugin-bundled skills
|
|
357
|
+
* (`plugins/<plugin>/skills/<name>` under any trusted base). Returns null if no
|
|
358
|
+
* source has a SKILL.md.
|
|
359
|
+
*
|
|
360
|
+
* Crediting plugins here is load-bearing (PHNX-3185): a skill materialized from
|
|
361
|
+
* `plugins/design/skills/design` has a real source, so it must resolve — else
|
|
362
|
+
* `versionSkillMatches` finds no source and the skill reads as drifted, and
|
|
363
|
+
* {@link diffVersionSkills} classes it an orphan `prune cleanup` would delete.
|
|
356
364
|
*/
|
|
357
365
|
export function resolveSkillSourcePath(skillName) {
|
|
358
366
|
for (const dir of [getUserSkillsDir(), getSystemSkillsDir()]) {
|
|
@@ -365,6 +373,11 @@ export function resolveSkillSourcePath(skillName) {
|
|
|
365
373
|
if (fs.existsSync(path.join(candidate, 'SKILL.md')))
|
|
366
374
|
return candidate;
|
|
367
375
|
}
|
|
376
|
+
for (const skillsDir of pluginSkillDirs()) {
|
|
377
|
+
const candidate = path.join(skillsDir, skillName);
|
|
378
|
+
if (fs.existsSync(path.join(candidate, 'SKILL.md')))
|
|
379
|
+
return candidate;
|
|
380
|
+
}
|
|
368
381
|
return null;
|
|
369
382
|
}
|
|
370
383
|
/**
|
|
@@ -443,7 +456,10 @@ function versionSkillMatches(agent, version, skillName) {
|
|
|
443
456
|
* Compare a version home's skills against central. Returns the reconciliation diff.
|
|
444
457
|
*/
|
|
445
458
|
export function diffVersionSkills(agent, version) {
|
|
446
|
-
|
|
459
|
+
// Plugin-provided skills (`plugins/<plugin>/skills`) are legitimate sources too, so a
|
|
460
|
+
// skill materialized from a plugin is NOT an orphan (PHNX-3185). Without this
|
|
461
|
+
// the detector offered to delete the entire plugin-installed skill library.
|
|
462
|
+
const available = new Set([...listAllSkills(), ...listPluginSkillNames({ agent })]);
|
|
447
463
|
// Goose and other native ~/.agents/skills consumers read central storage
|
|
448
464
|
// directly. They intentionally have no per-version copy to diff, so every
|
|
449
465
|
// available central skill is already current for every supported version.
|