@phnx-labs/agents-cli 1.20.10 → 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 +5 -0
- package/dist/commands/inspect.d.ts +35 -0
- package/dist/commands/inspect.js +160 -14
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
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
|
+
|
|
5
10
|
**`agents inspect .` reads the project `.agents/`, and plugin drill-down shows bundled skills**
|
|
6
11
|
|
|
7
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.
|
|
@@ -56,4 +56,39 @@ export interface RepoTarget {
|
|
|
56
56
|
export declare function resolveRepoTarget(target: string, cwd?: string): RepoTarget | null;
|
|
57
57
|
/** List one resource kind from a single repo root — no layering, no overrides. */
|
|
58
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;
|
|
59
94
|
export {};
|
package/dist/commands/inspect.js
CHANGED
|
@@ -229,6 +229,9 @@ export function collectRepoKind(repo, kind) {
|
|
|
229
229
|
for (const entry of entries) {
|
|
230
230
|
if (entry.name.startsWith('.'))
|
|
231
231
|
continue;
|
|
232
|
+
// Build/tooling caches are never resources — they only inflate counts.
|
|
233
|
+
if (entry.name === '__pycache__' || entry.name === 'node_modules')
|
|
234
|
+
continue;
|
|
232
235
|
const p = path.join(dir, entry.name);
|
|
233
236
|
items.push({
|
|
234
237
|
name: entry.name.replace(/\.(md|yaml|yml|toml|json)$/, ''),
|
|
@@ -240,14 +243,100 @@ export function collectRepoKind(repo, kind) {
|
|
|
240
243
|
}
|
|
241
244
|
return items.sort((a, b) => a.name.localeCompare(b.name));
|
|
242
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
|
+
}
|
|
243
327
|
function renderRepoSummary(repo, options) {
|
|
244
328
|
const git = repoGitInfo(repo.root);
|
|
245
329
|
const manifests = REPO_MARKER_FILES.filter(m => fs.existsSync(path.join(repo.root, m)));
|
|
246
|
-
const
|
|
330
|
+
const manifest = repoManifestSummary(repo.root);
|
|
331
|
+
const kindData = {};
|
|
332
|
+
let totalBytes = 0, totalFiles = 0;
|
|
247
333
|
if (!options.brief) {
|
|
248
334
|
for (const kind of DRILLABLE_KINDS) {
|
|
249
335
|
const items = collectRepoKind(repo, kind);
|
|
250
|
-
|
|
336
|
+
const size = pathSize(path.join(repo.root, kind));
|
|
337
|
+
kindData[kind] = { items, size };
|
|
338
|
+
totalBytes += size.bytes;
|
|
339
|
+
totalFiles += size.files;
|
|
251
340
|
}
|
|
252
341
|
}
|
|
253
342
|
if (options.json) {
|
|
@@ -256,32 +345,65 @@ function renderRepoSummary(repo, options) {
|
|
|
256
345
|
root: repo.root,
|
|
257
346
|
git,
|
|
258
347
|
manifests,
|
|
259
|
-
|
|
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
|
+
}])),
|
|
260
356
|
}, null, 2));
|
|
261
357
|
return;
|
|
262
358
|
}
|
|
263
359
|
console.log('\n' + chalk.bold(repo.label) + ' ' + chalk.gray('[dotagents repo]') + '\n');
|
|
264
|
-
|
|
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)}`);
|
|
265
363
|
if (git) {
|
|
266
364
|
const dirty = git.dirty > 0 ? ` ${chalk.gray('·')} ${chalk.yellow(`${git.dirty} dirty`)}` : '';
|
|
267
365
|
const url = git.url ? ` ${chalk.gray('·')} ${chalk.gray(git.url)}` : '';
|
|
268
|
-
|
|
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
|
+
}
|
|
269
390
|
}
|
|
270
|
-
if (manifests.length > 0)
|
|
271
|
-
rows.push(['manifests', manifests.join(', ')]);
|
|
272
|
-
for (const [k, v] of rows)
|
|
273
|
-
console.log(` ${k.padEnd(10)} ${v}`);
|
|
274
391
|
if (!options.brief) {
|
|
392
|
+
console.log(` ${'size'.padEnd(10)} ${formatBytes(totalBytes)} ${chalk.gray('·')} ${totalFiles} files`);
|
|
275
393
|
console.log('\n' + chalk.bold('Resources'));
|
|
276
394
|
for (const kind of DRILLABLE_KINDS) {
|
|
277
|
-
|
|
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());
|
|
278
400
|
}
|
|
279
401
|
}
|
|
280
402
|
console.log('');
|
|
281
403
|
console.log(chalk.gray(`Drill in: agents inspect ${repo.label} --skills <query>`));
|
|
282
404
|
console.log('');
|
|
283
405
|
}
|
|
284
|
-
function repoGitInfo(root) {
|
|
406
|
+
export function repoGitInfo(root) {
|
|
285
407
|
const git = (args) => {
|
|
286
408
|
try {
|
|
287
409
|
return execSync(`git -C ${JSON.stringify(root)} ${args}`, { stdio: ['ignore', 'pipe', 'ignore'] })
|
|
@@ -294,9 +416,33 @@ function repoGitInfo(root) {
|
|
|
294
416
|
const branch = git('rev-parse --abbrev-ref HEAD');
|
|
295
417
|
if (branch === null)
|
|
296
418
|
return null;
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
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 };
|
|
300
446
|
}
|
|
301
447
|
// ─── Summary mode ────────────────────────────────────────────────────────────
|
|
302
448
|
async function renderSummary(agent, version, versionHome, options) {
|
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",
|