quiver-cli 0.7.0 → 1.0.0

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 (200) hide show
  1. package/README.md +110 -45
  2. package/bin/quiver-cli.mjs +3 -1
  3. package/dist/cli.js +733 -499
  4. package/package.json +2 -2
  5. package/template/.agents/AGENTS.md +4 -3
  6. package/template/.agents/config.json +7 -0
  7. package/template/.agents/plugins/opencode/rtk.ts +34 -0
  8. package/template/.agents/skills/agent-browser/SKILL.md +1 -0
  9. package/template/.agents/skills/apps/skybridge/SKILL.md +4 -0
  10. package/template/.agents/skills/design/impeccable/SKILL.md +36 -118
  11. package/template/.agents/skills/design/impeccable/reference/adapt.md +1 -0
  12. package/template/.agents/skills/design/impeccable/reference/adapt.native.md +58 -0
  13. package/template/.agents/skills/design/impeccable/reference/android.md +40 -0
  14. package/template/.agents/skills/design/impeccable/reference/animate.md +73 -188
  15. package/template/.agents/skills/design/impeccable/reference/audit.md +12 -9
  16. package/template/.agents/skills/design/impeccable/reference/audit.native.md +139 -0
  17. package/template/.agents/skills/design/impeccable/reference/bolder.md +19 -101
  18. package/template/.agents/skills/design/impeccable/reference/clarify.md +59 -253
  19. package/template/.agents/skills/design/impeccable/reference/colorize.md +51 -222
  20. package/template/.agents/skills/design/impeccable/reference/craft-floor.md +42 -0
  21. package/template/.agents/skills/design/impeccable/reference/craft.md +3 -121
  22. package/template/.agents/skills/design/impeccable/reference/critique.md +44 -23
  23. package/template/.agents/skills/design/impeccable/reference/degraded/asset-producer.md +90 -0
  24. package/template/.agents/skills/design/impeccable/reference/degraded/documenter.md +24 -0
  25. package/template/.agents/skills/design/impeccable/reference/degraded/finish-reviewer.md +37 -0
  26. package/template/.agents/skills/design/impeccable/reference/degraded/manual-edit-applier.md +92 -0
  27. package/template/.agents/skills/design/impeccable/reference/delight.md +47 -279
  28. package/template/.agents/skills/design/impeccable/reference/distill.md +2 -2
  29. package/template/.agents/skills/design/impeccable/reference/doctor.md +53 -0
  30. package/template/.agents/skills/design/impeccable/reference/document.md +60 -73
  31. package/template/.agents/skills/design/impeccable/reference/harden.md +1 -12
  32. package/template/.agents/skills/design/impeccable/reference/hooks.md +20 -5
  33. package/template/.agents/skills/design/impeccable/reference/init.md +72 -119
  34. package/template/.agents/skills/design/impeccable/reference/ios.md +45 -0
  35. package/template/.agents/skills/design/impeccable/reference/layout.md +54 -131
  36. package/template/.agents/skills/design/impeccable/reference/live-setup.md +102 -0
  37. package/template/.agents/skills/design/impeccable/reference/live.md +116 -511
  38. package/template/.agents/skills/design/impeccable/reference/new-work.md +105 -0
  39. package/template/.agents/skills/design/impeccable/reference/{product.md → operate.md} +6 -5
  40. package/template/.agents/skills/design/impeccable/reference/optimize.md +4 -4
  41. package/template/.agents/skills/design/impeccable/reference/overdrive.md +1 -4
  42. package/template/.agents/skills/design/impeccable/reference/polish.md +68 -212
  43. package/template/.agents/skills/design/impeccable/reference/quieter.md +3 -3
  44. package/template/.agents/skills/design/impeccable/reference/routing.md +18 -0
  45. package/template/.agents/skills/design/impeccable/reference/shape.md +38 -144
  46. package/template/.agents/skills/design/impeccable/reference/typeset.md +51 -250
  47. package/template/.agents/skills/design/impeccable/reference/visualize.md +47 -0
  48. package/template/.agents/skills/design/impeccable/scripts/command-metadata.json +1 -1
  49. package/template/.agents/skills/design/impeccable/scripts/concept-seed.mjs +558 -0
  50. package/template/.agents/skills/design/impeccable/scripts/context-signals.mjs +119 -10
  51. package/template/.agents/skills/design/impeccable/scripts/context.mjs +534 -45
  52. package/template/.agents/skills/design/impeccable/scripts/critique-storage.mjs +18 -47
  53. package/template/.agents/skills/design/impeccable/scripts/detector/browser/injected/index.mjs +96 -10
  54. package/template/.agents/skills/design/impeccable/scripts/detector/cli/main.mjs +174 -26
  55. package/template/.agents/skills/design/impeccable/scripts/detector/design-system.mjs +233 -0
  56. package/template/.agents/skills/design/impeccable/scripts/detector/detect-antipatterns-browser.js +3348 -203
  57. package/template/.agents/skills/design/impeccable/scripts/detector/engines/browser/detect-url.mjs +102 -7
  58. package/template/.agents/skills/design/impeccable/scripts/detector/engines/regex/detect-text.mjs +297 -97
  59. package/template/.agents/skills/design/impeccable/scripts/detector/engines/static-html/css-cascade.mjs +187 -16
  60. package/template/.agents/skills/design/impeccable/scripts/detector/engines/static-html/detect-html.mjs +41 -11
  61. package/template/.agents/skills/design/impeccable/scripts/detector/findings.mjs +7 -1
  62. package/template/.agents/skills/design/impeccable/scripts/detector/node/file-system.mjs +16 -2
  63. package/template/.agents/skills/design/impeccable/scripts/detector/registry/antipatterns.mjs +207 -38
  64. package/template/.agents/skills/design/impeccable/scripts/detector/rules/checks.mjs +3082 -173
  65. package/template/.agents/skills/design/impeccable/scripts/detector/shared/constants.mjs +11 -0
  66. package/template/.agents/skills/design/impeccable/scripts/detector/shared/fonts.mjs +30 -0
  67. package/template/.agents/skills/design/impeccable/scripts/doctor.mjs +336 -0
  68. package/template/.agents/skills/design/impeccable/scripts/embed-prompt.mjs +133 -0
  69. package/template/.agents/skills/design/impeccable/scripts/generate-image.mjs +240 -0
  70. package/template/.agents/skills/design/impeccable/scripts/hook-admin.mjs +98 -18
  71. package/template/.agents/skills/design/impeccable/scripts/hook-before-edit.mjs +46 -6
  72. package/template/.agents/skills/design/impeccable/scripts/hook-lib.mjs +518 -50
  73. package/template/.agents/skills/design/impeccable/scripts/hook.mjs +25 -8
  74. package/template/.agents/skills/design/impeccable/scripts/lib/artifact-schema.mjs +93 -0
  75. package/template/.agents/skills/design/impeccable/scripts/lib/composition-catalog.mjs +200 -0
  76. package/template/.agents/skills/design/impeccable/scripts/lib/concept-catalog.mjs +357 -0
  77. package/template/.agents/skills/design/impeccable/scripts/lib/impeccable-config.mjs +27 -7
  78. package/template/.agents/skills/design/impeccable/scripts/lib/impeccable-paths.mjs +17 -8
  79. package/template/.agents/skills/design/impeccable/scripts/lib/provider.mjs +5 -0
  80. package/template/.agents/skills/design/impeccable/scripts/lib/roll-selection.mjs +362 -0
  81. package/template/.agents/skills/design/impeccable/scripts/lib/staleness-deep.mjs +457 -0
  82. package/template/.agents/skills/design/impeccable/scripts/lib/staleness-notice.mjs +169 -0
  83. package/template/.agents/skills/design/impeccable/scripts/lib/staleness.mjs +457 -0
  84. package/template/.agents/skills/design/impeccable/scripts/lib/surface-briefs.mjs +151 -0
  85. package/template/.agents/skills/design/impeccable/scripts/lib/target-slug.mjs +33 -0
  86. package/template/.agents/skills/design/impeccable/scripts/lib/template-extensions.mjs +146 -0
  87. package/template/.agents/skills/design/impeccable/scripts/live/accept-css.mjs +617 -0
  88. package/template/.agents/skills/design/impeccable/scripts/live/accept-verify.mjs +60 -0
  89. package/template/.agents/skills/design/impeccable/scripts/live/browser-script-parts.mjs +7 -1
  90. package/template/.agents/skills/design/impeccable/scripts/live/completion.mjs +10 -1
  91. package/template/.agents/skills/design/impeccable/scripts/live/event-validation.mjs +67 -5
  92. package/template/.agents/skills/design/impeccable/scripts/live/frameworks/astro.mjs +47 -0
  93. package/template/.agents/skills/design/impeccable/scripts/live/frameworks/detect-utils.mjs +73 -0
  94. package/template/.agents/skills/design/impeccable/scripts/live/frameworks/index.mjs +143 -0
  95. package/template/.agents/skills/design/impeccable/scripts/live/frameworks/journal.mjs +197 -0
  96. package/template/.agents/skills/design/impeccable/scripts/live/frameworks/nextjs.mjs +49 -0
  97. package/template/.agents/skills/design/impeccable/scripts/live/frameworks/nuxt.mjs +161 -0
  98. package/template/.agents/skills/design/impeccable/scripts/live/frameworks/script-src.mjs +17 -0
  99. package/template/.agents/skills/design/impeccable/scripts/live/frameworks/static-html.mjs +26 -0
  100. package/template/.agents/skills/design/impeccable/scripts/live/frameworks/sveltekit.mjs +71 -0
  101. package/template/.agents/skills/design/impeccable/scripts/live/frameworks/tag-strategy.mjs +247 -0
  102. package/template/.agents/skills/design/impeccable/scripts/live/frameworks/tanstack-start.mjs +70 -0
  103. package/template/.agents/skills/design/impeccable/scripts/live/frameworks/vite-generic.mjs +42 -0
  104. package/template/.agents/skills/design/impeccable/scripts/live/generation-preflight.mjs +149 -0
  105. package/template/.agents/skills/design/impeccable/scripts/live/instructions.mjs +142 -0
  106. package/template/.agents/skills/design/impeccable/scripts/live/poll-lanes.mjs +14 -0
  107. package/template/.agents/skills/design/impeccable/scripts/live/roots.mjs +508 -0
  108. package/template/.agents/skills/design/impeccable/scripts/live/session-store.mjs +324 -50
  109. package/template/.agents/skills/design/impeccable/scripts/live/source-lock.mjs +105 -0
  110. package/template/.agents/skills/design/impeccable/scripts/live/source-search.mjs +105 -0
  111. package/template/.agents/skills/design/impeccable/scripts/live/svelte-ast.mjs +961 -0
  112. package/template/.agents/skills/design/impeccable/scripts/live/svelte-component.mjs +588 -72
  113. package/template/.agents/skills/design/impeccable/scripts/live/sveltekit-adapter.mjs +59 -17
  114. package/template/.agents/skills/design/impeccable/scripts/live/tanstack-adapter.mjs +280 -0
  115. package/template/.agents/skills/design/impeccable/scripts/live/vocabulary.mjs +135 -0
  116. package/template/.agents/skills/design/impeccable/scripts/live-accept.mjs +210 -68
  117. package/template/.agents/skills/design/impeccable/scripts/live-browser.js +1562 -223
  118. package/template/.agents/skills/design/impeccable/scripts/live-commit-manual-edits.mjs +3 -0
  119. package/template/.agents/skills/design/impeccable/scripts/live-complete.mjs +33 -1
  120. package/template/.agents/skills/design/impeccable/scripts/live-inject.mjs +175 -255
  121. package/template/.agents/skills/design/impeccable/scripts/live-insert.mjs +26 -6
  122. package/template/.agents/skills/design/impeccable/scripts/live-manual-edit-evidence.mjs +6 -1
  123. package/template/.agents/skills/design/impeccable/scripts/live-poll.mjs +61 -16
  124. package/template/.agents/skills/design/impeccable/scripts/live-resume.mjs +39 -10
  125. package/template/.agents/skills/design/impeccable/scripts/live-server.mjs +573 -47
  126. package/template/.agents/skills/design/impeccable/scripts/live-status.mjs +17 -7
  127. package/template/.agents/skills/design/impeccable/scripts/live-wrap.mjs +124 -91
  128. package/template/.agents/skills/design/impeccable/scripts/live.mjs +88 -26
  129. package/template/.agents/skills/design/impeccable/scripts/palette.mjs +76 -81
  130. package/template/.agents/skills/design/impeccable/scripts/pin.mjs +18 -11
  131. package/template/.agents/skills/design/impeccable/scripts/serve-question.mjs +932 -0
  132. package/template/.agents/skills/design/impeccable/scripts/surface-brief.mjs +74 -0
  133. package/template/.agents/skills/design/shadcn/SKILL.md +46 -11
  134. package/template/.agents/skills/design/shadcn/cli.md +49 -16
  135. package/template/.agents/skills/design/shadcn/customization.md +14 -7
  136. package/template/.agents/skills/design/shadcn/evals/evals.json +30 -0
  137. package/template/.agents/skills/design/shadcn/mcp.md +27 -16
  138. package/template/.agents/skills/design/shadcn/registry.md +277 -0
  139. package/template/.agents/skills/design/shadcn/rules/chat.md +224 -0
  140. package/template/.agents/skills/design/shadcn/rules/composition.md +20 -2
  141. package/template/.agents/skills/design/shadcn/rules/styling.md +23 -0
  142. package/template/.agents/skills/find-skills/SKILL.md +2 -3
  143. package/template/.agents/skills/hono/SKILL.md +579 -0
  144. package/template/.agents/skills/integrations/langfuse/SKILL.md +10 -6
  145. package/template/.agents/skills/integrations/langfuse/references/ci-cd.md +41 -0
  146. package/template/.agents/skills/integrations/langfuse/references/cli.md +8 -0
  147. package/template/.agents/skills/integrations/langfuse/references/error-analysis.md +15 -27
  148. package/template/.agents/skills/integrations/langfuse/references/instrumentation.md +39 -52
  149. package/template/.agents/skills/integrations/langfuse/references/judge-calibration.md +5 -2
  150. package/template/.agents/skills/integrations/langfuse/references/prompt-engineering.md +35 -0
  151. package/template/.agents/skills/integrations/langfuse/references/prompt-migration.md +41 -196
  152. package/template/.agents/skills/integrations/langfuse/references/skill-feedback.md +3 -0
  153. package/template/.agents/skills/integrations/langfuse/references/trace-evaluator-upgrade.md +76 -0
  154. package/template/.agents/skills/integrations/langfuse/references/user-feedback.md +4 -0
  155. package/template/.agents/skills/integrations/langfuse/references/v4-project-migration.md +73 -0
  156. package/template/.agents/skills/supabase/CHANGELOG.md +71 -0
  157. package/template/.agents/skills/supabase/SKILL.md +145 -0
  158. package/template/.agents/skills/supabase/assets/feedback-issue-template.md +17 -0
  159. package/template/.agents/skills/supabase/references/skill-feedback.md +17 -0
  160. package/template/.agents/skills/supabase-postgres-best-practices/CHANGELOG.md +73 -0
  161. package/template/.agents/skills/supabase-postgres-best-practices/SKILL.md +64 -0
  162. package/template/.agents/skills/supabase-postgres-best-practices/references/_contributing.md +170 -0
  163. package/template/.agents/skills/supabase-postgres-best-practices/references/_sections.md +39 -0
  164. package/template/.agents/skills/supabase-postgres-best-practices/references/_template.md +34 -0
  165. package/template/.agents/skills/supabase-postgres-best-practices/references/advanced-full-text-search.md +55 -0
  166. package/template/.agents/skills/supabase-postgres-best-practices/references/advanced-jsonb-indexing.md +49 -0
  167. package/template/.agents/skills/supabase-postgres-best-practices/references/conn-idle-timeout.md +46 -0
  168. package/template/.agents/skills/supabase-postgres-best-practices/references/conn-limits.md +44 -0
  169. package/template/.agents/skills/supabase-postgres-best-practices/references/conn-pooling.md +41 -0
  170. package/template/.agents/skills/supabase-postgres-best-practices/references/conn-prepared-statements.md +46 -0
  171. package/template/.agents/skills/supabase-postgres-best-practices/references/data-batch-inserts.md +54 -0
  172. package/template/.agents/skills/supabase-postgres-best-practices/references/data-n-plus-one.md +53 -0
  173. package/template/.agents/skills/supabase-postgres-best-practices/references/data-pagination.md +50 -0
  174. package/template/.agents/skills/supabase-postgres-best-practices/references/data-upsert.md +50 -0
  175. package/template/.agents/skills/supabase-postgres-best-practices/references/lock-advisory.md +56 -0
  176. package/template/.agents/skills/supabase-postgres-best-practices/references/lock-deadlock-prevention.md +68 -0
  177. package/template/.agents/skills/supabase-postgres-best-practices/references/lock-short-transactions.md +50 -0
  178. package/template/.agents/skills/supabase-postgres-best-practices/references/lock-skip-locked.md +54 -0
  179. package/template/.agents/skills/supabase-postgres-best-practices/references/monitor-explain-analyze.md +45 -0
  180. package/template/.agents/skills/supabase-postgres-best-practices/references/monitor-pg-stat-statements.md +55 -0
  181. package/template/.agents/skills/supabase-postgres-best-practices/references/monitor-vacuum-analyze.md +55 -0
  182. package/template/.agents/skills/supabase-postgres-best-practices/references/query-composite-indexes.md +44 -0
  183. package/template/.agents/skills/supabase-postgres-best-practices/references/query-covering-indexes.md +40 -0
  184. package/template/.agents/skills/supabase-postgres-best-practices/references/query-index-types.md +48 -0
  185. package/template/.agents/skills/supabase-postgres-best-practices/references/query-missing-indexes.md +43 -0
  186. package/template/.agents/skills/supabase-postgres-best-practices/references/query-partial-indexes.md +45 -0
  187. package/template/.agents/skills/supabase-postgres-best-practices/references/schema-constraints.md +80 -0
  188. package/template/.agents/skills/supabase-postgres-best-practices/references/schema-data-types.md +46 -0
  189. package/template/.agents/skills/supabase-postgres-best-practices/references/schema-foreign-key-indexes.md +59 -0
  190. package/template/.agents/skills/supabase-postgres-best-practices/references/schema-lowercase-identifiers.md +55 -0
  191. package/template/.agents/skills/supabase-postgres-best-practices/references/schema-partitioning.md +55 -0
  192. package/template/.agents/skills/supabase-postgres-best-practices/references/schema-primary-keys.md +61 -0
  193. package/template/.agents/skills/supabase-postgres-best-practices/references/security-privileges.md +54 -0
  194. package/template/.agents/skills/supabase-postgres-best-practices/references/security-rls-basics.md +50 -0
  195. package/template/.agents/skills/supabase-postgres-best-practices/references/security-rls-performance.md +63 -0
  196. package/template/.agents/upstreams.json +34 -12
  197. package/template/.agents/skills/design/impeccable/reference/brand.md +0 -108
  198. package/template/.agents/skills/design/impeccable/reference/codex.md +0 -105
  199. package/template/.agents/skills/design/impeccable/reference/interaction-design.md +0 -189
  200. package/template/.agents/skills/integrations/langfuse/references/sdk-upgrade.md +0 -175
package/dist/cli.js CHANGED
@@ -9,7 +9,7 @@ var __export = (target, all) => {
9
9
  };
10
10
 
11
11
  // src/ui/prompts.ts
12
- var clackPromise, warnedClackUnavailable, loadClack, QUIVER_ART, TAGLINE, colorEnabled, palette, block, banner, outro, step, info, success, warn, error, password, selectGrouped, selectGroupedText;
12
+ var clackPromise, warnedClackUnavailable, loadClack, QUIVER_ART, TAGLINE, colorEnabled, palette, block, banner, outro, step, info, success, warn, error, selectGrouped, selectGroupedText;
13
13
  var init_prompts = __esm({
14
14
  "src/ui/prompts.ts"() {
15
15
  "use strict";
@@ -96,23 +96,6 @@ ${message}
96
96
  if (clack) clack.log.error(message);
97
97
  else console.error(message);
98
98
  };
99
- password = async (message) => {
100
- const clack = await loadClack();
101
- if (clack) {
102
- const value = await clack.password({ message });
103
- if (clack.isCancel(value)) return null;
104
- return value;
105
- }
106
- const { createInterface } = await import("readline");
107
- const rl = createInterface({ input: process.stdin, output: process.stdout });
108
- const answer = await new Promise((res) => {
109
- rl.question(`${message}: `, (a) => {
110
- rl.close();
111
- res(a);
112
- });
113
- });
114
- return answer.trim() || null;
115
- };
116
99
  selectGrouped = async ({
117
100
  message,
118
101
  groups,
@@ -205,7 +188,13 @@ var init_node_guard = __esm({
205
188
  "src/version/node-guard.ts"() {
206
189
  "use strict";
207
190
  MIN_NODE = "20.12.0";
208
- INTERACTIVE_COMMANDS = /* @__PURE__ */ new Set(["init", "add", "remove", "rm"]);
191
+ INTERACTIVE_COMMANDS = /* @__PURE__ */ new Set([
192
+ "init",
193
+ "add",
194
+ "remove",
195
+ "rm",
196
+ "providers"
197
+ ]);
209
198
  parse = (v) => {
210
199
  const [major = 0, minor = 0, patch = 0] = v.replace(/^v/, "").split(".").map((p) => Number.parseInt(p, 10) || 0);
211
200
  return [major, minor, patch];
@@ -320,7 +309,7 @@ var init_frontmatter = __esm({
320
309
  // src/catalog/discover.ts
321
310
  import { existsSync, readdirSync as readdirSync2, readFileSync as readFileSync2 } from "fs";
322
311
  import { relative as relative2, resolve as resolve2 } from "path";
323
- var readConfig, discoverSkills, discoverCommands, discoverMcp, loadCatalog;
312
+ var readConfig, discoverSkills, discoverCommands, discoverMcp, discoverPlugins, loadCatalog;
324
313
  var init_discover = __esm({
325
314
  "src/catalog/discover.ts"() {
326
315
  "use strict";
@@ -394,6 +383,20 @@ var init_discover = __esm({
394
383
  configDigest: jsonDigest(servers[name])
395
384
  }));
396
385
  };
386
+ discoverPlugins = (root, config) => Object.entries(config.plugins ?? {}).sort(([a], [b]) => a.localeCompare(b)).map(([name, plugin]) => {
387
+ const absPath = resolve2(root, plugin.sourcePath);
388
+ if (!existsSync(absPath)) {
389
+ throw new Error(`Plugin "${name}" source not found: ${absPath}`);
390
+ }
391
+ return {
392
+ name,
393
+ provider: plugin.provider,
394
+ sourcePath: plugin.sourcePath,
395
+ absPath,
396
+ digest: jsonDigest({ config: plugin, content: fileDigest(absPath) }),
397
+ requires: plugin.requires ?? []
398
+ };
399
+ });
397
400
  loadCatalog = (catalog) => {
398
401
  const { config, path } = readConfig(catalog.root);
399
402
  return {
@@ -401,14 +404,15 @@ var init_discover = __esm({
401
404
  configPath: path,
402
405
  skills: discoverSkills(catalog.root),
403
406
  commands: discoverCommands(catalog.root),
404
- mcp: discoverMcp(config)
407
+ mcp: discoverMcp(config),
408
+ plugins: discoverPlugins(catalog.root, config)
405
409
  };
406
410
  };
407
411
  }
408
412
  });
409
413
 
410
414
  // src/catalog/entries.ts
411
- var skillToEntry, commandToEntry, mcpToEntry;
415
+ var skillToEntry, commandToEntry, mcpToEntry, pluginToEntry;
412
416
  var init_entries = __esm({
413
417
  "src/catalog/entries.ts"() {
414
418
  "use strict";
@@ -431,6 +435,13 @@ var init_entries = __esm({
431
435
  tools: null,
432
436
  toolsFetchedAt: null
433
437
  });
438
+ pluginToEntry = (plugin) => ({
439
+ type: "plugin",
440
+ provider: plugin.provider,
441
+ sourcePath: plugin.sourcePath,
442
+ digest: plugin.digest,
443
+ requires: plugin.requires
444
+ });
434
445
  }
435
446
  });
436
447
 
@@ -520,6 +531,12 @@ var init_materialize = __esm({
520
531
  mkdirSync(dirname(dest), { recursive: true });
521
532
  cpSync(command.absPath, dest, { force: true });
522
533
  }
534
+ for (const plugin of catalog.plugins) {
535
+ if (!selection.plugins.includes(plugin.name)) continue;
536
+ const dest = resolve4(destRoot, plugin.sourcePath);
537
+ mkdirSync(dirname(dest), { recursive: true });
538
+ cpSync(plugin.absPath, dest, { force: true });
539
+ }
523
540
  const filteredConfig = {};
524
541
  if (catalog.config.shared) filteredConfig["shared"] = catalog.config.shared;
525
542
  const mcpServers = {};
@@ -527,6 +544,14 @@ var init_materialize = __esm({
527
544
  if (selection.mcp.includes(mcp.name)) mcpServers[mcp.name] = mcp.server;
528
545
  }
529
546
  if (Object.keys(mcpServers).length) filteredConfig["mcpServers"] = mcpServers;
547
+ const plugins = {};
548
+ for (const plugin of catalog.plugins) {
549
+ if (!selection.plugins.includes(plugin.name)) continue;
550
+ plugins[plugin.name] = catalog.config.plugins?.[plugin.name];
551
+ }
552
+ if (Object.keys(plugins).length) filteredConfig["plugins"] = plugins;
553
+ if (catalog.config.opencode) filteredConfig["opencode"] = catalog.config.opencode;
554
+ if (catalog.config.tui) filteredConfig["tui"] = catalog.config.tui;
530
555
  if (catalog.config.claude) filteredConfig["claude"] = catalog.config.claude;
531
556
  writeFileSync(
532
557
  resolve4(destRoot, "config.json"),
@@ -562,53 +587,10 @@ var init_materialize = __esm({
562
587
 
563
588
  // src/github/auth.ts
564
589
  import { execFileSync } from "child_process";
565
- import {
566
- chmodSync,
567
- existsSync as existsSync4,
568
- mkdirSync as mkdirSync2,
569
- readFileSync as readFileSync4,
570
- rmSync as rmSync2,
571
- writeFileSync as writeFileSync2
572
- } from "fs";
573
- import { homedir } from "os";
574
- import { resolve as resolve5 } from "path";
575
- var configDir, authFilePath, readAuthFile, readStoredToken, writeStoredToken, deleteStoredToken, cachedToken, ghToken, resolveGithubToken, resetTokenCache, validateToken;
590
+ var cachedToken, ghToken, resolveGithubToken;
576
591
  var init_auth = __esm({
577
592
  "src/github/auth.ts"() {
578
593
  "use strict";
579
- configDir = () => {
580
- const base = process.env["XDG_CONFIG_HOME"] || resolve5(homedir(), ".config");
581
- return resolve5(base, "quiver");
582
- };
583
- authFilePath = () => resolve5(configDir(), "auth.json");
584
- readAuthFile = () => {
585
- const path = authFilePath();
586
- if (!existsSync4(path)) return null;
587
- try {
588
- return JSON.parse(readFileSync4(path, "utf8"));
589
- } catch {
590
- return null;
591
- }
592
- };
593
- readStoredToken = () => readAuthFile()?.github?.token ?? null;
594
- writeStoredToken = (token, login2) => {
595
- mkdirSync2(configDir(), { recursive: true });
596
- const file = {
597
- github: { token, login: login2, createdAt: (/* @__PURE__ */ new Date()).toISOString() }
598
- };
599
- writeFileSync2(authFilePath(), JSON.stringify(file, null, 2) + "\n", {
600
- mode: 384
601
- });
602
- chmodSync(authFilePath(), 384);
603
- resetTokenCache();
604
- };
605
- deleteStoredToken = () => {
606
- const path = authFilePath();
607
- if (!existsSync4(path)) return false;
608
- rmSync2(path);
609
- resetTokenCache();
610
- return true;
611
- };
612
594
  ghToken = () => {
613
595
  try {
614
596
  const out = execFileSync("gh", ["auth", "token"], {
@@ -623,38 +605,14 @@ var init_auth = __esm({
623
605
  };
624
606
  resolveGithubToken = () => {
625
607
  if (cachedToken !== void 0) return cachedToken;
626
- cachedToken = process.env["GITHUB_TOKEN"] ?? process.env["GH_TOKEN"] ?? readStoredToken() ?? ghToken();
608
+ cachedToken = process.env["GITHUB_TOKEN"] ?? process.env["GH_TOKEN"] ?? ghToken();
627
609
  return cachedToken;
628
610
  };
629
- resetTokenCache = () => {
630
- cachedToken = void 0;
631
- };
632
- validateToken = async (token) => {
633
- let res;
634
- try {
635
- res = await fetch("https://api.github.com/user", {
636
- headers: {
637
- Accept: "application/vnd.github+json",
638
- "User-Agent": "quiver-cli",
639
- Authorization: `Bearer ${token}`
640
- }
641
- });
642
- } catch (err) {
643
- return {
644
- ok: false,
645
- reason: err instanceof Error ? err.message : "fetch failed"
646
- };
647
- }
648
- if (res.status === 401) return { ok: false, reason: "token is invalid or expired" };
649
- if (!res.ok) return { ok: false, reason: `HTTP ${res.status}` };
650
- const body = await res.json();
651
- return { ok: true, login: body.login ?? "unknown" };
652
- };
653
611
  }
654
612
  });
655
613
 
656
614
  // src/github/api.ts
657
- import { mkdirSync as mkdirSync3 } from "fs";
615
+ import { mkdirSync as mkdirSync2 } from "fs";
658
616
  import { Readable } from "stream";
659
617
  import { pipeline } from "stream/promises";
660
618
  import * as tar from "tar";
@@ -672,13 +630,14 @@ var init_api = __esm({
672
630
  if (token) h["Authorization"] = `Bearer ${token}`;
673
631
  return h;
674
632
  };
675
- failureReason = (status2) => {
676
- if (status2 === 401) return "authentication failed - run `quiver-cli login` with a valid token";
677
- if (status2 === 403 || status2 === 429)
678
- return "rate-limited or access denied (run `quiver-cli login` or set GITHUB_TOKEN)";
679
- if (status2 === 404)
680
- return "repo or ref not found (private repo? run `quiver-cli login`)";
681
- return `HTTP ${status2}`;
633
+ failureReason = (status) => {
634
+ if (status === 401)
635
+ return "authentication failed - set GITHUB_TOKEN or log in with the gh CLI";
636
+ if (status === 403 || status === 429)
637
+ return "rate-limited or access denied (set GITHUB_TOKEN or log in with the gh CLI)";
638
+ if (status === 404)
639
+ return "repo or ref not found (private repo? set GITHUB_TOKEN or log in with the gh CLI)";
640
+ return `HTTP ${status}`;
682
641
  };
683
642
  apiFetch = async (url) => {
684
643
  let res;
@@ -715,7 +674,7 @@ var init_api = __esm({
715
674
  );
716
675
  if (!res.ok) return res;
717
676
  if (!res.value.body) return { ok: false, reason: "empty tarball response" };
718
- mkdirSync3(destDir, { recursive: true });
677
+ mkdirSync2(destDir, { recursive: true });
719
678
  try {
720
679
  await pipeline(
721
680
  Readable.fromWeb(res.value.body),
@@ -740,9 +699,9 @@ __export(remote_exports, {
740
699
  fetchRemoteCatalog: () => fetchRemoteCatalog,
741
700
  parseGithubSource: () => parseGithubSource
742
701
  });
743
- import { existsSync as existsSync5, mkdtempSync, renameSync, rmSync as rmSync3, mkdirSync as mkdirSync4 } from "fs";
744
- import { homedir as homedir2 } from "os";
745
- import { dirname as dirname2, resolve as resolve6 } from "path";
702
+ import { existsSync as existsSync4, mkdtempSync, renameSync, rmSync as rmSync2, mkdirSync as mkdirSync3 } from "fs";
703
+ import { homedir } from "os";
704
+ import { dirname as dirname2, resolve as resolve5 } from "path";
746
705
  var parseGithubSource, catalogCacheRoot, catalogCacheDir, fetchRemoteCatalog;
747
706
  var init_remote = __esm({
748
707
  "src/catalog/remote.ts"() {
@@ -765,10 +724,10 @@ var init_remote = __esm({
765
724
  return { repo: `${owner}/${repo}`, path: pathParts.join("/"), ref };
766
725
  };
767
726
  catalogCacheRoot = () => {
768
- const base = process.env["XDG_CACHE_HOME"] || resolve6(homedir2(), ".cache");
769
- return resolve6(base, "quiver", "catalogs");
727
+ const base = process.env["XDG_CACHE_HOME"] || resolve5(homedir(), ".cache");
728
+ return resolve5(base, "quiver", "catalogs");
770
729
  };
771
- catalogCacheDir = (repo, sha) => resolve6(catalogCacheRoot(), `${repo.replace("/", "-")}-${sha.slice(0, 12)}`);
730
+ catalogCacheDir = (repo, sha) => resolve5(catalogCacheRoot(), `${repo.replace("/", "-")}-${sha.slice(0, 12)}`);
772
731
  fetchRemoteCatalog = async (source, options = {}) => {
773
732
  const spec = parseGithubSource(source);
774
733
  let ref = spec.ref;
@@ -788,22 +747,22 @@ var init_remote = __esm({
788
747
  sha = commit.value;
789
748
  }
790
749
  const cacheDir = catalogCacheDir(spec.repo, sha);
791
- if (!existsSync5(cacheDir)) {
792
- mkdirSync4(dirname2(cacheDir), { recursive: true });
750
+ if (!existsSync4(cacheDir)) {
751
+ mkdirSync3(dirname2(cacheDir), { recursive: true });
793
752
  const tmp = mkdtempSync(`${cacheDir}.tmp-`);
794
753
  const result = await downloadTarball(spec.repo, sha, tmp);
795
754
  if (!result.ok) {
796
- rmSync3(tmp, { recursive: true, force: true });
755
+ rmSync2(tmp, { recursive: true, force: true });
797
756
  throw new Error(`Cannot download ${spec.repo}@${sha.slice(0, 12)}: ${result.reason}`);
798
757
  }
799
758
  try {
800
759
  renameSync(tmp, cacheDir);
801
760
  } catch {
802
- rmSync3(tmp, { recursive: true, force: true });
761
+ rmSync2(tmp, { recursive: true, force: true });
803
762
  }
804
763
  }
805
- const root = spec.path ? resolve6(cacheDir, spec.path) : cacheDir;
806
- if (!existsSync5(root)) {
764
+ const root = spec.path ? resolve5(cacheDir, spec.path) : cacheDir;
765
+ if (!existsSync4(root)) {
807
766
  throw new Error(
808
767
  `Catalog path "${spec.path}" not found in ${spec.repo}@${sha.slice(0, 12)}.`
809
768
  );
@@ -814,21 +773,21 @@ var init_remote = __esm({
814
773
  });
815
774
 
816
775
  // src/catalog/resolve.ts
817
- import { accessSync, constants, existsSync as existsSync6 } from "fs";
818
- import { dirname as dirname3, relative as relative3, resolve as resolve7, sep } from "path";
776
+ import { accessSync, constants, existsSync as existsSync5 } from "fs";
777
+ import { dirname as dirname3, relative as relative3, resolve as resolve6, sep } from "path";
819
778
  import { fileURLToPath } from "url";
820
779
  var packageRoot, DEFAULT_CATALOG_SOURCE, resolveCatalog, isInstalledPackage, isBundledCatalog, isCatalogWritable;
821
780
  var init_resolve = __esm({
822
781
  "src/catalog/resolve.ts"() {
823
782
  "use strict";
824
- packageRoot = resolve7(dirname3(fileURLToPath(import.meta.url)), "..");
783
+ packageRoot = resolve6(dirname3(fileURLToPath(import.meta.url)), "..");
825
784
  DEFAULT_CATALOG_SOURCE = "local:template/.agents";
826
785
  resolveCatalog = async (source = DEFAULT_CATALOG_SOURCE, options = {}) => {
827
786
  const [scheme, ...rest] = source.split(":");
828
787
  const spec = rest.join(":");
829
788
  if (scheme === "local") {
830
- const root = resolve7(packageRoot, spec);
831
- if (!existsSync6(root)) {
789
+ const root = resolve6(packageRoot, spec);
790
+ if (!existsSync5(root)) {
832
791
  throw new Error(`Catalog not found at ${root} (source: ${source}).`);
833
792
  }
834
793
  return { source, root };
@@ -873,7 +832,9 @@ var init_schema = __esm({
873
832
  if (idx === -1) return null;
874
833
  const type = id.slice(0, idx);
875
834
  const name = id.slice(idx + 1);
876
- if (type !== "skill" && type !== "command" && type !== "mcp") return null;
835
+ if (type !== "skill" && type !== "command" && type !== "mcp" && type !== "plugin") {
836
+ return null;
837
+ }
877
838
  if (!name) return null;
878
839
  return { type, name };
879
840
  };
@@ -881,19 +842,19 @@ var init_schema = __esm({
881
842
  });
882
843
 
883
844
  // src/lockfile/io.ts
884
- import { existsSync as existsSync7, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
885
- import { resolve as resolve8 } from "path";
845
+ import { existsSync as existsSync6, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
846
+ import { resolve as resolve7 } from "path";
886
847
  var lockfilePath, lockfileExists, readLockfile, writeLockfile, emptyLockfile;
887
848
  var init_io = __esm({
888
849
  "src/lockfile/io.ts"() {
889
850
  "use strict";
890
851
  init_schema();
891
- lockfilePath = (targetRoot) => resolve8(targetRoot, LOCKFILE_NAME);
892
- lockfileExists = (targetRoot) => existsSync7(lockfilePath(targetRoot));
852
+ lockfilePath = (targetRoot) => resolve7(targetRoot, LOCKFILE_NAME);
853
+ lockfileExists = (targetRoot) => existsSync6(lockfilePath(targetRoot));
893
854
  readLockfile = (targetRoot) => {
894
855
  const path = lockfilePath(targetRoot);
895
- if (!existsSync7(path)) return null;
896
- const parsed = JSON.parse(readFileSync5(path, "utf8"));
856
+ if (!existsSync6(path)) return null;
857
+ const parsed = JSON.parse(readFileSync4(path, "utf8"));
897
858
  if (parsed.version !== LOCKFILE_VERSION) {
898
859
  throw new Error(
899
860
  `Unsupported ${LOCKFILE_NAME} version ${parsed.version}; expected ${LOCKFILE_VERSION}.`
@@ -912,7 +873,7 @@ var init_io = __esm({
912
873
  ...lock.providers !== void 0 ? { providers: lock.providers } : {},
913
874
  entries: sortedEntries
914
875
  };
915
- writeFileSync3(lockfilePath(targetRoot), JSON.stringify(ordered, null, 2) + "\n");
876
+ writeFileSync2(lockfilePath(targetRoot), JSON.stringify(ordered, null, 2) + "\n");
916
877
  };
917
878
  emptyLockfile = (catalogSource, remote = {}) => ({
918
879
  version: LOCKFILE_VERSION,
@@ -927,8 +888,64 @@ var init_io = __esm({
927
888
  }
928
889
  });
929
890
 
891
+ // src/providers/resolve.ts
892
+ var validateProviders, sameProviders, resolveProviders, selectProviders;
893
+ var init_resolve2 = __esm({
894
+ "src/providers/resolve.ts"() {
895
+ "use strict";
896
+ init_schema();
897
+ init_prompts();
898
+ validateProviders = (values) => {
899
+ const invalid = values.filter((v) => !isProvider(v));
900
+ if (invalid.length) return { invalid };
901
+ const seen = /* @__PURE__ */ new Set();
902
+ const providers2 = [];
903
+ for (const v of values) {
904
+ if (isProvider(v) && !seen.has(v)) {
905
+ seen.add(v);
906
+ providers2.push(v);
907
+ }
908
+ }
909
+ return { providers: providers2 };
910
+ };
911
+ sameProviders = (a, b) => a.length === b.length && (/* @__PURE__ */ new Set([...a, ...b])).size === a.length;
912
+ resolveProviders = async (options) => {
913
+ if (options.providers) {
914
+ const { providers: providers2, invalid } = validateProviders(options.providers);
915
+ if (invalid) {
916
+ await error(
917
+ `Unknown provider(s): ${invalid.join(", ")}. Valid: ${PROVIDERS.join(", ")}.`
918
+ );
919
+ process.exitCode = 1;
920
+ return null;
921
+ }
922
+ return providers2;
923
+ }
924
+ if (options.all || !process.stdin.isTTY) return [...PROVIDERS];
925
+ return selectProviders([...PROVIDERS]);
926
+ };
927
+ selectProviders = async (initialValues) => {
928
+ const picked = await selectGrouped({
929
+ message: "Generate configs for (space toggles, enter confirms)",
930
+ groups: [
931
+ {
932
+ name: "providers",
933
+ items: [
934
+ { value: "claude", label: "Claude Code" },
935
+ { value: "opencode", label: "opencode" },
936
+ { value: "codex", label: "Codex" }
937
+ ]
938
+ }
939
+ ],
940
+ initialValues
941
+ });
942
+ return picked.length ? picked : initialValues;
943
+ };
944
+ }
945
+ });
946
+
930
947
  // src/providers/claude.ts
931
- import { resolve as resolve9 } from "path";
948
+ import { resolve as resolve8 } from "path";
932
949
  var formatMcpJson, planClaude;
933
950
  var init_claude = __esm({
934
951
  "src/providers/claude.ts"() {
@@ -943,33 +960,33 @@ var init_claude = __esm({
943
960
  const removeFiles = [];
944
961
  if (claudeSettings) {
945
962
  files.push({
946
- path: resolve9(targetRoot, ".claude/settings.json"),
963
+ path: resolve8(targetRoot, ".claude/settings.json"),
947
964
  content: JSON.stringify(claudeSettings, null, 2) + "\n"
948
965
  });
949
966
  }
950
967
  const mcp = formatMcpJson(mcpServers);
951
- const mcpPath = resolve9(targetRoot, ".mcp.json");
968
+ const mcpPath = resolve8(targetRoot, ".mcp.json");
952
969
  if (mcp) files.push({ path: mcpPath, content: mcp });
953
970
  else removeFiles.push(mcpPath);
954
971
  const skillNames = selected.skills.map((s) => s.name);
955
972
  const commandFiles = selected.commands.map((c) => `${c.name}.md`);
956
973
  const symlinks = [
957
974
  ...selected.skills.map((s) => ({
958
- path: resolve9(targetRoot, ".claude/skills", s.name),
975
+ path: resolve8(targetRoot, ".claude/skills", s.name),
959
976
  target: s.absDir
960
977
  })),
961
978
  ...selected.commands.map((c) => ({
962
- path: resolve9(targetRoot, ".claude/commands", `${c.name}.md`),
979
+ path: resolve8(targetRoot, ".claude/commands", `${c.name}.md`),
963
980
  target: c.absPath
964
981
  }))
965
982
  ];
966
983
  const managedDirs = [
967
984
  {
968
- path: resolve9(targetRoot, ".claude/skills"),
985
+ path: resolve8(targetRoot, ".claude/skills"),
969
986
  expected: new Set(skillNames)
970
987
  },
971
988
  {
972
- path: resolve9(targetRoot, ".claude/commands"),
989
+ path: resolve8(targetRoot, ".claude/commands"),
973
990
  expected: new Set(commandFiles)
974
991
  }
975
992
  ];
@@ -979,7 +996,7 @@ var init_claude = __esm({
979
996
  });
980
997
 
981
998
  // src/providers/codex.ts
982
- import { resolve as resolve10 } from "path";
999
+ import { resolve as resolve9 } from "path";
983
1000
  import TOML from "@iarna/toml";
984
1001
  var buildConfig, planCodex;
985
1002
  var init_codex = __esm({
@@ -1023,7 +1040,7 @@ var init_codex = __esm({
1023
1040
  const files = [];
1024
1041
  const removeFiles = [];
1025
1042
  const config = buildConfig(rawMcpServers);
1026
- const path = resolve10(targetRoot, ".codex/config.toml");
1043
+ const path = resolve9(targetRoot, ".codex/config.toml");
1027
1044
  if (config) {
1028
1045
  files.push({
1029
1046
  path,
@@ -1039,19 +1056,19 @@ var init_codex = __esm({
1039
1056
 
1040
1057
  // src/providers/fsops.ts
1041
1058
  import {
1042
- existsSync as existsSync8,
1059
+ existsSync as existsSync7,
1043
1060
  lstatSync,
1044
- mkdirSync as mkdirSync5,
1061
+ mkdirSync as mkdirSync4,
1045
1062
  readdirSync as readdirSync3,
1046
- readFileSync as readFileSync6,
1063
+ readFileSync as readFileSync5,
1047
1064
  readlinkSync,
1048
1065
  rmdirSync,
1049
- rmSync as rmSync4,
1066
+ rmSync as rmSync3,
1050
1067
  symlinkSync,
1051
1068
  unlinkSync,
1052
- writeFileSync as writeFileSync4
1069
+ writeFileSync as writeFileSync3
1053
1070
  } from "fs";
1054
- import { dirname as dirname4, relative as relative4, resolve as resolve11 } from "path";
1071
+ import { dirname as dirname4, relative as relative4, resolve as resolve10 } from "path";
1055
1072
  var isENOENT, isMatchingSymlink, removePath, applyOutputs, checkOutputs;
1056
1073
  var init_fsops = __esm({
1057
1074
  "src/providers/fsops.ts"() {
@@ -1060,7 +1077,7 @@ var init_fsops = __esm({
1060
1077
  isMatchingSymlink = (path, target) => {
1061
1078
  const stats = lstatSync(path);
1062
1079
  if (!stats.isSymbolicLink()) return false;
1063
- return resolve11(dirname4(path), readlinkSync(path)) === target;
1080
+ return resolve10(dirname4(path), readlinkSync(path)) === target;
1064
1081
  };
1065
1082
  removePath = (path) => {
1066
1083
  let stats;
@@ -1071,7 +1088,7 @@ var init_fsops = __esm({
1071
1088
  throw e;
1072
1089
  }
1073
1090
  if (stats.isSymbolicLink()) unlinkSync(path);
1074
- else rmSync4(path, { force: true, recursive: true });
1091
+ else rmSync3(path, { force: true, recursive: true });
1075
1092
  };
1076
1093
  applyOutputs = ({
1077
1094
  files,
@@ -1084,22 +1101,22 @@ var init_fsops = __esm({
1084
1101
  for (const out of files) {
1085
1102
  let current = null;
1086
1103
  try {
1087
- current = readFileSync6(out.path, "utf8");
1104
+ current = readFileSync5(out.path, "utf8");
1088
1105
  } catch (e) {
1089
1106
  if (!isENOENT(e)) throw e;
1090
1107
  }
1091
1108
  if (current === out.content) continue;
1092
- mkdirSync5(dirname4(out.path), { recursive: true });
1093
- writeFileSync4(out.path, out.content);
1109
+ mkdirSync4(dirname4(out.path), { recursive: true });
1110
+ writeFileSync3(out.path, out.content);
1094
1111
  result.generated.push(out.path);
1095
1112
  }
1096
1113
  for (const path of removeFiles) {
1097
- if (!existsSync8(path)) continue;
1098
- rmSync4(path, { force: true });
1114
+ if (!existsSync7(path)) continue;
1115
+ rmSync3(path, { force: true });
1099
1116
  result.removed.push(path);
1100
1117
  }
1101
1118
  for (const path of removeDirs) {
1102
- if (!existsSync8(path)) continue;
1119
+ if (!existsSync7(path)) continue;
1103
1120
  removePath(path);
1104
1121
  result.removed.push(path);
1105
1122
  }
@@ -1110,7 +1127,7 @@ var init_fsops = __esm({
1110
1127
  }
1111
1128
  }
1112
1129
  for (const link of symlinks) {
1113
- mkdirSync5(dirname4(link.path), { recursive: true });
1130
+ mkdirSync4(dirname4(link.path), { recursive: true });
1114
1131
  try {
1115
1132
  if (isMatchingSymlink(link.path, link.target)) continue;
1116
1133
  } catch (e) {
@@ -1125,10 +1142,10 @@ var init_fsops = __esm({
1125
1142
  result.linked.push(link.path);
1126
1143
  }
1127
1144
  for (const dir of managedDirs) {
1128
- if (!existsSync8(dir.path)) continue;
1145
+ if (!existsSync7(dir.path)) continue;
1129
1146
  for (const child of readdirSync3(dir.path)) {
1130
1147
  if (dir.expected.has(child)) continue;
1131
- const childPath = resolve11(dir.path, child);
1148
+ const childPath = resolve10(dir.path, child);
1132
1149
  removePath(childPath);
1133
1150
  result.removed.push(childPath);
1134
1151
  }
@@ -1145,7 +1162,7 @@ var init_fsops = __esm({
1145
1162
  const problems = [];
1146
1163
  for (const out of files) {
1147
1164
  try {
1148
- if (readFileSync6(out.path, "utf8") !== out.content) {
1165
+ if (readFileSync5(out.path, "utf8") !== out.content) {
1149
1166
  problems.push(`out of sync: ${out.path}`);
1150
1167
  }
1151
1168
  } catch (e) {
@@ -1154,10 +1171,10 @@ var init_fsops = __esm({
1154
1171
  }
1155
1172
  }
1156
1173
  for (const path of removeFiles) {
1157
- if (existsSync8(path)) problems.push(`stale (should be removed): ${path}`);
1174
+ if (existsSync7(path)) problems.push(`stale (should be removed): ${path}`);
1158
1175
  }
1159
1176
  for (const path of removeDirs) {
1160
- if (existsSync8(path)) problems.push(`stale (should be removed): ${path}`);
1177
+ if (existsSync7(path)) problems.push(`stale (should be removed): ${path}`);
1161
1178
  }
1162
1179
  for (const link of symlinks) {
1163
1180
  try {
@@ -1170,10 +1187,10 @@ var init_fsops = __esm({
1170
1187
  }
1171
1188
  }
1172
1189
  for (const dir of managedDirs) {
1173
- if (!existsSync8(dir.path)) continue;
1190
+ if (!existsSync7(dir.path)) continue;
1174
1191
  for (const child of readdirSync3(dir.path)) {
1175
1192
  if (!dir.expected.has(child)) {
1176
- problems.push(`unexpected shim: ${resolve11(dir.path, child)}`);
1193
+ problems.push(`unexpected shim: ${resolve10(dir.path, child)}`);
1177
1194
  }
1178
1195
  }
1179
1196
  }
@@ -1183,61 +1200,111 @@ var init_fsops = __esm({
1183
1200
  });
1184
1201
 
1185
1202
  // src/providers/opencode.ts
1186
- import { resolve as resolve12 } from "path";
1187
- var formatOpenCodeJson, planOpenCode;
1203
+ import { existsSync as existsSync8 } from "fs";
1204
+ import { resolve as resolve11 } from "path";
1205
+ var formatOpenCodeJson, preserveEnvReference, preserveEnvReferences, planOpenCode;
1188
1206
  var init_opencode = __esm({
1189
1207
  "src/providers/opencode.ts"() {
1190
1208
  "use strict";
1191
- formatOpenCodeJson = (servers) => {
1192
- if (Object.keys(servers).length === 0) return null;
1209
+ formatOpenCodeJson = (servers, overlay) => {
1210
+ if (Object.keys(servers).length === 0 && !overlay) return null;
1193
1211
  const mcp = {};
1194
1212
  for (const [name, server] of Object.entries(servers)) {
1195
1213
  if (server.transport === "http") {
1196
1214
  mcp[name] = {
1197
1215
  type: "remote",
1198
- url: server.url,
1199
- ...server.headers ? { headers: server.headers } : {}
1216
+ url: preserveEnvReference(server.url),
1217
+ ...server.headers ? { headers: preserveEnvReferences(server.headers) } : {}
1200
1218
  };
1201
1219
  } else {
1202
1220
  mcp[name] = {
1203
1221
  type: "local",
1204
- command: [server.command, ...server.args ?? []],
1205
- ...server.env ? { environment: server.env } : {}
1222
+ command: [server.command, ...server.args ?? []].map(
1223
+ preserveEnvReference
1224
+ ),
1225
+ ...server.env ? { environment: preserveEnvReferences(server.env) } : {}
1206
1226
  };
1207
1227
  }
1208
1228
  }
1209
- return JSON.stringify(
1210
- { $schema: "https://opencode.ai/config.json", mcp },
1211
- null,
1212
- 2
1213
- ) + "\n";
1214
- };
1229
+ const overlayMcp = overlay?.["mcp"] && typeof overlay["mcp"] === "object" ? overlay["mcp"] : {};
1230
+ const mergedMcp = { ...overlayMcp, ...mcp };
1231
+ const config = {
1232
+ ...overlay,
1233
+ $schema: "https://opencode.ai/config.json",
1234
+ ...Object.keys(mergedMcp).length ? { mcp: mergedMcp } : {}
1235
+ };
1236
+ return JSON.stringify(config, null, 2) + "\n";
1237
+ };
1238
+ preserveEnvReference = (value) => value.replace(/\$\{([^}]+)\}/g, "{env:$1}");
1239
+ preserveEnvReferences = (values) => Object.fromEntries(
1240
+ Object.entries(values).map(([key, value]) => [
1241
+ key,
1242
+ preserveEnvReference(value)
1243
+ ])
1244
+ );
1215
1245
  planOpenCode = (inputs) => {
1216
- const { targetRoot, selected, mcpServers } = inputs;
1246
+ const {
1247
+ targetRoot,
1248
+ selected,
1249
+ rawMcpServers,
1250
+ opencodeConfig,
1251
+ tuiConfig
1252
+ } = inputs;
1217
1253
  const files = [];
1218
1254
  const removeFiles = [];
1219
- const json = formatOpenCodeJson(mcpServers);
1220
- const jsonPath = resolve12(targetRoot, "opencode.json");
1255
+ const json = formatOpenCodeJson(rawMcpServers, opencodeConfig);
1256
+ const jsonPath = resolve11(targetRoot, "opencode.json");
1221
1257
  if (json) files.push({ path: jsonPath, content: json });
1222
1258
  else removeFiles.push(jsonPath);
1259
+ const tuiPath = resolve11(targetRoot, ".opencode/tui.json");
1260
+ const tuiMarkerPath = resolve11(targetRoot, ".opencode/.quiver-tui");
1261
+ if (tuiConfig) {
1262
+ files.push({
1263
+ path: tuiPath,
1264
+ content: JSON.stringify(
1265
+ { ...tuiConfig, $schema: "https://opencode.ai/tui.json" },
1266
+ null,
1267
+ 2
1268
+ ) + "\n"
1269
+ });
1270
+ files.push({ path: tuiMarkerPath, content: "managed by quiver\n" });
1271
+ } else if (existsSync8(tuiMarkerPath)) {
1272
+ removeFiles.push(tuiPath, tuiMarkerPath);
1273
+ }
1223
1274
  const symlinks = [
1224
1275
  ...selected.skills.map((s) => ({
1225
- path: resolve12(targetRoot, ".opencode/skills", s.name),
1276
+ path: resolve11(targetRoot, ".opencode/skills", s.name),
1226
1277
  target: s.absDir
1227
1278
  })),
1228
1279
  ...selected.commands.map((c) => ({
1229
- path: resolve12(targetRoot, ".opencode/commands", `${c.name}.md`),
1280
+ path: resolve11(targetRoot, ".opencode/commands", `${c.name}.md`),
1230
1281
  target: c.absPath
1282
+ })),
1283
+ ...selected.plugins.map((plugin) => ({
1284
+ path: resolve11(
1285
+ targetRoot,
1286
+ ".opencode/plugins",
1287
+ `${plugin.name}${plugin.absPath.endsWith(".js") ? ".js" : ".ts"}`
1288
+ ),
1289
+ target: plugin.absPath
1231
1290
  }))
1232
1291
  ];
1233
1292
  const managedDirs = [
1234
1293
  {
1235
- path: resolve12(targetRoot, ".opencode/skills"),
1294
+ path: resolve11(targetRoot, ".opencode/skills"),
1236
1295
  expected: new Set(selected.skills.map((s) => s.name))
1237
1296
  },
1238
1297
  {
1239
- path: resolve12(targetRoot, ".opencode/commands"),
1298
+ path: resolve11(targetRoot, ".opencode/commands"),
1240
1299
  expected: new Set(selected.commands.map((c) => `${c.name}.md`))
1300
+ },
1301
+ {
1302
+ path: resolve11(targetRoot, ".opencode/plugins"),
1303
+ expected: new Set(
1304
+ selected.plugins.map(
1305
+ (plugin) => `${plugin.name}${plugin.absPath.endsWith(".js") ? ".js" : ".ts"}`
1306
+ )
1307
+ )
1241
1308
  }
1242
1309
  ];
1243
1310
  return { files, removeFiles, symlinks, managedDirs };
@@ -1255,17 +1322,20 @@ var init_selection = __esm({
1255
1322
  const skillNames = /* @__PURE__ */ new Set();
1256
1323
  const commandNames = /* @__PURE__ */ new Set();
1257
1324
  const mcpNames = /* @__PURE__ */ new Set();
1325
+ const pluginNames = /* @__PURE__ */ new Set();
1258
1326
  for (const id of Object.keys(lock.entries)) {
1259
1327
  const parsed = parseEntryId(id);
1260
1328
  if (!parsed) continue;
1261
1329
  if (parsed.type === "skill") skillNames.add(parsed.name);
1262
1330
  else if (parsed.type === "command") commandNames.add(parsed.name);
1263
1331
  else if (parsed.type === "mcp") mcpNames.add(parsed.name);
1332
+ else if (parsed.type === "plugin") pluginNames.add(parsed.name);
1264
1333
  }
1265
1334
  return {
1266
1335
  skills: catalog.skills.filter((s) => skillNames.has(s.name)),
1267
1336
  commands: catalog.commands.filter((c) => commandNames.has(c.name)),
1268
- mcp: catalog.mcp.filter((m) => mcpNames.has(m.name))
1337
+ mcp: catalog.mcp.filter((m) => mcpNames.has(m.name)),
1338
+ plugins: catalog.plugins.filter((p) => pluginNames.has(p.name))
1269
1339
  };
1270
1340
  };
1271
1341
  }
@@ -1273,8 +1343,8 @@ var init_selection = __esm({
1273
1343
 
1274
1344
  // src/providers/write.ts
1275
1345
  import { existsSync as existsSync9, lstatSync as lstatSync2 } from "fs";
1276
- import { basename, resolve as resolve13 } from "path";
1277
- var isLinkable, buildPlan, writeProviders, checkProviders;
1346
+ import { basename, relative as relative5, resolve as resolve12 } from "path";
1347
+ var isLinkable, buildPlan, writeProviders, formatWriteResult, checkProviders;
1278
1348
  var init_write = __esm({
1279
1349
  "src/providers/write.ts"() {
1280
1350
  "use strict";
@@ -1298,14 +1368,16 @@ var init_write = __esm({
1298
1368
  const rawMcpServers = {};
1299
1369
  for (const mcp of selected.mcp) rawMcpServers[mcp.name] = mcp.server;
1300
1370
  const mcpServers = interpolateEnvVars(rawMcpServers, onMissingEnv);
1301
- const agentsRoot = resolve13(targetRoot, ".agents");
1371
+ const agentsRoot = resolve12(targetRoot, ".agents");
1302
1372
  const inputs = {
1303
1373
  targetRoot,
1304
1374
  agentsRoot,
1305
1375
  selected,
1306
1376
  mcpServers,
1307
1377
  rawMcpServers,
1308
- claudeSettings: catalog.config.claude?.settings ?? null
1378
+ claudeSettings: catalog.config.claude?.settings ?? null,
1379
+ opencodeConfig: catalog.config.opencode ?? null,
1380
+ tuiConfig: catalog.config.tui ?? null
1309
1381
  };
1310
1382
  const active = lock.providers?.length ? lock.providers : [...PROVIDERS];
1311
1383
  const planByProvider = {
@@ -1329,12 +1401,12 @@ var init_write = __esm({
1329
1401
  }
1330
1402
  }
1331
1403
  const rootSymlinks = [];
1332
- const agentsMd = resolve13(agentsRoot, "AGENTS.md");
1404
+ const agentsMd = resolve12(agentsRoot, "AGENTS.md");
1333
1405
  if (existsSync9(agentsMd)) {
1334
- const rootAgents = resolve13(targetRoot, "AGENTS.md");
1406
+ const rootAgents = resolve12(targetRoot, "AGENTS.md");
1335
1407
  for (const link of [
1336
1408
  { path: rootAgents, target: agentsMd },
1337
- { path: resolve13(targetRoot, "CLAUDE.md"), target: rootAgents }
1409
+ { path: resolve12(targetRoot, "CLAUDE.md"), target: rootAgents }
1338
1410
  ]) {
1339
1411
  if (isLinkable(link.path)) rootSymlinks.push(link);
1340
1412
  else onSkippedRootFile?.(basename(link.path));
@@ -1371,6 +1443,21 @@ var init_write = __esm({
1371
1443
  }
1372
1444
  return result;
1373
1445
  };
1446
+ formatWriteResult = (targetRoot, result) => {
1447
+ const rel = (p) => relative5(targetRoot, p) || p;
1448
+ const lines = [];
1449
+ const groups = [
1450
+ ["generated", result.generated],
1451
+ ["linked", result.linked],
1452
+ ["removed", result.removed]
1453
+ ];
1454
+ for (const [label, paths] of groups) {
1455
+ if (!paths.length) continue;
1456
+ lines.push(` ${label}:`);
1457
+ for (const p of [...paths].map(rel).sort()) lines.push(` - ${p}`);
1458
+ }
1459
+ return lines;
1460
+ };
1374
1461
  checkProviders = (targetRoot, catalog, lock) => {
1375
1462
  const plan = buildPlan(targetRoot, catalog, lock, () => {
1376
1463
  });
@@ -1381,8 +1468,8 @@ var init_write = __esm({
1381
1468
 
1382
1469
  // src/commands/gitignore.ts
1383
1470
  import { execFileSync as execFileSync2 } from "child_process";
1384
- import { existsSync as existsSync10, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
1385
- import { resolve as resolve14 } from "path";
1471
+ import { existsSync as existsSync10, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
1472
+ import { resolve as resolve13 } from "path";
1386
1473
  var HEADER, SECRETS_COMMENT, PROVIDER_ENTRIES, SHARED_ENTRIES, SECRET_ENTRIES, patchGitignore, ignoredSourcePaths;
1387
1474
  var init_gitignore = __esm({
1388
1475
  "src/commands/gitignore.ts"() {
@@ -1397,11 +1484,11 @@ var init_gitignore = __esm({
1397
1484
  };
1398
1485
  SHARED_ENTRIES = ["AGENTS.md", "CLAUDE.md"];
1399
1486
  SECRET_ENTRIES = [".env.local"];
1400
- patchGitignore = (targetRoot, providers) => {
1401
- const path = resolve14(targetRoot, ".gitignore");
1402
- const current = existsSync10(path) ? readFileSync7(path, "utf8") : "";
1487
+ patchGitignore = (targetRoot, providers2) => {
1488
+ const path = resolve13(targetRoot, ".gitignore");
1489
+ const current = existsSync10(path) ? readFileSync6(path, "utf8") : "";
1403
1490
  const lines = new Set(current.split("\n").map((l) => l.trim()));
1404
- const active = providers?.length ? providers : [...PROVIDERS];
1491
+ const active = providers2?.length ? providers2 : [...PROVIDERS];
1405
1492
  const shimEntries = [
1406
1493
  ...active.flatMap((p) => PROVIDER_ENTRIES[p]),
1407
1494
  ...SHARED_ENTRIES
@@ -1413,7 +1500,7 @@ var init_gitignore = __esm({
1413
1500
  if (missingShims.length) block2.push(HEADER, ...missingShims);
1414
1501
  if (missingSecrets.length) block2.push(SECRETS_COMMENT, ...missingSecrets);
1415
1502
  const prefix = current && !current.endsWith("\n") ? "\n" : "";
1416
- writeFileSync5(
1503
+ writeFileSync4(
1417
1504
  path,
1418
1505
  current + prefix + (current ? "\n" : "") + block2.join("\n") + "\n"
1419
1506
  );
@@ -1458,12 +1545,24 @@ var init_select = __esm({
1458
1545
  const s = entry.server;
1459
1546
  return s.transport === "http" ? s.url : [s.command, ...s.args ?? []].filter(Boolean).join(" ");
1460
1547
  };
1461
- selectFromCatalog = async (catalog, { interactive }) => {
1548
+ selectFromCatalog = async (catalog, {
1549
+ interactive,
1550
+ providers: providers2
1551
+ }) => {
1462
1552
  const allSkills = catalog.skills.map((s) => s.name);
1463
1553
  const allCommands = catalog.commands.map((c) => c.name);
1464
1554
  const allMcp = catalog.mcp.map((m) => m.name);
1555
+ const availablePlugins = catalog.plugins.filter(
1556
+ (plugin) => providers2.includes(plugin.provider)
1557
+ );
1558
+ const allPlugins = availablePlugins.map((p) => p.name);
1465
1559
  if (!interactive || !process.stdin.isTTY) {
1466
- return { skills: allSkills, commands: allCommands, mcp: allMcp };
1560
+ return {
1561
+ skills: allSkills,
1562
+ commands: allCommands,
1563
+ mcp: allMcp,
1564
+ plugins: allPlugins
1565
+ };
1467
1566
  }
1468
1567
  const groupNames = [...new Set(catalog.skills.map((s) => s.group))].sort(
1469
1568
  (a, b) => a === "general" ? -1 : b === "general" ? 1 : a.localeCompare(b)
@@ -1518,7 +1617,21 @@ var init_select = __esm({
1518
1617
  groups: mcpGroups,
1519
1618
  initialValues: []
1520
1619
  }) : [];
1521
- return { skills, commands, mcp };
1620
+ const plugins = availablePlugins.length ? await selectGrouped({
1621
+ message: "Select plugins (space toggles, a all, enter confirms)",
1622
+ groups: [
1623
+ {
1624
+ name: "opencode",
1625
+ items: availablePlugins.map((plugin) => ({
1626
+ value: plugin.name,
1627
+ label: plugin.name,
1628
+ hint: plugin.requires.length ? `requires: ${plugin.requires.join(", ")}` : void 0
1629
+ }))
1630
+ }
1631
+ ],
1632
+ initialValues: []
1633
+ }) : [];
1634
+ return { skills, commands, mcp, plugins };
1522
1635
  };
1523
1636
  }
1524
1637
  });
@@ -1528,7 +1641,7 @@ var init_exports = {};
1528
1641
  __export(init_exports, {
1529
1642
  init: () => init
1530
1643
  });
1531
- var init, resolveProviders;
1644
+ var init;
1532
1645
  var init_init = __esm({
1533
1646
  "src/commands/init.ts"() {
1534
1647
  "use strict";
@@ -1538,6 +1651,7 @@ var init_init = __esm({
1538
1651
  init_resolve();
1539
1652
  init_io();
1540
1653
  init_schema();
1654
+ init_resolve2();
1541
1655
  init_write();
1542
1656
  init_interpolate();
1543
1657
  init_prompts();
@@ -1559,11 +1673,12 @@ var init_init = __esm({
1559
1673
  );
1560
1674
  }
1561
1675
  const sourceCatalog = loadCatalog(source);
1676
+ const providers2 = await resolveProviders(options);
1677
+ if (providers2 === null) return;
1562
1678
  const selection = await selectFromCatalog(sourceCatalog, {
1563
- interactive: !options.all
1679
+ interactive: !options.all,
1680
+ providers: providers2
1564
1681
  });
1565
- const providers = await resolveProviders(options);
1566
- if (providers === null) return;
1567
1682
  const resolved = materializeCatalog(
1568
1683
  options.targetRoot,
1569
1684
  source,
@@ -1575,7 +1690,7 @@ var init_init = __esm({
1575
1690
  ref: source.ref ?? null,
1576
1691
  resolved: source.resolved ?? null
1577
1692
  });
1578
- lock.providers = providers;
1693
+ lock.providers = providers2;
1579
1694
  for (const skill of catalog.skills) {
1580
1695
  if (selection.skills.includes(skill.name)) {
1581
1696
  lock.entries[entryId("skill", skill.name)] = skillToEntry(skill);
@@ -1591,9 +1706,14 @@ var init_init = __esm({
1591
1706
  lock.entries[entryId("mcp", mcp.name)] = mcpToEntry(mcp);
1592
1707
  }
1593
1708
  }
1709
+ for (const plugin of catalog.plugins) {
1710
+ if (selection.plugins.includes(plugin.name)) {
1711
+ lock.entries[entryId("plugin", plugin.name)] = pluginToEntry(plugin);
1712
+ }
1713
+ }
1594
1714
  writeLockfile(options.targetRoot, lock);
1595
1715
  await step(
1596
- `Wrote quiver.lock (${selection.skills.length} skills, ${selection.commands.length} commands, ${selection.mcp.length} MCP servers)`
1716
+ `Wrote quiver.lock (${selection.skills.length} skills, ${selection.commands.length} commands, ${selection.mcp.length} MCP servers, ${selection.plugins.length} plugins)`
1597
1717
  );
1598
1718
  const result = writeProviders(options.targetRoot, catalog, lock);
1599
1719
  if (result.generated.length || result.linked.length) {
@@ -1601,7 +1721,7 @@ var init_init = __esm({
1601
1721
  `Provider configs: ${result.generated.length} generated, ${result.linked.length} linked`
1602
1722
  );
1603
1723
  }
1604
- if (patchGitignore(options.targetRoot, providers)) {
1724
+ if (patchGitignore(options.targetRoot, providers2)) {
1605
1725
  await step("Updated .gitignore (generated shims + .env.local ignored)");
1606
1726
  }
1607
1727
  const ignored = ignoredSourcePaths(options.targetRoot);
@@ -1623,47 +1743,18 @@ var init_init = __esm({
1623
1743
  "Done. Commit .agents/ and quiver.lock; restart your AI tool to load the config."
1624
1744
  );
1625
1745
  };
1626
- resolveProviders = async (options) => {
1627
- if (options.providers) {
1628
- const invalid = options.providers.filter((p) => !isProvider(p));
1629
- if (invalid.length) {
1630
- await error(
1631
- `Unknown provider(s): ${invalid.join(", ")}. Valid: ${PROVIDERS.join(", ")}.`
1632
- );
1633
- process.exitCode = 1;
1634
- return null;
1635
- }
1636
- return options.providers.filter(isProvider);
1637
- }
1638
- if (options.all || !process.stdin.isTTY) return [...PROVIDERS];
1639
- const picked = await selectGrouped({
1640
- message: "Generate configs for (space toggles, enter confirms)",
1641
- groups: [
1642
- {
1643
- name: "providers",
1644
- items: [
1645
- { value: "claude", label: "Claude Code" },
1646
- { value: "opencode", label: "opencode" },
1647
- { value: "codex", label: "Codex" }
1648
- ]
1649
- }
1650
- ],
1651
- initialValues: [...PROVIDERS]
1652
- });
1653
- return picked.length ? picked : [...PROVIDERS];
1654
- };
1655
1746
  }
1656
1747
  });
1657
1748
 
1658
1749
  // src/catalog/repo.ts
1659
1750
  import { existsSync as existsSync11 } from "fs";
1660
- import { resolve as resolve15 } from "path";
1751
+ import { resolve as resolve14 } from "path";
1661
1752
  var repoCatalogRoot, repoCatalogExists, loadRepoCatalog;
1662
1753
  var init_repo = __esm({
1663
1754
  "src/catalog/repo.ts"() {
1664
1755
  "use strict";
1665
1756
  init_discover();
1666
- repoCatalogRoot = (targetRoot) => resolve15(targetRoot, ".agents");
1757
+ repoCatalogRoot = (targetRoot) => resolve14(targetRoot, ".agents");
1667
1758
  repoCatalogExists = (targetRoot) => existsSync11(repoCatalogRoot(targetRoot));
1668
1759
  loadRepoCatalog = (targetRoot, source) => {
1669
1760
  const resolved = {
@@ -1696,13 +1787,17 @@ var init_add = __esm({
1696
1787
  add = async (options) => {
1697
1788
  const id = options.positionals[0];
1698
1789
  if (!id) {
1699
- await error("Usage: quiver-cli add <skill:name|command:name|mcp:name>");
1790
+ await error(
1791
+ "Usage: quiver-cli add <skill:name|command:name|mcp:name|plugin:name>"
1792
+ );
1700
1793
  process.exitCode = 1;
1701
1794
  return;
1702
1795
  }
1703
1796
  const parsed = parseEntryId(id);
1704
1797
  if (!parsed) {
1705
- await error(`Invalid id "${id}". Expected skill:<name>, command:<name> or mcp:<name>.`);
1798
+ await error(
1799
+ `Invalid id "${id}". Expected skill:<name>, command:<name>, mcp:<name> or plugin:<name>.`
1800
+ );
1706
1801
  process.exitCode = 1;
1707
1802
  return;
1708
1803
  }
@@ -1723,16 +1818,25 @@ var init_add = __esm({
1723
1818
  const skill = sourceCatalog.skills.find((s) => s.name === parsed.name);
1724
1819
  const command = sourceCatalog.commands.find((c) => c.name === parsed.name);
1725
1820
  const mcp = sourceCatalog.mcp.find((m) => m.name === parsed.name);
1726
- const entry = parsed.type === "skill" && skill ? skillToEntry(skill) : parsed.type === "command" && command ? commandToEntry(command) : parsed.type === "mcp" && mcp ? mcpToEntry(mcp) : null;
1821
+ const plugin = sourceCatalog.plugins.find((p) => p.name === parsed.name);
1822
+ const entry = parsed.type === "skill" && skill ? skillToEntry(skill) : parsed.type === "command" && command ? commandToEntry(command) : parsed.type === "mcp" && mcp ? mcpToEntry(mcp) : parsed.type === "plugin" && plugin ? pluginToEntry(plugin) : null;
1727
1823
  if (!entry) {
1728
1824
  await error(`${id} not found in catalog.`);
1729
1825
  process.exitCode = 1;
1730
1826
  return;
1731
1827
  }
1828
+ if (entry.type === "plugin" && lock.providers?.length && !lock.providers.includes(entry.provider)) {
1829
+ await error(
1830
+ `${id} requires the ${entry.provider} provider. Enable it with \`quiver-cli providers ${entry.provider}\` first.`
1831
+ );
1832
+ process.exitCode = 1;
1833
+ return;
1834
+ }
1732
1835
  const selection = selectionFromLock(lock);
1733
1836
  if (parsed.type === "skill") selection.skills.push(parsed.name);
1734
1837
  if (parsed.type === "command") selection.commands.push(parsed.name);
1735
1838
  if (parsed.type === "mcp") selection.mcp.push(parsed.name);
1839
+ if (parsed.type === "plugin") selection.plugins.push(parsed.name);
1736
1840
  materializeCatalog(options.targetRoot, source, sourceCatalog, selection);
1737
1841
  lock.entries[id] = entry;
1738
1842
  writeLockfile(options.targetRoot, lock);
@@ -1741,13 +1845,19 @@ var init_add = __esm({
1741
1845
  await success(`Added ${id}.`);
1742
1846
  };
1743
1847
  selectionFromLock = (lock) => {
1744
- const selection = { skills: [], commands: [], mcp: [] };
1848
+ const selection = {
1849
+ skills: [],
1850
+ commands: [],
1851
+ mcp: [],
1852
+ plugins: []
1853
+ };
1745
1854
  for (const lid of Object.keys(lock.entries)) {
1746
1855
  const p = parseEntryId(lid);
1747
1856
  if (!p) continue;
1748
1857
  if (p.type === "skill") selection.skills.push(p.name);
1749
1858
  else if (p.type === "command") selection.commands.push(p.name);
1750
1859
  else if (p.type === "mcp") selection.mcp.push(p.name);
1860
+ else if (p.type === "plugin") selection.plugins.push(p.name);
1751
1861
  }
1752
1862
  return selection;
1753
1863
  };
@@ -1759,9 +1869,9 @@ var remove_exports = {};
1759
1869
  __export(remove_exports, {
1760
1870
  remove: () => remove
1761
1871
  });
1762
- import { existsSync as existsSync12, readdirSync as readdirSync4, readFileSync as readFileSync8, rmSync as rmSync5, writeFileSync as writeFileSync6 } from "fs";
1763
- import { resolve as resolve16 } from "path";
1764
- var remove, cleanupEmptyGroups, rewriteRepoMcp;
1872
+ import { existsSync as existsSync12, readdirSync as readdirSync4, readFileSync as readFileSync7, rmSync as rmSync4, writeFileSync as writeFileSync5 } from "fs";
1873
+ import { resolve as resolve15 } from "path";
1874
+ var remove, rewriteRepoPlugins, cleanupEmptyGroups, rewriteRepoMcp;
1765
1875
  var init_remove = __esm({
1766
1876
  "src/commands/remove.ts"() {
1767
1877
  "use strict";
@@ -1774,7 +1884,9 @@ var init_remove = __esm({
1774
1884
  remove = async (options) => {
1775
1885
  const id = options.positionals[0];
1776
1886
  if (!id) {
1777
- await error("Usage: quiver remove <skill:name|command:name|mcp:name>");
1887
+ await error(
1888
+ "Usage: quiver remove <skill:name|command:name|mcp:name|plugin:name>"
1889
+ );
1778
1890
  process.exitCode = 1;
1779
1891
  return;
1780
1892
  }
@@ -1795,31 +1907,46 @@ var init_remove = __esm({
1795
1907
  await info(`${id} is not installed.`);
1796
1908
  return;
1797
1909
  }
1798
- if (entry.type === "skill" || entry.type === "command") {
1910
+ if (entry.type === "skill" || entry.type === "command" || entry.type === "plugin") {
1799
1911
  removeArtifact(options.targetRoot, entry.sourcePath);
1800
1912
  cleanupEmptyGroups(options.targetRoot);
1801
1913
  }
1802
1914
  delete lock.entries[id];
1803
1915
  writeLockfile(options.targetRoot, lock);
1804
1916
  rewriteRepoMcp(options.targetRoot, lock);
1917
+ rewriteRepoPlugins(options.targetRoot, lock);
1805
1918
  const { catalog } = loadRepoCatalog(options.targetRoot, lock.catalog.source);
1806
1919
  writeProviders(options.targetRoot, catalog, lock);
1807
1920
  await success(`Removed ${id}.`);
1808
1921
  };
1922
+ rewriteRepoPlugins = (targetRoot, lock) => {
1923
+ const configPath = resolve15(targetRoot, ".agents/config.json");
1924
+ if (!existsSync12(configPath)) return;
1925
+ const config = JSON.parse(readFileSync7(configPath, "utf8"));
1926
+ if (!config.plugins) return;
1927
+ const keep = new Set(
1928
+ Object.keys(lock.entries).map(parseEntryId).filter((p) => p?.type === "plugin").map((p) => p.name)
1929
+ );
1930
+ config.plugins = Object.fromEntries(
1931
+ Object.entries(config.plugins).filter(([name]) => keep.has(name))
1932
+ );
1933
+ if (!Object.keys(config.plugins).length) delete config.plugins;
1934
+ writeFileSync5(configPath, JSON.stringify(config, null, 2) + "\n");
1935
+ };
1809
1936
  cleanupEmptyGroups = (targetRoot) => {
1810
- const skillsRoot = resolve16(targetRoot, ".agents/skills");
1937
+ const skillsRoot = resolve15(targetRoot, ".agents/skills");
1811
1938
  if (!existsSync12(skillsRoot)) return;
1812
1939
  for (const entry of readdirSync4(skillsRoot, { withFileTypes: true })) {
1813
1940
  if (!entry.isDirectory()) continue;
1814
- const dir = resolve16(skillsRoot, entry.name);
1815
- if (existsSync12(resolve16(dir, "SKILL.md"))) continue;
1816
- if (readdirSync4(dir).length === 0) rmSync5(dir, { recursive: true, force: true });
1941
+ const dir = resolve15(skillsRoot, entry.name);
1942
+ if (existsSync12(resolve15(dir, "SKILL.md"))) continue;
1943
+ if (readdirSync4(dir).length === 0) rmSync4(dir, { recursive: true, force: true });
1817
1944
  }
1818
1945
  };
1819
1946
  rewriteRepoMcp = (targetRoot, lock) => {
1820
- const configPath = resolve16(targetRoot, ".agents/config.json");
1947
+ const configPath = resolve15(targetRoot, ".agents/config.json");
1821
1948
  if (!existsSync12(configPath)) return;
1822
- const config = JSON.parse(readFileSync8(configPath, "utf8"));
1949
+ const config = JSON.parse(readFileSync7(configPath, "utf8"));
1823
1950
  if (!config.mcpServers) return;
1824
1951
  const keep = new Set(
1825
1952
  Object.keys(lock.entries).map(parseEntryId).filter((p) => p?.type === "mcp").map((p) => p.name)
@@ -1830,7 +1957,7 @@ var init_remove = __esm({
1830
1957
  }
1831
1958
  if (Object.keys(kept).length) config.mcpServers = kept;
1832
1959
  else delete config.mcpServers;
1833
- writeFileSync6(configPath, JSON.stringify(config, null, 2) + "\n");
1960
+ writeFileSync5(configPath, JSON.stringify(config, null, 2) + "\n");
1834
1961
  };
1835
1962
  }
1836
1963
  });
@@ -1845,6 +1972,7 @@ var init_locksync = __esm({
1845
1972
  const skillByName = new Map(catalog.skills.map((s) => [s.name, s]));
1846
1973
  const commandByName = new Map(catalog.commands.map((c) => [c.name, c]));
1847
1974
  const mcpByName = new Map(catalog.mcp.map((m) => [m.name, m]));
1975
+ const pluginByName = new Map(catalog.plugins.map((p) => [p.name, p]));
1848
1976
  for (const [id, entry] of Object.entries(lock.entries)) {
1849
1977
  if (entry.type === "skill") {
1850
1978
  const cat = skillByName.get(id.slice("skill:".length));
@@ -1869,6 +1997,15 @@ var init_locksync = __esm({
1869
1997
  entry.configDigest = cat.configDigest;
1870
1998
  entry.transport = cat.server.transport;
1871
1999
  }
2000
+ } else if (entry.type === "plugin") {
2001
+ const cat = pluginByName.get(id.slice("plugin:".length));
2002
+ if (!cat) continue;
2003
+ if (cat.digest !== entry.digest) {
2004
+ drift.push(`${id}: plugin content changed`);
2005
+ entry.digest = cat.digest;
2006
+ entry.sourcePath = cat.sourcePath;
2007
+ entry.requires = cat.requires;
2008
+ }
1872
2009
  }
1873
2010
  }
1874
2011
  return drift;
@@ -1888,6 +2025,7 @@ var init_sync = __esm({
1888
2025
  init_repo();
1889
2026
  init_io();
1890
2027
  init_schema();
2028
+ init_resolve2();
1891
2029
  init_write();
1892
2030
  init_prompts();
1893
2031
  init_gitignore();
@@ -1904,15 +2042,33 @@ var init_sync = __esm({
1904
2042
  process.exitCode = 1;
1905
2043
  return;
1906
2044
  }
2045
+ if (options.providers) {
2046
+ const { providers: valid, invalid } = validateProviders(options.providers);
2047
+ if (invalid) {
2048
+ await error(
2049
+ `Unknown provider(s): ${invalid.join(", ")}. Valid: ${PROVIDERS.join(", ")}.`
2050
+ );
2051
+ process.exitCode = 1;
2052
+ return;
2053
+ }
2054
+ if (!valid.length) {
2055
+ await error("At least one provider is required.");
2056
+ process.exitCode = 1;
2057
+ return;
2058
+ }
2059
+ lock.providers = valid;
2060
+ await step(`Providers set to: ${valid.join(", ")}`);
2061
+ }
1907
2062
  const { catalog } = loadRepoCatalog(options.targetRoot, lock.catalog.source);
1908
2063
  const haveSkills = new Set(catalog.skills.map((s) => s.name));
1909
2064
  const haveCommands = new Set(catalog.commands.map((c) => c.name));
1910
2065
  const haveMcp = new Set(catalog.mcp.map((m) => m.name));
2066
+ const havePlugins = new Set(catalog.plugins.map((p) => p.name));
1911
2067
  const orphans = [];
1912
2068
  for (const id of Object.keys(lock.entries)) {
1913
2069
  const p = parseEntryId(id);
1914
2070
  if (!p) continue;
1915
- const present = p.type === "skill" && haveSkills.has(p.name) || p.type === "command" && haveCommands.has(p.name) || p.type === "mcp" && haveMcp.has(p.name);
2071
+ const present = p.type === "skill" && haveSkills.has(p.name) || p.type === "command" && haveCommands.has(p.name) || p.type === "mcp" && haveMcp.has(p.name) || p.type === "plugin" && havePlugins.has(p.name);
1916
2072
  if (!present) orphans.push(id);
1917
2073
  }
1918
2074
  if (orphans.length) {
@@ -1936,6 +2092,79 @@ var init_sync = __esm({
1936
2092
  await success(
1937
2093
  `Synced: ${result.generated.length} generated, ${result.linked.length} linked, ${result.removed.length} removed`
1938
2094
  );
2095
+ const detail = formatWriteResult(options.targetRoot, result);
2096
+ if (detail.length) block(detail);
2097
+ };
2098
+ }
2099
+ });
2100
+
2101
+ // src/commands/providers.ts
2102
+ var providers_exports = {};
2103
+ __export(providers_exports, {
2104
+ providers: () => providers
2105
+ });
2106
+ var providers;
2107
+ var init_providers = __esm({
2108
+ "src/commands/providers.ts"() {
2109
+ "use strict";
2110
+ init_repo();
2111
+ init_io();
2112
+ init_schema();
2113
+ init_resolve2();
2114
+ init_write();
2115
+ init_prompts();
2116
+ providers = async (options) => {
2117
+ const lock = readLockfile(options.targetRoot);
2118
+ if (!lock) {
2119
+ await error("No quiver.lock found. Run `quiver-cli init` first.");
2120
+ process.exitCode = 1;
2121
+ return;
2122
+ }
2123
+ if (!repoCatalogExists(options.targetRoot)) {
2124
+ await error("No .agents/ directory found. Run `quiver-cli init` first.");
2125
+ process.exitCode = 1;
2126
+ return;
2127
+ }
2128
+ const current = lock.providers?.length ? lock.providers : [...PROVIDERS];
2129
+ const explicit = options.providers ?? (options.positionals[0] ? options.positionals[0].split(",").map((p) => p.trim()).filter(Boolean) : null);
2130
+ let next;
2131
+ if (explicit) {
2132
+ const { providers: valid, invalid } = validateProviders(explicit);
2133
+ if (invalid) {
2134
+ await error(
2135
+ `Unknown provider(s): ${invalid.join(", ")}. Valid: ${PROVIDERS.join(", ")}.`
2136
+ );
2137
+ process.exitCode = 1;
2138
+ return;
2139
+ }
2140
+ next = valid;
2141
+ } else if (process.stdin.isTTY && !options.all) {
2142
+ next = await selectProviders(current);
2143
+ } else {
2144
+ await error(
2145
+ `No providers given. Usage: quiver-cli providers <${PROVIDERS.join(",")}>`
2146
+ );
2147
+ process.exitCode = 1;
2148
+ return;
2149
+ }
2150
+ if (!next.length) {
2151
+ await error("At least one provider is required.");
2152
+ process.exitCode = 1;
2153
+ return;
2154
+ }
2155
+ if (sameProviders(next, current)) {
2156
+ await info(`Providers already set to: ${current.join(", ")}.`);
2157
+ return;
2158
+ }
2159
+ lock.providers = next;
2160
+ writeLockfile(options.targetRoot, lock);
2161
+ const { catalog } = loadRepoCatalog(options.targetRoot, lock.catalog.source);
2162
+ const result = writeProviders(options.targetRoot, catalog, lock);
2163
+ await success(
2164
+ `Providers set to ${next.join(", ")}: ${result.generated.length} generated, ${result.linked.length} linked, ${result.removed.length} removed`
2165
+ );
2166
+ const detail = formatWriteResult(options.targetRoot, result);
2167
+ if (detail.length) block(detail);
1939
2168
  };
1940
2169
  }
1941
2170
  });
@@ -1945,8 +2174,8 @@ var update_exports = {};
1945
2174
  __export(update_exports, {
1946
2175
  update: () => update
1947
2176
  });
1948
- import { cpSync as cpSync2, mkdirSync as mkdirSync6, readFileSync as readFileSync9, rmSync as rmSync6, writeFileSync as writeFileSync7 } from "fs";
1949
- import { dirname as dirname5, resolve as resolve17 } from "path";
2177
+ import { cpSync as cpSync2, mkdirSync as mkdirSync5, readFileSync as readFileSync8, rmSync as rmSync5, writeFileSync as writeFileSync6 } from "fs";
2178
+ import { dirname as dirname5, resolve as resolve16 } from "path";
1950
2179
  var update, applyUpdate, report;
1951
2180
  var init_update = __esm({
1952
2181
  "src/commands/update.ts"() {
@@ -2002,21 +2231,27 @@ var init_update = __esm({
2002
2231
  sourceCatalog,
2003
2232
  repoCatalog,
2004
2233
  lock,
2005
- options.force
2234
+ options.force,
2235
+ options.dryRun
2006
2236
  );
2007
2237
  if (report4.status === "updated") changed = true;
2008
2238
  reports.push(report4);
2009
2239
  }
2010
- if (changed || catalogMoved) {
2011
- writeLockfile(options.targetRoot, lock);
2012
- }
2013
- if (changed) {
2014
- const { catalog } = loadRepoCatalog(options.targetRoot, lock.catalog.source);
2015
- writeProviders(options.targetRoot, catalog, lock);
2240
+ if (!options.dryRun) {
2241
+ if (changed || catalogMoved) {
2242
+ writeLockfile(options.targetRoot, lock);
2243
+ }
2244
+ if (changed) {
2245
+ const { catalog } = loadRepoCatalog(
2246
+ options.targetRoot,
2247
+ lock.catalog.source
2248
+ );
2249
+ writeProviders(options.targetRoot, catalog, lock);
2250
+ }
2016
2251
  }
2017
2252
  report(reports, options);
2018
2253
  };
2019
- applyUpdate = (targetRoot, parsed, entry, sourceCatalog, repoCatalog, lock, force) => {
2254
+ applyUpdate = (targetRoot, parsed, entry, sourceCatalog, repoCatalog, lock, force, dryRun) => {
2020
2255
  const id = `${parsed.type}:${parsed.name}`;
2021
2256
  if (entry.type === "skill") {
2022
2257
  const src2 = sourceCatalog.skills.find((s) => s.name === parsed.name);
@@ -2026,9 +2261,10 @@ var init_update = __esm({
2026
2261
  if (repo2 && repo2.digest !== entry.digest && !force) {
2027
2262
  return { id, status: "local-changes" };
2028
2263
  }
2029
- const dest = resolve17(targetRoot, ".agents", src2.sourcePath);
2030
- rmSync6(dest, { recursive: true, force: true });
2031
- mkdirSync6(dirname5(dest), { recursive: true });
2264
+ if (dryRun) return { id, status: "updated" };
2265
+ const dest = resolve16(targetRoot, ".agents", src2.sourcePath);
2266
+ rmSync5(dest, { recursive: true, force: true });
2267
+ mkdirSync5(dirname5(dest), { recursive: true });
2032
2268
  cpSync2(src2.absDir, dest, { recursive: true });
2033
2269
  entry.digest = src2.digest;
2034
2270
  entry.frontmatter = src2.frontmatter;
@@ -2043,13 +2279,38 @@ var init_update = __esm({
2043
2279
  if (repo2 && repo2.digest !== entry.digest && !force) {
2044
2280
  return { id, status: "local-changes" };
2045
2281
  }
2046
- const dest = resolve17(targetRoot, ".agents", src2.sourcePath);
2047
- mkdirSync6(dirname5(dest), { recursive: true });
2282
+ if (dryRun) return { id, status: "updated" };
2283
+ const dest = resolve16(targetRoot, ".agents", src2.sourcePath);
2284
+ mkdirSync5(dirname5(dest), { recursive: true });
2048
2285
  cpSync2(src2.absPath, dest, { force: true });
2049
2286
  entry.digest = src2.digest;
2050
2287
  entry.sourcePath = src2.sourcePath;
2051
2288
  return { id, status: "updated" };
2052
2289
  }
2290
+ if (entry.type === "plugin") {
2291
+ const src2 = sourceCatalog.plugins.find((p) => p.name === parsed.name);
2292
+ if (!src2) return { id, status: "not-in-catalog" };
2293
+ if (src2.digest === entry.digest) return { id, status: "up-to-date" };
2294
+ const repo2 = repoCatalog.plugins.find((p) => p.name === parsed.name);
2295
+ if (repo2 && repo2.digest !== entry.digest && !force) {
2296
+ return { id, status: "local-changes" };
2297
+ }
2298
+ if (dryRun) return { id, status: "updated" };
2299
+ const dest = resolve16(targetRoot, ".agents", src2.sourcePath);
2300
+ mkdirSync5(dirname5(dest), { recursive: true });
2301
+ cpSync2(src2.absPath, dest, { force: true });
2302
+ const configPath2 = resolve16(targetRoot, ".agents/config.json");
2303
+ const config2 = JSON.parse(readFileSync8(configPath2, "utf8"));
2304
+ config2.plugins = {
2305
+ ...config2.plugins,
2306
+ [parsed.name]: sourceCatalog.config.plugins?.[parsed.name]
2307
+ };
2308
+ writeFileSync6(configPath2, JSON.stringify(config2, null, 2) + "\n");
2309
+ entry.digest = src2.digest;
2310
+ entry.sourcePath = src2.sourcePath;
2311
+ entry.requires = src2.requires;
2312
+ return { id, status: "updated" };
2313
+ }
2053
2314
  const src = sourceCatalog.mcp.find((m) => m.name === parsed.name);
2054
2315
  if (!src) return { id, status: "not-in-catalog" };
2055
2316
  if (src.configDigest === entry.configDigest) return { id, status: "up-to-date" };
@@ -2057,10 +2318,11 @@ var init_update = __esm({
2057
2318
  if (repo && repo.configDigest !== entry.configDigest && !force) {
2058
2319
  return { id, status: "local-changes" };
2059
2320
  }
2060
- const configPath = resolve17(targetRoot, ".agents", "config.json");
2061
- const config = JSON.parse(readFileSync9(configPath, "utf8"));
2321
+ if (dryRun) return { id, status: "updated" };
2322
+ const configPath = resolve16(targetRoot, ".agents", "config.json");
2323
+ const config = JSON.parse(readFileSync8(configPath, "utf8"));
2062
2324
  config.mcpServers = { ...config.mcpServers, [parsed.name]: src.server };
2063
- writeFileSync7(configPath, JSON.stringify(config, null, 2) + "\n");
2325
+ writeFileSync6(configPath, JSON.stringify(config, null, 2) + "\n");
2064
2326
  entry.configDigest = src.configDigest;
2065
2327
  entry.transport = src.server.transport;
2066
2328
  entry.tools = null;
@@ -2074,6 +2336,7 @@ var init_update = __esm({
2074
2336
  JSON.stringify(
2075
2337
  {
2076
2338
  ok: true,
2339
+ dryRun: options.dryRun,
2077
2340
  updated: by("updated").map((r) => r.id),
2078
2341
  upToDate: by("up-to-date").map((r) => r.id),
2079
2342
  localChanges: by("local-changes").map((r) => r.id),
@@ -2087,7 +2350,8 @@ var init_update = __esm({
2087
2350
  }
2088
2351
  const c = palette();
2089
2352
  const lines = [""];
2090
- for (const r of by("updated")) lines.push(` ${c.cyan("\u2191")} ${r.id} updated`);
2353
+ const verb = options.dryRun ? "would update" : "updated";
2354
+ for (const r of by("updated")) lines.push(` ${c.cyan("\u2191")} ${r.id} ${verb}`);
2091
2355
  for (const r of by("local-changes"))
2092
2356
  lines.push(
2093
2357
  ` ${c.yellow("\u25B2")} ${r.id} ${c.yellow("local changes - skipped (use --force)")}`
@@ -2099,7 +2363,11 @@ var init_update = __esm({
2099
2363
  const updated = by("updated").length;
2100
2364
  lines.push(
2101
2365
  "",
2102
- ` ${updated ? c.cyan(`\u2191 ${updated} updated - review and commit .agents/ + quiver.lock`) : c.green("\u2713 everything up to date with the catalog")}`,
2366
+ ` ${updated ? options.dryRun ? c.cyan(
2367
+ `\u2191 ${updated} would be updated - run without --dry-run to apply`
2368
+ ) : c.cyan(
2369
+ `\u2191 ${updated} updated - review and commit .agents/ + quiver.lock`
2370
+ ) : c.green("\u2713 everything up to date with the catalog")}`,
2103
2371
  ""
2104
2372
  );
2105
2373
  block(lines);
@@ -2147,14 +2415,16 @@ var init_list = __esm({
2147
2415
  const skills = [];
2148
2416
  const commands = [];
2149
2417
  const mcp = [];
2418
+ const plugins = [];
2150
2419
  for (const [id, entry] of Object.entries(lock.entries)) {
2151
2420
  const p = parseEntryId(id);
2152
2421
  if (!p) continue;
2153
2422
  if (entry.type === "skill") skills.push({ name: p.name, entry });
2154
2423
  else if (entry.type === "command") commands.push({ name: p.name, entry });
2155
2424
  else if (entry.type === "mcp") mcp.push({ name: p.name, entry });
2425
+ else if (entry.type === "plugin") plugins.push({ name: p.name, entry });
2156
2426
  }
2157
- for (const group of [skills, commands, mcp]) {
2427
+ for (const group of [skills, commands, mcp, plugins]) {
2158
2428
  group.sort((a, b) => a.name.localeCompare(b.name));
2159
2429
  }
2160
2430
  if (options.json) {
@@ -2173,6 +2443,11 @@ var init_list = __esm({
2173
2443
  transport: entry.transport,
2174
2444
  detail: serverDetail2.get(name) ?? null,
2175
2445
  toolCount: entry.tools ? Object.keys(entry.tools).length : null
2446
+ })),
2447
+ plugins: plugins.map(({ name, entry }) => ({
2448
+ name,
2449
+ provider: entry.provider,
2450
+ requires: entry.requires
2176
2451
  }))
2177
2452
  },
2178
2453
  null,
@@ -2230,12 +2505,19 @@ var init_list = __esm({
2230
2505
  );
2231
2506
  }
2232
2507
  }
2233
- const providers = lock.providers?.length ? lock.providers.join(", ") : "claude, opencode, codex";
2508
+ if (plugins.length) {
2509
+ lines.push("", ` ${c.bold("plugins")}`);
2510
+ for (const { name, entry } of plugins) {
2511
+ const requires = entry.requires.length ? ` ${c.dim(`requires: ${entry.requires.join(", ")}`)}` : "";
2512
+ lines.push(` ${name} ${c.dim(entry.provider)}${requires}`);
2513
+ }
2514
+ }
2515
+ const providers2 = lock.providers?.length ? lock.providers.join(", ") : "claude, opencode, codex";
2234
2516
  lines.push(
2235
2517
  "",
2236
2518
  ` ${c.bold(
2237
- `${skills.length} skills \xB7 ${commands.length} commands \xB7 ${mcp.length} MCP servers`
2238
- )} ${c.dim(`providers: ${providers}`)}`
2519
+ `${skills.length} skills \xB7 ${commands.length} commands \xB7 ${mcp.length} MCP servers \xB7 ${plugins.length} plugins`
2520
+ )} ${c.dim(`providers: ${providers2}`)}`
2239
2521
  );
2240
2522
  if (missingTools) {
2241
2523
  lines.push(` ${c.dim("run 'quiver-cli check' to populate tool counts")}`);
@@ -2246,84 +2528,6 @@ var init_list = __esm({
2246
2528
  }
2247
2529
  });
2248
2530
 
2249
- // src/commands/status.ts
2250
- var status_exports = {};
2251
- __export(status_exports, {
2252
- status: () => status
2253
- });
2254
- var status;
2255
- var init_status = __esm({
2256
- "src/commands/status.ts"() {
2257
- "use strict";
2258
- init_repo();
2259
- init_io();
2260
- init_schema();
2261
- init_write();
2262
- init_prompts();
2263
- status = async (options) => {
2264
- const lock = readLockfile(options.targetRoot);
2265
- if (!lock) {
2266
- if (options.json) console.log(JSON.stringify({ ok: false, error: "no-lockfile" }));
2267
- else await error("No quiver.lock found. Run `quiver-cli init` first.");
2268
- process.exitCode = 1;
2269
- return;
2270
- }
2271
- if (!repoCatalogExists(options.targetRoot)) {
2272
- if (options.json) console.log(JSON.stringify({ ok: false, error: "no-agents" }));
2273
- else await error("No .agents/ directory found. Run `quiver-cli init` first.");
2274
- process.exitCode = 1;
2275
- return;
2276
- }
2277
- const { catalog } = loadRepoCatalog(options.targetRoot, lock.catalog.source);
2278
- const haveSkills = new Set(catalog.skills.map((s) => s.name));
2279
- const haveCommands = new Set(catalog.commands.map((c) => c.name));
2280
- const haveMcp = new Set(catalog.mcp.map((m) => m.name));
2281
- const missing = [];
2282
- const changed = [];
2283
- const skillByName = new Map(catalog.skills.map((s) => [s.name, s]));
2284
- const commandByName = new Map(catalog.commands.map((c) => [c.name, c]));
2285
- const mcpByName = new Map(catalog.mcp.map((m) => [m.name, m]));
2286
- for (const [id, entry] of Object.entries(lock.entries)) {
2287
- const p = parseEntryId(id);
2288
- if (!p) continue;
2289
- if (p.type === "skill") {
2290
- if (!haveSkills.has(p.name)) missing.push(id);
2291
- else if (entry.type === "skill" && skillByName.get(p.name).digest !== entry.digest)
2292
- changed.push(id);
2293
- } else if (p.type === "command") {
2294
- if (!haveCommands.has(p.name)) missing.push(id);
2295
- else if (entry.type === "command" && commandByName.get(p.name).digest !== entry.digest)
2296
- changed.push(id);
2297
- } else if (p.type === "mcp") {
2298
- if (!haveMcp.has(p.name)) missing.push(id);
2299
- else if (entry.type === "mcp" && mcpByName.get(p.name).configDigest !== entry.configDigest)
2300
- changed.push(id);
2301
- }
2302
- }
2303
- const shimProblems = checkProviders(options.targetRoot, catalog, lock);
2304
- const ok = missing.length === 0 && changed.length === 0 && shimProblems.length === 0;
2305
- if (options.json) {
2306
- console.log(JSON.stringify({ ok, missing, changed, shims: shimProblems }, null, 2));
2307
- if (!ok) process.exitCode = 1;
2308
- return;
2309
- }
2310
- if (ok) {
2311
- await success("In sync: lockfile, .agents/ and provider shims agree.");
2312
- return;
2313
- }
2314
- if (missing.length)
2315
- await warn(`Missing in .agents/: ${missing.join(", ")}`);
2316
- if (changed.length)
2317
- await warn(`Content changed since lockfile: ${changed.join(", ")}`);
2318
- if (shimProblems.length)
2319
- await warn(`Provider shims out of date:
2320
- - ${shimProblems.join("\n - ")}`);
2321
- await info("Run `quiver-cli sync` to regenerate provider shims and refresh digests.");
2322
- process.exitCode = 1;
2323
- };
2324
- }
2325
- });
2326
-
2327
2531
  // src/mcp/diff.ts
2328
2532
  var diffSnapshots, isEmptyDiff;
2329
2533
  var init_diff = __esm({
@@ -2458,9 +2662,12 @@ var init_snapshot = __esm({
2458
2662
  var check_exports = {};
2459
2663
  __export(check_exports, {
2460
2664
  check: () => check,
2665
+ hasCommand: () => hasCommand,
2461
2666
  summarize: () => summarize
2462
2667
  });
2463
- var check, report2, driftLines, list2, recommend, summarize, truncate2, fail;
2668
+ import { accessSync as accessSync2, constants as constants2 } from "fs";
2669
+ import { delimiter, resolve as resolve17 } from "path";
2670
+ var check, report2, driftLines, list2, recommend, summarize, hasCommand, truncate2, fail;
2464
2671
  var init_check = __esm({
2465
2672
  "src/commands/check.ts"() {
2466
2673
  "use strict";
@@ -2470,6 +2677,7 @@ var init_check = __esm({
2470
2677
  init_diff();
2471
2678
  init_introspect();
2472
2679
  init_snapshot();
2680
+ init_write();
2473
2681
  init_interpolate();
2474
2682
  init_prompts();
2475
2683
  check = async (options) => {
@@ -2484,8 +2692,10 @@ var init_check = __esm({
2484
2692
  const { catalog } = loadRepoCatalog(options.targetRoot, lock.catalog.source);
2485
2693
  const skillByName = new Map(catalog.skills.map((s) => [s.name, s]));
2486
2694
  const commandByName = new Map(catalog.commands.map((c) => [c.name, c]));
2695
+ const pluginByName = new Map(catalog.plugins.map((p) => [p.name, p]));
2487
2696
  const skillDrift = [];
2488
- const checked = { skills: 0, commands: 0, mcp: 0 };
2697
+ const pluginRequirements = [];
2698
+ const checked = { skills: 0, commands: 0, mcp: 0, plugins: 0 };
2489
2699
  for (const [id, entry] of Object.entries(lock.entries)) {
2490
2700
  const p = parseEntryId(id);
2491
2701
  if (!p) continue;
@@ -2499,11 +2709,24 @@ var init_check = __esm({
2499
2709
  if (!cat) continue;
2500
2710
  checked.commands += 1;
2501
2711
  if (cat.digest !== entry.digest) skillDrift.push({ id, kind: "content" });
2712
+ } else if (entry.type === "plugin") {
2713
+ const cat = pluginByName.get(p.name);
2714
+ if (!cat) continue;
2715
+ checked.plugins += 1;
2716
+ if (cat.digest !== entry.digest) skillDrift.push({ id, kind: "content" });
2717
+ if (lock.providers?.length && !lock.providers.includes(entry.provider)) {
2718
+ continue;
2719
+ }
2720
+ for (const command of entry.requires) {
2721
+ if (!hasCommand(command)) pluginRequirements.push({ id, command });
2722
+ }
2502
2723
  }
2503
2724
  }
2725
+ const shimProblems = checkProviders(options.targetRoot, catalog, lock);
2504
2726
  const mcpReports = [];
2505
2727
  let lockChanged = false;
2506
2728
  for (const [id, entry] of Object.entries(lock.entries)) {
2729
+ if (options.offline) break;
2507
2730
  if (entry.type !== "mcp") continue;
2508
2731
  const p = parseEntryId(id);
2509
2732
  const catMcp = catalog.mcp.find((m) => m.name === p.name);
@@ -2537,11 +2760,18 @@ var init_check = __esm({
2537
2760
  }
2538
2761
  }
2539
2762
  if (lockChanged) writeLockfile(options.targetRoot, lock);
2540
- const hasDrift = skillDrift.length > 0 || mcpReports.some((r) => r.status === "drift");
2763
+ const hasDrift = skillDrift.length > 0 || pluginRequirements.length > 0 || shimProblems.length > 0 || mcpReports.some((r) => r.status === "drift");
2541
2764
  if (options.json) {
2542
2765
  console.log(
2543
2766
  JSON.stringify(
2544
- { ok: !hasDrift, checked, skillDrift, mcp: mcpReports },
2767
+ {
2768
+ ok: !hasDrift,
2769
+ checked,
2770
+ skillDrift,
2771
+ pluginRequirements,
2772
+ shims: shimProblems,
2773
+ mcp: mcpReports
2774
+ },
2545
2775
  null,
2546
2776
  2
2547
2777
  )
@@ -2549,16 +2779,35 @@ var init_check = __esm({
2549
2779
  if (hasDrift) process.exitCode = 1;
2550
2780
  return;
2551
2781
  }
2552
- await report2(skillDrift, mcpReports, checked, options);
2782
+ await report2(
2783
+ skillDrift,
2784
+ pluginRequirements,
2785
+ shimProblems,
2786
+ mcpReports,
2787
+ checked,
2788
+ options
2789
+ );
2553
2790
  if (hasDrift) process.exitCode = 1;
2554
2791
  };
2555
- report2 = async (skillDrift, mcpReports, checked, options) => {
2792
+ report2 = async (skillDrift, pluginRequirements, shimProblems, mcpReports, checked, options) => {
2556
2793
  if (skillDrift.length) {
2557
2794
  await warn(
2558
- `Skill/command content changed since lockfile:
2795
+ `Managed content changed since lockfile:
2559
2796
  - ${skillDrift.map((s) => s.id).join("\n - ")}`
2560
2797
  );
2561
2798
  }
2799
+ if (pluginRequirements.length) {
2800
+ await warn(
2801
+ `Plugin requirements missing:
2802
+ - ${pluginRequirements.map((issue) => `${issue.id}: ${issue.command}`).join("\n - ")}`
2803
+ );
2804
+ }
2805
+ if (shimProblems.length) {
2806
+ await warn(
2807
+ `Provider shims out of date:
2808
+ - ${shimProblems.join("\n - ")}`
2809
+ );
2810
+ }
2562
2811
  const skipped = mcpReports.filter((r) => r.status === "skipped");
2563
2812
  if (skipped.length) {
2564
2813
  const names = skipped.map((r) => parseEntryId(r.id)?.name ?? r.id);
@@ -2583,12 +2832,13 @@ var init_check = __esm({
2583
2832
  - ${driftLines(r.diff, options.verbose).join("\n - ")}`);
2584
2833
  }
2585
2834
  const summary = summarize(checked);
2586
- const hasDrift = skillDrift.length > 0 || drifted.length > 0;
2587
- if (!hasDrift) {
2835
+ const hasDrift = skillDrift.length > 0 || shimProblems.length > 0 || drifted.length > 0;
2836
+ const hasProblems = hasDrift || pluginRequirements.length > 0;
2837
+ if (!hasProblems) {
2588
2838
  await success(`check passed: ${summary}, no drift detected.`);
2589
2839
  } else {
2590
2840
  await info(`checked ${summary}, drift detected.`);
2591
- await recommend(skillDrift, drifted);
2841
+ await recommend(skillDrift, shimProblems, drifted);
2592
2842
  }
2593
2843
  };
2594
2844
  driftLines = (diff, verbose) => {
@@ -2622,9 +2872,12 @@ var init_check = __esm({
2622
2872
  const rest = names.length - sample;
2623
2873
  return `${names.length} tools (${names.slice(0, sample).join(", ")}, \u2026 +${rest} more)`;
2624
2874
  };
2625
- recommend = async (skillDrift, drifted) => {
2875
+ recommend = async (skillDrift, shimProblems, drifted) => {
2626
2876
  const c = palette();
2627
2877
  const lines = ["to update the lockfile baseline:"];
2878
+ if (shimProblems.length) {
2879
+ lines.push(` ${c.cyan("quiver-cli sync")} regenerate provider shims`);
2880
+ }
2628
2881
  if (drifted.length) {
2629
2882
  lines.push(` ${c.cyan("quiver-cli check --accept")} accept the new MCP tool snapshots`);
2630
2883
  }
@@ -2646,8 +2899,23 @@ var init_check = __esm({
2646
2899
  if (c.skills) parts.push(plural(c.skills, "skill"));
2647
2900
  if (c.commands) parts.push(plural(c.commands, "command"));
2648
2901
  if (c.mcp) parts.push(plural(c.mcp, "MCP server"));
2902
+ if (c.plugins) parts.push(plural(c.plugins, "plugin"));
2649
2903
  return parts.length ? parts.join(", ") : "nothing";
2650
2904
  };
2905
+ hasCommand = (command) => {
2906
+ if (!/^[A-Za-z0-9._-]+$/.test(command)) return false;
2907
+ const extensions = process.platform === "win32" ? (process.env["PATHEXT"] ?? ".EXE;.CMD;.BAT;.COM").split(";") : [""];
2908
+ for (const dir of (process.env["PATH"] ?? "").split(delimiter)) {
2909
+ for (const extension of extensions) {
2910
+ try {
2911
+ accessSync2(resolve17(dir, command + extension), constants2.X_OK);
2912
+ return true;
2913
+ } catch {
2914
+ }
2915
+ }
2916
+ }
2917
+ return false;
2918
+ };
2651
2919
  truncate2 = (s, max = 120) => s.length > max ? s.slice(0, max) + "\u2026" : s;
2652
2920
  fail = async (options, code, message) => {
2653
2921
  if (options.json) console.log(JSON.stringify({ ok: false, error: code }));
@@ -2662,9 +2930,9 @@ import { execFileSync as execFileSync3 } from "child_process";
2662
2930
  import {
2663
2931
  existsSync as existsSync13,
2664
2932
  mkdtempSync as mkdtempSync2,
2665
- readFileSync as readFileSync10,
2666
- rmSync as rmSync7,
2667
- writeFileSync as writeFileSync8
2933
+ readFileSync as readFileSync9,
2934
+ rmSync as rmSync6,
2935
+ writeFileSync as writeFileSync7
2668
2936
  } from "fs";
2669
2937
  import { tmpdir } from "os";
2670
2938
  import { join, resolve as resolve18 } from "path";
@@ -2679,10 +2947,10 @@ var init_upstreams = __esm({
2679
2947
  loadUpstreams = (catalog) => {
2680
2948
  const path = upstreamsPath(catalog);
2681
2949
  if (!existsSync13(path)) return {};
2682
- return JSON.parse(readFileSync10(path, "utf8"));
2950
+ return JSON.parse(readFileSync9(path, "utf8"));
2683
2951
  };
2684
2952
  writeUpstreams = (catalog, map) => {
2685
- writeFileSync8(upstreamsPath(catalog), JSON.stringify(map, null, 2) + "\n");
2953
+ writeFileSync7(upstreamsPath(catalog), JSON.stringify(map, null, 2) + "\n");
2686
2954
  };
2687
2955
  fetchLatestCommit = async (origin) => {
2688
2956
  const url = `https://api.github.com/repos/${origin.repo}/commits?path=${encodeURIComponent(origin.path)}&sha=${encodeURIComponent(origin.ref)}&per_page=1`;
@@ -2721,7 +2989,7 @@ var init_upstreams = __esm({
2721
2989
  };
2722
2990
  fetchUpstreamDir = (origin) => {
2723
2991
  const tmp = mkdtempSync2(join(tmpdir(), "quiver-pull-"));
2724
- const cleanup = () => rmSync7(tmp, { recursive: true, force: true });
2992
+ const cleanup = () => rmSync6(tmp, { recursive: true, force: true });
2725
2993
  const git = (args, cwd) => {
2726
2994
  execFileSync3("git", args, {
2727
2995
  cwd,
@@ -2796,7 +3064,7 @@ var upstream_exports = {};
2796
3064
  __export(upstream_exports, {
2797
3065
  upstream: () => upstream
2798
3066
  });
2799
- import { cpSync as cpSync3, rmSync as rmSync8 } from "fs";
3067
+ import { cpSync as cpSync3, rmSync as rmSync7 } from "fs";
2800
3068
  var upstream, guardWritableCatalog, STATUS_ORDER, pull, report3, countByStatus;
2801
3069
  var init_upstream = __esm({
2802
3070
  "src/commands/upstream.ts"() {
@@ -2848,7 +3116,7 @@ var init_upstream = __esm({
2848
3116
  guardWritableCatalog = async (catalog) => {
2849
3117
  if (isCatalogWritable(catalog)) return false;
2850
3118
  await error(
2851
- "upstream is a catalog-maintenance command and the catalog here is not writable (the installed package or a remote cache).\nRun it inside the quiver-cli repo, or point at a writable local catalog with --catalog <path>.\nTo update a consuming repo's installed entries, use quiver-cli check / quiver-cli update instead."
3119
+ "upstream is a catalog-maintenance command and the catalog here is not writable (the installed package or a remote cache).\nRun it inside the quiver-cli repo, or point at a writable local catalog with --catalog=local:/absolute/path/.agents.\nTo update a consuming repo's installed entries, use quiver-cli check / quiver-cli update instead."
2852
3120
  );
2853
3121
  process.exitCode = 1;
2854
3122
  return true;
@@ -2906,7 +3174,7 @@ var init_upstream = __esm({
2906
3174
  continue;
2907
3175
  }
2908
3176
  try {
2909
- rmSync8(skill.absDir, { recursive: true, force: true });
3177
+ rmSync7(skill.absDir, { recursive: true, force: true });
2910
3178
  cpSync3(fetched.dir, skill.absDir, { recursive: true, dereference: true });
2911
3179
  } finally {
2912
3180
  fetched.cleanup();
@@ -2980,14 +3248,23 @@ var init_upstream = __esm({
2980
3248
  lines.push(` ${mark[r.status]} ${c.bold(name)} ${detail} ${where}`);
2981
3249
  }
2982
3250
  block(lines);
2983
- const drifted = sorted.filter(
2984
- (r) => r.status === "drift" || r.status === "drift-curated"
2985
- );
2986
- if (drifted.length) {
3251
+ const drifted = sorted.filter((r) => r.status === "drift");
3252
+ const curated = sorted.filter((r) => r.status === "drift-curated");
3253
+ if (drifted.length || curated.length) {
2987
3254
  const hint = [""];
2988
- for (const r of drifted) {
2989
- const note = r.status === "drift-curated" ? "curated \u2014 reconcile changes by hand" : "re-fetch with the skills CLI, then copy into the catalog";
2990
- hint.push(` ${c.yellow(r.name)}: ${c.dim(note)}`);
3255
+ if (drifted.length) {
3256
+ hint.push(
3257
+ ` ${c.cyan("quiver-cli upstream pull")} ${c.dim(
3258
+ `update the catalog copy (${drifted.map((r) => r.name).join(", ")})`
3259
+ )}`
3260
+ );
3261
+ }
3262
+ for (const r of curated) {
3263
+ hint.push(
3264
+ ` ${c.yellow(r.name)}: ${c.dim(
3265
+ "curated \u2014 reconcile by hand, or upstream pull --force"
3266
+ )}`
3267
+ );
2991
3268
  }
2992
3269
  block(hint);
2993
3270
  }
@@ -3023,83 +3300,6 @@ var init_upstream = __esm({
3023
3300
  }
3024
3301
  });
3025
3302
 
3026
- // src/commands/login.ts
3027
- var login_exports = {};
3028
- __export(login_exports, {
3029
- login: () => login
3030
- });
3031
- import process3 from "process";
3032
- var login, promptToken, readStdin;
3033
- var init_login = __esm({
3034
- "src/commands/login.ts"() {
3035
- "use strict";
3036
- init_auth();
3037
- init_prompts();
3038
- login = async (options) => {
3039
- const token = process3.stdin.isTTY ? await promptToken() : await readStdin();
3040
- if (!token) {
3041
- await error(
3042
- "No token provided. Create a GitHub personal access token with `repo` read access and try again."
3043
- );
3044
- process3.exitCode = 1;
3045
- return;
3046
- }
3047
- const validation = await validateToken(token);
3048
- if (!validation.ok) {
3049
- await error(`Token validation failed: ${validation.reason}`);
3050
- process3.exitCode = 1;
3051
- return;
3052
- }
3053
- writeStoredToken(token, validation.login);
3054
- if (options.json) {
3055
- console.log(JSON.stringify({ ok: true, login: validation.login }, null, 2));
3056
- return;
3057
- }
3058
- await success(
3059
- `Logged in as ${validation.login}. Token stored in ${authFilePath()}.`
3060
- );
3061
- };
3062
- promptToken = async () => {
3063
- await info(
3064
- "Paste a GitHub personal access token (used for github: catalogs; needs read access to the catalog repo)."
3065
- );
3066
- const value = await password("GitHub token");
3067
- return value?.trim() || null;
3068
- };
3069
- readStdin = async () => {
3070
- let data = "";
3071
- for await (const chunk of process3.stdin) data += chunk;
3072
- return data.trim() || null;
3073
- };
3074
- }
3075
- });
3076
-
3077
- // src/commands/logout.ts
3078
- var logout_exports = {};
3079
- __export(logout_exports, {
3080
- logout: () => logout
3081
- });
3082
- var logout;
3083
- var init_logout = __esm({
3084
- "src/commands/logout.ts"() {
3085
- "use strict";
3086
- init_auth();
3087
- init_prompts();
3088
- logout = async (options) => {
3089
- const removed = deleteStoredToken();
3090
- if (options.json) {
3091
- console.log(JSON.stringify({ ok: true, removed }, null, 2));
3092
- return;
3093
- }
3094
- if (removed) {
3095
- await success(`Logged out. Removed ${authFilePath()}.`);
3096
- } else {
3097
- await info("Not logged in - nothing to remove.");
3098
- }
3099
- };
3100
- }
3101
- });
3102
-
3103
3303
  // src/version/notifier.ts
3104
3304
  var notifier_exports = {};
3105
3305
  __export(notifier_exports, {
@@ -3109,8 +3309,8 @@ __export(notifier_exports, {
3109
3309
  installHint: () => installHint,
3110
3310
  notifierSuppressed: () => notifierSuppressed
3111
3311
  });
3112
- import { existsSync as existsSync14, mkdirSync as mkdirSync7, readFileSync as readFileSync11, writeFileSync as writeFileSync9 } from "fs";
3113
- import { homedir as homedir3 } from "os";
3312
+ import { existsSync as existsSync14, mkdirSync as mkdirSync6, readFileSync as readFileSync10, writeFileSync as writeFileSync8 } from "fs";
3313
+ import { homedir as homedir2 } from "os";
3114
3314
  import { dirname as dirname6, resolve as resolve19 } from "path";
3115
3315
  var REGISTRY_URL, CHECK_TTL_MS, FETCH_TIMEOUT_MS, INSTALL_HINT, cacheFilePath, installHint, getCurrentVersion, compareSemver, readCache, writeCache, fetchLatestVersion, checkForUpdate, notifierSuppressed;
3116
3316
  var init_notifier = __esm({
@@ -3122,14 +3322,14 @@ var init_notifier = __esm({
3122
3322
  FETCH_TIMEOUT_MS = 2e3;
3123
3323
  INSTALL_HINT = "pnpm add -g quiver-cli";
3124
3324
  cacheFilePath = () => {
3125
- const base = process.env["XDG_CACHE_HOME"] || resolve19(homedir3(), ".cache");
3325
+ const base = process.env["XDG_CACHE_HOME"] || resolve19(homedir2(), ".cache");
3126
3326
  return resolve19(base, "quiver", "update-check.json");
3127
3327
  };
3128
3328
  installHint = () => INSTALL_HINT;
3129
3329
  getCurrentVersion = () => {
3130
3330
  try {
3131
3331
  const pkg = JSON.parse(
3132
- readFileSync11(resolve19(packageRoot, "package.json"), "utf8")
3332
+ readFileSync10(resolve19(packageRoot, "package.json"), "utf8")
3133
3333
  );
3134
3334
  return pkg.version;
3135
3335
  } catch {
@@ -3157,7 +3357,7 @@ var init_notifier = __esm({
3157
3357
  const path = cacheFilePath();
3158
3358
  if (!existsSync14(path)) return null;
3159
3359
  try {
3160
- return JSON.parse(readFileSync11(path, "utf8"));
3360
+ return JSON.parse(readFileSync10(path, "utf8"));
3161
3361
  } catch {
3162
3362
  return null;
3163
3363
  }
@@ -3165,8 +3365,8 @@ var init_notifier = __esm({
3165
3365
  writeCache = (cache) => {
3166
3366
  try {
3167
3367
  const path = cacheFilePath();
3168
- mkdirSync7(dirname6(path), { recursive: true });
3169
- writeFileSync9(path, JSON.stringify(cache, null, 2) + "\n");
3368
+ mkdirSync6(dirname6(path), { recursive: true });
3369
+ writeFileSync8(path, JSON.stringify(cache, null, 2) + "\n");
3170
3370
  } catch {
3171
3371
  }
3172
3372
  };
@@ -3210,72 +3410,109 @@ var init_notifier = __esm({
3210
3410
 
3211
3411
  // src/cli.ts
3212
3412
  init_prompts();
3213
- import process4 from "process";
3214
- var HELP = `quiver-cli - compose agent skills, commands & MCP servers into any repo
3413
+ import process3 from "process";
3414
+ var HELP = `quiver-cli - compose agent skills, commands, plugins & MCP servers into any repo
3215
3415
 
3216
3416
  Usage:
3217
3417
  quiver-cli <command> [options]
3218
3418
 
3219
3419
  Commands:
3220
3420
  init Interactive picker over the catalog; write native configs + quiver.lock
3221
- add <id> Add a single catalog entry (skill:<name>, command:<name>, mcp:<name>)
3421
+ add <id> Add an entry (skill:<name>, command:<name>, mcp:<name>, plugin:<name>)
3222
3422
  remove <id> Remove a single entry; keep lockfile + configs consistent
3223
3423
  sync Regenerate provider configs from .agents/ (warns on drift)
3424
+ providers [a,b] Change which tools get configs (claude, opencode, codex)
3224
3425
  update [id] Pull newer catalog content into .agents/ (all or one entry)
3225
- list Show installed entries (skills, commands, MCP tool counts)
3226
- status Diff the lockfile against what is actually in the repo
3227
- check Detect upstream drift (skill digests, MCP tool snapshots)
3426
+ list Show installed entries (skills, commands, plugins, MCP tool counts)
3427
+ check Detect drift: skill digests, provider shims, MCP tool
3428
+ snapshots (--offline skips MCP re-introspection)
3228
3429
  upstream Catalog maintenance: check source repos for skill updates
3229
3430
  (run in the quiver-cli repo or with a writable --catalog)
3230
3431
  upstream pull Pull latest upstream content into the catalog [skill]
3231
- login Store a GitHub token for remote (github:) catalogs
3232
- logout Remove the stored GitHub token
3233
3432
  help Show this help
3234
3433
  version Show the quiver-cli version
3235
3434
 
3236
3435
  Options:
3237
3436
  -f, --force Overwrite existing files
3238
3437
  --all, -y Keep everything without prompting (non-interactive)
3239
- --json Machine-readable output (status/check/upstream/list)
3438
+ --json Machine-readable output (check/upstream/list)
3240
3439
  -V, --verbose Show full tool lists and description diffs (check)
3241
3440
  --accept Record the current MCP tool snapshots as the new baseline (check)
3242
- --providers=a,b Generate configs only for these tools (claude, opencode, codex)
3441
+ --offline Skip MCP re-introspection; check digests + shims only (check)
3442
+ --dry-run Report what would change without writing (update)
3443
+ --providers=a,b Generate configs only for these tools (init, sync, providers)
3243
3444
  --catalog=<source> Catalog source for init (e.g. github:owner/repo[/path][#ref])
3244
3445
  --introspect-stdio Allow introspecting stdio MCP servers (runs foreign code)
3245
3446
  -v, --version Show the quiver-cli version
3246
3447
  `;
3448
+ var KNOWN_FLAGS = /* @__PURE__ */ new Set([
3449
+ "--force",
3450
+ "-f",
3451
+ "--all",
3452
+ "--yes",
3453
+ "-y",
3454
+ "--json",
3455
+ "--verbose",
3456
+ "-V",
3457
+ "--accept",
3458
+ "--offline",
3459
+ "--dry-run",
3460
+ "--introspect-stdio",
3461
+ "--help",
3462
+ "-h",
3463
+ "--version",
3464
+ "-v"
3465
+ ]);
3466
+ var KNOWN_VALUE_FLAGS = ["--providers", "--catalog"];
3467
+ var isKnownFlag = (arg) => {
3468
+ if (KNOWN_FLAGS.has(arg)) return true;
3469
+ return KNOWN_VALUE_FLAGS.some((f) => arg.startsWith(`${f}=`));
3470
+ };
3247
3471
  var parse2 = (argv) => {
3248
3472
  const [command = "init", ...rest] = argv;
3249
3473
  const flags = new Set(rest.filter((a) => a.startsWith("-")));
3250
3474
  const positionals = rest.filter((a) => !a.startsWith("-"));
3475
+ const unknownFlags = [...flags].filter((a) => !isKnownFlag(a));
3251
3476
  const providersFlag = rest.find((a) => a.startsWith("--providers="));
3252
- const providers = providersFlag ? providersFlag.slice("--providers=".length).split(",").map((p) => p.trim()).filter(Boolean) : null;
3477
+ const providers2 = providersFlag ? providersFlag.slice("--providers=".length).split(",").map((p) => p.trim()).filter(Boolean) : null;
3253
3478
  const catalogFlag = rest.find((a) => a.startsWith("--catalog="));
3254
3479
  const catalog = catalogFlag ? catalogFlag.slice("--catalog=".length).trim() || null : null;
3255
3480
  return {
3256
3481
  command,
3482
+ unknownFlags,
3257
3483
  options: {
3258
- targetRoot: process4.cwd(),
3484
+ targetRoot: process3.cwd(),
3259
3485
  force: flags.has("--force") || flags.has("-f"),
3260
3486
  all: flags.has("--all") || flags.has("--yes") || flags.has("-y"),
3261
3487
  json: flags.has("--json"),
3262
3488
  verbose: flags.has("--verbose") || flags.has("-V"),
3263
3489
  accept: flags.has("--accept"),
3490
+ offline: flags.has("--offline"),
3491
+ dryRun: flags.has("--dry-run"),
3264
3492
  introspectStdio: flags.has("--introspect-stdio"),
3265
- providers,
3493
+ providers: providers2,
3266
3494
  catalog,
3267
3495
  positionals
3268
3496
  }
3269
3497
  };
3270
3498
  };
3271
3499
  var run = async () => {
3272
- const { command, options } = parse2(process4.argv.slice(2));
3273
- if (process4.stdin.isTTY && !options.all) {
3500
+ const { command, options, unknownFlags } = parse2(process3.argv.slice(2));
3501
+ if (unknownFlags.length) {
3502
+ await error(
3503
+ `Unknown option${unknownFlags.length === 1 ? "" : "s"}: ${unknownFlags.join(", ")}
3504
+ `
3505
+ );
3506
+ console.log(HELP);
3507
+ process3.exitCode = 1;
3508
+ return;
3509
+ }
3510
+ if (process3.stdin.isTTY && !options.all) {
3274
3511
  const { checkNodeForCommand: checkNodeForCommand2 } = await Promise.resolve().then(() => (init_node_guard(), node_guard_exports));
3275
3512
  const guard = checkNodeForCommand2(command);
3276
3513
  if (!guard.ok) {
3277
3514
  await error(guard.message);
3278
- process4.exitCode = 1;
3515
+ process3.exitCode = 1;
3279
3516
  return;
3280
3517
  }
3281
3518
  }
@@ -3301,6 +3538,11 @@ var run = async () => {
3301
3538
  await sync2(options);
3302
3539
  break;
3303
3540
  }
3541
+ case "providers": {
3542
+ const { providers: providers2 } = await Promise.resolve().then(() => (init_providers(), providers_exports));
3543
+ await providers2(options);
3544
+ break;
3545
+ }
3304
3546
  case "update": {
3305
3547
  const { update: update2 } = await Promise.resolve().then(() => (init_update(), update_exports));
3306
3548
  await update2(options);
@@ -3312,11 +3554,6 @@ var run = async () => {
3312
3554
  await list3(options);
3313
3555
  break;
3314
3556
  }
3315
- case "status": {
3316
- const { status: status2 } = await Promise.resolve().then(() => (init_status(), status_exports));
3317
- await status2(options);
3318
- break;
3319
- }
3320
3557
  case "check": {
3321
3558
  const { check: check2 } = await Promise.resolve().then(() => (init_check(), check_exports));
3322
3559
  await check2(options);
@@ -3327,16 +3564,6 @@ var run = async () => {
3327
3564
  await upstream2(options);
3328
3565
  break;
3329
3566
  }
3330
- case "login": {
3331
- const { login: login2 } = await Promise.resolve().then(() => (init_login(), login_exports));
3332
- await login2(options);
3333
- break;
3334
- }
3335
- case "logout": {
3336
- const { logout: logout2 } = await Promise.resolve().then(() => (init_logout(), logout_exports));
3337
- await logout2(options);
3338
- break;
3339
- }
3340
3567
  case "help":
3341
3568
  case "--help":
3342
3569
  case "-h":
@@ -3359,7 +3586,7 @@ var run = async () => {
3359
3586
  console.error(`Unknown command: ${command}
3360
3587
  `);
3361
3588
  console.log(HELP);
3362
- process4.exitCode = 1;
3589
+ process3.exitCode = 1;
3363
3590
  }
3364
3591
  if (!["version", "--version", "-v", "help", "--help", "-h"].includes(command)) {
3365
3592
  await maybeNotifyUpdate(options.json);
@@ -3381,7 +3608,14 @@ ${c.yellow("\u25B2")} ${c.bold("update available")} ${c.dim(
3381
3608
  } catch {
3382
3609
  }
3383
3610
  };
3384
- run().catch(async (err) => {
3385
- await error(err instanceof Error ? err.message : String(err));
3386
- process4.exitCode = 1;
3387
- });
3611
+ var main = () => {
3612
+ run().catch(async (err) => {
3613
+ await error(err instanceof Error ? err.message : String(err));
3614
+ process3.exitCode = 1;
3615
+ });
3616
+ };
3617
+ export {
3618
+ main,
3619
+ parse2 as parse,
3620
+ run
3621
+ };