@vibe-agent-toolkit/cli 0.1.42 → 0.2.0-rc.2

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 (57) hide show
  1. package/dist/bin.js +24 -2
  2. package/dist/bin.js.map +1 -1
  3. package/dist/commands/audit/distributed-tree.d.ts +58 -0
  4. package/dist/commands/audit/distributed-tree.d.ts.map +1 -1
  5. package/dist/commands/audit/distributed-tree.js +68 -0
  6. package/dist/commands/audit/distributed-tree.js.map +1 -1
  7. package/dist/commands/audit.d.ts.map +1 -1
  8. package/dist/commands/audit.js +8 -2
  9. package/dist/commands/audit.js.map +1 -1
  10. package/dist/commands/build.js +1 -1
  11. package/dist/commands/cache/clear.d.ts +77 -0
  12. package/dist/commands/cache/clear.d.ts.map +1 -0
  13. package/dist/commands/cache/clear.js +162 -0
  14. package/dist/commands/cache/clear.js.map +1 -0
  15. package/dist/commands/cache/index.d.ts +68 -0
  16. package/dist/commands/cache/index.d.ts.map +1 -0
  17. package/dist/commands/cache/index.js +126 -0
  18. package/dist/commands/cache/index.js.map +1 -0
  19. package/dist/commands/claude/plugin/build.d.ts +15 -1
  20. package/dist/commands/claude/plugin/build.d.ts.map +1 -1
  21. package/dist/commands/claude/plugin/build.js +54 -9
  22. package/dist/commands/claude/plugin/build.js.map +1 -1
  23. package/dist/commands/claude/plugin/marketplace-json.d.ts +24 -9
  24. package/dist/commands/claude/plugin/marketplace-json.d.ts.map +1 -1
  25. package/dist/commands/claude/plugin/marketplace-json.js +10 -9
  26. package/dist/commands/claude/plugin/marketplace-json.js.map +1 -1
  27. package/dist/commands/inventory.d.ts.map +1 -1
  28. package/dist/commands/inventory.js +10 -3
  29. package/dist/commands/inventory.js.map +1 -1
  30. package/dist/commands/positional-args.js +1 -1
  31. package/dist/commands/positional-args.js.map +1 -1
  32. package/dist/commands/resources/index.d.ts.map +1 -1
  33. package/dist/commands/resources/index.js +7 -2
  34. package/dist/commands/resources/index.js.map +1 -1
  35. package/dist/commands/resources/validate.d.ts +15 -2
  36. package/dist/commands/resources/validate.d.ts.map +1 -1
  37. package/dist/commands/resources/validate.js +16 -1
  38. package/dist/commands/resources/validate.js.map +1 -1
  39. package/dist/commands/skills/package.d.ts +23 -2
  40. package/dist/commands/skills/package.d.ts.map +1 -1
  41. package/dist/commands/skills/package.js +36 -9
  42. package/dist/commands/skills/package.js.map +1 -1
  43. package/dist/commands/skills/scope-guard.js +1 -1
  44. package/dist/commands/skills/scope-guard.js.map +1 -1
  45. package/dist/commands/skills/skill-discovery.d.ts.map +1 -1
  46. package/dist/commands/skills/skill-discovery.js +5 -5
  47. package/dist/commands/skills/skill-discovery.js.map +1 -1
  48. package/dist/commands/validate.d.ts.map +1 -1
  49. package/dist/commands/validate.js +2 -1
  50. package/dist/commands/validate.js.map +1 -1
  51. package/dist/commands/verify.js +1 -1
  52. package/dist/utils/command-error.d.ts +19 -0
  53. package/dist/utils/command-error.d.ts.map +1 -1
  54. package/dist/utils/command-error.js +27 -0
  55. package/dist/utils/command-error.js.map +1 -1
  56. package/docs/index.md +21 -1
  57. package/package.json +16 -14
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clear.d.ts","sourceRoot":"","sources":["../../../src/commands/cache/clear.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAeH,MAAM,WAAW,iBAAiB;IAChC,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,gDAAgD;AAChD,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,SAAS,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,iFAAiF;IACjF,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;CACtB;AASD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,YAAY,IAAI,MAAM,CAcrC;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAkBrF;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CAAC,OAAO,GAAE,iBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CAYtF"}
@@ -0,0 +1,162 @@
1
+ /**
2
+ * `vat cache clear` — delete VAT's shared temp-directory cache tree.
3
+ *
4
+ * "Recovery is rescan" was true but had no user-invocable form: a corrupt or
5
+ * merely stale cache could only be cleared by hand-deleting a path the docs
6
+ * described in prose. This is that path, named once.
7
+ *
8
+ * Scope is the WHOLE `<tmpdir>/.vat-cache/` tree, not just the parse tenant.
9
+ * That directory is shared — `external-links.json`, `auth-<user>/` and `parse/`
10
+ * all live under it — and "clear the cache" cannot honestly mean "clear one of
11
+ * the three". All of them are disposable by construction.
12
+ */
13
+ import { promises as fs } from 'node:fs';
14
+ import { basename, dirname } from 'node:path';
15
+ import { parseCacheDirectory } from '@vibe-agent-toolkit/resources';
16
+ import { safePath } from '@vibe-agent-toolkit/utils';
17
+ import { handleCommandError } from '../../utils/command-error.js';
18
+ import { createLogger } from '../../utils/logger.js';
19
+ import { writeYamlOutput } from '../../utils/output.js';
20
+ /** The one name the shared cache root is allowed to have. See {@link vatCacheRoot}. */
21
+ const VAT_CACHE_DIR_NAME = '.vat-cache';
22
+ const EMPTY_USAGE = { entries: 0, bytes: 0 };
23
+ /**
24
+ * The shared cache root, `<tmpdir>/.vat-cache`.
25
+ *
26
+ * Derived from `parseCacheDirectory()` rather than re-joining `normalizedTmpdir()`
27
+ * with a third copy of the `.vat-cache` literal — one authority for where the
28
+ * tree lives, and the CLI stays out of the path-resolution business.
29
+ *
30
+ * The name check is not decoration: this function's return value is handed
31
+ * straight to a recursive delete, so if `parseCacheDirectory()` ever stopped
32
+ * carrying a `parse/` leaf, the naive parent would be the system temp directory
33
+ * itself. Refusing an unexpected shape turns that into an error instead of an
34
+ * `rm -rf /tmp`.
35
+ *
36
+ * ⚠ `@vibe-agent-toolkit/resources` now also exports a `vatCacheRoot()`, and
37
+ * `parseCacheDirectory()` is built from it — so on paper this derivation is a
38
+ * round trip. It is kept deliberately: the guard's job is to notice if that
39
+ * relationship ever changes, and importing the root directly would retire the
40
+ * only check standing between a shape change and a recursive delete of
41
+ * `<tmpdir>` itself. Deriving-then-verifying is the point.
42
+ *
43
+ * ✅ **It has now earned that keep.** When the cache gained a per-build
44
+ * namespace, the layout went from `.vat-cache/parse` to
45
+ * `.vat-cache/<namespace>/parse` and the old single-`dirname` derivation
46
+ * started returning `.vat-cache/<namespace>`. The name check turned a silent
47
+ * change of delete target into a loud refusal. Anyone tempted to simplify this
48
+ * into an import should read that sentence twice.
49
+ *
50
+ * @returns Absolute path to the cache root
51
+ * @throws {Error} If the derived ancestor is not named `.vat-cache`
52
+ */
53
+ export function vatCacheRoot() {
54
+ const parseDir = parseCacheDirectory();
55
+ // Up two: `<root>/<namespace>/parse` → `<root>`. The namespace level is what
56
+ // makes this two rather than one; see the note above about why it is still
57
+ // derived and verified rather than imported.
58
+ const root = dirname(dirname(parseDir));
59
+ if (basename(root) !== VAT_CACHE_DIR_NAME) {
60
+ throw new Error(`Refusing to clear ${root}: expected a directory named ${VAT_CACHE_DIR_NAME} (derived from ${parseDir}).`);
61
+ }
62
+ return root;
63
+ }
64
+ /**
65
+ * Measure, then delete, an entire cache tree.
66
+ *
67
+ * Measured BEFORE the delete, because afterwards there is nothing left to count
68
+ * and a report of "removed: unknown" would make the command unverifiable.
69
+ *
70
+ * Deliberately reads no environment: `vat cache clear` runs whether or not
71
+ * caching is enabled for this process. A `VAT_CACHE=0` that also disarmed the
72
+ * cleanup would leave an operator with a cache they can neither use nor remove.
73
+ *
74
+ * @param cacheDir - Directory to remove, in full
75
+ * @returns What was removed
76
+ */
77
+ export async function clearCacheDirectory(cacheDir) {
78
+ const entries = await readdirOrNull(cacheDir);
79
+ if (entries === null) {
80
+ return { status: 'success', cacheDir, existed: false, removed: [], entriesRemoved: 0, bytesRemoved: 0 };
81
+ }
82
+ const usage = await measureEntries(cacheDir, entries);
83
+ await fs.rm(cacheDir, { recursive: true, force: true });
84
+ return {
85
+ status: 'success',
86
+ cacheDir,
87
+ existed: true,
88
+ removed: entries.map((entry) => entry.name).sort((a, b) => a.localeCompare(b)),
89
+ entriesRemoved: usage.entries,
90
+ bytesRemoved: usage.bytes,
91
+ };
92
+ }
93
+ /**
94
+ * Command entry point: clear the real cache root and publish the report.
95
+ *
96
+ * @param options - Command options (only `--debug`, inherited from the root)
97
+ */
98
+ export async function cacheClearCommand(options = {}) {
99
+ const startTime = Date.now();
100
+ const logger = createLogger(options.debug ? { debug: true } : {});
101
+ try {
102
+ const report = await clearCacheDirectory(vatCacheRoot());
103
+ logger.debug(`Cleared ${String(report.entriesRemoved)} cache entries from ${report.cacheDir}`);
104
+ writeYamlOutput(report);
105
+ process.exit(0);
106
+ }
107
+ catch (error) {
108
+ handleCommandError(error, logger, startTime, 'CacheClear');
109
+ }
110
+ }
111
+ /**
112
+ * `readdir` that reports a missing directory as `null` rather than throwing.
113
+ *
114
+ * Only ENOENT is absorbed. EACCES on a directory that exists is a genuine
115
+ * failure — reporting it as "nothing to clear" would tell the operator their
116
+ * cache is gone when it is still on disk.
117
+ */
118
+ async function readdirOrNull(dir) {
119
+ try {
120
+ // eslint-disable-next-line security/detect-non-literal-fs-filename -- path is the derived cache root or a caller-injected test directory
121
+ return await fs.readdir(dir, { withFileTypes: true });
122
+ }
123
+ catch (error) {
124
+ if (error.code === 'ENOENT')
125
+ return null;
126
+ throw error;
127
+ }
128
+ }
129
+ /** Sum file count and bytes over already-listed directory entries. */
130
+ async function measureEntries(dir, entries) {
131
+ const usages = await Promise.all(entries.map(async (entry) => {
132
+ const child = safePath.join(dir, entry.name);
133
+ // Not `isFile()`: a symlink or socket is still an entry that is about to
134
+ // be removed, and counting only regular files would under-report it.
135
+ return entry.isDirectory() ? measureTree(child) : { entries: 1, bytes: await sizeOf(child) };
136
+ }));
137
+ return usages.reduce((total, usage) => ({ entries: total.entries + usage.entries, bytes: total.bytes + usage.bytes }), EMPTY_USAGE);
138
+ }
139
+ /** Recursive measure. A directory that vanished mid-walk contributes nothing. */
140
+ async function measureTree(dir) {
141
+ const entries = await readdirOrNull(dir);
142
+ return entries === null ? EMPTY_USAGE : measureEntries(dir, entries);
143
+ }
144
+ /**
145
+ * Size of one entry, without following symlinks.
146
+ *
147
+ * A file that disappears between the listing and the stat contributes 0 rather
148
+ * than failing the clear — the whole tree is about to be deleted anyway, and a
149
+ * concurrent vat run pruning its own temp file must not turn cleanup into an
150
+ * error.
151
+ */
152
+ async function sizeOf(target) {
153
+ try {
154
+ // eslint-disable-next-line security/detect-non-literal-fs-filename -- path is inside the cache root being measured
155
+ const stats = await fs.lstat(target);
156
+ return stats.size;
157
+ }
158
+ catch {
159
+ return 0;
160
+ }
161
+ }
162
+ //# sourceMappingURL=clear.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clear.js","sourceRoot":"","sources":["../../../src/commands/cache/clear.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Cache command group, plus the root-level `--no-cache` control surface.
3
+ *
4
+ * Both live here so the flag and the command that cleans up after it stay in one
5
+ * place: `--no-cache` decides whether this run *writes* to the caches, `vat cache
6
+ * clear` decides whether the caches *survive*.
7
+ */
8
+ import { Command } from 'commander';
9
+ /**
10
+ * Root options this module reads. Commander represents `--no-cache` as the
11
+ * POSITIVE key `cache` — `true` by default, `false` only when the negated flag
12
+ * is passed. It NEVER produces `noCache`; reading that key is the exact bug that
13
+ * made three flags in this package silent no-ops (see
14
+ * `test/commands/commander-option-keys.test.ts`).
15
+ */
16
+ export interface CacheControlOptions {
17
+ cache?: boolean;
18
+ }
19
+ /**
20
+ * Register the root-level `--no-cache` flag and the hook that gives it effect.
21
+ *
22
+ * ## Why this sets an environment variable instead of plumbing a flag
23
+ *
24
+ * `vat validate`, `vat verify` and `vat build` enumerate nothing in their own
25
+ * process: each `spawnSync`s the vat binary once per phase, and the child is
26
+ * what parses the corpus. A flag parsed in the parent therefore never reaches
27
+ * the process whose caching it is supposed to control, whereas `process.env` is
28
+ * inherited by every child for free. `bin.ts` already leans on exactly that for
29
+ * `VAT_CONTEXT` / `VAT_CONTEXT_PATH`.
30
+ *
31
+ * `VAT_CACHE=0` is read by `ParseCache` (packages/resources/src/parse-cache.ts)
32
+ * per construction, never at module load, so setting it from a `preAction` hook
33
+ * — after Commander has parsed, before any command body runs — is observed.
34
+ *
35
+ * ## The collision with `vat resources validate --no-cache`
36
+ *
37
+ * This root program does NOT use `enablePositionalOptions()`, so Commander scans
38
+ * the WHOLE argv for root options, including everything after the subcommand
39
+ * name. That is the same mechanism that let a root `-v` shadow the subcommands'
40
+ * own `-v, --verbose` (see the incident recorded in `bin.ts`). Measured against
41
+ * commander 12: `vat resources validate docs --no-cache` leaves the ROOT with
42
+ * `cache: false` and the SUBCOMMAND with `cache: true` — the subcommand's
43
+ * identically-named flag is swallowed before it ever sees it, which would have
44
+ * silently re-enabled the external-URL cache the flag exists to bypass.
45
+ *
46
+ * Rather than break a shipped flag, the hook hands the value back down to
47
+ * whichever action command declares a `cache` option of its own. `--no-cache` is
48
+ * a superset — every cache off — so the handoff is a strengthening, never a
49
+ * reversal.
50
+ *
51
+ * @param program - The root program to register the flag and hook on
52
+ */
53
+ export declare function registerCacheControl(program: Command): void;
54
+ /**
55
+ * Apply the root `--no-cache` decision: export it to child processes, and hand
56
+ * it down to an action command that declares its own `cache` option.
57
+ *
58
+ * Exported so a test can drive it from real parsed options rather than from a
59
+ * hand-built bag. Deliberately does nothing when the flag was not passed — a
60
+ * toggle that writes `VAT_CACHE=1` by default would be indistinguishable from
61
+ * one that is always on.
62
+ *
63
+ * @param rootOptions - Options bag from the ROOT program
64
+ * @param actionCommand - The command whose action is about to run
65
+ */
66
+ export declare function applyCacheControl(rootOptions: CacheControlOptions, actionCommand: Command): void;
67
+ export declare function createCacheCommand(): Command;
68
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/cache/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAIpC;;;;;;GAMG;AACH,MAAM,WAAW,mBAAmB;IAClC,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAM3D;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,mBAAmB,EAAE,aAAa,EAAE,OAAO,GAAG,IAAI,CAWhG;AAED,wBAAgB,kBAAkB,IAAI,OAAO,CA6D5C"}
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Cache command group, plus the root-level `--no-cache` control surface.
3
+ *
4
+ * Both live here so the flag and the command that cleans up after it stay in one
5
+ * place: `--no-cache` decides whether this run *writes* to the caches, `vat cache
6
+ * clear` decides whether the caches *survive*.
7
+ */
8
+ import { Command } from 'commander';
9
+ import { cacheClearCommand } from './clear.js';
10
+ /**
11
+ * Register the root-level `--no-cache` flag and the hook that gives it effect.
12
+ *
13
+ * ## Why this sets an environment variable instead of plumbing a flag
14
+ *
15
+ * `vat validate`, `vat verify` and `vat build` enumerate nothing in their own
16
+ * process: each `spawnSync`s the vat binary once per phase, and the child is
17
+ * what parses the corpus. A flag parsed in the parent therefore never reaches
18
+ * the process whose caching it is supposed to control, whereas `process.env` is
19
+ * inherited by every child for free. `bin.ts` already leans on exactly that for
20
+ * `VAT_CONTEXT` / `VAT_CONTEXT_PATH`.
21
+ *
22
+ * `VAT_CACHE=0` is read by `ParseCache` (packages/resources/src/parse-cache.ts)
23
+ * per construction, never at module load, so setting it from a `preAction` hook
24
+ * — after Commander has parsed, before any command body runs — is observed.
25
+ *
26
+ * ## The collision with `vat resources validate --no-cache`
27
+ *
28
+ * This root program does NOT use `enablePositionalOptions()`, so Commander scans
29
+ * the WHOLE argv for root options, including everything after the subcommand
30
+ * name. That is the same mechanism that let a root `-v` shadow the subcommands'
31
+ * own `-v, --verbose` (see the incident recorded in `bin.ts`). Measured against
32
+ * commander 12: `vat resources validate docs --no-cache` leaves the ROOT with
33
+ * `cache: false` and the SUBCOMMAND with `cache: true` — the subcommand's
34
+ * identically-named flag is swallowed before it ever sees it, which would have
35
+ * silently re-enabled the external-URL cache the flag exists to bypass.
36
+ *
37
+ * Rather than break a shipped flag, the hook hands the value back down to
38
+ * whichever action command declares a `cache` option of its own. `--no-cache` is
39
+ * a superset — every cache off — so the handoff is a strengthening, never a
40
+ * reversal.
41
+ *
42
+ * @param program - The root program to register the flag and hook on
43
+ */
44
+ export function registerCacheControl(program) {
45
+ program.option('--no-cache', "Disable VAT's disk caches for this run (also applies to spawned phases)");
46
+ program.hook('preAction', (_thisCommand, actionCommand) => {
47
+ applyCacheControl(program.opts(), actionCommand);
48
+ });
49
+ }
50
+ /**
51
+ * Apply the root `--no-cache` decision: export it to child processes, and hand
52
+ * it down to an action command that declares its own `cache` option.
53
+ *
54
+ * Exported so a test can drive it from real parsed options rather than from a
55
+ * hand-built bag. Deliberately does nothing when the flag was not passed — a
56
+ * toggle that writes `VAT_CACHE=1` by default would be indistinguishable from
57
+ * one that is always on.
58
+ *
59
+ * @param rootOptions - Options bag from the ROOT program
60
+ * @param actionCommand - The command whose action is about to run
61
+ */
62
+ export function applyCacheControl(rootOptions, actionCommand) {
63
+ if (rootOptions.cache !== false)
64
+ return;
65
+ process.env['VAT_CACHE'] = '0';
66
+ // `attributeName()`, not `name()`: for `--no-cache` the latter returns
67
+ // 'no-cache', so a check written against it never matches and the handoff
68
+ // above becomes a no-op that looks like it works.
69
+ if (actionCommand.options.some((option) => option.attributeName() === 'cache')) {
70
+ actionCommand.setOptionValue('cache', false);
71
+ }
72
+ }
73
+ export function createCacheCommand() {
74
+ const cache = new Command('cache');
75
+ cache
76
+ .description("Manage VAT's shared on-disk caches in the system temp directory")
77
+ .helpCommand(false)
78
+ .addHelpText('after', `
79
+ Description:
80
+ VAT keeps three disposable caches under <tmpdir>/.vat-cache: parse facts
81
+ keyed by file content, external-URL validation results, and per-OS-user
82
+ authenticated-link content. None of them is durable — recovery is always
83
+ "rescan", and the OS temp purge is the eviction policy.
84
+
85
+ To run WITHOUT the caches rather than remove them, use --no-cache on any
86
+ command (or VAT_CACHE=0), which also applies to spawned phases.
87
+
88
+ Example:
89
+ $ vat cache clear # Reclaim the temp-directory cache tree
90
+ `);
91
+ cache
92
+ .command('clear')
93
+ .description('Delete the whole VAT cache tree from the system temp directory')
94
+ .action(async function () {
95
+ await cacheClearCommand(this.optsWithGlobals());
96
+ })
97
+ .addHelpText('after', `
98
+ Description:
99
+ Removes <tmpdir>/.vat-cache in its entirety — the parse cache, the
100
+ external-URL validation cache, and the per-OS-user authenticated-link cache.
101
+ Every one of them is disposable by design: the next run repopulates what it
102
+ needs, so the only cost of clearing is one cold pass.
103
+
104
+ Runs regardless of --no-cache / VAT_CACHE=0. Turning caching off must not
105
+ disarm the one command that reclaims the space.
106
+
107
+ A cache directory that does not exist is not an error — nothing to remove is
108
+ a successful clear.
109
+
110
+ Output:
111
+ - cacheDir: absolute path that was targeted
112
+ - existed: whether the directory was there at all
113
+ - removed: top-level entries that were deleted
114
+ - entriesRemoved / bytesRemoved: file count and total size, measured before
115
+ deletion
116
+
117
+ Exit Codes:
118
+ 0 - Cache cleared (or already absent)
119
+ 2 - Could not remove the cache (permissions, busy file)
120
+
121
+ Example:
122
+ $ vat cache clear # Reclaim the temp-directory cache tree
123
+ `);
124
+ return cache;
125
+ }
126
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/commands/cache/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAE,iBAAiB,EAA0B,MAAM,YAAY,CAAC;AAavE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,MAAM,UAAU,oBAAoB,CAAC,OAAgB;IACnD,OAAO,CAAC,MAAM,CAAC,YAAY,EAAE,yEAAyE,CAAC,CAAC;IAExG,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,YAAY,EAAE,aAAa,EAAE,EAAE;QACxD,iBAAiB,CAAC,OAAO,CAAC,IAAI,EAAuB,EAAE,aAAa,CAAC,CAAC;IACxE,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,iBAAiB,CAAC,WAAgC,EAAE,aAAsB;IACxF,IAAI,WAAW,CAAC,KAAK,KAAK,KAAK;QAAE,OAAO;IAExC,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,GAAG,GAAG,CAAC;IAE/B,uEAAuE;IACvE,0EAA0E;IAC1E,kDAAkD;IAClD,IAAI,aAAa,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,aAAa,EAAE,KAAK,OAAO,CAAC,EAAE,CAAC;QAC/E,aAAa,CAAC,cAAc,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IAC/C,CAAC;AACH,CAAC;AAED,MAAM,UAAU,kBAAkB;IAChC,MAAM,KAAK,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IAEnC,KAAK;SACF,WAAW,CAAC,iEAAiE,CAAC;SAC9E,WAAW,CAAC,KAAK,CAAC;SAClB,WAAW,CACV,OAAO,EACP;;;;;;;;;;;;CAYL,CACI,CAAC;IAEJ,KAAK;SACF,OAAO,CAAC,OAAO,CAAC;SAChB,WAAW,CAAC,gEAAgE,CAAC;SAC7E,MAAM,CAAC,KAAK;QACX,MAAM,iBAAiB,CAAC,IAAI,CAAC,eAAe,EAAuB,CAAC,CAAC;IACvE,CAAC,CAAC;SACD,WAAW,CACV,OAAO,EACP;;;;;;;;;;;;;;;;;;;;;;;;;;CA0BL,CACI,CAAC;IAEJ,OAAO,KAAK,CAAC;AACf,CAAC"}
@@ -8,7 +8,7 @@
8
8
  */
9
9
  import { type SeverityCounts } from '@vibe-agent-toolkit/agent-schema';
10
10
  import { type DeclaredEvalSuite, type PackageSkillResult } from '@vibe-agent-toolkit/agent-skills';
11
- import type { ResourceRegistry, SkillsConfig } from '@vibe-agent-toolkit/resources';
11
+ import type { ExternalPluginSource, ResourceRegistry, SkillsConfig } from '@vibe-agent-toolkit/resources';
12
12
  import { Command } from 'commander';
13
13
  import { createLogger } from '../../../utils/logger.js';
14
14
  export interface PluginBuildCommandOptions {
@@ -52,11 +52,25 @@ interface PluginBuildResult {
52
52
  */
53
53
  issueCounts: SeverityCounts;
54
54
  }
55
+ /**
56
+ * A plugin entry that references another marketplace/repo's plugin instead of
57
+ * being built locally (`pluginDef.externalSource` is set). VAT never creates a
58
+ * directory or writes a plugin.json for it — the empty-plugin guard, tree-copy,
59
+ * skills packaging, and files[] mapping in {@link buildPlugin} all assume local
60
+ * content, none of which applies here.
61
+ */
62
+ export interface ExternalPluginBuildResult {
63
+ pluginName: string;
64
+ pluginVersion: string | undefined;
65
+ source: ExternalPluginSource;
66
+ }
55
67
  export interface MarketplaceBuildResult {
56
68
  name: string;
57
69
  status: 'built' | 'error';
58
70
  reason?: string;
59
71
  plugins: PluginBuildResult[];
72
+ /** Plugins referenced via `externalSource` — see {@link ExternalPluginBuildResult}. */
73
+ externalPlugins: ExternalPluginBuildResult[];
60
74
  }
61
75
  export declare function createPluginBuildCommand(): Command;
62
76
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../../../src/commands/claude/plugin/build.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../../../src/commands/claude/plugin/build.ts"],"names":[],"mappings":"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"}
@@ -53,6 +53,12 @@ Description:
53
53
  - Merges plugin.json with author, description, and VAT-supplied metadata
54
54
  - Generates marketplace.json with plugin registry and relative source paths
55
55
 
56
+ A plugin entry with externalSource is REFERENCED, not built: no local dir,
57
+ tree-copy, or skills packaging — its externalSource object (github/url/npm/pip)
58
+ is emitted verbatim as that entry's marketplace.json source, so it resolves to
59
+ wherever the other marketplace/repo actually publishes it. Use this to
60
+ cherry-pick a plugin from one marketplace into another without vendoring it.
61
+
56
62
  Output structure:
57
63
  dist/.claude/plugins/marketplaces/<marketplace>/
58
64
  .claude-plugin/marketplace.json
@@ -214,6 +220,7 @@ async function pluginBuildCommand(options) {
214
220
  });
215
221
  const duration = Date.now() - startTime;
216
222
  const totalPlugins = results.flatMap((r) => r.plugins).length;
223
+ const totalExternalPlugins = results.flatMap((r) => r.externalPlugins).length;
217
224
  const totalSkills = results.flatMap((r) => r.plugins).flatMap((p) => p.skillsCopied).length;
218
225
  const allPlugins = results.flatMap((r) => r.plugins);
219
226
  // KNOWN, DELIBERATELY NOT FIXED — this lane NAMES NOTHING. Every level of this
@@ -235,6 +242,7 @@ async function pluginBuildCommand(options) {
235
242
  issueCounts: sumSeverityCounts(allPlugins.map((p) => p.issueCounts)),
236
243
  marketplacesBuilt: results.filter((r) => r.status === 'built').length,
237
244
  pluginsBuilt: totalPlugins,
245
+ pluginsReferenced: totalExternalPlugins,
238
246
  skillsPackaged: totalSkills,
239
247
  marketplaces: results.map((r) => ({
240
248
  name: r.name,
@@ -263,6 +271,14 @@ async function pluginBuildCommand(options) {
263
271
  localSkillsPackaged: p.localSkillsPackaged,
264
272
  issueCounts: p.issueCounts,
265
273
  })),
274
+ // Referenced, not built — no dir, no counts to report. `source` is the
275
+ // same object emitted verbatim into marketplace.json, so the report and
276
+ // the artifact can never disagree about what a reference points at.
277
+ externalPlugins: r.externalPlugins.map((p) => ({
278
+ name: p.pluginName,
279
+ ...(p.pluginVersion ? { version: p.pluginVersion } : {}),
280
+ source: p.source,
281
+ })),
266
282
  })),
267
283
  duration: `${duration}ms`,
268
284
  });
@@ -299,6 +315,7 @@ async function copyDistributionFiles(marketplaceDir, configDir, config, logger)
299
315
  async function buildMarketplace(input) {
300
316
  const { name, config, availableSkills, configDir, skillsConfig, rootVersion, registry, projectSkills, logger, verbose } = input;
301
317
  const plugins = [];
318
+ const externalPlugins = [];
302
319
  // Clean stale marketplace directory before rebuilding — removes orphaned plugins
303
320
  const marketplaceBaseDir = safePath.join(configDir, 'dist', '.claude', 'plugins', 'marketplaces', name);
304
321
  // eslint-disable-next-line security/detect-non-literal-fs-filename -- resolved from config
@@ -308,6 +325,23 @@ async function buildMarketplace(input) {
308
325
  // Marketplace-level skills filter restricts pool available to plugins that use "*"
309
326
  const marketplaceAvailable = resolveMarketplaceAvailableSkills(config, availableSkills);
310
327
  for (const pluginDef of config.plugins) {
328
+ // externalSource plugins are never built or copied — they route straight
329
+ // to a marketplace.json entry referencing the other repo. Every phase
330
+ // buildPlugin runs (empty-plugin guard, tree-copy, skills packaging,
331
+ // plugin.json merge) assumes local content, none of which exists here.
332
+ if (pluginDef.externalSource) {
333
+ logger.info(` Referencing external plugin: ${pluginDef.name} (${pluginDef.externalSource.source})`);
334
+ // No rootVersion fallback here (unlike a built plugin): this marketplace
335
+ // does not own or build the referenced plugin, so tagging it with THIS
336
+ // repo's package.json version would misrepresent what version the other
337
+ // repo actually publishes. Only an explicit config version applies.
338
+ externalPlugins.push({
339
+ pluginName: pluginDef.name,
340
+ pluginVersion: pluginDef.version,
341
+ source: pluginDef.externalSource,
342
+ });
343
+ continue;
344
+ }
311
345
  const pluginResult = await buildPlugin({
312
346
  marketplaceName: name,
313
347
  pluginDef,
@@ -328,23 +362,34 @@ async function buildMarketplace(input) {
328
362
  const claudePluginDir = safePath.join(marketplaceDir, CLAUDE_PLUGIN_DIRNAME);
329
363
  // eslint-disable-next-line security/detect-non-literal-fs-filename -- resolved paths
330
364
  await mkdir(claudePluginDir, { recursive: true });
331
- // Each entry's author is the plugin's own MERGED author (see marketplace-json.ts),
332
- // so marketplace.json and that plugin's plugin.json cannot disagree.
365
+ // Each BUILT entry's author is the plugin's own MERGED author (see
366
+ // marketplace-json.ts), so marketplace.json and that plugin's plugin.json
367
+ // cannot disagree. External entries carry no author — see marketplace-json.ts.
333
368
  const marketplaceJson = buildMarketplaceJson({
334
369
  name,
335
370
  owner: config.owner,
336
- plugins: plugins.map((p) => ({
337
- name: p.pluginName,
338
- description: config.plugins.find((pd) => pd.name === p.pluginName)?.description,
339
- version: p.pluginVersion,
340
- author: p.pluginAuthor,
341
- })),
371
+ plugins: [
372
+ ...plugins.map((p) => ({
373
+ kind: 'built',
374
+ name: p.pluginName,
375
+ description: config.plugins.find((pd) => pd.name === p.pluginName)?.description,
376
+ version: p.pluginVersion,
377
+ author: p.pluginAuthor,
378
+ })),
379
+ ...externalPlugins.map((p) => ({
380
+ kind: 'external',
381
+ name: p.pluginName,
382
+ description: config.plugins.find((pd) => pd.name === p.pluginName)?.description,
383
+ version: p.pluginVersion,
384
+ source: p.source,
385
+ })),
386
+ ],
342
387
  });
343
388
  // eslint-disable-next-line security/detect-non-literal-fs-filename -- resolved paths
344
389
  await writeFile(safePath.join(claudePluginDir, 'marketplace.json'), JSON.stringify(marketplaceJson, null, 2));
345
390
  logger.info(` .claude-plugin/marketplace.json`);
346
391
  await copyDistributionFiles(marketplaceDir, configDir, config, logger);
347
- return { name, status: 'built', plugins };
392
+ return { name, status: 'built', plugins, externalPlugins };
348
393
  }
349
394
  /**
350
395
  * Resolve which pool skills are available to plugins in this marketplace.