@rbbtsn0w/adg 0.3.0-beta.3 → 0.3.0-beta.4

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/dist/bin/adg.js CHANGED
@@ -1,9 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { parseArgs } from "node:util";
3
- import pc from "picocolors";
4
3
  import { spawnSync } from "node:child_process";
5
4
  import { readFileSync, realpathSync } from "node:fs";
6
- import { homedir } from "node:os";
7
5
  import { dirname, join, resolve } from "node:path";
8
6
  import { fileURLToPath } from "node:url";
9
7
  import { checkForUpdate, formatUpdateNotice } from "../src/update-check.js";
@@ -23,24 +21,11 @@ import { selectTargetsInteractive } from "../src/commands/select-agents.js";
23
21
  import { selectPluginsInteractive } from "../src/commands/select-plugins.js";
24
22
  import { selectScopeInteractive } from "../src/commands/select-scope.js";
25
23
  import { confirmFullInstall, selectComponentsInteractive } from "../src/commands/select-components.js";
26
- import { globalPluginsDir, installedPluginDir, projectPluginsDir } from "../src/paths.js";
24
+ import { globalPluginsDir, projectPluginsDir } from "../src/paths.js";
27
25
  import { COMPONENT_TYPES } from "../src/types.js";
28
- import { agentsForComponents, getAgent } from "../src/agents/index.js";
29
- // ---------------------------------------------------------------------------
30
- // Semantic colors, mirroring `adg skills list` so output reads the same across
31
- // commands: cyan = primary identifiers (plugins, agents, sources), dim =
32
- // secondary metadata (paths, hashes, sub-details), green = success, yellow =
33
- // notes/warnings, red = errors, bold = section titles. picocolors auto-disables
34
- // on non-TTY / NO_COLOR, so piped output and tests stay plain.
35
- // ---------------------------------------------------------------------------
36
- const ui = {
37
- title: (s) => pc.bold(s),
38
- name: (s) => pc.cyan(s),
39
- meta: (s) => pc.dim(s),
40
- ok: (s) => pc.green(s),
41
- warn: (s) => pc.yellow(s),
42
- err: (s) => pc.red(s),
43
- };
26
+ import { getAgent } from "../src/agents/index.js";
27
+ import { ui } from "../src/render/ui.js";
28
+ import { renderAgentReport, renderMarketplaceList, renderPluginList, } from "../src/render/plugins.js";
44
29
  const FLAGS = {
45
30
  // Short flags are first-letter aliases. Where several long flags share a
46
31
  // first letter, the highest-frequency one wins the short and the rest stay
@@ -243,16 +228,6 @@ function scopeInfo(values) {
243
228
  function scopeOf(values) {
244
229
  return values.global ? "user" : "project";
245
230
  }
246
- /** Print per-agent sync outcomes (enabled/disabled/re-synced) generically. */
247
- function reportAgents(agents, verb) {
248
- for (const r of agents ?? []) {
249
- const name = getAgent(r.agent)?.displayName ?? r.agent;
250
- if (r.affected.length > 0)
251
- console.log(`${ui.ok(verb)} in ${ui.name(name)}: ${r.affected.join(", ")}`);
252
- else if (r.skipped)
253
- console.log(ui.warn(`note: \`${r.agent}\` CLI not found — run \`adg plugins link --target ${r.agent}\` after installing it.`));
254
- }
255
- }
256
231
  /** Friendly `--target` aliases mapped onto canonical adapter target ids. */
257
232
  const TARGET_ALIASES = {
258
233
  anthropic: "claude",
@@ -294,45 +269,6 @@ function parseVerb(name, flags, rest) {
294
269
  process.exit(1);
295
270
  }
296
271
  }
297
- /**
298
- * Lay items out in aligned columns sized to the terminal width (row-major).
299
- * Items longer than `maxColWidth` are truncated with an ellipsis. Falls back to
300
- * a single column on narrow terminals. Returns the block as a string.
301
- */
302
- function formatColumns(items, opts = {}) {
303
- const indent = opts.indent ?? 2;
304
- const gutter = opts.gutter ?? 2;
305
- const maxColWidth = opts.maxColWidth ?? 24;
306
- const termWidth = opts.width ?? process.stdout.columns ?? 80;
307
- const cells = items.map((s) => (s.length > maxColWidth ? s.slice(0, maxColWidth - 1) + "…" : s));
308
- const colWidth = Math.min(Math.max(1, ...cells.map((c) => c.length)), maxColWidth);
309
- const cols = Math.max(1, Math.floor((termWidth - indent + gutter) / (colWidth + gutter)));
310
- const lines = [];
311
- for (let i = 0; i < cells.length; i += cols) {
312
- const row = cells.slice(i, i + cols);
313
- const padded = row.map((c, j) => (j === row.length - 1 ? c : c.padEnd(colWidth)));
314
- lines.push(" ".repeat(indent) + padded.join(" ".repeat(gutter)));
315
- }
316
- return lines.join("\n");
317
- }
318
- /** Abbreviate the home-directory prefix of an absolute path to `~` (POSIX `/` or Windows `\`). */
319
- function abbrevHome(p) {
320
- const home = homedir();
321
- if (p === home)
322
- return "~";
323
- if (p.startsWith(home + "/") || p.startsWith(home + "\\"))
324
- return "~" + p.slice(home.length);
325
- return p;
326
- }
327
- /** Print a plugin's components, each expanded to its member names (verbose view). */
328
- function printContents(contents, headerIndent) {
329
- const entries = Object.entries(contents ?? {}).filter(([, names]) => names.length > 0);
330
- for (const [type, names] of entries) {
331
- const maxColWidth = Math.max(1, ...names.map((n) => n.length));
332
- console.log(`${" ".repeat(headerIndent)}${ui.name(type)} ${ui.meta(`(${names.length}):`)}`);
333
- console.log(formatColumns(names, { indent: headerIndent + 2, maxColWidth }));
334
- }
335
- }
336
272
  async function runPlugins(rawVerb, rest) {
337
273
  // `adg plugins` (no verb) or an explicit help request → the L1 overview.
338
274
  if (rawVerb === undefined || rawVerb === "-h" || rawVerb === "--help" || rawVerb === "help") {
@@ -447,7 +383,8 @@ async function runPlugins(rawVerb, rest) {
447
383
  for (const f of res.adapted)
448
384
  console.log(ui.meta(` adapted: ${f}`));
449
385
  }
450
- reportAgents(agents, "enabled");
386
+ for (const line of renderAgentReport(agents, "enabled"))
387
+ console.log(line);
451
388
  return;
452
389
  }
453
390
  case "import-skills": {
@@ -494,7 +431,8 @@ async function runPlugins(rawVerb, rest) {
494
431
  console.log(`${r.changed ? ui.ok("updated") : ui.meta("unchanged")} ${ui.name(`${r.name}@${r.version}`)}`);
495
432
  for (const m of missing)
496
433
  console.error(ui.warn(` ! missing directory for locked plugin: ${m}`));
497
- reportAgents(agents, "re-synced");
434
+ for (const line of renderAgentReport(agents, "re-synced"))
435
+ console.log(line);
498
436
  return;
499
437
  }
500
438
  case "remove": {
@@ -528,43 +466,8 @@ async function runPlugins(rawVerb, rest) {
528
466
  const { values } = parseVerb(verb, cmd.flags, rest);
529
467
  const pluginsDir = resolveScopeDir(values);
530
468
  const plugins = listPlugins(pluginsDir);
531
- if (plugins.length === 0) {
532
- console.log(ui.meta(`no plugins recorded in ${pluginsDir}`));
533
- return;
534
- }
535
- // Pre-compute each plugin's display row so the name/path columns can be
536
- // aligned across rows (à la `adg skills list`). The `Agents:` column is
537
- // derived from the exposed component types — which agents can adapt it.
538
- const PATH_MAX = 44;
539
- const rows = plugins.map((p) => {
540
- const exposed = Object.entries(p.contents ?? {}).filter(([, names]) => names.length > 0);
541
- const types = exposed.map(([type]) => type);
542
- const agents = agentsForComponents(types).map((a) => a.displayName);
543
- return {
544
- p,
545
- label: `${p.name}@${p.version}`,
546
- path: abbrevHome(installedPluginDir(pluginsDir, p.name, p.origin)),
547
- agents: agents.length > 0 ? agents.join(", ") : "—",
548
- counts: exposed.map(([type, names]) => `${type}: ${names.length}`),
549
- };
550
- });
551
- const nameW = Math.max(...rows.map((r) => r.label.length));
552
- const pathW = Math.min(PATH_MAX, Math.max(...rows.map((r) => r.path.length)));
553
- const ellip = (s, w) => (s.length > w ? "…" + s.slice(s.length - w + 1) : s);
554
- // Color mirrors `adg skills list`: cyan name, dim path / dim "Agents:"
555
- // label with the agent names left bright, and the provenance/counts line
556
- // fully dimmed as secondary metadata. Widths are measured on the uncolored
557
- // strings (above), so wrapping the padded text keeps columns aligned.
558
- // picocolors auto-disables on non-TTY / NO_COLOR, so pipes stay plain.
559
- for (const r of rows) {
560
- const partial = r.p.selection ? " (partial)" : "";
561
- const name = ui.name(r.label.padEnd(nameW));
562
- const path = ui.meta(ellip(r.path, pathW).padEnd(pathW));
563
- console.log(`${name} ${path} ${ui.meta("Agents:")} ${r.agents}`);
564
- const provenance = `[${r.p.origin.type}] ${r.p.folderHash.slice(0, 19)}${partial}`;
565
- console.log(ui.meta(` ${[provenance, ...r.counts].join(" ")}`));
566
- if (values.verbose)
567
- printContents(r.p.contents, 4);
469
+ for (const line of renderPluginList(plugins, pluginsDir, { verbose: values.verbose })) {
470
+ console.log(line);
568
471
  }
569
472
  return;
570
473
  }
@@ -618,28 +521,10 @@ async function runMarketplace(args) {
618
521
  const { values } = parseVerb("marketplace", ["verbose", ...SCOPE], rest);
619
522
  const dir = resolveScopeDir(values);
620
523
  const groups = marketplaceList({ pluginsDir: dir });
621
- if (groups.length === 0) {
622
- console.log(ui.meta("No plugins installed."));
623
- return;
624
- }
625
524
  // Verbose: drill each plugin down to its components (reuses `plugins list -v`).
626
525
  const byName = values.verbose ? new Map(listPlugins(dir).map((p) => [p.name, p])) : undefined;
627
- for (const g of groups) {
628
- const ref = g.ref ? `@${g.ref}` : "";
629
- const n = g.installed.length;
630
- const tag = g.remote ? "" : ui.warn(" (local — re-run add to update)");
631
- console.log(`${ui.name(`${g.source}${ref}`)} ${ui.meta(`(${n} plugin${n !== 1 ? "s" : ""})`)}${tag}`);
632
- if (byName) {
633
- for (const name of g.installed) {
634
- const p = byName.get(name);
635
- console.log(` ${ui.name(name)}${p?.selection ? ui.meta(" (partial)") : ""}`);
636
- printContents(p?.contents, 4);
637
- }
638
- }
639
- else {
640
- console.log(formatColumns(g.installed));
641
- }
642
- }
526
+ for (const line of renderMarketplaceList(groups, byName))
527
+ console.log(line);
643
528
  return;
644
529
  }
645
530
  case "upgrade": {
@@ -0,0 +1,100 @@
1
+ import { ui, formatColumns, abbrevHome, ellipsizeStart } from "./ui.js";
2
+ import { installedPluginDir } from "../paths.js";
3
+ import { agentsForComponents, getAgent } from "../agents/index.js";
4
+ // ---------------------------------------------------------------------------
5
+ // Presentation layer for `adg plugins`. Each function turns command-layer data
6
+ // into terminal-ready lines (returned as string[]), so bin/adg.ts only parses
7
+ // args, calls a command, prints the lines — and the formatting is unit-testable
8
+ // without spawning the CLI. Color mirrors `adg skills list` throughout.
9
+ // ---------------------------------------------------------------------------
10
+ /** A plugin's components, each expanded to its member names (verbose view). */
11
+ export function renderContents(contents, headerIndent) {
12
+ const out = [];
13
+ const entries = Object.entries(contents ?? {}).filter(([, names]) => names.length > 0);
14
+ for (const [type, names] of entries) {
15
+ const maxColWidth = Math.max(1, ...names.map((n) => n.length));
16
+ out.push(`${" ".repeat(headerIndent)}${ui.name(type)} ${ui.meta(`(${names.length}):`)}`);
17
+ // formatColumns returns one string with embedded newlines; split so `out`
18
+ // stays a flat list of single lines.
19
+ out.push(...formatColumns(names, { indent: headerIndent + 2, maxColWidth }).split("\n"));
20
+ }
21
+ return out;
22
+ }
23
+ /** Per-agent sync outcomes (enabled/disabled/re-synced), printed generically. */
24
+ export function renderAgentReport(agents, verb) {
25
+ const out = [];
26
+ for (const r of agents ?? []) {
27
+ const name = getAgent(r.agent)?.displayName ?? r.agent;
28
+ if (r.affected.length > 0)
29
+ out.push(`${ui.ok(verb)} in ${ui.name(name)}: ${r.affected.join(", ")}`);
30
+ else if (r.skipped)
31
+ out.push(ui.warn(`note: \`${r.agent}\` CLI not found — run \`adg plugins link --target ${r.agent}\` after installing it.`));
32
+ }
33
+ return out;
34
+ }
35
+ const PATH_MAX = 44;
36
+ /** `adg plugins list` — aligned name/path/agents rows, with optional verbose contents. */
37
+ export function renderPluginList(plugins, pluginsDir, opts = {}) {
38
+ if (plugins.length === 0)
39
+ return [ui.meta(`no plugins recorded in ${pluginsDir}`)];
40
+ // Pre-compute each plugin's display row so the name/path columns can be
41
+ // aligned across rows (à la `adg skills list`). The `Agents:` column is
42
+ // derived from the exposed component types — which agents can adapt it.
43
+ const rows = plugins.map((p) => {
44
+ const exposed = Object.entries(p.contents ?? {}).filter(([, names]) => names.length > 0);
45
+ const types = exposed.map(([type]) => type);
46
+ const agents = agentsForComponents(types).map((a) => a.displayName);
47
+ return {
48
+ p,
49
+ label: `${p.name}@${p.version}`,
50
+ path: abbrevHome(installedPluginDir(pluginsDir, p.name, p.origin)),
51
+ agents: agents.length > 0 ? agents.join(", ") : "—",
52
+ counts: exposed.map(([type, names]) => `${type}: ${names.length}`),
53
+ };
54
+ });
55
+ const nameW = Math.max(...rows.map((r) => r.label.length));
56
+ const pathW = Math.min(PATH_MAX, Math.max(...rows.map((r) => r.path.length)));
57
+ // Color mirrors `adg skills list`: cyan name, dim path / dim "Agents:" label
58
+ // with the agent names left bright, and the provenance/counts line fully
59
+ // dimmed as secondary metadata. Widths are measured on the uncolored strings
60
+ // (above), so wrapping the padded text keeps columns aligned.
61
+ const out = [];
62
+ for (const r of rows) {
63
+ const partial = r.p.selection ? " (partial)" : "";
64
+ const name = ui.name(r.label.padEnd(nameW));
65
+ const path = ui.meta(ellipsizeStart(r.path, pathW).padEnd(pathW));
66
+ out.push(`${name} ${path} ${ui.meta("Agents:")} ${r.agents}`);
67
+ const provenance = `[${r.p.origin.type}] ${(r.p.folderHash ?? "").slice(0, 19)}${partial}`;
68
+ out.push(ui.meta(` ${[provenance, ...r.counts].join(" ")}`));
69
+ if (opts.verbose)
70
+ out.push(...renderContents(r.p.contents, 4));
71
+ }
72
+ return out;
73
+ }
74
+ /**
75
+ * `adg plugins marketplace list` — installed plugins grouped by source.
76
+ * `details` (name → ListedPlugin) enables the verbose per-plugin component drill;
77
+ * pass it only when --verbose is set.
78
+ */
79
+ export function renderMarketplaceList(groups, details) {
80
+ if (groups.length === 0)
81
+ return [ui.meta("No plugins installed.")];
82
+ const out = [];
83
+ for (const g of groups) {
84
+ const ref = g.ref ? `@${g.ref}` : "";
85
+ const n = g.installed.length;
86
+ const tag = g.remote ? "" : ui.warn(" (local — re-run add to update)");
87
+ out.push(`${ui.name(`${g.source}${ref}`)} ${ui.meta(`(${n} plugin${n !== 1 ? "s" : ""})`)}${tag}`);
88
+ if (details) {
89
+ for (const name of g.installed) {
90
+ const p = details.get(name);
91
+ out.push(` ${ui.name(name)}${p?.selection ? ui.meta(" (partial)") : ""}`);
92
+ out.push(...renderContents(p?.contents, 4));
93
+ }
94
+ }
95
+ else {
96
+ out.push(...formatColumns(g.installed).split("\n"));
97
+ }
98
+ }
99
+ return out;
100
+ }
@@ -0,0 +1,51 @@
1
+ import pc from "picocolors";
2
+ import { homedir } from "node:os";
3
+ // ---------------------------------------------------------------------------
4
+ // Semantic colors, mirroring `adg skills list` so output reads the same across
5
+ // commands: cyan = primary identifiers (plugins, agents, sources), dim =
6
+ // secondary metadata (paths, hashes, sub-details), green = success, yellow =
7
+ // notes/warnings, red = errors, bold = section titles. picocolors auto-disables
8
+ // on non-TTY / NO_COLOR, so piped output and tests stay plain.
9
+ // ---------------------------------------------------------------------------
10
+ export const ui = {
11
+ title: (s) => pc.bold(s),
12
+ name: (s) => pc.cyan(s),
13
+ meta: (s) => pc.dim(s),
14
+ ok: (s) => pc.green(s),
15
+ warn: (s) => pc.yellow(s),
16
+ err: (s) => pc.red(s),
17
+ };
18
+ /**
19
+ * Lay items out in aligned columns sized to the terminal width (row-major).
20
+ * Items longer than `maxColWidth` are truncated with an ellipsis. Falls back to
21
+ * a single column on narrow terminals. Returns the block as a string.
22
+ */
23
+ export function formatColumns(items, opts = {}) {
24
+ const indent = opts.indent ?? 2;
25
+ const gutter = opts.gutter ?? 2;
26
+ const maxColWidth = opts.maxColWidth ?? 24;
27
+ const termWidth = opts.width ?? process.stdout.columns ?? 80;
28
+ const cells = items.map((s) => (s.length > maxColWidth ? s.slice(0, maxColWidth - 1) + "…" : s));
29
+ const colWidth = Math.min(Math.max(1, ...cells.map((c) => c.length)), maxColWidth);
30
+ const cols = Math.max(1, Math.floor((termWidth - indent + gutter) / (colWidth + gutter)));
31
+ const lines = [];
32
+ for (let i = 0; i < cells.length; i += cols) {
33
+ const row = cells.slice(i, i + cols);
34
+ const padded = row.map((c, j) => (j === row.length - 1 ? c : c.padEnd(colWidth)));
35
+ lines.push(" ".repeat(indent) + padded.join(" ".repeat(gutter)));
36
+ }
37
+ return lines.join("\n");
38
+ }
39
+ /** Abbreviate the home-directory prefix of an absolute path to `~` (POSIX `/` or Windows `\`). */
40
+ export function abbrevHome(p) {
41
+ const home = homedir();
42
+ if (p === home)
43
+ return "~";
44
+ if (p.startsWith(home + "/") || p.startsWith(home + "\\"))
45
+ return "~" + p.slice(home.length);
46
+ return p;
47
+ }
48
+ /** Tail-truncate a string to width `w`, prefixing an ellipsis when it overflows. */
49
+ export function ellipsizeStart(s, w) {
50
+ return s.length > w ? "…" + s.slice(s.length - w + 1) : s;
51
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rbbtsn0w/adg",
3
- "version": "0.3.0-beta.3",
3
+ "version": "0.3.0-beta.4",
4
4
  "description": "Agent Directory Group (ADG) toolkit — two domains: plugins and skills.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -25,6 +25,7 @@
25
25
  "adg": "node ./bin/adg.ts",
26
26
  "build": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.build.json --noCheck && node -e \"require('fs').chmodSync('dist/bin/adg.js',0o755)\"",
27
27
  "test": "node --test 'test/**/*.test.ts'",
28
+ "check:vendor-deps": "node scripts/check-vendor-deps.mjs",
28
29
  "typecheck": "tsc --noEmit",
29
30
  "prepare": "npm run typecheck",
30
31
  "prepack": "npm run build"
@@ -35,12 +36,12 @@
35
36
  "vendor",
36
37
  "docs"
37
38
  ],
38
- "comment:dependencies": "Runtime deps below are required only by the vendored skills CLI (vendor/skills). ADG's own plugins code has zero runtime deps.",
39
+ "comment:dependencies": "Runtime deps below are required only by the vendored skills CLI (vendor/skills); ADG's own plugins code has zero runtime deps. Root is the single source of truth: each range MUST be >= the floor declared in vendor/skills/package.json, since that is the version the vendored source was authored against. `npm run check:vendor-deps` enforces this and fails on drift.",
39
40
  "dependencies": {
40
- "@clack/prompts": "^0.7.0",
41
+ "@clack/prompts": "^0.11.0",
41
42
  "@vercel/detect-agent": "^1.2.3",
42
- "picocolors": "^1.0.0",
43
- "simple-git": "^3.0.0",
43
+ "picocolors": "^1.1.1",
44
+ "simple-git": "^3.36.0",
44
45
  "xdg-basedir": "^5.1.0",
45
46
  "yaml": "^2.8.3"
46
47
  },