@phnx-labs/agents-cli 1.20.9 → 1.20.11
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 +10 -0
- package/dist/commands/inspect.d.ts +37 -0
- package/dist/commands/inspect.js +218 -34
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
**`agents inspect <repo>` summary now shows what's actually inside, not just counts**
|
|
6
|
+
|
|
7
|
+
- The bare repo summary gained four enrichments so it reads as an inventory instead of a tally: (1) **resource name previews** — each kind lists its first few names with a `…(+N)` tail; (2) **manifest summary** — `agents.yaml` is parsed for its `run.<agent>.strategy` and any `agents.<agent>` version pins, shown under `manifests` instead of just the filename; (3) **git detail** — last commit (sha, subject, relative time), ahead/behind upstream when non-zero, and the names of dirty files; (4) **size + file counts** — total repo size and a per-kind byte size. `--json` carries all of it (`git.lastCommit`, `git.ahead/behind`, `manifest`, `size`, and per-kind `{count, bytes, files, names}`); `--brief` still skips resources and size.
|
|
8
|
+
- Fixed a path-parse bug surfaced by the dirty-files list: the shared git helper trimmed leading whitespace, which clipped the first character off the first `git status --porcelain` path; status is now read untrimmed.
|
|
9
|
+
|
|
10
|
+
**`agents inspect .` reads the project `.agents/`, and plugin drill-down shows bundled skills**
|
|
11
|
+
|
|
12
|
+
- `agents inspect .` (and any path to a repo root) now resolves to the project's nested `.agents/` tree when that tree is a populated DotAgents root, instead of the project root itself. Previously a top-level `agents.yaml` version-pin or an unrelated source `skills/` dir at the repo root was mistaken for a DotAgents root, so `inspect .` reported the wrong directory's resources (e.g. `plugins 0` while the real `.agents/plugins/` held a plugin). A bare `.agents`-named dir still resolves to itself, and standalone clones / extra repos that keep resources at the top level (using `.agents/` only for worktrees) are unaffected — their nested `.agents/` is not a DotAgents root, so the top level still wins.
|
|
13
|
+
- `agents inspect <repo> --plugins` now reads plugin bundles through the plugin discoverer: the list shows each plugin's manifest description, and drilling into one (`--plugins <name>`) reports its bundled skills, commands, subagents, hooks, MCP servers, and version. Previously plugins were treated as opaque directories with no description and no view into what they ship.
|
|
14
|
+
|
|
5
15
|
**Single-typo agent names auto-correct everywhere, not just `agents run`**
|
|
6
16
|
|
|
7
17
|
- `agents view cladue` used to print `Unknown agent 'cladue'` even though `agents run cladue` auto-corrected. `resolveAgentName` — the canonical resolver behind `view`, `usage`, `inspect`, `doctor`, `sync`, `models`, `skills`, `hooks`, `import`, `sessions --agent`, and every `agent@version` spec (`agents add claud@latest`, `agents use codx@2.1.170`) — now falls back to Damerau-Levenshtein distance-1 matching against canonical ids and multi-letter aliases: `cladue` -> `claude` (transposition), `kim` -> `kimi`, `codx` -> `codex`, `gemni` -> `gemini`.
|
|
@@ -24,6 +24,8 @@ interface ResourceItem {
|
|
|
24
24
|
linkTarget: string;
|
|
25
25
|
/** One-line description (frontmatter `description:` or first non-frontmatter line). */
|
|
26
26
|
description: string;
|
|
27
|
+
/** Extra detail rows surfaced in detail mode (e.g. a plugin's bundled skills/commands). */
|
|
28
|
+
extra?: Array<[string, string]>;
|
|
27
29
|
}
|
|
28
30
|
interface InspectOptions {
|
|
29
31
|
brief?: boolean;
|
|
@@ -54,4 +56,39 @@ export interface RepoTarget {
|
|
|
54
56
|
export declare function resolveRepoTarget(target: string, cwd?: string): RepoTarget | null;
|
|
55
57
|
/** List one resource kind from a single repo root — no layering, no overrides. */
|
|
56
58
|
export declare function collectRepoKind(repo: RepoTarget, kind: DrillableKind): ResourceItem[];
|
|
59
|
+
/** Recursive size + file count of a path; symlinks are not followed. */
|
|
60
|
+
export declare function pathSize(p: string): {
|
|
61
|
+
bytes: number;
|
|
62
|
+
files: number;
|
|
63
|
+
};
|
|
64
|
+
/** Human byte size: "84 KB", "3.1 MB". */
|
|
65
|
+
export declare function formatBytes(n: number): string;
|
|
66
|
+
export interface ManifestSummary {
|
|
67
|
+
/** `run.<agent>.strategy` pairs from agents.yaml. */
|
|
68
|
+
strategies: Array<{
|
|
69
|
+
agent: string;
|
|
70
|
+
strategy: string;
|
|
71
|
+
}>;
|
|
72
|
+
/** `agents.<agent>` version pins from agents.yaml, when present. */
|
|
73
|
+
versions: Array<{
|
|
74
|
+
agent: string;
|
|
75
|
+
version: string;
|
|
76
|
+
}>;
|
|
77
|
+
}
|
|
78
|
+
/** Parse the repo's own agents.yaml into the version pins + run strategies it declares. */
|
|
79
|
+
export declare function repoManifestSummary(root: string): ManifestSummary | null;
|
|
80
|
+
export interface RepoGitInfo {
|
|
81
|
+
branch: string;
|
|
82
|
+
dirty: number;
|
|
83
|
+
dirtyFiles: string[];
|
|
84
|
+
url: string | null;
|
|
85
|
+
lastCommit: {
|
|
86
|
+
sha: string;
|
|
87
|
+
subject: string;
|
|
88
|
+
relative: string;
|
|
89
|
+
} | null;
|
|
90
|
+
ahead: number | null;
|
|
91
|
+
behind: number | null;
|
|
92
|
+
}
|
|
93
|
+
export declare function repoGitInfo(root: string): RepoGitInfo | null;
|
|
57
94
|
export {};
|
package/dist/commands/inspect.js
CHANGED
|
@@ -23,7 +23,7 @@ import { readMeta, getUserAgentsDir, getSystemAgentsDir, getProjectAgentsDir, ge
|
|
|
23
23
|
import { getVersionHomePath } from '../lib/versions.js';
|
|
24
24
|
import { getShimsDir, getVersionedAliasPath } from '../lib/shims.js';
|
|
25
25
|
import { getAgentResources, listResources, } from '../lib/resources.js';
|
|
26
|
-
import { discoverPlugins } from '../lib/plugins.js';
|
|
26
|
+
import { discoverPlugins, discoverPluginsInDir } from '../lib/plugins.js';
|
|
27
27
|
import { countSessionsInScope } from '../lib/session/discover.js';
|
|
28
28
|
import { damerauLevenshtein } from '../lib/fuzzy.js';
|
|
29
29
|
/** Resource kinds the inspect command can drill into. */
|
|
@@ -161,18 +161,23 @@ export function resolveRepoTarget(target, cwd) {
|
|
|
161
161
|
const stat = safeStat(abs);
|
|
162
162
|
if (!stat || !stat.isDirectory())
|
|
163
163
|
return null;
|
|
164
|
-
// A dir
|
|
165
|
-
|
|
166
|
-
|
|
164
|
+
// A dir literally named `.agents` is the root itself.
|
|
165
|
+
if (path.basename(abs) === '.agents') {
|
|
166
|
+
return { label: path.basename(path.dirname(abs)), root: abs };
|
|
167
|
+
}
|
|
168
|
+
// A nested `.agents/` that is a populated DotAgents root wins over `abs` — the
|
|
169
|
+
// project case (`agents inspect .` from a repo root whose resources live under
|
|
170
|
+
// `.agents/`, while the repo's own top-level `skills/`, `agents.yaml` pin, etc.
|
|
171
|
+
// are unrelated source, not a DotAgents tree).
|
|
172
|
+
const nested = path.join(abs, '.agents');
|
|
173
|
+
if (isDotAgentsRoot(nested)) {
|
|
174
|
+
return { label: path.basename(abs), root: nested };
|
|
175
|
+
}
|
|
176
|
+
// Otherwise treat `abs` itself as the root: standalone clones and extra repos
|
|
177
|
+
// like ~/.agents-extras keep resources at the top level and use `.agents/`
|
|
178
|
+
// only for worktrees (so their nested `.agents/` is not a DotAgents root).
|
|
167
179
|
if (isDotAgentsRoot(abs)) {
|
|
168
|
-
|
|
169
|
-
return { label, root: abs };
|
|
170
|
-
}
|
|
171
|
-
if (path.basename(abs) !== '.agents') {
|
|
172
|
-
const nested = path.join(abs, '.agents');
|
|
173
|
-
if (safeStat(nested)?.isDirectory()) {
|
|
174
|
-
return { label: path.basename(abs), root: nested };
|
|
175
|
-
}
|
|
180
|
+
return { label: path.basename(abs), root: abs };
|
|
176
181
|
}
|
|
177
182
|
return null;
|
|
178
183
|
}
|
|
@@ -204,6 +209,14 @@ async function inspectRepo(repo, options) {
|
|
|
204
209
|
}
|
|
205
210
|
/** List one resource kind from a single repo root — no layering, no overrides. */
|
|
206
211
|
export function collectRepoKind(repo, kind) {
|
|
212
|
+
// Plugins are bundles with a manifest + nested skills/commands/hooks — read
|
|
213
|
+
// them through the plugin discoverer so the manifest description and bundled
|
|
214
|
+
// resources surface, rather than treating each as an opaque directory.
|
|
215
|
+
if (kind === 'plugins') {
|
|
216
|
+
return discoverPluginsInDir(path.join(repo.root, 'plugins'))
|
|
217
|
+
.map(p => pluginToItem(p, repo.label))
|
|
218
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
219
|
+
}
|
|
207
220
|
const dir = path.join(repo.root, kind);
|
|
208
221
|
let entries;
|
|
209
222
|
try {
|
|
@@ -216,6 +229,9 @@ export function collectRepoKind(repo, kind) {
|
|
|
216
229
|
for (const entry of entries) {
|
|
217
230
|
if (entry.name.startsWith('.'))
|
|
218
231
|
continue;
|
|
232
|
+
// Build/tooling caches are never resources — they only inflate counts.
|
|
233
|
+
if (entry.name === '__pycache__' || entry.name === 'node_modules')
|
|
234
|
+
continue;
|
|
219
235
|
const p = path.join(dir, entry.name);
|
|
220
236
|
items.push({
|
|
221
237
|
name: entry.name.replace(/\.(md|yaml|yml|toml|json)$/, ''),
|
|
@@ -227,14 +243,100 @@ export function collectRepoKind(repo, kind) {
|
|
|
227
243
|
}
|
|
228
244
|
return items.sort((a, b) => a.name.localeCompare(b.name));
|
|
229
245
|
}
|
|
246
|
+
/** A few resource names for the at-a-glance preview, with a `…(+N)` tail. */
|
|
247
|
+
function previewNames(items, n) {
|
|
248
|
+
if (items.length === 0)
|
|
249
|
+
return '';
|
|
250
|
+
const shown = items.slice(0, n).map(i => i.name);
|
|
251
|
+
const extra = items.length - shown.length;
|
|
252
|
+
return shown.join(', ') + (extra > 0 ? ` …(+${extra})` : '');
|
|
253
|
+
}
|
|
254
|
+
/** Recursive size + file count of a path; symlinks are not followed. */
|
|
255
|
+
export function pathSize(p) {
|
|
256
|
+
let stat;
|
|
257
|
+
try {
|
|
258
|
+
stat = fs.lstatSync(p);
|
|
259
|
+
}
|
|
260
|
+
catch {
|
|
261
|
+
return { bytes: 0, files: 0 };
|
|
262
|
+
}
|
|
263
|
+
if (stat.isSymbolicLink())
|
|
264
|
+
return { bytes: 0, files: 0 };
|
|
265
|
+
if (stat.isFile())
|
|
266
|
+
return { bytes: stat.size, files: 1 };
|
|
267
|
+
if (!stat.isDirectory())
|
|
268
|
+
return { bytes: 0, files: 0 };
|
|
269
|
+
let entries;
|
|
270
|
+
try {
|
|
271
|
+
entries = fs.readdirSync(p, { withFileTypes: true });
|
|
272
|
+
}
|
|
273
|
+
catch {
|
|
274
|
+
return { bytes: 0, files: 0 };
|
|
275
|
+
}
|
|
276
|
+
let bytes = 0, files = 0;
|
|
277
|
+
for (const e of entries) {
|
|
278
|
+
const sub = pathSize(path.join(p, e.name));
|
|
279
|
+
bytes += sub.bytes;
|
|
280
|
+
files += sub.files;
|
|
281
|
+
}
|
|
282
|
+
return { bytes, files };
|
|
283
|
+
}
|
|
284
|
+
/** Human byte size: "84 KB", "3.1 MB". */
|
|
285
|
+
export function formatBytes(n) {
|
|
286
|
+
if (n < 1024)
|
|
287
|
+
return `${n} B`;
|
|
288
|
+
const units = ['KB', 'MB', 'GB', 'TB'];
|
|
289
|
+
let v = n / 1024, i = 0;
|
|
290
|
+
while (v >= 1024 && i < units.length - 1) {
|
|
291
|
+
v /= 1024;
|
|
292
|
+
i++;
|
|
293
|
+
}
|
|
294
|
+
return `${v >= 10 ? Math.round(v) : v.toFixed(1)} ${units[i]}`;
|
|
295
|
+
}
|
|
296
|
+
/** Parse the repo's own agents.yaml into the version pins + run strategies it declares. */
|
|
297
|
+
export function repoManifestSummary(root) {
|
|
298
|
+
let parsed;
|
|
299
|
+
try {
|
|
300
|
+
parsed = yaml.parse(fs.readFileSync(path.join(root, 'agents.yaml'), 'utf-8'));
|
|
301
|
+
}
|
|
302
|
+
catch {
|
|
303
|
+
return null;
|
|
304
|
+
}
|
|
305
|
+
if (!parsed || typeof parsed !== 'object')
|
|
306
|
+
return null;
|
|
307
|
+
const obj = parsed;
|
|
308
|
+
const strategies = [];
|
|
309
|
+
if (obj.run && typeof obj.run === 'object') {
|
|
310
|
+
for (const [agent, cfg] of Object.entries(obj.run)) {
|
|
311
|
+
const strategy = cfg && typeof cfg === 'object' ? cfg.strategy : undefined;
|
|
312
|
+
if (typeof strategy === 'string')
|
|
313
|
+
strategies.push({ agent, strategy });
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
const versions = [];
|
|
317
|
+
if (obj.agents && typeof obj.agents === 'object') {
|
|
318
|
+
for (const [agent, ver] of Object.entries(obj.agents)) {
|
|
319
|
+
if (typeof ver === 'string')
|
|
320
|
+
versions.push({ agent, version: ver });
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
if (strategies.length === 0 && versions.length === 0)
|
|
324
|
+
return null;
|
|
325
|
+
return { strategies, versions };
|
|
326
|
+
}
|
|
230
327
|
function renderRepoSummary(repo, options) {
|
|
231
328
|
const git = repoGitInfo(repo.root);
|
|
232
329
|
const manifests = REPO_MARKER_FILES.filter(m => fs.existsSync(path.join(repo.root, m)));
|
|
233
|
-
const
|
|
330
|
+
const manifest = repoManifestSummary(repo.root);
|
|
331
|
+
const kindData = {};
|
|
332
|
+
let totalBytes = 0, totalFiles = 0;
|
|
234
333
|
if (!options.brief) {
|
|
235
334
|
for (const kind of DRILLABLE_KINDS) {
|
|
236
335
|
const items = collectRepoKind(repo, kind);
|
|
237
|
-
|
|
336
|
+
const size = pathSize(path.join(repo.root, kind));
|
|
337
|
+
kindData[kind] = { items, size };
|
|
338
|
+
totalBytes += size.bytes;
|
|
339
|
+
totalFiles += size.files;
|
|
238
340
|
}
|
|
239
341
|
}
|
|
240
342
|
if (options.json) {
|
|
@@ -243,32 +345,65 @@ function renderRepoSummary(repo, options) {
|
|
|
243
345
|
root: repo.root,
|
|
244
346
|
git,
|
|
245
347
|
manifests,
|
|
246
|
-
|
|
348
|
+
manifest,
|
|
349
|
+
size: options.brief ? null : { bytes: totalBytes, files: totalFiles },
|
|
350
|
+
resources: options.brief ? null : Object.fromEntries(DRILLABLE_KINDS.map(kind => [kind, {
|
|
351
|
+
count: kindData[kind].items.length,
|
|
352
|
+
bytes: kindData[kind].size.bytes,
|
|
353
|
+
files: kindData[kind].size.files,
|
|
354
|
+
names: kindData[kind].items.map(i => i.name),
|
|
355
|
+
}])),
|
|
247
356
|
}, null, 2));
|
|
248
357
|
return;
|
|
249
358
|
}
|
|
250
359
|
console.log('\n' + chalk.bold(repo.label) + ' ' + chalk.gray('[dotagents repo]') + '\n');
|
|
251
|
-
|
|
360
|
+
// Indent for continuation sub-rows: 2 leading + 10 key column + 1 space.
|
|
361
|
+
const sub = (label, value) => console.log(` ${''.padEnd(10)} ${chalk.gray(label.padEnd(8))} ${value}`);
|
|
362
|
+
console.log(` ${'root'.padEnd(10)} ${termLink(repo.root, repo.root)}`);
|
|
252
363
|
if (git) {
|
|
253
364
|
const dirty = git.dirty > 0 ? ` ${chalk.gray('·')} ${chalk.yellow(`${git.dirty} dirty`)}` : '';
|
|
254
365
|
const url = git.url ? ` ${chalk.gray('·')} ${chalk.gray(git.url)}` : '';
|
|
255
|
-
|
|
366
|
+
console.log(` ${'git'.padEnd(10)} ${git.branch}${dirty}${url}`);
|
|
367
|
+
if (git.lastCommit) {
|
|
368
|
+
const rel = git.lastCommit.relative ? ` ${chalk.gray(`(${git.lastCommit.relative})`)}` : '';
|
|
369
|
+
sub('last', `${chalk.cyan(git.lastCommit.sha)} ${truncate(git.lastCommit.subject, 60)}${rel}`);
|
|
370
|
+
}
|
|
371
|
+
if (git.ahead !== null && git.behind !== null && (git.ahead > 0 || git.behind > 0)) {
|
|
372
|
+
sub('sync', `ahead ${git.ahead} ${chalk.gray('·')} behind ${git.behind}`);
|
|
373
|
+
}
|
|
374
|
+
if (git.dirtyFiles.length > 0) {
|
|
375
|
+
const shown = git.dirtyFiles.slice(0, 4).join(', ');
|
|
376
|
+
const extra = git.dirtyFiles.length - Math.min(4, git.dirtyFiles.length);
|
|
377
|
+
sub('dirty', chalk.yellow(shown + (extra > 0 ? ` …(+${extra})` : '')));
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
if (manifests.length > 0) {
|
|
381
|
+
console.log(` ${'manifests'.padEnd(10)} ${manifests.join(', ')}`);
|
|
382
|
+
if (manifest) {
|
|
383
|
+
if (manifest.versions.length > 0) {
|
|
384
|
+
sub('versions', manifest.versions.map(v => `${v.agent} ${chalk.cyan(v.version)}`).join(chalk.gray(' · ')));
|
|
385
|
+
}
|
|
386
|
+
if (manifest.strategies.length > 0) {
|
|
387
|
+
sub('run', manifest.strategies.map(s => `${s.agent}:${s.strategy}`).join(chalk.gray(' · ')));
|
|
388
|
+
}
|
|
389
|
+
}
|
|
256
390
|
}
|
|
257
|
-
if (manifests.length > 0)
|
|
258
|
-
rows.push(['manifests', manifests.join(', ')]);
|
|
259
|
-
for (const [k, v] of rows)
|
|
260
|
-
console.log(` ${k.padEnd(10)} ${v}`);
|
|
261
391
|
if (!options.brief) {
|
|
392
|
+
console.log(` ${'size'.padEnd(10)} ${formatBytes(totalBytes)} ${chalk.gray('·')} ${totalFiles} files`);
|
|
262
393
|
console.log('\n' + chalk.bold('Resources'));
|
|
263
394
|
for (const kind of DRILLABLE_KINDS) {
|
|
264
|
-
|
|
395
|
+
const { items, size } = kindData[kind];
|
|
396
|
+
const count = String(items.length).padStart(4);
|
|
397
|
+
const sz = items.length > 0 ? formatBytes(size.bytes).padStart(8) : ''.padEnd(8);
|
|
398
|
+
const preview = items.length > 0 ? chalk.gray(truncate(previewNames(items, 4), 60)) : '';
|
|
399
|
+
console.log(` ${kind.padEnd(10)} ${count} ${sz} ${preview}`.trimEnd());
|
|
265
400
|
}
|
|
266
401
|
}
|
|
267
402
|
console.log('');
|
|
268
403
|
console.log(chalk.gray(`Drill in: agents inspect ${repo.label} --skills <query>`));
|
|
269
404
|
console.log('');
|
|
270
405
|
}
|
|
271
|
-
function repoGitInfo(root) {
|
|
406
|
+
export function repoGitInfo(root) {
|
|
272
407
|
const git = (args) => {
|
|
273
408
|
try {
|
|
274
409
|
return execSync(`git -C ${JSON.stringify(root)} ${args}`, { stdio: ['ignore', 'pipe', 'ignore'] })
|
|
@@ -281,9 +416,33 @@ function repoGitInfo(root) {
|
|
|
281
416
|
const branch = git('rev-parse --abbrev-ref HEAD');
|
|
282
417
|
if (branch === null)
|
|
283
418
|
return null;
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
419
|
+
// Read status WITHOUT trimming — git()'s .trim() would strip the leading
|
|
420
|
+
// space of the first porcelain line (`XY path`), corrupting the path slice.
|
|
421
|
+
let statusRaw;
|
|
422
|
+
try {
|
|
423
|
+
statusRaw = execSync(`git -C ${JSON.stringify(root)} status --porcelain`, { stdio: ['ignore', 'pipe', 'ignore'] }).toString();
|
|
424
|
+
}
|
|
425
|
+
catch {
|
|
426
|
+
statusRaw = null;
|
|
427
|
+
}
|
|
428
|
+
const dirtyFiles = statusRaw ? statusRaw.split('\n').filter(Boolean).map(l => l.slice(3)) : [];
|
|
429
|
+
let lastCommit = null;
|
|
430
|
+
const log = git('log -1 --format=%h%x1f%s%x1f%cr');
|
|
431
|
+
if (log) {
|
|
432
|
+
const [sha, subject, relative] = log.split('\x1f');
|
|
433
|
+
if (sha)
|
|
434
|
+
lastCommit = { sha, subject: subject ?? '', relative: relative ?? '' };
|
|
435
|
+
}
|
|
436
|
+
let ahead = null, behind = null;
|
|
437
|
+
const counts = git("rev-list --left-right --count '@{upstream}...HEAD'");
|
|
438
|
+
if (counts) {
|
|
439
|
+
const [b, a] = counts.split(/\s+/).map(n => parseInt(n, 10));
|
|
440
|
+
if (Number.isFinite(b) && Number.isFinite(a)) {
|
|
441
|
+
behind = b;
|
|
442
|
+
ahead = a;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
return { branch, dirty: dirtyFiles.length, dirtyFiles, url: git('remote get-url origin'), lastCommit, ahead, behind };
|
|
287
446
|
}
|
|
288
447
|
// ─── Summary mode ────────────────────────────────────────────────────────────
|
|
289
448
|
async function renderSummary(agent, version, versionHome, options) {
|
|
@@ -486,14 +645,35 @@ function collectKind(agent, versionHome, kind) {
|
|
|
486
645
|
}
|
|
487
646
|
}
|
|
488
647
|
function pluginItems() {
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
648
|
+
return discoverPlugins().map(p => pluginToItem(p, 'user'));
|
|
649
|
+
}
|
|
650
|
+
/**
|
|
651
|
+
* Map a discovered plugin to a resource item, surfacing the manifest description
|
|
652
|
+
* and the bundle's nested resources (skills, commands, hooks, ...) as detail rows.
|
|
653
|
+
*/
|
|
654
|
+
function pluginToItem(plugin, source) {
|
|
655
|
+
const extra = [];
|
|
656
|
+
const list = (names) => names.length <= 8 ? names.join(', ') : `${names.slice(0, 8).join(', ')}, +${names.length - 8} more`;
|
|
657
|
+
if (plugin.skills.length)
|
|
658
|
+
extra.push(['skills', `${plugin.skills.length} (${list(plugin.skills)})`]);
|
|
659
|
+
if (plugin.commands.length)
|
|
660
|
+
extra.push(['commands', `${plugin.commands.length} (${list(plugin.commands)})`]);
|
|
661
|
+
if (plugin.agentDefs.length)
|
|
662
|
+
extra.push(['subagents', `${plugin.agentDefs.length} (${list(plugin.agentDefs)})`]);
|
|
663
|
+
if (plugin.hooks.length)
|
|
664
|
+
extra.push(['hooks', String(plugin.hooks.length)]);
|
|
665
|
+
if (plugin.mcpServers.length)
|
|
666
|
+
extra.push(['mcp', list(plugin.mcpServers)]);
|
|
667
|
+
if (plugin.manifest.version)
|
|
668
|
+
extra.push(['version', plugin.manifest.version]);
|
|
669
|
+
return {
|
|
670
|
+
name: plugin.name,
|
|
671
|
+
source,
|
|
672
|
+
path: plugin.root,
|
|
673
|
+
linkTarget: linkTarget(plugin.root),
|
|
674
|
+
description: plugin.manifest.description ?? '',
|
|
675
|
+
extra,
|
|
676
|
+
};
|
|
497
677
|
}
|
|
498
678
|
function entriesFromAgentResources(agent, versionHome, kind) {
|
|
499
679
|
const res = getAgentResources(agent, { home: versionHome });
|
|
@@ -562,6 +742,10 @@ function buildDetailRows(item, kind) {
|
|
|
562
742
|
rows.push(['tools', fm.tools.join(', ')]);
|
|
563
743
|
}
|
|
564
744
|
}
|
|
745
|
+
// Plugin bundles carry their nested resources as pre-built rows.
|
|
746
|
+
if (kind === 'plugins' && item.extra) {
|
|
747
|
+
rows.push(...item.extra);
|
|
748
|
+
}
|
|
565
749
|
return rows;
|
|
566
750
|
}
|
|
567
751
|
function findMatches(items, query) {
|
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.11",
|
|
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",
|