@rbbtsn0w/adg 0.1.0-alpha.1 → 0.1.0-beta.1

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.
Files changed (110) hide show
  1. package/dist/bin/adg.js +703 -0
  2. package/dist/src/adapters/anthropic.js +54 -0
  3. package/dist/src/adapters/index.js +10 -0
  4. package/dist/src/adapters/openai.js +30 -0
  5. package/dist/src/adapters/reverse.js +53 -0
  6. package/dist/src/agents/claude.js +118 -0
  7. package/dist/src/agents/codex.js +61 -0
  8. package/{src/agents/index.ts → dist/src/agents/index.js} +6 -8
  9. package/dist/src/agents/registry.js +24 -0
  10. package/dist/src/agents/types.js +1 -0
  11. package/dist/src/commands/adapt.js +26 -0
  12. package/dist/src/commands/import.js +51 -0
  13. package/dist/src/commands/init.js +104 -0
  14. package/dist/src/commands/install.js +257 -0
  15. package/dist/src/commands/link.js +34 -0
  16. package/dist/src/commands/list.js +19 -0
  17. package/dist/src/commands/marketplace.js +124 -0
  18. package/dist/src/commands/migrate.js +60 -0
  19. package/dist/src/commands/multiselect-skills.js +103 -0
  20. package/dist/src/commands/remove.js +102 -0
  21. package/dist/src/commands/select-agents.js +40 -0
  22. package/dist/src/commands/select-components.js +61 -0
  23. package/dist/src/commands/select-plugins.js +25 -0
  24. package/dist/src/commands/select-scope.js +20 -0
  25. package/dist/src/commands/update.js +50 -0
  26. package/dist/src/commands/validate.js +50 -0
  27. package/dist/src/components.js +90 -0
  28. package/dist/src/deps.js +46 -0
  29. package/dist/src/fsutil.js +32 -0
  30. package/dist/src/hash.js +51 -0
  31. package/dist/src/lock.js +51 -0
  32. package/dist/src/manifest.js +110 -0
  33. package/dist/src/marketplace.js +39 -0
  34. package/{src/package.ts → dist/src/package.js} +37 -42
  35. package/{src/paths.ts → dist/src/paths.js} +54 -60
  36. package/dist/src/semver.js +55 -0
  37. package/dist/src/skills.js +79 -0
  38. package/dist/src/sources.js +122 -0
  39. package/dist/src/types.js +19 -0
  40. package/dist/vendor/skills/package.json +143 -0
  41. package/dist/vendor/skills/src/add.js +1663 -0
  42. package/dist/vendor/skills/src/agents.js +729 -0
  43. package/dist/vendor/skills/src/blob.js +436 -0
  44. package/dist/vendor/skills/src/cli.js +340 -0
  45. package/dist/vendor/skills/src/constants.js +3 -0
  46. package/dist/vendor/skills/src/detect-agent.js +56 -0
  47. package/dist/vendor/skills/src/find.js +294 -0
  48. package/dist/vendor/skills/src/frontmatter.js +13 -0
  49. package/dist/vendor/skills/src/git-tree.js +32 -0
  50. package/dist/vendor/skills/src/git.js +235 -0
  51. package/dist/vendor/skills/src/install.js +75 -0
  52. package/dist/vendor/skills/src/installer.js +924 -0
  53. package/dist/vendor/skills/src/list.js +201 -0
  54. package/dist/vendor/skills/src/local-lock.js +109 -0
  55. package/dist/vendor/skills/src/plugin-manifest.js +152 -0
  56. package/dist/vendor/skills/src/prompts/search-multiselect.js +312 -0
  57. package/dist/vendor/skills/src/providers/index.js +4 -0
  58. package/dist/vendor/skills/src/providers/registry.js +42 -0
  59. package/dist/vendor/skills/src/providers/types.js +1 -0
  60. package/dist/vendor/skills/src/providers/wellknown.js +625 -0
  61. package/dist/vendor/skills/src/remove.js +263 -0
  62. package/dist/vendor/skills/src/sanitize.js +57 -0
  63. package/dist/vendor/skills/src/self-cli.js +15 -0
  64. package/dist/vendor/skills/src/skill-lock.js +237 -0
  65. package/dist/vendor/skills/src/skills.js +264 -0
  66. package/dist/vendor/skills/src/source-parser.js +367 -0
  67. package/dist/vendor/skills/src/sync.js +404 -0
  68. package/dist/vendor/skills/src/telemetry.js +101 -0
  69. package/dist/vendor/skills/src/test-utils.js +59 -0
  70. package/dist/vendor/skills/src/types.js +1 -0
  71. package/dist/vendor/skills/src/update-source.js +76 -0
  72. package/dist/vendor/skills/src/update.js +590 -0
  73. package/dist/vendor/skills/src/use.js +505 -0
  74. package/package.json +15 -7
  75. package/bin/adg.ts +0 -758
  76. package/src/adapters/anthropic.ts +0 -54
  77. package/src/adapters/index.ts +0 -24
  78. package/src/adapters/openai.ts +0 -37
  79. package/src/adapters/reverse.ts +0 -60
  80. package/src/agents/claude.ts +0 -124
  81. package/src/agents/codex.ts +0 -67
  82. package/src/agents/registry.ts +0 -30
  83. package/src/agents/types.ts +0 -47
  84. package/src/commands/adapt.ts +0 -36
  85. package/src/commands/import.ts +0 -69
  86. package/src/commands/init.ts +0 -146
  87. package/src/commands/install.ts +0 -411
  88. package/src/commands/link.ts +0 -61
  89. package/src/commands/list.ts +0 -28
  90. package/src/commands/marketplace.ts +0 -198
  91. package/src/commands/migrate.ts +0 -84
  92. package/src/commands/multiselect-skills.ts +0 -137
  93. package/src/commands/remove.ts +0 -136
  94. package/src/commands/select-agents.ts +0 -45
  95. package/src/commands/select-components.ts +0 -66
  96. package/src/commands/select-plugins.ts +0 -28
  97. package/src/commands/select-scope.ts +0 -21
  98. package/src/commands/update.ts +0 -85
  99. package/src/commands/validate.ts +0 -57
  100. package/src/components.ts +0 -90
  101. package/src/deps.ts +0 -64
  102. package/src/fsutil.ts +0 -38
  103. package/src/hash.ts +0 -61
  104. package/src/lock.ts +0 -57
  105. package/src/manifest.ts +0 -113
  106. package/src/marketplace.ts +0 -41
  107. package/src/semver.ts +0 -67
  108. package/src/skills.ts +0 -88
  109. package/src/sources.ts +0 -159
  110. package/src/types.ts +0 -140
@@ -0,0 +1,703 @@
1
+ #!/usr/bin/env node
2
+ import { parseArgs } from "node:util";
3
+ import { spawnSync } from "node:child_process";
4
+ import { realpathSync } from "node:fs";
5
+ import { dirname, join, resolve } from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+ import { ADAPTER_TARGETS } from "../src/adapters/index.js";
8
+ import { initScaffold } from "../src/commands/init.js";
9
+ import { adaptPlugin } from "../src/commands/adapt.js";
10
+ import { addPlugins } from "../src/commands/install.js";
11
+ import { updateLock } from "../src/commands/update.js";
12
+ import { validatePlugin } from "../src/commands/validate.js";
13
+ import { listPlugins } from "../src/commands/list.js";
14
+ import { importSkills } from "../src/commands/import.js";
15
+ import { linkPlugins } from "../src/commands/link.js";
16
+ import { removePlugin } from "../src/commands/remove.js";
17
+ import { migrateLayout } from "../src/commands/migrate.js";
18
+ import { marketplaceList, marketplaceRemove, marketplaceUpgrade } from "../src/commands/marketplace.js";
19
+ import { selectTargetsInteractive } from "../src/commands/select-agents.js";
20
+ import { selectPluginsInteractive } from "../src/commands/select-plugins.js";
21
+ import { selectScopeInteractive } from "../src/commands/select-scope.js";
22
+ import { confirmFullInstall, selectComponentsInteractive } from "../src/commands/select-components.js";
23
+ import { globalPluginsDir, projectPluginsDir } from "../src/paths.js";
24
+ import { COMPONENT_TYPES } from "../src/types.js";
25
+ import { getAgent } from "../src/agents/index.js";
26
+ const FLAGS = {
27
+ // Short flags are first-letter aliases. Where several long flags share a
28
+ // first letter, the highest-frequency one wins the short and the rest stay
29
+ // long-only: d → dir (not description) a → all (not author/as)
30
+ // s → skill (not sparse) p → plugin (not path/project/prefix)
31
+ dir: { type: "string", short: "d", hint: "<dir>", help: "install into an explicit directory" },
32
+ global: { type: "boolean", short: "g", help: "use ~/.agents/plugins (across all projects)" },
33
+ project: { type: "boolean", help: "use <repo>/.agents/plugins (default)" },
34
+ target: { type: "string", short: "t", hint: "claude|codex|all", help: "runtime(s) to adapt for" },
35
+ all: { type: "boolean", short: "a", help: "select all available plugins" },
36
+ plugin: { type: "string", short: "p", multiple: true, hint: "<name>", help: "select a specific plugin (repeatable)" },
37
+ "no-deps": { type: "boolean", short: "n", help: "don't install dependencies" },
38
+ path: { type: "string", hint: "<subdir>", help: "install only this subdir of the source" },
39
+ ref: { type: "string", short: "r", hint: "<ref>", help: "pin a git ref (branch/tag/sha)" },
40
+ sparse: { type: "string", multiple: true, hint: "<path>", help: "sparse-checkout path (repeatable)" },
41
+ "marketplace-name": { type: "string", hint: "<name>", help: "override the marketplace key" },
42
+ force: { type: "boolean", short: "f", help: "skip confirmation / force" },
43
+ description: { type: "string", hint: "<text>", help: "plugin description" },
44
+ author: { type: "string", hint: "<name>", help: "plugin author" },
45
+ type: { type: "string", hint: "plugin|marketplace|all", help: "init: which .agents/ artifact to scaffold (default plugin)" },
46
+ skill: { type: "string", short: "s", multiple: true, hint: "<name>", help: "skill name (init: seed one · add: limit to these)" },
47
+ only: { type: "string", hint: "<types>", help: "limit to these component types (skills,agents,commands,mcp,hooks,apps)" },
48
+ as: { type: "string", hint: "<name>", help: "plugin name to wrap the skills as" },
49
+ prefix: { type: "string", hint: "<p>", help: "prefix imported skill names" },
50
+ verbose: { type: "boolean", short: "v", help: "expand each component to its member names" },
51
+ };
52
+ // Scope flags recur on almost every command; name the group once.
53
+ const SCOPE = ["project", "global", "dir"];
54
+ function optionsFor(names) {
55
+ const out = {};
56
+ for (const n of names) {
57
+ const f = FLAGS[n];
58
+ out[n] = { type: f.type, ...(f.short ? { short: f.short } : {}), ...(f.multiple ? { multiple: true } : {}) };
59
+ }
60
+ return out;
61
+ }
62
+ const PLUGIN_COMMANDS = {
63
+ add: {
64
+ summary: "install plugins from a source",
65
+ synopsis: "adg plugins add <source>",
66
+ positional: "<source> = <local-dir> | <owner/repo[@ref]> | <github-url>",
67
+ blurb: "In a terminal, add guides you through: scope → plugins → agents → what to install.\n" +
68
+ "By default it installs everything; choose 'No, let me choose' to pick components,\n" +
69
+ "or pass the flags below to skip any prompt (e.g. for scripts/CI).",
70
+ flags: ["all", "plugin", "only", "skill", "target", "no-deps", "path", "ref", "sparse", "marketplace-name", ...SCOPE],
71
+ examples: [
72
+ "adg plugins add owner/repo # guided",
73
+ "adg plugins add owner/repo --all --global # all plugins, global, non-interactive",
74
+ "adg plugins add owner/repo --only skills # expose only the skills",
75
+ "adg plugins add owner/repo --skill brainstorming --skill writing-plans",
76
+ ],
77
+ start: true,
78
+ },
79
+ list: {
80
+ summary: "list installed plugins",
81
+ synopsis: "adg plugins list",
82
+ blurb: "Each plugin shows a one-line summary of what it contains. Add --verbose\nto expand every component (skills, agents, commands, …) to its member names.",
83
+ flags: ["verbose", ...SCOPE],
84
+ },
85
+ update: {
86
+ summary: "re-sync installed plugins to their sources",
87
+ synopsis: "adg plugins update",
88
+ flags: [...SCOPE],
89
+ },
90
+ remove: {
91
+ summary: "uninstall a plugin",
92
+ synopsis: "adg plugins remove <name>",
93
+ positional: "<name> an installed plugin name (see `adg plugins list`)",
94
+ flags: ["force", ...SCOPE],
95
+ },
96
+ init: {
97
+ summary: "scaffold a new plugin or marketplace (.agents/ only)",
98
+ synopsis: "adg plugins init <name> [--type plugin|marketplace|all]",
99
+ positional: "<name> directory name for the new plugin/marketplace",
100
+ flags: ["dir", "description", "author", "skill", "type"],
101
+ },
102
+ adapt: {
103
+ summary: "regenerate Claude/Codex manifests from .agents/.plugin.json",
104
+ synopsis: "adg plugins adapt [<dir>]",
105
+ positional: "<dir> plugin directory (default: current directory)",
106
+ flags: ["target"],
107
+ },
108
+ validate: {
109
+ summary: "check a directory is a valid ADG plugin",
110
+ synopsis: "adg plugins validate [<dir>]",
111
+ positional: "<dir> plugin directory (default: current directory)",
112
+ flags: [],
113
+ },
114
+ "import-skills": {
115
+ summary: "wrap an existing skills/ dir as a plugin",
116
+ synopsis: "adg plugins import-skills <skills-dir> --as <name>",
117
+ positional: "<skills-dir> a directory of SKILL.md folders",
118
+ flags: ["as", "prefix", "description", ...SCOPE],
119
+ },
120
+ link: {
121
+ summary: "link installed plugins into a runtime",
122
+ synopsis: "adg plugins link --target claude|codex",
123
+ flags: ["target", ...SCOPE],
124
+ },
125
+ migrate: {
126
+ summary: "move flat installs into per-marketplace dirs",
127
+ synopsis: "adg plugins migrate",
128
+ flags: [...SCOPE],
129
+ },
130
+ marketplace: {
131
+ summary: "view installed plugins grouped by source",
132
+ synopsis: "adg plugins marketplace <list|upgrade|remove>",
133
+ flags: [],
134
+ delegated: true,
135
+ },
136
+ };
137
+ // Aliases tolerated for plugin verbs.
138
+ const PLUGIN_ALIASES = { rm: "remove", mp: "marketplace" };
139
+ // ---------------------------------------------------------------------------
140
+ // Help rendering (all generated from the tables above).
141
+ // ---------------------------------------------------------------------------
142
+ const TOP_USAGE = `adg — Agent Directory Group toolkit
143
+
144
+ Group scattered skills and plugins, by source, into versioned and reproducible
145
+ plugin sets — and adapt each to both Claude and Codex runtimes from one manifest.
146
+
147
+ Quick start:
148
+ adg plugins add <owner/repo> install plugins from a source (guided in a terminal)
149
+ adg plugins list see what's installed
150
+
151
+ Two domains:
152
+ adg plugins <verb> manage plugins (run \`adg plugins -h\`)
153
+ adg skills <verb> manage skills (run \`adg skills -h\`)
154
+
155
+ Concepts & architecture: see README.md and docs/agents-spec.md`;
156
+ function flagLabel(name) {
157
+ const f = FLAGS[name];
158
+ const lead = f.short ? `-${f.short}, --${name}` : ` --${name}`;
159
+ return f.hint ? `${lead} ${f.hint}` : lead;
160
+ }
161
+ function renderFlags(names) {
162
+ if (names.length === 0)
163
+ return "";
164
+ const labels = names.map(flagLabel);
165
+ const width = Math.max(...labels.map((l) => l.length));
166
+ const lines = names.map((n, i) => ` ${labels[i].padEnd(width)} ${FLAGS[n].help}`);
167
+ return `Flags:\n${lines.join("\n")}`;
168
+ }
169
+ /** L1: `adg plugins -h` — overview of the domain and a one-line verb list. */
170
+ function renderPluginsHelp() {
171
+ const names = Object.keys(PLUGIN_COMMANDS);
172
+ const width = Math.max(...names.map((n) => n.length));
173
+ const rows = names.map((n) => {
174
+ const cmd = PLUGIN_COMMANDS[n];
175
+ const tag = cmd.start ? " ← start here" : "";
176
+ return ` ${n.padEnd(width)} ${cmd.summary}${tag}`;
177
+ });
178
+ return `adg plugins — manage agent plugins
179
+
180
+ A plugin bundles skills/agents/commands, installed from a source (a local dir, or
181
+ a GitHub repo holding one plugin or many). Authored once in .agents/.plugin.json,
182
+ then adapted to Claude and Codex automatically.
183
+
184
+ Commands (run \`adg plugins <verb> -h\` for details & flags):
185
+ ${rows.join("\n")}
186
+
187
+ Scope (most commands): --project (default) | --global | --dir <dir>
188
+ --global honors XDG_STATE_HOME / ADG_PLUGINS_HOME. Only the plugins/ subtree is
189
+ touched; AGENTS.md and skills/ are never modified.`;
190
+ }
191
+ /** L2: `adg plugins <verb> -h` — just this command's region. */
192
+ function renderVerbHelp(name) {
193
+ const cmd = PLUGIN_COMMANDS[name];
194
+ const parts = [`adg plugins ${name} — ${cmd.summary}`, "", cmd.synopsis];
195
+ if (cmd.positional)
196
+ parts.push(` ${cmd.positional}`);
197
+ if (cmd.blurb)
198
+ parts.push("", cmd.blurb);
199
+ const flags = renderFlags(cmd.flags);
200
+ if (flags)
201
+ parts.push("", flags);
202
+ if (cmd.examples)
203
+ parts.push("", "Examples:", ...cmd.examples.map((e) => ` ${e}`));
204
+ return parts.join("\n");
205
+ }
206
+ function wantsHelp(args) {
207
+ return args.includes("-h") || args.includes("--help");
208
+ }
209
+ function fail(msg) {
210
+ console.error(`error: ${msg}\n`);
211
+ console.error(TOP_USAGE);
212
+ process.exit(1);
213
+ }
214
+ function resolveScopeDir(values) {
215
+ if (typeof values.dir === "string")
216
+ return resolve(values.dir);
217
+ return values.global ? globalPluginsDir() : projectPluginsDir();
218
+ }
219
+ /** Describe the active scope so "source not found" errors can name where they looked. */
220
+ function scopeInfo(values) {
221
+ const label = typeof values.dir === "string" ? resolve(values.dir) : values.global ? "global" : "project";
222
+ return { label, globalDir: globalPluginsDir() };
223
+ }
224
+ /** Map the active scope to an agent install scope (global → user, else project). */
225
+ function scopeOf(values) {
226
+ return values.global ? "user" : "project";
227
+ }
228
+ /** Print per-agent sync outcomes (enabled/disabled/re-synced) generically. */
229
+ function reportAgents(agents, verb) {
230
+ for (const r of agents ?? []) {
231
+ const name = getAgent(r.agent)?.displayName ?? r.agent;
232
+ if (r.affected.length > 0)
233
+ console.log(`${verb} in ${name}: ${r.affected.join(", ")}`);
234
+ else if (r.skipped)
235
+ console.log(`note: \`${r.agent}\` CLI not found — run \`adg plugins link --target ${r.agent}\` after installing it.`);
236
+ }
237
+ }
238
+ function resolveTargets(target) {
239
+ if (!target || target === "all")
240
+ return [...ADAPTER_TARGETS];
241
+ if (target === "claude" || target === "codex")
242
+ return [target];
243
+ fail(`invalid --target "${String(target)}" (expected claude|codex|all)`);
244
+ }
245
+ /** Parse a `--only skills,commands` list into validated component types. */
246
+ function resolveComponents(only) {
247
+ if (only === undefined)
248
+ return undefined;
249
+ const parts = only.split(",").map((s) => s.trim()).filter(Boolean);
250
+ const bad = parts.filter((p) => !COMPONENT_TYPES.includes(p));
251
+ if (bad.length > 0)
252
+ fail(`invalid --only "${bad.join(", ")}" (expected ${COMPONENT_TYPES.join("|")})`);
253
+ return parts;
254
+ }
255
+ /**
256
+ * Parse a verb's args against only the flags it declares. An unknown flag (or a
257
+ * malformed value) prints that command's own help — so `-h` and a typo lead to
258
+ * the same place. Returns parsed values + positionals.
259
+ */
260
+ function parseVerb(name, flags, rest) {
261
+ try {
262
+ const { values, positionals } = parseArgs({ args: rest, options: optionsFor(flags), allowPositionals: true });
263
+ return { values: values, positionals };
264
+ }
265
+ catch (err) {
266
+ console.error(`error: ${err instanceof Error ? err.message : String(err)}\n`);
267
+ console.error(renderVerbHelp(name));
268
+ process.exit(1);
269
+ }
270
+ }
271
+ /**
272
+ * Lay items out in aligned columns sized to the terminal width (row-major).
273
+ * Items longer than `maxColWidth` are truncated with an ellipsis. Falls back to
274
+ * a single column on narrow terminals. Returns the block as a string.
275
+ */
276
+ function formatColumns(items, opts = {}) {
277
+ const indent = opts.indent ?? 2;
278
+ const gutter = opts.gutter ?? 2;
279
+ const maxColWidth = opts.maxColWidth ?? 24;
280
+ const termWidth = opts.width ?? process.stdout.columns ?? 80;
281
+ const cells = items.map((s) => (s.length > maxColWidth ? s.slice(0, maxColWidth - 1) + "…" : s));
282
+ const colWidth = Math.min(Math.max(1, ...cells.map((c) => c.length)), maxColWidth);
283
+ const cols = Math.max(1, Math.floor((termWidth - indent + gutter) / (colWidth + gutter)));
284
+ const lines = [];
285
+ for (let i = 0; i < cells.length; i += cols) {
286
+ const row = cells.slice(i, i + cols);
287
+ const padded = row.map((c, j) => (j === row.length - 1 ? c : c.padEnd(colWidth)));
288
+ lines.push(" ".repeat(indent) + padded.join(" ".repeat(gutter)));
289
+ }
290
+ return lines.join("\n");
291
+ }
292
+ /** Print a plugin's components, each expanded to its member names (verbose view). */
293
+ function printContents(contents, headerIndent) {
294
+ const entries = Object.entries(contents ?? {}).filter(([, names]) => names.length > 0);
295
+ for (const [type, names] of entries) {
296
+ const maxColWidth = Math.max(1, ...names.map((n) => n.length));
297
+ console.log(`${" ".repeat(headerIndent)}${type} (${names.length}):`);
298
+ console.log(formatColumns(names, { indent: headerIndent + 2, maxColWidth }));
299
+ }
300
+ }
301
+ async function runPlugins(rawVerb, rest) {
302
+ // `adg plugins` (no verb) or an explicit help request → the L1 overview.
303
+ if (rawVerb === undefined || rawVerb === "-h" || rawVerb === "--help" || rawVerb === "help") {
304
+ console.log(renderPluginsHelp());
305
+ return;
306
+ }
307
+ const verb = PLUGIN_ALIASES[rawVerb] ?? rawVerb;
308
+ const cmd = PLUGIN_COMMANDS[verb];
309
+ if (!cmd) {
310
+ console.error(`error: unknown plugins subcommand: ${rawVerb}\n`);
311
+ console.error(renderPluginsHelp());
312
+ process.exit(1);
313
+ }
314
+ // `adg plugins <verb> -h` → just this command's help. (marketplace handles
315
+ // its own sub-help, so let it through.)
316
+ if (!cmd.delegated && wantsHelp(rest)) {
317
+ console.log(renderVerbHelp(verb));
318
+ return;
319
+ }
320
+ switch (verb) {
321
+ case "init": {
322
+ const { values, positionals } = parseVerb(verb, cmd.flags, rest);
323
+ const name = positionals[0];
324
+ if (!name)
325
+ fail("plugins init requires a <name>");
326
+ const dir = values.dir ? resolve(values.dir) : resolve(process.cwd(), "plugins");
327
+ const type = (values.type ?? "plugin");
328
+ if (type !== "plugin" && type !== "marketplace" && type !== "all") {
329
+ fail(`invalid --type "${values.type}" (expected plugin|marketplace|all)`);
330
+ }
331
+ const res = initScaffold({ name, dir, type, description: values.description, author: values.author, skill: values.skill?.[0] });
332
+ console.log(`created ${type} at ${res.pluginDir}`);
333
+ for (const f of res.created)
334
+ console.log(` + ${f}`);
335
+ return;
336
+ }
337
+ case "adapt": {
338
+ const { values, positionals } = parseVerb(verb, cmd.flags, rest);
339
+ const pluginDir = resolve(positionals[0] ?? process.cwd());
340
+ for (const r of adaptPlugin(pluginDir, resolveTargets(values.target))) {
341
+ console.log(`adapted ${r.target} -> ${r.file}`);
342
+ }
343
+ return;
344
+ }
345
+ case "validate": {
346
+ const { positionals } = parseVerb(verb, cmd.flags, rest);
347
+ const pluginDir = resolve(positionals[0] ?? process.cwd());
348
+ const res = validatePlugin(pluginDir);
349
+ if (res.ok) {
350
+ console.log(`ok: ${pluginDir} is a valid ADG plugin`);
351
+ }
352
+ else {
353
+ console.error(`invalid: ${pluginDir}`);
354
+ for (const i of res.issues)
355
+ console.error(` - ${i}`);
356
+ process.exit(1);
357
+ }
358
+ return;
359
+ }
360
+ case "add": {
361
+ const { values, positionals } = parseVerb(verb, cmd.flags, rest);
362
+ const spec = positionals[0];
363
+ if (!spec)
364
+ fail("plugins add requires a <plugin-dir | owner/repo[@ref] | github-url>");
365
+ const tty = process.stdin.isTTY;
366
+ // Scope: honor an explicit --dir/--global/--project, else ask in a terminal
367
+ // (defaulting to project non-interactively).
368
+ let global = Boolean(values.global);
369
+ if (!values.dir && values.global === undefined && values.project === undefined && tty) {
370
+ global = await selectScopeInteractive();
371
+ }
372
+ const pluginsDir = values.dir
373
+ ? resolve(values.dir)
374
+ : global
375
+ ? globalPluginsDir()
376
+ : projectPluginsDir();
377
+ // A source may hold one plugin or a whole marketplace. addPlugins discovers
378
+ // all of them; in a terminal the user picks scope, then plugins, then agents,
379
+ // then (unless --only/--skill narrow it) chooses what to install per plugin.
380
+ // --all / --plugin / --path / --only / --skill narrow non-interactively.
381
+ const only = resolveComponents(values.only);
382
+ const skillsSubset = values.skill && values.skill.length > 0 ? values.skill : undefined;
383
+ const narrowed = only !== undefined || skillsSubset !== undefined;
384
+ const { order, installed, converted, agents } = await addPlugins({
385
+ spec,
386
+ pluginsDir,
387
+ ref: values.ref,
388
+ sparse: values.sparse,
389
+ path: values.path,
390
+ all: values.all,
391
+ plugins: values.plugin,
392
+ only,
393
+ skillsSubset,
394
+ withDeps: !values["no-deps"],
395
+ marketplaceName: values["marketplace-name"],
396
+ targets: values.target !== undefined ? resolveTargets(values.target) : undefined,
397
+ selectPlugins: tty ? selectPluginsInteractive : undefined,
398
+ selectTargets: tty && values.target === undefined ? selectTargetsInteractive : undefined,
399
+ confirmFull: tty && !narrowed ? confirmFullInstall : undefined,
400
+ selectComponents: tty && !narrowed ? selectComponentsInteractive : undefined,
401
+ // Make the install actually usable in the chosen agents, not just stored.
402
+ activate: true,
403
+ scope: global ? "user" : "project",
404
+ });
405
+ for (const name of converted)
406
+ console.log(`converted native manifest -> .agents/.plugin.json: ${name}`);
407
+ if (order.length > 1)
408
+ console.log(`install order: ${order.join(" -> ")}`);
409
+ for (const res of installed) {
410
+ console.log(`added ${res.name} -> ${res.installedTo}`);
411
+ console.log(` folderHash: ${res.folderHash}`);
412
+ for (const f of res.adapted)
413
+ console.log(` adapted: ${f}`);
414
+ }
415
+ reportAgents(agents, "enabled");
416
+ return;
417
+ }
418
+ case "import-skills": {
419
+ const { values, positionals } = parseVerb(verb, cmd.flags, rest);
420
+ const dir = positionals[0];
421
+ if (!dir)
422
+ fail("plugins import-skills requires a <skills-dir>");
423
+ if (!values.as)
424
+ fail("plugins import-skills requires --as <plugin-name>");
425
+ const res = importSkills({
426
+ skillsDir: resolve(dir),
427
+ as: values.as,
428
+ prefix: values.prefix,
429
+ pluginsDir: resolveScopeDir(values),
430
+ description: values.description,
431
+ });
432
+ console.log(`imported skills into ${res.name} -> ${res.installedTo}`);
433
+ return;
434
+ }
435
+ case "link": {
436
+ const { values } = parseVerb(verb, cmd.flags, rest);
437
+ const target = values.target;
438
+ if (target !== "claude" && target !== "codex")
439
+ fail("plugins link requires --target claude|codex");
440
+ const res = linkPlugins({ pluginsDir: resolveScopeDir(values), target, global: Boolean(values.global) });
441
+ for (const a of res.actions) {
442
+ console.log(`linked ${a.name} [${res.target}]${a.linkedTo ? ` -> ${a.linkedTo}` : ""}`);
443
+ for (const f of a.adapted)
444
+ console.log(` adapted: ${f}`);
445
+ }
446
+ if (res.cliSkipped) {
447
+ console.log(`note: \`${target}\` CLI not found — manifests were generated, but nothing was enabled in ${target}.`);
448
+ }
449
+ return;
450
+ }
451
+ case "update": {
452
+ const { values } = parseVerb(verb, cmd.flags, rest);
453
+ const { results, missing, agents } = updateLock(resolveScopeDir(values), undefined, {
454
+ resync: true,
455
+ scope: scopeOf(values),
456
+ });
457
+ for (const r of results)
458
+ console.log(`${r.changed ? "updated" : "unchanged"} ${r.name}@${r.version}`);
459
+ for (const m of missing)
460
+ console.error(` ! missing directory for locked plugin: ${m}`);
461
+ reportAgents(agents, "re-synced");
462
+ return;
463
+ }
464
+ case "remove": {
465
+ const { values, positionals } = parseVerb(verb, cmd.flags, rest);
466
+ const name = positionals[0];
467
+ if (!name)
468
+ fail("plugins remove requires a <name>");
469
+ const res = removePlugin({
470
+ pluginsDir: resolveScopeDir(values),
471
+ name,
472
+ force: values.force,
473
+ deactivate: true,
474
+ scope: scopeOf(values),
475
+ });
476
+ if (res.removedDir)
477
+ console.log(`removed ${res.name} -> ${res.removedDir}`);
478
+ else
479
+ console.log(`removed ${res.name} (no directory on disk)`);
480
+ for (const link of res.unlinked)
481
+ console.log(` unlinked: ${link}`);
482
+ for (const r of res.agents ?? []) {
483
+ if (r.affected.length > 0)
484
+ console.log(` disabled in ${getAgent(r.agent)?.displayName ?? r.agent}`);
485
+ }
486
+ if (!res.removedFromLock && !res.removedDir) {
487
+ console.log(` ${res.name} was not recorded in the lock`);
488
+ }
489
+ return;
490
+ }
491
+ case "list": {
492
+ const { values } = parseVerb(verb, cmd.flags, rest);
493
+ const pluginsDir = resolveScopeDir(values);
494
+ const plugins = listPlugins(pluginsDir);
495
+ if (plugins.length === 0) {
496
+ console.log(`no plugins recorded in ${pluginsDir}`);
497
+ return;
498
+ }
499
+ for (const p of plugins) {
500
+ const partial = p.selection ? " (partial)" : "";
501
+ console.log(`${p.name}@${p.version} [${p.origin.type}] ${p.folderHash.slice(0, 19)}${partial}`);
502
+ const entries = Object.entries(p.contents ?? {}).filter(([, names]) => names.length > 0);
503
+ if (entries.length === 0)
504
+ continue;
505
+ if (values.verbose) {
506
+ printContents(p.contents, 2);
507
+ }
508
+ else {
509
+ console.log(` ${entries.map(([type, names]) => `${type}: ${names.length}`).join(" ")}`);
510
+ }
511
+ }
512
+ return;
513
+ }
514
+ case "migrate": {
515
+ const { values } = parseVerb(verb, cmd.flags, rest);
516
+ const res = migrateLayout(resolveScopeDir(values));
517
+ for (const m of res.moved)
518
+ console.log(`moved ${m.name}: ${m.from} -> ${m.to}`);
519
+ for (const m of res.missing)
520
+ console.error(` ! missing directory for locked plugin: ${m}`);
521
+ if (res.moved.length === 0)
522
+ console.log(`nothing to migrate (${res.unchanged.length} already in place)`);
523
+ return;
524
+ }
525
+ case "marketplace":
526
+ return runMarketplace(rest);
527
+ }
528
+ }
529
+ const MARKETPLACE_USAGE = `adg plugins marketplace — view installed plugins by source, and re-sync
530
+
531
+ A marketplace is just your installed plugins grouped by where they came from
532
+ (no separate registry). You add sources with \`adg plugins add\`; these commands
533
+ look back over what you installed.
534
+
535
+ Commands:
536
+ adg plugins marketplace list [--verbose] [--global | --project | --dir <dir>]
537
+ Group installed plugins by source. --verbose expands each plugin to its
538
+ components (skills, agents, commands, …).
539
+ adg plugins marketplace upgrade [<source>] [--all] [--target claude|codex|all] [--global | --project | --dir <dir>]
540
+ Re-fetch a source and update its installed plugins (--all also installs
541
+ anything new it now offers). No <source> upgrades every remote source.
542
+ adg plugins marketplace remove <source> [--force] [--global | --project | --dir <dir>]
543
+ Uninstall every plugin that came from <source>.
544
+
545
+ <source> is a key from \`marketplace list\` (e.g. owner/repo).`;
546
+ async function runMarketplace(args) {
547
+ const [sub, ...rest] = args;
548
+ // `marketplace <sub> -h` → the marketplace help (it documents every sub + flags).
549
+ if (sub !== undefined && wantsHelp(rest)) {
550
+ console.log(MARKETPLACE_USAGE);
551
+ return;
552
+ }
553
+ switch (sub) {
554
+ case undefined:
555
+ case "-h":
556
+ case "--help":
557
+ case "help":
558
+ console.log(MARKETPLACE_USAGE);
559
+ return;
560
+ case "list": {
561
+ const { values } = parseVerb("marketplace", ["verbose", ...SCOPE], rest);
562
+ const dir = resolveScopeDir(values);
563
+ const groups = marketplaceList({ pluginsDir: dir });
564
+ if (groups.length === 0) {
565
+ console.log("No plugins installed.");
566
+ return;
567
+ }
568
+ // Verbose: drill each plugin down to its components (reuses `plugins list -v`).
569
+ const byName = values.verbose ? new Map(listPlugins(dir).map((p) => [p.name, p])) : undefined;
570
+ for (const g of groups) {
571
+ const ref = g.ref ? `@${g.ref}` : "";
572
+ const n = g.installed.length;
573
+ const tag = g.remote ? "" : " (local — re-run add to update)";
574
+ console.log(`${g.source}${ref} (${n} plugin${n !== 1 ? "s" : ""})${tag}`);
575
+ if (byName) {
576
+ for (const name of g.installed) {
577
+ const p = byName.get(name);
578
+ console.log(` ${name}${p?.selection ? " (partial)" : ""}`);
579
+ printContents(p?.contents, 4);
580
+ }
581
+ }
582
+ else {
583
+ console.log(formatColumns(g.installed));
584
+ }
585
+ }
586
+ return;
587
+ }
588
+ case "upgrade": {
589
+ const { values, positionals } = parseVerb("marketplace", ["all", "target", ...SCOPE], rest);
590
+ const results = await marketplaceUpgrade({
591
+ pluginsDir: resolveScopeDir(values),
592
+ scope: scopeInfo(values),
593
+ activate: true,
594
+ agentScope: scopeOf(values),
595
+ source: positionals[0],
596
+ all: values.all,
597
+ targets: resolveTargets(values.target),
598
+ });
599
+ for (const r of results) {
600
+ const conv = r.converted.length ? ` (${r.converted.length} converted from native)` : "";
601
+ console.log(`upgraded ${r.source}: ${r.updated.length} plugin(s)${conv}`);
602
+ for (const p of r.updated)
603
+ console.log(` ${p.name} -> ${p.installedTo}`);
604
+ if (r.available.length > 0) {
605
+ console.log(` ${r.available.length} more available (use --all): ${r.available.join(", ")}`);
606
+ }
607
+ }
608
+ return;
609
+ }
610
+ case "remove":
611
+ case "rm": {
612
+ const { values, positionals } = parseVerb("marketplace", ["force", ...SCOPE], rest);
613
+ const source = positionals[0];
614
+ if (!source)
615
+ fail("marketplace remove requires a <source>");
616
+ const res = marketplaceRemove({
617
+ pluginsDir: resolveScopeDir(values),
618
+ scope: scopeInfo(values),
619
+ agentScope: scopeOf(values),
620
+ source,
621
+ force: values.force,
622
+ deactivate: true,
623
+ });
624
+ console.log(`removed ${res.removed.length} plugin(s) from ${res.source}: ${res.removed.join(", ")}`);
625
+ return;
626
+ }
627
+ default: {
628
+ console.error(`error: unknown marketplace subcommand: ${sub}\n`);
629
+ console.error(MARKETPLACE_USAGE);
630
+ process.exit(1);
631
+ }
632
+ }
633
+ }
634
+ /**
635
+ * Delegate to the vendored `skills` CLI (vendor/skills, a fork of
636
+ * vercel-labs/skills — see vendor/skills/PROVENANCE.md). We run its source
637
+ * entry directly under Node's TypeScript support and forward all args/stdio.
638
+ */
639
+ /**
640
+ * Args for re-invoking Node on a `.ts` entry. `process.execArgv` is forwarded so
641
+ * the child inherits the parent's Node flags (e.g. --experimental-strip-types,
642
+ * required to run TypeScript directly on Node 22.6–23.5). `execArgv` is a
643
+ * parameter so the forwarding can be tested with a non-empty flag set.
644
+ */
645
+ export function skillsChildArgv(entry, args, execArgv = process.execArgv) {
646
+ return [...execArgv, entry, ...args];
647
+ }
648
+ function runSkills(verb, rest) {
649
+ const self = fileURLToPath(import.meta.url);
650
+ const here = dirname(self);
651
+ // Resolve the vendored CLI with the same extension we ourselves run as: `.ts`
652
+ // when running source directly under Node's type stripping (dev / npm link),
653
+ // `.js` when running the compiled `dist/` output (published install). Node
654
+ // refuses to strip types under node_modules, so the published bin and the
655
+ // vendored entry must both be the built `.js`.
656
+ const ext = self.endsWith(".ts") ? ".ts" : ".js";
657
+ const entry = join(here, "..", "vendor", "skills", "src", `cli${ext}`);
658
+ const args = [verb, ...rest].filter((x) => x !== undefined);
659
+ const r = spawnSync(process.execPath, skillsChildArgv(entry, args), { stdio: "inherit" });
660
+ process.exit(r.status ?? 1);
661
+ }
662
+ async function main(argv) {
663
+ const [domain, verb, ...rest] = argv;
664
+ if (!domain || domain === "help" || domain === "--help" || domain === "-h") {
665
+ console.log(TOP_USAGE);
666
+ return;
667
+ }
668
+ switch (domain) {
669
+ case "plugins":
670
+ case "plugin": // tolerated alias
671
+ return runPlugins(verb, rest);
672
+ case "skills":
673
+ case "skill":
674
+ return runSkills(verb, rest);
675
+ default:
676
+ fail(`unknown domain: ${domain} (expected \`plugins\` or \`skills\`)`);
677
+ }
678
+ }
679
+ // Only run the CLI when executed directly, so the module can be imported by tests.
680
+ // `import.meta.url` is already realpath-resolved by Node, but `process.argv[1]`
681
+ // is the path as invoked — when the bin is reached through a symlink (e.g.
682
+ // `npm link`'s global shim), that path is the unresolved symlink, so a raw
683
+ // string compare misses and `main()` never runs. Resolve argv[1] to its
684
+ // realpath before comparing so symlinked invocations still start the CLI.
685
+ function isInvokedDirectly() {
686
+ const entry = process.argv[1];
687
+ if (!entry)
688
+ return false;
689
+ let resolved;
690
+ try {
691
+ resolved = realpathSync(entry);
692
+ }
693
+ catch {
694
+ resolved = resolve(entry);
695
+ }
696
+ return fileURLToPath(import.meta.url) === resolved;
697
+ }
698
+ if (isInvokedDirectly()) {
699
+ main(process.argv.slice(2)).catch((err) => {
700
+ console.error(`error: ${err instanceof Error ? err.message : String(err)}`);
701
+ process.exit(1);
702
+ });
703
+ }