quiver-cli 0.8.0 → 1.1.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 +127 -45
  2. package/bin/quiver-cli.mjs +3 -1
  3. package/dist/cli.js +666 -436
  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,
@@ -326,7 +309,7 @@ var init_frontmatter = __esm({
326
309
  // src/catalog/discover.ts
327
310
  import { existsSync, readdirSync as readdirSync2, readFileSync as readFileSync2 } from "fs";
328
311
  import { relative as relative2, resolve as resolve2 } from "path";
329
- var readConfig, discoverSkills, discoverCommands, discoverMcp, loadCatalog;
312
+ var readConfig, discoverSkills, discoverCommands, discoverMcp, discoverPlugins, loadCatalog;
330
313
  var init_discover = __esm({
331
314
  "src/catalog/discover.ts"() {
332
315
  "use strict";
@@ -400,6 +383,20 @@ var init_discover = __esm({
400
383
  configDigest: jsonDigest(servers[name])
401
384
  }));
402
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
+ });
403
400
  loadCatalog = (catalog) => {
404
401
  const { config, path } = readConfig(catalog.root);
405
402
  return {
@@ -407,14 +404,15 @@ var init_discover = __esm({
407
404
  configPath: path,
408
405
  skills: discoverSkills(catalog.root),
409
406
  commands: discoverCommands(catalog.root),
410
- mcp: discoverMcp(config)
407
+ mcp: discoverMcp(config),
408
+ plugins: discoverPlugins(catalog.root, config)
411
409
  };
412
410
  };
413
411
  }
414
412
  });
415
413
 
416
414
  // src/catalog/entries.ts
417
- var skillToEntry, commandToEntry, mcpToEntry;
415
+ var skillToEntry, commandToEntry, mcpToEntry, pluginToEntry;
418
416
  var init_entries = __esm({
419
417
  "src/catalog/entries.ts"() {
420
418
  "use strict";
@@ -437,6 +435,13 @@ var init_entries = __esm({
437
435
  tools: null,
438
436
  toolsFetchedAt: null
439
437
  });
438
+ pluginToEntry = (plugin) => ({
439
+ type: "plugin",
440
+ provider: plugin.provider,
441
+ sourcePath: plugin.sourcePath,
442
+ digest: plugin.digest,
443
+ requires: plugin.requires
444
+ });
440
445
  }
441
446
  });
442
447
 
@@ -526,6 +531,12 @@ var init_materialize = __esm({
526
531
  mkdirSync(dirname(dest), { recursive: true });
527
532
  cpSync(command.absPath, dest, { force: true });
528
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
+ }
529
540
  const filteredConfig = {};
530
541
  if (catalog.config.shared) filteredConfig["shared"] = catalog.config.shared;
531
542
  const mcpServers = {};
@@ -533,6 +544,14 @@ var init_materialize = __esm({
533
544
  if (selection.mcp.includes(mcp.name)) mcpServers[mcp.name] = mcp.server;
534
545
  }
535
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;
536
555
  if (catalog.config.claude) filteredConfig["claude"] = catalog.config.claude;
537
556
  writeFileSync(
538
557
  resolve4(destRoot, "config.json"),
@@ -568,53 +587,10 @@ var init_materialize = __esm({
568
587
 
569
588
  // src/github/auth.ts
570
589
  import { execFileSync } from "child_process";
571
- import {
572
- chmodSync,
573
- existsSync as existsSync4,
574
- mkdirSync as mkdirSync2,
575
- readFileSync as readFileSync4,
576
- rmSync as rmSync2,
577
- writeFileSync as writeFileSync2
578
- } from "fs";
579
- import { homedir } from "os";
580
- import { resolve as resolve5 } from "path";
581
- var configDir, authFilePath, readAuthFile, readStoredToken, writeStoredToken, deleteStoredToken, cachedToken, ghToken, resolveGithubToken, resetTokenCache, validateToken;
590
+ var cachedToken, ghToken, resolveGithubToken;
582
591
  var init_auth = __esm({
583
592
  "src/github/auth.ts"() {
584
593
  "use strict";
585
- configDir = () => {
586
- const base = process.env["XDG_CONFIG_HOME"] || resolve5(homedir(), ".config");
587
- return resolve5(base, "quiver");
588
- };
589
- authFilePath = () => resolve5(configDir(), "auth.json");
590
- readAuthFile = () => {
591
- const path = authFilePath();
592
- if (!existsSync4(path)) return null;
593
- try {
594
- return JSON.parse(readFileSync4(path, "utf8"));
595
- } catch {
596
- return null;
597
- }
598
- };
599
- readStoredToken = () => readAuthFile()?.github?.token ?? null;
600
- writeStoredToken = (token, login2) => {
601
- mkdirSync2(configDir(), { recursive: true });
602
- const file = {
603
- github: { token, login: login2, createdAt: (/* @__PURE__ */ new Date()).toISOString() }
604
- };
605
- writeFileSync2(authFilePath(), JSON.stringify(file, null, 2) + "\n", {
606
- mode: 384
607
- });
608
- chmodSync(authFilePath(), 384);
609
- resetTokenCache();
610
- };
611
- deleteStoredToken = () => {
612
- const path = authFilePath();
613
- if (!existsSync4(path)) return false;
614
- rmSync2(path);
615
- resetTokenCache();
616
- return true;
617
- };
618
594
  ghToken = () => {
619
595
  try {
620
596
  const out = execFileSync("gh", ["auth", "token"], {
@@ -629,38 +605,14 @@ var init_auth = __esm({
629
605
  };
630
606
  resolveGithubToken = () => {
631
607
  if (cachedToken !== void 0) return cachedToken;
632
- cachedToken = process.env["GITHUB_TOKEN"] ?? process.env["GH_TOKEN"] ?? readStoredToken() ?? ghToken();
608
+ cachedToken = process.env["GITHUB_TOKEN"] ?? process.env["GH_TOKEN"] ?? ghToken();
633
609
  return cachedToken;
634
610
  };
635
- resetTokenCache = () => {
636
- cachedToken = void 0;
637
- };
638
- validateToken = async (token) => {
639
- let res;
640
- try {
641
- res = await fetch("https://api.github.com/user", {
642
- headers: {
643
- Accept: "application/vnd.github+json",
644
- "User-Agent": "quiver-cli",
645
- Authorization: `Bearer ${token}`
646
- }
647
- });
648
- } catch (err) {
649
- return {
650
- ok: false,
651
- reason: err instanceof Error ? err.message : "fetch failed"
652
- };
653
- }
654
- if (res.status === 401) return { ok: false, reason: "token is invalid or expired" };
655
- if (!res.ok) return { ok: false, reason: `HTTP ${res.status}` };
656
- const body = await res.json();
657
- return { ok: true, login: body.login ?? "unknown" };
658
- };
659
611
  }
660
612
  });
661
613
 
662
614
  // src/github/api.ts
663
- import { mkdirSync as mkdirSync3 } from "fs";
615
+ import { mkdirSync as mkdirSync2 } from "fs";
664
616
  import { Readable } from "stream";
665
617
  import { pipeline } from "stream/promises";
666
618
  import * as tar from "tar";
@@ -678,13 +630,14 @@ var init_api = __esm({
678
630
  if (token) h["Authorization"] = `Bearer ${token}`;
679
631
  return h;
680
632
  };
681
- failureReason = (status2) => {
682
- if (status2 === 401) return "authentication failed - run `quiver-cli login` with a valid token";
683
- if (status2 === 403 || status2 === 429)
684
- return "rate-limited or access denied (run `quiver-cli login` or set GITHUB_TOKEN)";
685
- if (status2 === 404)
686
- return "repo or ref not found (private repo? run `quiver-cli login`)";
687
- 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}`;
688
641
  };
689
642
  apiFetch = async (url) => {
690
643
  let res;
@@ -721,7 +674,7 @@ var init_api = __esm({
721
674
  );
722
675
  if (!res.ok) return res;
723
676
  if (!res.value.body) return { ok: false, reason: "empty tarball response" };
724
- mkdirSync3(destDir, { recursive: true });
677
+ mkdirSync2(destDir, { recursive: true });
725
678
  try {
726
679
  await pipeline(
727
680
  Readable.fromWeb(res.value.body),
@@ -746,9 +699,9 @@ __export(remote_exports, {
746
699
  fetchRemoteCatalog: () => fetchRemoteCatalog,
747
700
  parseGithubSource: () => parseGithubSource
748
701
  });
749
- import { existsSync as existsSync5, mkdtempSync, renameSync, rmSync as rmSync3, mkdirSync as mkdirSync4 } from "fs";
750
- import { homedir as homedir2 } from "os";
751
- 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";
752
705
  var parseGithubSource, catalogCacheRoot, catalogCacheDir, fetchRemoteCatalog;
753
706
  var init_remote = __esm({
754
707
  "src/catalog/remote.ts"() {
@@ -771,10 +724,10 @@ var init_remote = __esm({
771
724
  return { repo: `${owner}/${repo}`, path: pathParts.join("/"), ref };
772
725
  };
773
726
  catalogCacheRoot = () => {
774
- const base = process.env["XDG_CACHE_HOME"] || resolve6(homedir2(), ".cache");
775
- return resolve6(base, "quiver", "catalogs");
727
+ const base = process.env["XDG_CACHE_HOME"] || resolve5(homedir(), ".cache");
728
+ return resolve5(base, "quiver", "catalogs");
776
729
  };
777
- catalogCacheDir = (repo, sha) => resolve6(catalogCacheRoot(), `${repo.replace("/", "-")}-${sha.slice(0, 12)}`);
730
+ catalogCacheDir = (repo, sha) => resolve5(catalogCacheRoot(), `${repo.replace("/", "-")}-${sha.slice(0, 12)}`);
778
731
  fetchRemoteCatalog = async (source, options = {}) => {
779
732
  const spec = parseGithubSource(source);
780
733
  let ref = spec.ref;
@@ -794,22 +747,22 @@ var init_remote = __esm({
794
747
  sha = commit.value;
795
748
  }
796
749
  const cacheDir = catalogCacheDir(spec.repo, sha);
797
- if (!existsSync5(cacheDir)) {
798
- mkdirSync4(dirname2(cacheDir), { recursive: true });
750
+ if (!existsSync4(cacheDir)) {
751
+ mkdirSync3(dirname2(cacheDir), { recursive: true });
799
752
  const tmp = mkdtempSync(`${cacheDir}.tmp-`);
800
753
  const result = await downloadTarball(spec.repo, sha, tmp);
801
754
  if (!result.ok) {
802
- rmSync3(tmp, { recursive: true, force: true });
755
+ rmSync2(tmp, { recursive: true, force: true });
803
756
  throw new Error(`Cannot download ${spec.repo}@${sha.slice(0, 12)}: ${result.reason}`);
804
757
  }
805
758
  try {
806
759
  renameSync(tmp, cacheDir);
807
760
  } catch {
808
- rmSync3(tmp, { recursive: true, force: true });
761
+ rmSync2(tmp, { recursive: true, force: true });
809
762
  }
810
763
  }
811
- const root = spec.path ? resolve6(cacheDir, spec.path) : cacheDir;
812
- if (!existsSync5(root)) {
764
+ const root = spec.path ? resolve5(cacheDir, spec.path) : cacheDir;
765
+ if (!existsSync4(root)) {
813
766
  throw new Error(
814
767
  `Catalog path "${spec.path}" not found in ${spec.repo}@${sha.slice(0, 12)}.`
815
768
  );
@@ -820,21 +773,21 @@ var init_remote = __esm({
820
773
  });
821
774
 
822
775
  // src/catalog/resolve.ts
823
- import { accessSync, constants, existsSync as existsSync6 } from "fs";
824
- 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";
825
778
  import { fileURLToPath } from "url";
826
779
  var packageRoot, DEFAULT_CATALOG_SOURCE, resolveCatalog, isInstalledPackage, isBundledCatalog, isCatalogWritable;
827
780
  var init_resolve = __esm({
828
781
  "src/catalog/resolve.ts"() {
829
782
  "use strict";
830
- packageRoot = resolve7(dirname3(fileURLToPath(import.meta.url)), "..");
783
+ packageRoot = resolve6(dirname3(fileURLToPath(import.meta.url)), "..");
831
784
  DEFAULT_CATALOG_SOURCE = "local:template/.agents";
832
785
  resolveCatalog = async (source = DEFAULT_CATALOG_SOURCE, options = {}) => {
833
786
  const [scheme, ...rest] = source.split(":");
834
787
  const spec = rest.join(":");
835
788
  if (scheme === "local") {
836
- const root = resolve7(packageRoot, spec);
837
- if (!existsSync6(root)) {
789
+ const root = resolve6(packageRoot, spec);
790
+ if (!existsSync5(root)) {
838
791
  throw new Error(`Catalog not found at ${root} (source: ${source}).`);
839
792
  }
840
793
  return { source, root };
@@ -879,7 +832,9 @@ var init_schema = __esm({
879
832
  if (idx === -1) return null;
880
833
  const type = id.slice(0, idx);
881
834
  const name = id.slice(idx + 1);
882
- if (type !== "skill" && type !== "command" && type !== "mcp") return null;
835
+ if (type !== "skill" && type !== "command" && type !== "mcp" && type !== "plugin") {
836
+ return null;
837
+ }
883
838
  if (!name) return null;
884
839
  return { type, name };
885
840
  };
@@ -887,19 +842,19 @@ var init_schema = __esm({
887
842
  });
888
843
 
889
844
  // src/lockfile/io.ts
890
- import { existsSync as existsSync7, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
891
- 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";
892
847
  var lockfilePath, lockfileExists, readLockfile, writeLockfile, emptyLockfile;
893
848
  var init_io = __esm({
894
849
  "src/lockfile/io.ts"() {
895
850
  "use strict";
896
851
  init_schema();
897
- lockfilePath = (targetRoot) => resolve8(targetRoot, LOCKFILE_NAME);
898
- lockfileExists = (targetRoot) => existsSync7(lockfilePath(targetRoot));
852
+ lockfilePath = (targetRoot) => resolve7(targetRoot, LOCKFILE_NAME);
853
+ lockfileExists = (targetRoot) => existsSync6(lockfilePath(targetRoot));
899
854
  readLockfile = (targetRoot) => {
900
855
  const path = lockfilePath(targetRoot);
901
- if (!existsSync7(path)) return null;
902
- const parsed = JSON.parse(readFileSync5(path, "utf8"));
856
+ if (!existsSync6(path)) return null;
857
+ const parsed = JSON.parse(readFileSync4(path, "utf8"));
903
858
  if (parsed.version !== LOCKFILE_VERSION) {
904
859
  throw new Error(
905
860
  `Unsupported ${LOCKFILE_NAME} version ${parsed.version}; expected ${LOCKFILE_VERSION}.`
@@ -918,7 +873,7 @@ var init_io = __esm({
918
873
  ...lock.providers !== void 0 ? { providers: lock.providers } : {},
919
874
  entries: sortedEntries
920
875
  };
921
- writeFileSync3(lockfilePath(targetRoot), JSON.stringify(ordered, null, 2) + "\n");
876
+ writeFileSync2(lockfilePath(targetRoot), JSON.stringify(ordered, null, 2) + "\n");
922
877
  };
923
878
  emptyLockfile = (catalogSource, remote = {}) => ({
924
879
  version: LOCKFILE_VERSION,
@@ -990,7 +945,7 @@ var init_resolve2 = __esm({
990
945
  });
991
946
 
992
947
  // src/providers/claude.ts
993
- import { resolve as resolve9 } from "path";
948
+ import { resolve as resolve8 } from "path";
994
949
  var formatMcpJson, planClaude;
995
950
  var init_claude = __esm({
996
951
  "src/providers/claude.ts"() {
@@ -1005,33 +960,33 @@ var init_claude = __esm({
1005
960
  const removeFiles = [];
1006
961
  if (claudeSettings) {
1007
962
  files.push({
1008
- path: resolve9(targetRoot, ".claude/settings.json"),
963
+ path: resolve8(targetRoot, ".claude/settings.json"),
1009
964
  content: JSON.stringify(claudeSettings, null, 2) + "\n"
1010
965
  });
1011
966
  }
1012
967
  const mcp = formatMcpJson(mcpServers);
1013
- const mcpPath = resolve9(targetRoot, ".mcp.json");
968
+ const mcpPath = resolve8(targetRoot, ".mcp.json");
1014
969
  if (mcp) files.push({ path: mcpPath, content: mcp });
1015
970
  else removeFiles.push(mcpPath);
1016
971
  const skillNames = selected.skills.map((s) => s.name);
1017
972
  const commandFiles = selected.commands.map((c) => `${c.name}.md`);
1018
973
  const symlinks = [
1019
974
  ...selected.skills.map((s) => ({
1020
- path: resolve9(targetRoot, ".claude/skills", s.name),
975
+ path: resolve8(targetRoot, ".claude/skills", s.name),
1021
976
  target: s.absDir
1022
977
  })),
1023
978
  ...selected.commands.map((c) => ({
1024
- path: resolve9(targetRoot, ".claude/commands", `${c.name}.md`),
979
+ path: resolve8(targetRoot, ".claude/commands", `${c.name}.md`),
1025
980
  target: c.absPath
1026
981
  }))
1027
982
  ];
1028
983
  const managedDirs = [
1029
984
  {
1030
- path: resolve9(targetRoot, ".claude/skills"),
985
+ path: resolve8(targetRoot, ".claude/skills"),
1031
986
  expected: new Set(skillNames)
1032
987
  },
1033
988
  {
1034
- path: resolve9(targetRoot, ".claude/commands"),
989
+ path: resolve8(targetRoot, ".claude/commands"),
1035
990
  expected: new Set(commandFiles)
1036
991
  }
1037
992
  ];
@@ -1041,7 +996,7 @@ var init_claude = __esm({
1041
996
  });
1042
997
 
1043
998
  // src/providers/codex.ts
1044
- import { resolve as resolve10 } from "path";
999
+ import { resolve as resolve9 } from "path";
1045
1000
  import TOML from "@iarna/toml";
1046
1001
  var buildConfig, planCodex;
1047
1002
  var init_codex = __esm({
@@ -1085,7 +1040,7 @@ var init_codex = __esm({
1085
1040
  const files = [];
1086
1041
  const removeFiles = [];
1087
1042
  const config = buildConfig(rawMcpServers);
1088
- const path = resolve10(targetRoot, ".codex/config.toml");
1043
+ const path = resolve9(targetRoot, ".codex/config.toml");
1089
1044
  if (config) {
1090
1045
  files.push({
1091
1046
  path,
@@ -1101,19 +1056,19 @@ var init_codex = __esm({
1101
1056
 
1102
1057
  // src/providers/fsops.ts
1103
1058
  import {
1104
- existsSync as existsSync8,
1059
+ existsSync as existsSync7,
1105
1060
  lstatSync,
1106
- mkdirSync as mkdirSync5,
1061
+ mkdirSync as mkdirSync4,
1107
1062
  readdirSync as readdirSync3,
1108
- readFileSync as readFileSync6,
1063
+ readFileSync as readFileSync5,
1109
1064
  readlinkSync,
1110
1065
  rmdirSync,
1111
- rmSync as rmSync4,
1066
+ rmSync as rmSync3,
1112
1067
  symlinkSync,
1113
1068
  unlinkSync,
1114
- writeFileSync as writeFileSync4
1069
+ writeFileSync as writeFileSync3
1115
1070
  } from "fs";
1116
- 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";
1117
1072
  var isENOENT, isMatchingSymlink, removePath, applyOutputs, checkOutputs;
1118
1073
  var init_fsops = __esm({
1119
1074
  "src/providers/fsops.ts"() {
@@ -1122,7 +1077,7 @@ var init_fsops = __esm({
1122
1077
  isMatchingSymlink = (path, target) => {
1123
1078
  const stats = lstatSync(path);
1124
1079
  if (!stats.isSymbolicLink()) return false;
1125
- return resolve11(dirname4(path), readlinkSync(path)) === target;
1080
+ return resolve10(dirname4(path), readlinkSync(path)) === target;
1126
1081
  };
1127
1082
  removePath = (path) => {
1128
1083
  let stats;
@@ -1133,7 +1088,7 @@ var init_fsops = __esm({
1133
1088
  throw e;
1134
1089
  }
1135
1090
  if (stats.isSymbolicLink()) unlinkSync(path);
1136
- else rmSync4(path, { force: true, recursive: true });
1091
+ else rmSync3(path, { force: true, recursive: true });
1137
1092
  };
1138
1093
  applyOutputs = ({
1139
1094
  files,
@@ -1146,22 +1101,22 @@ var init_fsops = __esm({
1146
1101
  for (const out of files) {
1147
1102
  let current = null;
1148
1103
  try {
1149
- current = readFileSync6(out.path, "utf8");
1104
+ current = readFileSync5(out.path, "utf8");
1150
1105
  } catch (e) {
1151
1106
  if (!isENOENT(e)) throw e;
1152
1107
  }
1153
1108
  if (current === out.content) continue;
1154
- mkdirSync5(dirname4(out.path), { recursive: true });
1155
- writeFileSync4(out.path, out.content);
1109
+ mkdirSync4(dirname4(out.path), { recursive: true });
1110
+ writeFileSync3(out.path, out.content);
1156
1111
  result.generated.push(out.path);
1157
1112
  }
1158
1113
  for (const path of removeFiles) {
1159
- if (!existsSync8(path)) continue;
1160
- rmSync4(path, { force: true });
1114
+ if (!existsSync7(path)) continue;
1115
+ rmSync3(path, { force: true });
1161
1116
  result.removed.push(path);
1162
1117
  }
1163
1118
  for (const path of removeDirs) {
1164
- if (!existsSync8(path)) continue;
1119
+ if (!existsSync7(path)) continue;
1165
1120
  removePath(path);
1166
1121
  result.removed.push(path);
1167
1122
  }
@@ -1172,7 +1127,7 @@ var init_fsops = __esm({
1172
1127
  }
1173
1128
  }
1174
1129
  for (const link of symlinks) {
1175
- mkdirSync5(dirname4(link.path), { recursive: true });
1130
+ mkdirSync4(dirname4(link.path), { recursive: true });
1176
1131
  try {
1177
1132
  if (isMatchingSymlink(link.path, link.target)) continue;
1178
1133
  } catch (e) {
@@ -1187,10 +1142,10 @@ var init_fsops = __esm({
1187
1142
  result.linked.push(link.path);
1188
1143
  }
1189
1144
  for (const dir of managedDirs) {
1190
- if (!existsSync8(dir.path)) continue;
1145
+ if (!existsSync7(dir.path)) continue;
1191
1146
  for (const child of readdirSync3(dir.path)) {
1192
1147
  if (dir.expected.has(child)) continue;
1193
- const childPath = resolve11(dir.path, child);
1148
+ const childPath = resolve10(dir.path, child);
1194
1149
  removePath(childPath);
1195
1150
  result.removed.push(childPath);
1196
1151
  }
@@ -1207,7 +1162,7 @@ var init_fsops = __esm({
1207
1162
  const problems = [];
1208
1163
  for (const out of files) {
1209
1164
  try {
1210
- if (readFileSync6(out.path, "utf8") !== out.content) {
1165
+ if (readFileSync5(out.path, "utf8") !== out.content) {
1211
1166
  problems.push(`out of sync: ${out.path}`);
1212
1167
  }
1213
1168
  } catch (e) {
@@ -1216,10 +1171,10 @@ var init_fsops = __esm({
1216
1171
  }
1217
1172
  }
1218
1173
  for (const path of removeFiles) {
1219
- if (existsSync8(path)) problems.push(`stale (should be removed): ${path}`);
1174
+ if (existsSync7(path)) problems.push(`stale (should be removed): ${path}`);
1220
1175
  }
1221
1176
  for (const path of removeDirs) {
1222
- if (existsSync8(path)) problems.push(`stale (should be removed): ${path}`);
1177
+ if (existsSync7(path)) problems.push(`stale (should be removed): ${path}`);
1223
1178
  }
1224
1179
  for (const link of symlinks) {
1225
1180
  try {
@@ -1232,10 +1187,10 @@ var init_fsops = __esm({
1232
1187
  }
1233
1188
  }
1234
1189
  for (const dir of managedDirs) {
1235
- if (!existsSync8(dir.path)) continue;
1190
+ if (!existsSync7(dir.path)) continue;
1236
1191
  for (const child of readdirSync3(dir.path)) {
1237
1192
  if (!dir.expected.has(child)) {
1238
- problems.push(`unexpected shim: ${resolve11(dir.path, child)}`);
1193
+ problems.push(`unexpected shim: ${resolve10(dir.path, child)}`);
1239
1194
  }
1240
1195
  }
1241
1196
  }
@@ -1244,44 +1199,116 @@ var init_fsops = __esm({
1244
1199
  }
1245
1200
  });
1246
1201
 
1202
+ // src/providers/local-config.ts
1203
+ import { existsSync as existsSync8, readFileSync as readFileSync6, rmSync as rmSync4, writeFileSync as writeFileSync4 } from "fs";
1204
+ import { resolve as resolve11 } from "path";
1205
+ var localConfigPath, readLocalConfig, disabledMcpServers, setMcpEnabled;
1206
+ var init_local_config = __esm({
1207
+ "src/providers/local-config.ts"() {
1208
+ "use strict";
1209
+ localConfigPath = (targetRoot) => resolve11(targetRoot, ".agents", "config.local.json");
1210
+ readLocalConfig = (targetRoot) => {
1211
+ const path = localConfigPath(targetRoot);
1212
+ if (!existsSync8(path)) return {};
1213
+ return JSON.parse(readFileSync6(path, "utf8"));
1214
+ };
1215
+ disabledMcpServers = (targetRoot) => {
1216
+ const disabled = /* @__PURE__ */ new Set();
1217
+ const servers = readLocalConfig(targetRoot).mcpServers ?? {};
1218
+ for (const [name, override] of Object.entries(servers)) {
1219
+ if (override.enabled === false) disabled.add(name);
1220
+ }
1221
+ return disabled;
1222
+ };
1223
+ setMcpEnabled = (targetRoot, name, enabled) => {
1224
+ const config = readLocalConfig(targetRoot);
1225
+ const servers = { ...config.mcpServers ?? {} };
1226
+ if (enabled) delete servers[name];
1227
+ else servers[name] = { enabled: false };
1228
+ if (Object.keys(servers).length) config.mcpServers = servers;
1229
+ else delete config.mcpServers;
1230
+ const path = localConfigPath(targetRoot);
1231
+ if (Object.keys(config).length === 0) {
1232
+ rmSync4(path, { force: true });
1233
+ return;
1234
+ }
1235
+ writeFileSync4(path, JSON.stringify(config, null, 2) + "\n");
1236
+ };
1237
+ }
1238
+ });
1239
+
1247
1240
  // src/providers/opencode.ts
1241
+ import { existsSync as existsSync9 } from "fs";
1248
1242
  import { resolve as resolve12 } from "path";
1249
- var formatOpenCodeJson, planOpenCode;
1243
+ var formatOpenCodeJson, preserveEnvReference, preserveEnvReferences, planOpenCode;
1250
1244
  var init_opencode = __esm({
1251
1245
  "src/providers/opencode.ts"() {
1252
1246
  "use strict";
1253
- formatOpenCodeJson = (servers) => {
1254
- if (Object.keys(servers).length === 0) return null;
1247
+ formatOpenCodeJson = (servers, overlay) => {
1248
+ if (Object.keys(servers).length === 0 && !overlay) return null;
1255
1249
  const mcp = {};
1256
1250
  for (const [name, server] of Object.entries(servers)) {
1257
1251
  if (server.transport === "http") {
1258
1252
  mcp[name] = {
1259
1253
  type: "remote",
1260
- url: server.url,
1261
- ...server.headers ? { headers: server.headers } : {}
1254
+ url: preserveEnvReference(server.url),
1255
+ ...server.headers ? { headers: preserveEnvReferences(server.headers) } : {}
1262
1256
  };
1263
1257
  } else {
1264
1258
  mcp[name] = {
1265
1259
  type: "local",
1266
- command: [server.command, ...server.args ?? []],
1267
- ...server.env ? { environment: server.env } : {}
1260
+ command: [server.command, ...server.args ?? []].map(
1261
+ preserveEnvReference
1262
+ ),
1263
+ ...server.env ? { environment: preserveEnvReferences(server.env) } : {}
1268
1264
  };
1269
1265
  }
1270
1266
  }
1271
- return JSON.stringify(
1272
- { $schema: "https://opencode.ai/config.json", mcp },
1273
- null,
1274
- 2
1275
- ) + "\n";
1276
- };
1267
+ const overlayMcp = overlay?.["mcp"] && typeof overlay["mcp"] === "object" ? overlay["mcp"] : {};
1268
+ const mergedMcp = { ...overlayMcp, ...mcp };
1269
+ const config = {
1270
+ ...overlay,
1271
+ $schema: "https://opencode.ai/config.json",
1272
+ ...Object.keys(mergedMcp).length ? { mcp: mergedMcp } : {}
1273
+ };
1274
+ return JSON.stringify(config, null, 2) + "\n";
1275
+ };
1276
+ preserveEnvReference = (value) => value.replace(/\$\{([^}]+)\}/g, "{env:$1}");
1277
+ preserveEnvReferences = (values) => Object.fromEntries(
1278
+ Object.entries(values).map(([key, value]) => [
1279
+ key,
1280
+ preserveEnvReference(value)
1281
+ ])
1282
+ );
1277
1283
  planOpenCode = (inputs) => {
1278
- const { targetRoot, selected, mcpServers } = inputs;
1284
+ const {
1285
+ targetRoot,
1286
+ selected,
1287
+ rawMcpServers,
1288
+ opencodeConfig,
1289
+ tuiConfig
1290
+ } = inputs;
1279
1291
  const files = [];
1280
1292
  const removeFiles = [];
1281
- const json = formatOpenCodeJson(mcpServers);
1293
+ const json = formatOpenCodeJson(rawMcpServers, opencodeConfig);
1282
1294
  const jsonPath = resolve12(targetRoot, "opencode.json");
1283
1295
  if (json) files.push({ path: jsonPath, content: json });
1284
1296
  else removeFiles.push(jsonPath);
1297
+ const tuiPath = resolve12(targetRoot, ".opencode/tui.json");
1298
+ const tuiMarkerPath = resolve12(targetRoot, ".opencode/.quiver-tui");
1299
+ if (tuiConfig) {
1300
+ files.push({
1301
+ path: tuiPath,
1302
+ content: JSON.stringify(
1303
+ { ...tuiConfig, $schema: "https://opencode.ai/tui.json" },
1304
+ null,
1305
+ 2
1306
+ ) + "\n"
1307
+ });
1308
+ files.push({ path: tuiMarkerPath, content: "managed by quiver\n" });
1309
+ } else if (existsSync9(tuiMarkerPath)) {
1310
+ removeFiles.push(tuiPath, tuiMarkerPath);
1311
+ }
1285
1312
  const symlinks = [
1286
1313
  ...selected.skills.map((s) => ({
1287
1314
  path: resolve12(targetRoot, ".opencode/skills", s.name),
@@ -1290,6 +1317,14 @@ var init_opencode = __esm({
1290
1317
  ...selected.commands.map((c) => ({
1291
1318
  path: resolve12(targetRoot, ".opencode/commands", `${c.name}.md`),
1292
1319
  target: c.absPath
1320
+ })),
1321
+ ...selected.plugins.map((plugin) => ({
1322
+ path: resolve12(
1323
+ targetRoot,
1324
+ ".opencode/plugins",
1325
+ `${plugin.name}${plugin.absPath.endsWith(".js") ? ".js" : ".ts"}`
1326
+ ),
1327
+ target: plugin.absPath
1293
1328
  }))
1294
1329
  ];
1295
1330
  const managedDirs = [
@@ -1300,6 +1335,14 @@ var init_opencode = __esm({
1300
1335
  {
1301
1336
  path: resolve12(targetRoot, ".opencode/commands"),
1302
1337
  expected: new Set(selected.commands.map((c) => `${c.name}.md`))
1338
+ },
1339
+ {
1340
+ path: resolve12(targetRoot, ".opencode/plugins"),
1341
+ expected: new Set(
1342
+ selected.plugins.map(
1343
+ (plugin) => `${plugin.name}${plugin.absPath.endsWith(".js") ? ".js" : ".ts"}`
1344
+ )
1345
+ )
1303
1346
  }
1304
1347
  ];
1305
1348
  return { files, removeFiles, symlinks, managedDirs };
@@ -1317,24 +1360,27 @@ var init_selection = __esm({
1317
1360
  const skillNames = /* @__PURE__ */ new Set();
1318
1361
  const commandNames = /* @__PURE__ */ new Set();
1319
1362
  const mcpNames = /* @__PURE__ */ new Set();
1363
+ const pluginNames = /* @__PURE__ */ new Set();
1320
1364
  for (const id of Object.keys(lock.entries)) {
1321
1365
  const parsed = parseEntryId(id);
1322
1366
  if (!parsed) continue;
1323
1367
  if (parsed.type === "skill") skillNames.add(parsed.name);
1324
1368
  else if (parsed.type === "command") commandNames.add(parsed.name);
1325
1369
  else if (parsed.type === "mcp") mcpNames.add(parsed.name);
1370
+ else if (parsed.type === "plugin") pluginNames.add(parsed.name);
1326
1371
  }
1327
1372
  return {
1328
1373
  skills: catalog.skills.filter((s) => skillNames.has(s.name)),
1329
1374
  commands: catalog.commands.filter((c) => commandNames.has(c.name)),
1330
- mcp: catalog.mcp.filter((m) => mcpNames.has(m.name))
1375
+ mcp: catalog.mcp.filter((m) => mcpNames.has(m.name)),
1376
+ plugins: catalog.plugins.filter((p) => pluginNames.has(p.name))
1331
1377
  };
1332
1378
  };
1333
1379
  }
1334
1380
  });
1335
1381
 
1336
1382
  // src/providers/write.ts
1337
- import { existsSync as existsSync9, lstatSync as lstatSync2 } from "fs";
1383
+ import { existsSync as existsSync10, lstatSync as lstatSync2 } from "fs";
1338
1384
  import { basename, relative as relative5, resolve as resolve13 } from "path";
1339
1385
  var isLinkable, buildPlan, writeProviders, formatWriteResult, checkProviders;
1340
1386
  var init_write = __esm({
@@ -1345,6 +1391,7 @@ var init_write = __esm({
1345
1391
  init_claude();
1346
1392
  init_codex();
1347
1393
  init_fsops();
1394
+ init_local_config();
1348
1395
  init_opencode();
1349
1396
  init_selection();
1350
1397
  isLinkable = (path) => {
@@ -1357,6 +1404,10 @@ var init_write = __esm({
1357
1404
  buildPlan = (targetRoot, catalog, lock, onMissingEnv, onSkippedRootFile) => {
1358
1405
  loadEnvLocal(targetRoot);
1359
1406
  const selected = resolveSelection(catalog, lock);
1407
+ const disabled = disabledMcpServers(targetRoot);
1408
+ if (disabled.size) {
1409
+ selected.mcp = selected.mcp.filter((m) => !disabled.has(m.name));
1410
+ }
1360
1411
  const rawMcpServers = {};
1361
1412
  for (const mcp of selected.mcp) rawMcpServers[mcp.name] = mcp.server;
1362
1413
  const mcpServers = interpolateEnvVars(rawMcpServers, onMissingEnv);
@@ -1367,7 +1418,9 @@ var init_write = __esm({
1367
1418
  selected,
1368
1419
  mcpServers,
1369
1420
  rawMcpServers,
1370
- claudeSettings: catalog.config.claude?.settings ?? null
1421
+ claudeSettings: catalog.config.claude?.settings ?? null,
1422
+ opencodeConfig: catalog.config.opencode ?? null,
1423
+ tuiConfig: catalog.config.tui ?? null
1371
1424
  };
1372
1425
  const active = lock.providers?.length ? lock.providers : [...PROVIDERS];
1373
1426
  const planByProvider = {
@@ -1392,7 +1445,7 @@ var init_write = __esm({
1392
1445
  }
1393
1446
  const rootSymlinks = [];
1394
1447
  const agentsMd = resolve13(agentsRoot, "AGENTS.md");
1395
- if (existsSync9(agentsMd)) {
1448
+ if (existsSync10(agentsMd)) {
1396
1449
  const rootAgents = resolve13(targetRoot, "AGENTS.md");
1397
1450
  for (const link of [
1398
1451
  { path: rootAgents, target: agentsMd },
@@ -1458,15 +1511,16 @@ var init_write = __esm({
1458
1511
 
1459
1512
  // src/commands/gitignore.ts
1460
1513
  import { execFileSync as execFileSync2 } from "child_process";
1461
- import { existsSync as existsSync10, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
1514
+ import { existsSync as existsSync11, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
1462
1515
  import { resolve as resolve14 } from "path";
1463
- var HEADER, SECRETS_COMMENT, PROVIDER_ENTRIES, SHARED_ENTRIES, SECRET_ENTRIES, patchGitignore, ignoredSourcePaths;
1516
+ var HEADER, SECRETS_COMMENT, LOCAL_COMMENT, PROVIDER_ENTRIES, SHARED_ENTRIES, SECRET_ENTRIES, LOCAL_ENTRIES, patchGitignore, ensureLocalOverrideIgnored, ignoredSourcePaths;
1464
1517
  var init_gitignore = __esm({
1465
1518
  "src/commands/gitignore.ts"() {
1466
1519
  "use strict";
1467
1520
  init_schema();
1468
1521
  HEADER = "# Generated by quiver (source of truth: .agents/ + quiver.lock)";
1469
1522
  SECRETS_COMMENT = "# Local secrets for MCP env-var interpolation";
1523
+ LOCAL_COMMENT = "# Local overrides (quiver-cli enable/disable)";
1470
1524
  PROVIDER_ENTRIES = {
1471
1525
  claude: [".claude/", ".mcp.json"],
1472
1526
  opencode: [".opencode/", "opencode.json"],
@@ -1474,9 +1528,10 @@ var init_gitignore = __esm({
1474
1528
  };
1475
1529
  SHARED_ENTRIES = ["AGENTS.md", "CLAUDE.md"];
1476
1530
  SECRET_ENTRIES = [".env.local"];
1531
+ LOCAL_ENTRIES = [".agents/config.local.json"];
1477
1532
  patchGitignore = (targetRoot, providers2) => {
1478
1533
  const path = resolve14(targetRoot, ".gitignore");
1479
- const current = existsSync10(path) ? readFileSync7(path, "utf8") : "";
1534
+ const current = existsSync11(path) ? readFileSync7(path, "utf8") : "";
1480
1535
  const lines = new Set(current.split("\n").map((l) => l.trim()));
1481
1536
  const active = providers2?.length ? providers2 : [...PROVIDERS];
1482
1537
  const shimEntries = [
@@ -1485,10 +1540,14 @@ var init_gitignore = __esm({
1485
1540
  ];
1486
1541
  const missingShims = shimEntries.filter((e) => !lines.has(e));
1487
1542
  const missingSecrets = SECRET_ENTRIES.filter((e) => !lines.has(e));
1488
- if (missingShims.length === 0 && missingSecrets.length === 0) return false;
1543
+ const missingLocal = LOCAL_ENTRIES.filter((e) => !lines.has(e));
1544
+ if (missingShims.length === 0 && missingSecrets.length === 0 && missingLocal.length === 0) {
1545
+ return false;
1546
+ }
1489
1547
  const block2 = [];
1490
1548
  if (missingShims.length) block2.push(HEADER, ...missingShims);
1491
1549
  if (missingSecrets.length) block2.push(SECRETS_COMMENT, ...missingSecrets);
1550
+ if (missingLocal.length) block2.push(LOCAL_COMMENT, ...missingLocal);
1492
1551
  const prefix = current && !current.endsWith("\n") ? "\n" : "";
1493
1552
  writeFileSync5(
1494
1553
  path,
@@ -1496,6 +1555,19 @@ var init_gitignore = __esm({
1496
1555
  );
1497
1556
  return true;
1498
1557
  };
1558
+ ensureLocalOverrideIgnored = (targetRoot) => {
1559
+ const path = resolve14(targetRoot, ".gitignore");
1560
+ const current = existsSync11(path) ? readFileSync7(path, "utf8") : "";
1561
+ const lines = new Set(current.split("\n").map((l) => l.trim()));
1562
+ const missing = LOCAL_ENTRIES.filter((e) => !lines.has(e));
1563
+ if (!missing.length) return false;
1564
+ const prefix = current && !current.endsWith("\n") ? "\n" : "";
1565
+ writeFileSync5(
1566
+ path,
1567
+ current + prefix + (current ? "\n" : "") + [LOCAL_COMMENT, ...missing].join("\n") + "\n"
1568
+ );
1569
+ return true;
1570
+ };
1499
1571
  ignoredSourcePaths = (targetRoot) => {
1500
1572
  try {
1501
1573
  const out = execFileSync2(
@@ -1535,12 +1607,24 @@ var init_select = __esm({
1535
1607
  const s = entry.server;
1536
1608
  return s.transport === "http" ? s.url : [s.command, ...s.args ?? []].filter(Boolean).join(" ");
1537
1609
  };
1538
- selectFromCatalog = async (catalog, { interactive }) => {
1610
+ selectFromCatalog = async (catalog, {
1611
+ interactive,
1612
+ providers: providers2
1613
+ }) => {
1539
1614
  const allSkills = catalog.skills.map((s) => s.name);
1540
1615
  const allCommands = catalog.commands.map((c) => c.name);
1541
1616
  const allMcp = catalog.mcp.map((m) => m.name);
1617
+ const availablePlugins = catalog.plugins.filter(
1618
+ (plugin) => providers2.includes(plugin.provider)
1619
+ );
1620
+ const allPlugins = availablePlugins.map((p) => p.name);
1542
1621
  if (!interactive || !process.stdin.isTTY) {
1543
- return { skills: allSkills, commands: allCommands, mcp: allMcp };
1622
+ return {
1623
+ skills: allSkills,
1624
+ commands: allCommands,
1625
+ mcp: allMcp,
1626
+ plugins: allPlugins
1627
+ };
1544
1628
  }
1545
1629
  const groupNames = [...new Set(catalog.skills.map((s) => s.group))].sort(
1546
1630
  (a, b) => a === "general" ? -1 : b === "general" ? 1 : a.localeCompare(b)
@@ -1595,7 +1679,21 @@ var init_select = __esm({
1595
1679
  groups: mcpGroups,
1596
1680
  initialValues: []
1597
1681
  }) : [];
1598
- return { skills, commands, mcp };
1682
+ const plugins = availablePlugins.length ? await selectGrouped({
1683
+ message: "Select plugins (space toggles, a all, enter confirms)",
1684
+ groups: [
1685
+ {
1686
+ name: "opencode",
1687
+ items: availablePlugins.map((plugin) => ({
1688
+ value: plugin.name,
1689
+ label: plugin.name,
1690
+ hint: plugin.requires.length ? `requires: ${plugin.requires.join(", ")}` : void 0
1691
+ }))
1692
+ }
1693
+ ],
1694
+ initialValues: []
1695
+ }) : [];
1696
+ return { skills, commands, mcp, plugins };
1599
1697
  };
1600
1698
  }
1601
1699
  });
@@ -1637,11 +1735,12 @@ var init_init = __esm({
1637
1735
  );
1638
1736
  }
1639
1737
  const sourceCatalog = loadCatalog(source);
1640
- const selection = await selectFromCatalog(sourceCatalog, {
1641
- interactive: !options.all
1642
- });
1643
1738
  const providers2 = await resolveProviders(options);
1644
1739
  if (providers2 === null) return;
1740
+ const selection = await selectFromCatalog(sourceCatalog, {
1741
+ interactive: !options.all,
1742
+ providers: providers2
1743
+ });
1645
1744
  const resolved = materializeCatalog(
1646
1745
  options.targetRoot,
1647
1746
  source,
@@ -1669,9 +1768,14 @@ var init_init = __esm({
1669
1768
  lock.entries[entryId("mcp", mcp.name)] = mcpToEntry(mcp);
1670
1769
  }
1671
1770
  }
1771
+ for (const plugin of catalog.plugins) {
1772
+ if (selection.plugins.includes(plugin.name)) {
1773
+ lock.entries[entryId("plugin", plugin.name)] = pluginToEntry(plugin);
1774
+ }
1775
+ }
1672
1776
  writeLockfile(options.targetRoot, lock);
1673
1777
  await step(
1674
- `Wrote quiver.lock (${selection.skills.length} skills, ${selection.commands.length} commands, ${selection.mcp.length} MCP servers)`
1778
+ `Wrote quiver.lock (${selection.skills.length} skills, ${selection.commands.length} commands, ${selection.mcp.length} MCP servers, ${selection.plugins.length} plugins)`
1675
1779
  );
1676
1780
  const result = writeProviders(options.targetRoot, catalog, lock);
1677
1781
  if (result.generated.length || result.linked.length) {
@@ -1705,7 +1809,7 @@ var init_init = __esm({
1705
1809
  });
1706
1810
 
1707
1811
  // src/catalog/repo.ts
1708
- import { existsSync as existsSync11 } from "fs";
1812
+ import { existsSync as existsSync12 } from "fs";
1709
1813
  import { resolve as resolve15 } from "path";
1710
1814
  var repoCatalogRoot, repoCatalogExists, loadRepoCatalog;
1711
1815
  var init_repo = __esm({
@@ -1713,7 +1817,7 @@ var init_repo = __esm({
1713
1817
  "use strict";
1714
1818
  init_discover();
1715
1819
  repoCatalogRoot = (targetRoot) => resolve15(targetRoot, ".agents");
1716
- repoCatalogExists = (targetRoot) => existsSync11(repoCatalogRoot(targetRoot));
1820
+ repoCatalogExists = (targetRoot) => existsSync12(repoCatalogRoot(targetRoot));
1717
1821
  loadRepoCatalog = (targetRoot, source) => {
1718
1822
  const resolved = {
1719
1823
  source,
@@ -1745,13 +1849,17 @@ var init_add = __esm({
1745
1849
  add = async (options) => {
1746
1850
  const id = options.positionals[0];
1747
1851
  if (!id) {
1748
- await error("Usage: quiver-cli add <skill:name|command:name|mcp:name>");
1852
+ await error(
1853
+ "Usage: quiver-cli add <skill:name|command:name|mcp:name|plugin:name>"
1854
+ );
1749
1855
  process.exitCode = 1;
1750
1856
  return;
1751
1857
  }
1752
1858
  const parsed = parseEntryId(id);
1753
1859
  if (!parsed) {
1754
- await error(`Invalid id "${id}". Expected skill:<name>, command:<name> or mcp:<name>.`);
1860
+ await error(
1861
+ `Invalid id "${id}". Expected skill:<name>, command:<name>, mcp:<name> or plugin:<name>.`
1862
+ );
1755
1863
  process.exitCode = 1;
1756
1864
  return;
1757
1865
  }
@@ -1772,16 +1880,25 @@ var init_add = __esm({
1772
1880
  const skill = sourceCatalog.skills.find((s) => s.name === parsed.name);
1773
1881
  const command = sourceCatalog.commands.find((c) => c.name === parsed.name);
1774
1882
  const mcp = sourceCatalog.mcp.find((m) => m.name === parsed.name);
1775
- const entry = parsed.type === "skill" && skill ? skillToEntry(skill) : parsed.type === "command" && command ? commandToEntry(command) : parsed.type === "mcp" && mcp ? mcpToEntry(mcp) : null;
1883
+ const plugin = sourceCatalog.plugins.find((p) => p.name === parsed.name);
1884
+ 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;
1776
1885
  if (!entry) {
1777
1886
  await error(`${id} not found in catalog.`);
1778
1887
  process.exitCode = 1;
1779
1888
  return;
1780
1889
  }
1890
+ if (entry.type === "plugin" && lock.providers?.length && !lock.providers.includes(entry.provider)) {
1891
+ await error(
1892
+ `${id} requires the ${entry.provider} provider. Enable it with \`quiver-cli providers ${entry.provider}\` first.`
1893
+ );
1894
+ process.exitCode = 1;
1895
+ return;
1896
+ }
1781
1897
  const selection = selectionFromLock(lock);
1782
1898
  if (parsed.type === "skill") selection.skills.push(parsed.name);
1783
1899
  if (parsed.type === "command") selection.commands.push(parsed.name);
1784
1900
  if (parsed.type === "mcp") selection.mcp.push(parsed.name);
1901
+ if (parsed.type === "plugin") selection.plugins.push(parsed.name);
1785
1902
  materializeCatalog(options.targetRoot, source, sourceCatalog, selection);
1786
1903
  lock.entries[id] = entry;
1787
1904
  writeLockfile(options.targetRoot, lock);
@@ -1790,13 +1907,19 @@ var init_add = __esm({
1790
1907
  await success(`Added ${id}.`);
1791
1908
  };
1792
1909
  selectionFromLock = (lock) => {
1793
- const selection = { skills: [], commands: [], mcp: [] };
1910
+ const selection = {
1911
+ skills: [],
1912
+ commands: [],
1913
+ mcp: [],
1914
+ plugins: []
1915
+ };
1794
1916
  for (const lid of Object.keys(lock.entries)) {
1795
1917
  const p = parseEntryId(lid);
1796
1918
  if (!p) continue;
1797
1919
  if (p.type === "skill") selection.skills.push(p.name);
1798
1920
  else if (p.type === "command") selection.commands.push(p.name);
1799
1921
  else if (p.type === "mcp") selection.mcp.push(p.name);
1922
+ else if (p.type === "plugin") selection.plugins.push(p.name);
1800
1923
  }
1801
1924
  return selection;
1802
1925
  };
@@ -1808,9 +1931,9 @@ var remove_exports = {};
1808
1931
  __export(remove_exports, {
1809
1932
  remove: () => remove
1810
1933
  });
1811
- import { existsSync as existsSync12, readdirSync as readdirSync4, readFileSync as readFileSync8, rmSync as rmSync5, writeFileSync as writeFileSync6 } from "fs";
1934
+ import { existsSync as existsSync13, readdirSync as readdirSync4, readFileSync as readFileSync8, rmSync as rmSync5, writeFileSync as writeFileSync6 } from "fs";
1812
1935
  import { resolve as resolve16 } from "path";
1813
- var remove, cleanupEmptyGroups, rewriteRepoMcp;
1936
+ var remove, rewriteRepoPlugins, cleanupEmptyGroups, rewriteRepoMcp;
1814
1937
  var init_remove = __esm({
1815
1938
  "src/commands/remove.ts"() {
1816
1939
  "use strict";
@@ -1823,7 +1946,9 @@ var init_remove = __esm({
1823
1946
  remove = async (options) => {
1824
1947
  const id = options.positionals[0];
1825
1948
  if (!id) {
1826
- await error("Usage: quiver remove <skill:name|command:name|mcp:name>");
1949
+ await error(
1950
+ "Usage: quiver remove <skill:name|command:name|mcp:name|plugin:name>"
1951
+ );
1827
1952
  process.exitCode = 1;
1828
1953
  return;
1829
1954
  }
@@ -1844,30 +1969,45 @@ var init_remove = __esm({
1844
1969
  await info(`${id} is not installed.`);
1845
1970
  return;
1846
1971
  }
1847
- if (entry.type === "skill" || entry.type === "command") {
1972
+ if (entry.type === "skill" || entry.type === "command" || entry.type === "plugin") {
1848
1973
  removeArtifact(options.targetRoot, entry.sourcePath);
1849
1974
  cleanupEmptyGroups(options.targetRoot);
1850
1975
  }
1851
1976
  delete lock.entries[id];
1852
1977
  writeLockfile(options.targetRoot, lock);
1853
1978
  rewriteRepoMcp(options.targetRoot, lock);
1979
+ rewriteRepoPlugins(options.targetRoot, lock);
1854
1980
  const { catalog } = loadRepoCatalog(options.targetRoot, lock.catalog.source);
1855
1981
  writeProviders(options.targetRoot, catalog, lock);
1856
1982
  await success(`Removed ${id}.`);
1857
1983
  };
1984
+ rewriteRepoPlugins = (targetRoot, lock) => {
1985
+ const configPath = resolve16(targetRoot, ".agents/config.json");
1986
+ if (!existsSync13(configPath)) return;
1987
+ const config = JSON.parse(readFileSync8(configPath, "utf8"));
1988
+ if (!config.plugins) return;
1989
+ const keep = new Set(
1990
+ Object.keys(lock.entries).map(parseEntryId).filter((p) => p?.type === "plugin").map((p) => p.name)
1991
+ );
1992
+ config.plugins = Object.fromEntries(
1993
+ Object.entries(config.plugins).filter(([name]) => keep.has(name))
1994
+ );
1995
+ if (!Object.keys(config.plugins).length) delete config.plugins;
1996
+ writeFileSync6(configPath, JSON.stringify(config, null, 2) + "\n");
1997
+ };
1858
1998
  cleanupEmptyGroups = (targetRoot) => {
1859
1999
  const skillsRoot = resolve16(targetRoot, ".agents/skills");
1860
- if (!existsSync12(skillsRoot)) return;
2000
+ if (!existsSync13(skillsRoot)) return;
1861
2001
  for (const entry of readdirSync4(skillsRoot, { withFileTypes: true })) {
1862
2002
  if (!entry.isDirectory()) continue;
1863
2003
  const dir = resolve16(skillsRoot, entry.name);
1864
- if (existsSync12(resolve16(dir, "SKILL.md"))) continue;
2004
+ if (existsSync13(resolve16(dir, "SKILL.md"))) continue;
1865
2005
  if (readdirSync4(dir).length === 0) rmSync5(dir, { recursive: true, force: true });
1866
2006
  }
1867
2007
  };
1868
2008
  rewriteRepoMcp = (targetRoot, lock) => {
1869
2009
  const configPath = resolve16(targetRoot, ".agents/config.json");
1870
- if (!existsSync12(configPath)) return;
2010
+ if (!existsSync13(configPath)) return;
1871
2011
  const config = JSON.parse(readFileSync8(configPath, "utf8"));
1872
2012
  if (!config.mcpServers) return;
1873
2013
  const keep = new Set(
@@ -1884,6 +2024,68 @@ var init_remove = __esm({
1884
2024
  }
1885
2025
  });
1886
2026
 
2027
+ // src/commands/toggle.ts
2028
+ var toggle_exports = {};
2029
+ __export(toggle_exports, {
2030
+ toggle: () => toggle
2031
+ });
2032
+ var toggle;
2033
+ var init_toggle = __esm({
2034
+ "src/commands/toggle.ts"() {
2035
+ "use strict";
2036
+ init_repo();
2037
+ init_io();
2038
+ init_schema();
2039
+ init_local_config();
2040
+ init_write();
2041
+ init_prompts();
2042
+ init_gitignore();
2043
+ toggle = async (options, enabled) => {
2044
+ const verb = enabled ? "enable" : "disable";
2045
+ const id = options.positionals[0];
2046
+ const parsed = id ? parseEntryId(id) : null;
2047
+ if (!parsed || parsed.type !== "mcp") {
2048
+ await error(`Usage: quiver-cli ${verb} mcp:<name>`);
2049
+ process.exitCode = 1;
2050
+ return;
2051
+ }
2052
+ const lock = readLockfile(options.targetRoot);
2053
+ if (!lock) {
2054
+ await error("No quiver.lock found. Run `quiver-cli init` first.");
2055
+ process.exitCode = 1;
2056
+ return;
2057
+ }
2058
+ if (!repoCatalogExists(options.targetRoot)) {
2059
+ await error("No .agents/ directory found. Run `quiver-cli init` first.");
2060
+ process.exitCode = 1;
2061
+ return;
2062
+ }
2063
+ if (!lock.entries[id]) {
2064
+ const installed = Object.keys(lock.entries).map(parseEntryId).filter((p) => p?.type === "mcp").map((p) => p.name).sort((a, b) => a.localeCompare(b));
2065
+ await error(
2066
+ `${id} is not installed.` + (installed.length ? ` Installed MCP servers: ${installed.join(", ")}.` : " No MCP servers installed.")
2067
+ );
2068
+ process.exitCode = 1;
2069
+ return;
2070
+ }
2071
+ const disabled = disabledMcpServers(options.targetRoot);
2072
+ if (enabled !== disabled.has(parsed.name)) {
2073
+ await info(`${id} is already ${enabled ? "enabled" : "disabled"}.`);
2074
+ return;
2075
+ }
2076
+ setMcpEnabled(options.targetRoot, parsed.name, enabled);
2077
+ if (!enabled && ensureLocalOverrideIgnored(options.targetRoot)) {
2078
+ await step("Added .agents/config.local.json to .gitignore");
2079
+ }
2080
+ const { catalog } = loadRepoCatalog(options.targetRoot, lock.catalog.source);
2081
+ writeProviders(options.targetRoot, catalog, lock);
2082
+ await success(
2083
+ enabled ? `Enabled ${id}.` : `Disabled ${id} locally (.agents/config.local.json). Re-enable with \`quiver-cli enable ${id}\`.`
2084
+ );
2085
+ };
2086
+ }
2087
+ });
2088
+
1887
2089
  // src/commands/locksync.ts
1888
2090
  var refreshLockDigests;
1889
2091
  var init_locksync = __esm({
@@ -1894,6 +2096,7 @@ var init_locksync = __esm({
1894
2096
  const skillByName = new Map(catalog.skills.map((s) => [s.name, s]));
1895
2097
  const commandByName = new Map(catalog.commands.map((c) => [c.name, c]));
1896
2098
  const mcpByName = new Map(catalog.mcp.map((m) => [m.name, m]));
2099
+ const pluginByName = new Map(catalog.plugins.map((p) => [p.name, p]));
1897
2100
  for (const [id, entry] of Object.entries(lock.entries)) {
1898
2101
  if (entry.type === "skill") {
1899
2102
  const cat = skillByName.get(id.slice("skill:".length));
@@ -1918,6 +2121,15 @@ var init_locksync = __esm({
1918
2121
  entry.configDigest = cat.configDigest;
1919
2122
  entry.transport = cat.server.transport;
1920
2123
  }
2124
+ } else if (entry.type === "plugin") {
2125
+ const cat = pluginByName.get(id.slice("plugin:".length));
2126
+ if (!cat) continue;
2127
+ if (cat.digest !== entry.digest) {
2128
+ drift.push(`${id}: plugin content changed`);
2129
+ entry.digest = cat.digest;
2130
+ entry.sourcePath = cat.sourcePath;
2131
+ entry.requires = cat.requires;
2132
+ }
1921
2133
  }
1922
2134
  }
1923
2135
  return drift;
@@ -1975,11 +2187,12 @@ var init_sync = __esm({
1975
2187
  const haveSkills = new Set(catalog.skills.map((s) => s.name));
1976
2188
  const haveCommands = new Set(catalog.commands.map((c) => c.name));
1977
2189
  const haveMcp = new Set(catalog.mcp.map((m) => m.name));
2190
+ const havePlugins = new Set(catalog.plugins.map((p) => p.name));
1978
2191
  const orphans = [];
1979
2192
  for (const id of Object.keys(lock.entries)) {
1980
2193
  const p = parseEntryId(id);
1981
2194
  if (!p) continue;
1982
- const present = p.type === "skill" && haveSkills.has(p.name) || p.type === "command" && haveCommands.has(p.name) || p.type === "mcp" && haveMcp.has(p.name);
2195
+ 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);
1983
2196
  if (!present) orphans.push(id);
1984
2197
  }
1985
2198
  if (orphans.length) {
@@ -2085,7 +2298,7 @@ var update_exports = {};
2085
2298
  __export(update_exports, {
2086
2299
  update: () => update
2087
2300
  });
2088
- import { cpSync as cpSync2, mkdirSync as mkdirSync6, readFileSync as readFileSync9, rmSync as rmSync6, writeFileSync as writeFileSync7 } from "fs";
2301
+ import { cpSync as cpSync2, mkdirSync as mkdirSync5, readFileSync as readFileSync9, rmSync as rmSync6, writeFileSync as writeFileSync7 } from "fs";
2089
2302
  import { dirname as dirname5, resolve as resolve17 } from "path";
2090
2303
  var update, applyUpdate, report;
2091
2304
  var init_update = __esm({
@@ -2142,21 +2355,27 @@ var init_update = __esm({
2142
2355
  sourceCatalog,
2143
2356
  repoCatalog,
2144
2357
  lock,
2145
- options.force
2358
+ options.force,
2359
+ options.dryRun
2146
2360
  );
2147
2361
  if (report4.status === "updated") changed = true;
2148
2362
  reports.push(report4);
2149
2363
  }
2150
- if (changed || catalogMoved) {
2151
- writeLockfile(options.targetRoot, lock);
2152
- }
2153
- if (changed) {
2154
- const { catalog } = loadRepoCatalog(options.targetRoot, lock.catalog.source);
2155
- writeProviders(options.targetRoot, catalog, lock);
2364
+ if (!options.dryRun) {
2365
+ if (changed || catalogMoved) {
2366
+ writeLockfile(options.targetRoot, lock);
2367
+ }
2368
+ if (changed) {
2369
+ const { catalog } = loadRepoCatalog(
2370
+ options.targetRoot,
2371
+ lock.catalog.source
2372
+ );
2373
+ writeProviders(options.targetRoot, catalog, lock);
2374
+ }
2156
2375
  }
2157
2376
  report(reports, options);
2158
2377
  };
2159
- applyUpdate = (targetRoot, parsed, entry, sourceCatalog, repoCatalog, lock, force) => {
2378
+ applyUpdate = (targetRoot, parsed, entry, sourceCatalog, repoCatalog, lock, force, dryRun) => {
2160
2379
  const id = `${parsed.type}:${parsed.name}`;
2161
2380
  if (entry.type === "skill") {
2162
2381
  const src2 = sourceCatalog.skills.find((s) => s.name === parsed.name);
@@ -2166,9 +2385,10 @@ var init_update = __esm({
2166
2385
  if (repo2 && repo2.digest !== entry.digest && !force) {
2167
2386
  return { id, status: "local-changes" };
2168
2387
  }
2388
+ if (dryRun) return { id, status: "updated" };
2169
2389
  const dest = resolve17(targetRoot, ".agents", src2.sourcePath);
2170
2390
  rmSync6(dest, { recursive: true, force: true });
2171
- mkdirSync6(dirname5(dest), { recursive: true });
2391
+ mkdirSync5(dirname5(dest), { recursive: true });
2172
2392
  cpSync2(src2.absDir, dest, { recursive: true });
2173
2393
  entry.digest = src2.digest;
2174
2394
  entry.frontmatter = src2.frontmatter;
@@ -2183,13 +2403,38 @@ var init_update = __esm({
2183
2403
  if (repo2 && repo2.digest !== entry.digest && !force) {
2184
2404
  return { id, status: "local-changes" };
2185
2405
  }
2406
+ if (dryRun) return { id, status: "updated" };
2186
2407
  const dest = resolve17(targetRoot, ".agents", src2.sourcePath);
2187
- mkdirSync6(dirname5(dest), { recursive: true });
2408
+ mkdirSync5(dirname5(dest), { recursive: true });
2188
2409
  cpSync2(src2.absPath, dest, { force: true });
2189
2410
  entry.digest = src2.digest;
2190
2411
  entry.sourcePath = src2.sourcePath;
2191
2412
  return { id, status: "updated" };
2192
2413
  }
2414
+ if (entry.type === "plugin") {
2415
+ const src2 = sourceCatalog.plugins.find((p) => p.name === parsed.name);
2416
+ if (!src2) return { id, status: "not-in-catalog" };
2417
+ if (src2.digest === entry.digest) return { id, status: "up-to-date" };
2418
+ const repo2 = repoCatalog.plugins.find((p) => p.name === parsed.name);
2419
+ if (repo2 && repo2.digest !== entry.digest && !force) {
2420
+ return { id, status: "local-changes" };
2421
+ }
2422
+ if (dryRun) return { id, status: "updated" };
2423
+ const dest = resolve17(targetRoot, ".agents", src2.sourcePath);
2424
+ mkdirSync5(dirname5(dest), { recursive: true });
2425
+ cpSync2(src2.absPath, dest, { force: true });
2426
+ const configPath2 = resolve17(targetRoot, ".agents/config.json");
2427
+ const config2 = JSON.parse(readFileSync9(configPath2, "utf8"));
2428
+ config2.plugins = {
2429
+ ...config2.plugins,
2430
+ [parsed.name]: sourceCatalog.config.plugins?.[parsed.name]
2431
+ };
2432
+ writeFileSync7(configPath2, JSON.stringify(config2, null, 2) + "\n");
2433
+ entry.digest = src2.digest;
2434
+ entry.sourcePath = src2.sourcePath;
2435
+ entry.requires = src2.requires;
2436
+ return { id, status: "updated" };
2437
+ }
2193
2438
  const src = sourceCatalog.mcp.find((m) => m.name === parsed.name);
2194
2439
  if (!src) return { id, status: "not-in-catalog" };
2195
2440
  if (src.configDigest === entry.configDigest) return { id, status: "up-to-date" };
@@ -2197,6 +2442,7 @@ var init_update = __esm({
2197
2442
  if (repo && repo.configDigest !== entry.configDigest && !force) {
2198
2443
  return { id, status: "local-changes" };
2199
2444
  }
2445
+ if (dryRun) return { id, status: "updated" };
2200
2446
  const configPath = resolve17(targetRoot, ".agents", "config.json");
2201
2447
  const config = JSON.parse(readFileSync9(configPath, "utf8"));
2202
2448
  config.mcpServers = { ...config.mcpServers, [parsed.name]: src.server };
@@ -2214,6 +2460,7 @@ var init_update = __esm({
2214
2460
  JSON.stringify(
2215
2461
  {
2216
2462
  ok: true,
2463
+ dryRun: options.dryRun,
2217
2464
  updated: by("updated").map((r) => r.id),
2218
2465
  upToDate: by("up-to-date").map((r) => r.id),
2219
2466
  localChanges: by("local-changes").map((r) => r.id),
@@ -2227,7 +2474,8 @@ var init_update = __esm({
2227
2474
  }
2228
2475
  const c = palette();
2229
2476
  const lines = [""];
2230
- for (const r of by("updated")) lines.push(` ${c.cyan("\u2191")} ${r.id} updated`);
2477
+ const verb = options.dryRun ? "would update" : "updated";
2478
+ for (const r of by("updated")) lines.push(` ${c.cyan("\u2191")} ${r.id} ${verb}`);
2231
2479
  for (const r of by("local-changes"))
2232
2480
  lines.push(
2233
2481
  ` ${c.yellow("\u25B2")} ${r.id} ${c.yellow("local changes - skipped (use --force)")}`
@@ -2239,7 +2487,11 @@ var init_update = __esm({
2239
2487
  const updated = by("updated").length;
2240
2488
  lines.push(
2241
2489
  "",
2242
- ` ${updated ? c.cyan(`\u2191 ${updated} updated - review and commit .agents/ + quiver.lock`) : c.green("\u2713 everything up to date with the catalog")}`,
2490
+ ` ${updated ? options.dryRun ? c.cyan(
2491
+ `\u2191 ${updated} would be updated - run without --dry-run to apply`
2492
+ ) : c.cyan(
2493
+ `\u2191 ${updated} updated - review and commit .agents/ + quiver.lock`
2494
+ ) : c.green("\u2713 everything up to date with the catalog")}`,
2243
2495
  ""
2244
2496
  );
2245
2497
  block(lines);
@@ -2259,6 +2511,7 @@ var init_list = __esm({
2259
2511
  init_repo();
2260
2512
  init_io();
2261
2513
  init_schema();
2514
+ init_local_config();
2262
2515
  init_prompts();
2263
2516
  truncate = (s, max) => {
2264
2517
  const flat = s.replace(/\s+/g, " ").trim();
@@ -2287,16 +2540,19 @@ var init_list = __esm({
2287
2540
  const skills = [];
2288
2541
  const commands = [];
2289
2542
  const mcp = [];
2543
+ const plugins = [];
2290
2544
  for (const [id, entry] of Object.entries(lock.entries)) {
2291
2545
  const p = parseEntryId(id);
2292
2546
  if (!p) continue;
2293
2547
  if (entry.type === "skill") skills.push({ name: p.name, entry });
2294
2548
  else if (entry.type === "command") commands.push({ name: p.name, entry });
2295
2549
  else if (entry.type === "mcp") mcp.push({ name: p.name, entry });
2550
+ else if (entry.type === "plugin") plugins.push({ name: p.name, entry });
2296
2551
  }
2297
- for (const group of [skills, commands, mcp]) {
2552
+ for (const group of [skills, commands, mcp, plugins]) {
2298
2553
  group.sort((a, b) => a.name.localeCompare(b.name));
2299
2554
  }
2555
+ const disabled = disabledMcpServers(options.targetRoot);
2300
2556
  if (options.json) {
2301
2557
  console.log(
2302
2558
  JSON.stringify(
@@ -2311,8 +2567,14 @@ var init_list = __esm({
2311
2567
  mcp: mcp.map(({ name, entry }) => ({
2312
2568
  name,
2313
2569
  transport: entry.transport,
2570
+ enabled: !disabled.has(name),
2314
2571
  detail: serverDetail2.get(name) ?? null,
2315
2572
  toolCount: entry.tools ? Object.keys(entry.tools).length : null
2573
+ })),
2574
+ plugins: plugins.map(({ name, entry }) => ({
2575
+ name,
2576
+ provider: entry.provider,
2577
+ requires: entry.requires
2316
2578
  }))
2317
2579
  },
2318
2580
  null,
@@ -2365,16 +2627,24 @@ var init_list = __esm({
2365
2627
  count === null ? c.dim : c.green
2366
2628
  );
2367
2629
  const detail = serverDetail2.get(name);
2630
+ const off = disabled.has(name) ? ` ${c.yellow("disabled")}` : "";
2368
2631
  lines.push(
2369
- ` ${name.padEnd(nameW)} ${entry.transport.padEnd(5)} ${tools}` + (detail ? ` ${c.dim(detail)}` : "")
2632
+ ` ${name.padEnd(nameW)} ${entry.transport.padEnd(5)} ${tools}` + (detail ? ` ${c.dim(detail)}` : "") + off
2370
2633
  );
2371
2634
  }
2372
2635
  }
2636
+ if (plugins.length) {
2637
+ lines.push("", ` ${c.bold("plugins")}`);
2638
+ for (const { name, entry } of plugins) {
2639
+ const requires = entry.requires.length ? ` ${c.dim(`requires: ${entry.requires.join(", ")}`)}` : "";
2640
+ lines.push(` ${name} ${c.dim(entry.provider)}${requires}`);
2641
+ }
2642
+ }
2373
2643
  const providers2 = lock.providers?.length ? lock.providers.join(", ") : "claude, opencode, codex";
2374
2644
  lines.push(
2375
2645
  "",
2376
2646
  ` ${c.bold(
2377
- `${skills.length} skills \xB7 ${commands.length} commands \xB7 ${mcp.length} MCP servers`
2647
+ `${skills.length} skills \xB7 ${commands.length} commands \xB7 ${mcp.length} MCP servers \xB7 ${plugins.length} plugins`
2378
2648
  )} ${c.dim(`providers: ${providers2}`)}`
2379
2649
  );
2380
2650
  if (missingTools) {
@@ -2386,84 +2656,6 @@ var init_list = __esm({
2386
2656
  }
2387
2657
  });
2388
2658
 
2389
- // src/commands/status.ts
2390
- var status_exports = {};
2391
- __export(status_exports, {
2392
- status: () => status
2393
- });
2394
- var status;
2395
- var init_status = __esm({
2396
- "src/commands/status.ts"() {
2397
- "use strict";
2398
- init_repo();
2399
- init_io();
2400
- init_schema();
2401
- init_write();
2402
- init_prompts();
2403
- status = async (options) => {
2404
- const lock = readLockfile(options.targetRoot);
2405
- if (!lock) {
2406
- if (options.json) console.log(JSON.stringify({ ok: false, error: "no-lockfile" }));
2407
- else await error("No quiver.lock found. Run `quiver-cli init` first.");
2408
- process.exitCode = 1;
2409
- return;
2410
- }
2411
- if (!repoCatalogExists(options.targetRoot)) {
2412
- if (options.json) console.log(JSON.stringify({ ok: false, error: "no-agents" }));
2413
- else await error("No .agents/ directory found. Run `quiver-cli init` first.");
2414
- process.exitCode = 1;
2415
- return;
2416
- }
2417
- const { catalog } = loadRepoCatalog(options.targetRoot, lock.catalog.source);
2418
- const haveSkills = new Set(catalog.skills.map((s) => s.name));
2419
- const haveCommands = new Set(catalog.commands.map((c) => c.name));
2420
- const haveMcp = new Set(catalog.mcp.map((m) => m.name));
2421
- const missing = [];
2422
- const changed = [];
2423
- const skillByName = new Map(catalog.skills.map((s) => [s.name, s]));
2424
- const commandByName = new Map(catalog.commands.map((c) => [c.name, c]));
2425
- const mcpByName = new Map(catalog.mcp.map((m) => [m.name, m]));
2426
- for (const [id, entry] of Object.entries(lock.entries)) {
2427
- const p = parseEntryId(id);
2428
- if (!p) continue;
2429
- if (p.type === "skill") {
2430
- if (!haveSkills.has(p.name)) missing.push(id);
2431
- else if (entry.type === "skill" && skillByName.get(p.name).digest !== entry.digest)
2432
- changed.push(id);
2433
- } else if (p.type === "command") {
2434
- if (!haveCommands.has(p.name)) missing.push(id);
2435
- else if (entry.type === "command" && commandByName.get(p.name).digest !== entry.digest)
2436
- changed.push(id);
2437
- } else if (p.type === "mcp") {
2438
- if (!haveMcp.has(p.name)) missing.push(id);
2439
- else if (entry.type === "mcp" && mcpByName.get(p.name).configDigest !== entry.configDigest)
2440
- changed.push(id);
2441
- }
2442
- }
2443
- const shimProblems = checkProviders(options.targetRoot, catalog, lock);
2444
- const ok = missing.length === 0 && changed.length === 0 && shimProblems.length === 0;
2445
- if (options.json) {
2446
- console.log(JSON.stringify({ ok, missing, changed, shims: shimProblems }, null, 2));
2447
- if (!ok) process.exitCode = 1;
2448
- return;
2449
- }
2450
- if (ok) {
2451
- await success("In sync: lockfile, .agents/ and provider shims agree.");
2452
- return;
2453
- }
2454
- if (missing.length)
2455
- await warn(`Missing in .agents/: ${missing.join(", ")}`);
2456
- if (changed.length)
2457
- await warn(`Content changed since lockfile: ${changed.join(", ")}`);
2458
- if (shimProblems.length)
2459
- await warn(`Provider shims out of date:
2460
- - ${shimProblems.join("\n - ")}`);
2461
- await info("Run `quiver-cli sync` to regenerate provider shims and refresh digests.");
2462
- process.exitCode = 1;
2463
- };
2464
- }
2465
- });
2466
-
2467
2659
  // src/mcp/diff.ts
2468
2660
  var diffSnapshots, isEmptyDiff;
2469
2661
  var init_diff = __esm({
@@ -2598,9 +2790,12 @@ var init_snapshot = __esm({
2598
2790
  var check_exports = {};
2599
2791
  __export(check_exports, {
2600
2792
  check: () => check,
2793
+ hasCommand: () => hasCommand,
2601
2794
  summarize: () => summarize
2602
2795
  });
2603
- var check, report2, driftLines, list2, recommend, summarize, truncate2, fail;
2796
+ import { accessSync as accessSync2, constants as constants2 } from "fs";
2797
+ import { delimiter, resolve as resolve18 } from "path";
2798
+ var check, report2, driftLines, list2, recommend, summarize, hasCommand, truncate2, fail;
2604
2799
  var init_check = __esm({
2605
2800
  "src/commands/check.ts"() {
2606
2801
  "use strict";
@@ -2610,6 +2805,8 @@ var init_check = __esm({
2610
2805
  init_diff();
2611
2806
  init_introspect();
2612
2807
  init_snapshot();
2808
+ init_local_config();
2809
+ init_write();
2613
2810
  init_interpolate();
2614
2811
  init_prompts();
2615
2812
  check = async (options) => {
@@ -2624,8 +2821,10 @@ var init_check = __esm({
2624
2821
  const { catalog } = loadRepoCatalog(options.targetRoot, lock.catalog.source);
2625
2822
  const skillByName = new Map(catalog.skills.map((s) => [s.name, s]));
2626
2823
  const commandByName = new Map(catalog.commands.map((c) => [c.name, c]));
2824
+ const pluginByName = new Map(catalog.plugins.map((p) => [p.name, p]));
2627
2825
  const skillDrift = [];
2628
- const checked = { skills: 0, commands: 0, mcp: 0 };
2826
+ const pluginRequirements = [];
2827
+ const checked = { skills: 0, commands: 0, mcp: 0, plugins: 0 };
2629
2828
  for (const [id, entry] of Object.entries(lock.entries)) {
2630
2829
  const p = parseEntryId(id);
2631
2830
  if (!p) continue;
@@ -2639,15 +2838,33 @@ var init_check = __esm({
2639
2838
  if (!cat) continue;
2640
2839
  checked.commands += 1;
2641
2840
  if (cat.digest !== entry.digest) skillDrift.push({ id, kind: "content" });
2841
+ } else if (entry.type === "plugin") {
2842
+ const cat = pluginByName.get(p.name);
2843
+ if (!cat) continue;
2844
+ checked.plugins += 1;
2845
+ if (cat.digest !== entry.digest) skillDrift.push({ id, kind: "content" });
2846
+ if (lock.providers?.length && !lock.providers.includes(entry.provider)) {
2847
+ continue;
2848
+ }
2849
+ for (const command of entry.requires) {
2850
+ if (!hasCommand(command)) pluginRequirements.push({ id, command });
2851
+ }
2642
2852
  }
2643
2853
  }
2854
+ const shimProblems = checkProviders(options.targetRoot, catalog, lock);
2644
2855
  const mcpReports = [];
2856
+ const disabled = disabledMcpServers(options.targetRoot);
2645
2857
  let lockChanged = false;
2646
2858
  for (const [id, entry] of Object.entries(lock.entries)) {
2859
+ if (options.offline) break;
2647
2860
  if (entry.type !== "mcp") continue;
2648
2861
  const p = parseEntryId(id);
2649
2862
  const catMcp = catalog.mcp.find((m) => m.name === p.name);
2650
2863
  if (!catMcp) continue;
2864
+ if (disabled.has(p.name)) {
2865
+ mcpReports.push({ id, status: "skipped", reason: "disabled locally" });
2866
+ continue;
2867
+ }
2651
2868
  checked.mcp += 1;
2652
2869
  const server = interpolateEnvVars(catMcp.server);
2653
2870
  const res = await introspect(server, { allowStdio: options.introspectStdio });
@@ -2677,11 +2894,18 @@ var init_check = __esm({
2677
2894
  }
2678
2895
  }
2679
2896
  if (lockChanged) writeLockfile(options.targetRoot, lock);
2680
- const hasDrift = skillDrift.length > 0 || mcpReports.some((r) => r.status === "drift");
2897
+ const hasDrift = skillDrift.length > 0 || pluginRequirements.length > 0 || shimProblems.length > 0 || mcpReports.some((r) => r.status === "drift");
2681
2898
  if (options.json) {
2682
2899
  console.log(
2683
2900
  JSON.stringify(
2684
- { ok: !hasDrift, checked, skillDrift, mcp: mcpReports },
2901
+ {
2902
+ ok: !hasDrift,
2903
+ checked,
2904
+ skillDrift,
2905
+ pluginRequirements,
2906
+ shims: shimProblems,
2907
+ mcp: mcpReports
2908
+ },
2685
2909
  null,
2686
2910
  2
2687
2911
  )
@@ -2689,16 +2913,35 @@ var init_check = __esm({
2689
2913
  if (hasDrift) process.exitCode = 1;
2690
2914
  return;
2691
2915
  }
2692
- await report2(skillDrift, mcpReports, checked, options);
2916
+ await report2(
2917
+ skillDrift,
2918
+ pluginRequirements,
2919
+ shimProblems,
2920
+ mcpReports,
2921
+ checked,
2922
+ options
2923
+ );
2693
2924
  if (hasDrift) process.exitCode = 1;
2694
2925
  };
2695
- report2 = async (skillDrift, mcpReports, checked, options) => {
2926
+ report2 = async (skillDrift, pluginRequirements, shimProblems, mcpReports, checked, options) => {
2696
2927
  if (skillDrift.length) {
2697
2928
  await warn(
2698
- `Skill/command content changed since lockfile:
2929
+ `Managed content changed since lockfile:
2699
2930
  - ${skillDrift.map((s) => s.id).join("\n - ")}`
2700
2931
  );
2701
2932
  }
2933
+ if (pluginRequirements.length) {
2934
+ await warn(
2935
+ `Plugin requirements missing:
2936
+ - ${pluginRequirements.map((issue) => `${issue.id}: ${issue.command}`).join("\n - ")}`
2937
+ );
2938
+ }
2939
+ if (shimProblems.length) {
2940
+ await warn(
2941
+ `Provider shims out of date:
2942
+ - ${shimProblems.join("\n - ")}`
2943
+ );
2944
+ }
2702
2945
  const skipped = mcpReports.filter((r) => r.status === "skipped");
2703
2946
  if (skipped.length) {
2704
2947
  const names = skipped.map((r) => parseEntryId(r.id)?.name ?? r.id);
@@ -2723,12 +2966,13 @@ var init_check = __esm({
2723
2966
  - ${driftLines(r.diff, options.verbose).join("\n - ")}`);
2724
2967
  }
2725
2968
  const summary = summarize(checked);
2726
- const hasDrift = skillDrift.length > 0 || drifted.length > 0;
2727
- if (!hasDrift) {
2969
+ const hasDrift = skillDrift.length > 0 || shimProblems.length > 0 || drifted.length > 0;
2970
+ const hasProblems = hasDrift || pluginRequirements.length > 0;
2971
+ if (!hasProblems) {
2728
2972
  await success(`check passed: ${summary}, no drift detected.`);
2729
2973
  } else {
2730
2974
  await info(`checked ${summary}, drift detected.`);
2731
- await recommend(skillDrift, drifted);
2975
+ await recommend(skillDrift, shimProblems, drifted);
2732
2976
  }
2733
2977
  };
2734
2978
  driftLines = (diff, verbose) => {
@@ -2762,9 +3006,12 @@ var init_check = __esm({
2762
3006
  const rest = names.length - sample;
2763
3007
  return `${names.length} tools (${names.slice(0, sample).join(", ")}, \u2026 +${rest} more)`;
2764
3008
  };
2765
- recommend = async (skillDrift, drifted) => {
3009
+ recommend = async (skillDrift, shimProblems, drifted) => {
2766
3010
  const c = palette();
2767
3011
  const lines = ["to update the lockfile baseline:"];
3012
+ if (shimProblems.length) {
3013
+ lines.push(` ${c.cyan("quiver-cli sync")} regenerate provider shims`);
3014
+ }
2768
3015
  if (drifted.length) {
2769
3016
  lines.push(` ${c.cyan("quiver-cli check --accept")} accept the new MCP tool snapshots`);
2770
3017
  }
@@ -2786,8 +3033,23 @@ var init_check = __esm({
2786
3033
  if (c.skills) parts.push(plural(c.skills, "skill"));
2787
3034
  if (c.commands) parts.push(plural(c.commands, "command"));
2788
3035
  if (c.mcp) parts.push(plural(c.mcp, "MCP server"));
3036
+ if (c.plugins) parts.push(plural(c.plugins, "plugin"));
2789
3037
  return parts.length ? parts.join(", ") : "nothing";
2790
3038
  };
3039
+ hasCommand = (command) => {
3040
+ if (!/^[A-Za-z0-9._-]+$/.test(command)) return false;
3041
+ const extensions = process.platform === "win32" ? (process.env["PATHEXT"] ?? ".EXE;.CMD;.BAT;.COM").split(";") : [""];
3042
+ for (const dir of (process.env["PATH"] ?? "").split(delimiter)) {
3043
+ for (const extension of extensions) {
3044
+ try {
3045
+ accessSync2(resolve18(dir, command + extension), constants2.X_OK);
3046
+ return true;
3047
+ } catch {
3048
+ }
3049
+ }
3050
+ }
3051
+ return false;
3052
+ };
2791
3053
  truncate2 = (s, max = 120) => s.length > max ? s.slice(0, max) + "\u2026" : s;
2792
3054
  fail = async (options, code, message) => {
2793
3055
  if (options.json) console.log(JSON.stringify({ ok: false, error: code }));
@@ -2800,14 +3062,14 @@ var init_check = __esm({
2800
3062
  // src/catalog/upstreams.ts
2801
3063
  import { execFileSync as execFileSync3 } from "child_process";
2802
3064
  import {
2803
- existsSync as existsSync13,
3065
+ existsSync as existsSync14,
2804
3066
  mkdtempSync as mkdtempSync2,
2805
3067
  readFileSync as readFileSync10,
2806
3068
  rmSync as rmSync7,
2807
3069
  writeFileSync as writeFileSync8
2808
3070
  } from "fs";
2809
3071
  import { tmpdir } from "os";
2810
- import { join, resolve as resolve18 } from "path";
3072
+ import { join, resolve as resolve19 } from "path";
2811
3073
  var UPSTREAMS_FILE, upstreamsPath, loadUpstreams, writeUpstreams, fetchLatestCommit, fetchUpstreamDir, short, evaluateOrigin;
2812
3074
  var init_upstreams = __esm({
2813
3075
  "src/catalog/upstreams.ts"() {
@@ -2815,10 +3077,10 @@ var init_upstreams = __esm({
2815
3077
  init_auth();
2816
3078
  init_auth();
2817
3079
  UPSTREAMS_FILE = "upstreams.json";
2818
- upstreamsPath = (catalog) => resolve18(catalog.root, UPSTREAMS_FILE);
3080
+ upstreamsPath = (catalog) => resolve19(catalog.root, UPSTREAMS_FILE);
2819
3081
  loadUpstreams = (catalog) => {
2820
3082
  const path = upstreamsPath(catalog);
2821
- if (!existsSync13(path)) return {};
3083
+ if (!existsSync14(path)) return {};
2822
3084
  return JSON.parse(readFileSync10(path, "utf8"));
2823
3085
  };
2824
3086
  writeUpstreams = (catalog, map) => {
@@ -2887,8 +3149,8 @@ var init_upstreams = __esm({
2887
3149
  const msg = err instanceof Error && "stderr" in err ? String(err.stderr).trim().split("\n").pop() : err instanceof Error ? err.message : "git clone failed";
2888
3150
  return { ok: false, reason: msg || "git clone failed" };
2889
3151
  }
2890
- const dir = resolve18(tmp, origin.path);
2891
- if (!existsSync13(resolve18(dir, "SKILL.md"))) {
3152
+ const dir = resolve19(tmp, origin.path);
3153
+ if (!existsSync14(resolve19(dir, "SKILL.md"))) {
2892
3154
  cleanup();
2893
3155
  return { ok: false, reason: `no SKILL.md at ${origin.path} in ${origin.repo}` };
2894
3156
  }
@@ -2988,7 +3250,7 @@ var init_upstream = __esm({
2988
3250
  guardWritableCatalog = async (catalog) => {
2989
3251
  if (isCatalogWritable(catalog)) return false;
2990
3252
  await error(
2991
- "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."
3253
+ "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."
2992
3254
  );
2993
3255
  process.exitCode = 1;
2994
3256
  return true;
@@ -3120,14 +3382,23 @@ var init_upstream = __esm({
3120
3382
  lines.push(` ${mark[r.status]} ${c.bold(name)} ${detail} ${where}`);
3121
3383
  }
3122
3384
  block(lines);
3123
- const drifted = sorted.filter(
3124
- (r) => r.status === "drift" || r.status === "drift-curated"
3125
- );
3126
- if (drifted.length) {
3385
+ const drifted = sorted.filter((r) => r.status === "drift");
3386
+ const curated = sorted.filter((r) => r.status === "drift-curated");
3387
+ if (drifted.length || curated.length) {
3127
3388
  const hint = [""];
3128
- for (const r of drifted) {
3129
- const note = r.status === "drift-curated" ? "curated \u2014 reconcile changes by hand" : "re-fetch with the skills CLI, then copy into the catalog";
3130
- hint.push(` ${c.yellow(r.name)}: ${c.dim(note)}`);
3389
+ if (drifted.length) {
3390
+ hint.push(
3391
+ ` ${c.cyan("quiver-cli upstream pull")} ${c.dim(
3392
+ `update the catalog copy (${drifted.map((r) => r.name).join(", ")})`
3393
+ )}`
3394
+ );
3395
+ }
3396
+ for (const r of curated) {
3397
+ hint.push(
3398
+ ` ${c.yellow(r.name)}: ${c.dim(
3399
+ "curated \u2014 reconcile by hand, or upstream pull --force"
3400
+ )}`
3401
+ );
3131
3402
  }
3132
3403
  block(hint);
3133
3404
  }
@@ -3163,83 +3434,6 @@ var init_upstream = __esm({
3163
3434
  }
3164
3435
  });
3165
3436
 
3166
- // src/commands/login.ts
3167
- var login_exports = {};
3168
- __export(login_exports, {
3169
- login: () => login
3170
- });
3171
- import process3 from "process";
3172
- var login, promptToken, readStdin;
3173
- var init_login = __esm({
3174
- "src/commands/login.ts"() {
3175
- "use strict";
3176
- init_auth();
3177
- init_prompts();
3178
- login = async (options) => {
3179
- const token = process3.stdin.isTTY ? await promptToken() : await readStdin();
3180
- if (!token) {
3181
- await error(
3182
- "No token provided. Create a GitHub personal access token with `repo` read access and try again."
3183
- );
3184
- process3.exitCode = 1;
3185
- return;
3186
- }
3187
- const validation = await validateToken(token);
3188
- if (!validation.ok) {
3189
- await error(`Token validation failed: ${validation.reason}`);
3190
- process3.exitCode = 1;
3191
- return;
3192
- }
3193
- writeStoredToken(token, validation.login);
3194
- if (options.json) {
3195
- console.log(JSON.stringify({ ok: true, login: validation.login }, null, 2));
3196
- return;
3197
- }
3198
- await success(
3199
- `Logged in as ${validation.login}. Token stored in ${authFilePath()}.`
3200
- );
3201
- };
3202
- promptToken = async () => {
3203
- await info(
3204
- "Paste a GitHub personal access token (used for github: catalogs; needs read access to the catalog repo)."
3205
- );
3206
- const value = await password("GitHub token");
3207
- return value?.trim() || null;
3208
- };
3209
- readStdin = async () => {
3210
- let data = "";
3211
- for await (const chunk of process3.stdin) data += chunk;
3212
- return data.trim() || null;
3213
- };
3214
- }
3215
- });
3216
-
3217
- // src/commands/logout.ts
3218
- var logout_exports = {};
3219
- __export(logout_exports, {
3220
- logout: () => logout
3221
- });
3222
- var logout;
3223
- var init_logout = __esm({
3224
- "src/commands/logout.ts"() {
3225
- "use strict";
3226
- init_auth();
3227
- init_prompts();
3228
- logout = async (options) => {
3229
- const removed = deleteStoredToken();
3230
- if (options.json) {
3231
- console.log(JSON.stringify({ ok: true, removed }, null, 2));
3232
- return;
3233
- }
3234
- if (removed) {
3235
- await success(`Logged out. Removed ${authFilePath()}.`);
3236
- } else {
3237
- await info("Not logged in - nothing to remove.");
3238
- }
3239
- };
3240
- }
3241
- });
3242
-
3243
3437
  // src/version/notifier.ts
3244
3438
  var notifier_exports = {};
3245
3439
  __export(notifier_exports, {
@@ -3249,9 +3443,9 @@ __export(notifier_exports, {
3249
3443
  installHint: () => installHint,
3250
3444
  notifierSuppressed: () => notifierSuppressed
3251
3445
  });
3252
- import { existsSync as existsSync14, mkdirSync as mkdirSync7, readFileSync as readFileSync11, writeFileSync as writeFileSync9 } from "fs";
3253
- import { homedir as homedir3 } from "os";
3254
- import { dirname as dirname6, resolve as resolve19 } from "path";
3446
+ import { existsSync as existsSync15, mkdirSync as mkdirSync6, readFileSync as readFileSync11, writeFileSync as writeFileSync9 } from "fs";
3447
+ import { homedir as homedir2 } from "os";
3448
+ import { dirname as dirname6, resolve as resolve20 } from "path";
3255
3449
  var REGISTRY_URL, CHECK_TTL_MS, FETCH_TIMEOUT_MS, INSTALL_HINT, cacheFilePath, installHint, getCurrentVersion, compareSemver, readCache, writeCache, fetchLatestVersion, checkForUpdate, notifierSuppressed;
3256
3450
  var init_notifier = __esm({
3257
3451
  "src/version/notifier.ts"() {
@@ -3262,14 +3456,14 @@ var init_notifier = __esm({
3262
3456
  FETCH_TIMEOUT_MS = 2e3;
3263
3457
  INSTALL_HINT = "pnpm add -g quiver-cli";
3264
3458
  cacheFilePath = () => {
3265
- const base = process.env["XDG_CACHE_HOME"] || resolve19(homedir3(), ".cache");
3266
- return resolve19(base, "quiver", "update-check.json");
3459
+ const base = process.env["XDG_CACHE_HOME"] || resolve20(homedir2(), ".cache");
3460
+ return resolve20(base, "quiver", "update-check.json");
3267
3461
  };
3268
3462
  installHint = () => INSTALL_HINT;
3269
3463
  getCurrentVersion = () => {
3270
3464
  try {
3271
3465
  const pkg = JSON.parse(
3272
- readFileSync11(resolve19(packageRoot, "package.json"), "utf8")
3466
+ readFileSync11(resolve20(packageRoot, "package.json"), "utf8")
3273
3467
  );
3274
3468
  return pkg.version;
3275
3469
  } catch {
@@ -3295,7 +3489,7 @@ var init_notifier = __esm({
3295
3489
  };
3296
3490
  readCache = () => {
3297
3491
  const path = cacheFilePath();
3298
- if (!existsSync14(path)) return null;
3492
+ if (!existsSync15(path)) return null;
3299
3493
  try {
3300
3494
  return JSON.parse(readFileSync11(path, "utf8"));
3301
3495
  } catch {
@@ -3305,7 +3499,7 @@ var init_notifier = __esm({
3305
3499
  writeCache = (cache) => {
3306
3500
  try {
3307
3501
  const path = cacheFilePath();
3308
- mkdirSync7(dirname6(path), { recursive: true });
3502
+ mkdirSync6(dirname6(path), { recursive: true });
3309
3503
  writeFileSync9(path, JSON.stringify(cache, null, 2) + "\n");
3310
3504
  } catch {
3311
3505
  }
@@ -3350,58 +3544,87 @@ var init_notifier = __esm({
3350
3544
 
3351
3545
  // src/cli.ts
3352
3546
  init_prompts();
3353
- import process4 from "process";
3354
- var HELP = `quiver-cli - compose agent skills, commands & MCP servers into any repo
3547
+ import process3 from "process";
3548
+ var HELP = `quiver-cli - compose agent skills, commands, plugins & MCP servers into any repo
3355
3549
 
3356
3550
  Usage:
3357
3551
  quiver-cli <command> [options]
3358
3552
 
3359
3553
  Commands:
3360
3554
  init Interactive picker over the catalog; write native configs + quiver.lock
3361
- add <id> Add a single catalog entry (skill:<name>, command:<name>, mcp:<name>)
3555
+ add <id> Add an entry (skill:<name>, command:<name>, mcp:<name>, plugin:<name>)
3362
3556
  remove <id> Remove a single entry; keep lockfile + configs consistent
3557
+ disable <id> Turn an MCP server off locally (mcp:<name>, gitignored override)
3558
+ enable <id> Turn a locally disabled MCP server back on
3363
3559
  sync Regenerate provider configs from .agents/ (warns on drift)
3364
3560
  providers [a,b] Change which tools get configs (claude, opencode, codex)
3365
3561
  update [id] Pull newer catalog content into .agents/ (all or one entry)
3366
- list Show installed entries (skills, commands, MCP tool counts)
3367
- status Diff the lockfile against what is actually in the repo
3368
- check Detect upstream drift (skill digests, MCP tool snapshots)
3562
+ list Show installed entries (skills, commands, plugins, MCP tool counts)
3563
+ check Detect drift: skill digests, provider shims, MCP tool
3564
+ snapshots (--offline skips MCP re-introspection)
3369
3565
  upstream Catalog maintenance: check source repos for skill updates
3370
3566
  (run in the quiver-cli repo or with a writable --catalog)
3371
3567
  upstream pull Pull latest upstream content into the catalog [skill]
3372
- login Store a GitHub token for remote (github:) catalogs
3373
- logout Remove the stored GitHub token
3374
3568
  help Show this help
3375
3569
  version Show the quiver-cli version
3376
3570
 
3377
3571
  Options:
3378
3572
  -f, --force Overwrite existing files
3379
3573
  --all, -y Keep everything without prompting (non-interactive)
3380
- --json Machine-readable output (status/check/upstream/list)
3574
+ --json Machine-readable output (check/upstream/list)
3381
3575
  -V, --verbose Show full tool lists and description diffs (check)
3382
3576
  --accept Record the current MCP tool snapshots as the new baseline (check)
3577
+ --offline Skip MCP re-introspection; check digests + shims only (check)
3578
+ --dry-run Report what would change without writing (update)
3383
3579
  --providers=a,b Generate configs only for these tools (init, sync, providers)
3384
3580
  --catalog=<source> Catalog source for init (e.g. github:owner/repo[/path][#ref])
3385
3581
  --introspect-stdio Allow introspecting stdio MCP servers (runs foreign code)
3386
3582
  -v, --version Show the quiver-cli version
3387
3583
  `;
3584
+ var KNOWN_FLAGS = /* @__PURE__ */ new Set([
3585
+ "--force",
3586
+ "-f",
3587
+ "--all",
3588
+ "--yes",
3589
+ "-y",
3590
+ "--json",
3591
+ "--verbose",
3592
+ "-V",
3593
+ "--accept",
3594
+ "--offline",
3595
+ "--dry-run",
3596
+ "--introspect-stdio",
3597
+ "--help",
3598
+ "-h",
3599
+ "--version",
3600
+ "-v"
3601
+ ]);
3602
+ var KNOWN_VALUE_FLAGS = ["--providers", "--catalog"];
3603
+ var isKnownFlag = (arg) => {
3604
+ if (KNOWN_FLAGS.has(arg)) return true;
3605
+ return KNOWN_VALUE_FLAGS.some((f) => arg.startsWith(`${f}=`));
3606
+ };
3388
3607
  var parse2 = (argv) => {
3389
3608
  const [command = "init", ...rest] = argv;
3390
3609
  const flags = new Set(rest.filter((a) => a.startsWith("-")));
3391
3610
  const positionals = rest.filter((a) => !a.startsWith("-"));
3611
+ const unknownFlags = [...flags].filter((a) => !isKnownFlag(a));
3392
3612
  const providersFlag = rest.find((a) => a.startsWith("--providers="));
3393
3613
  const providers2 = providersFlag ? providersFlag.slice("--providers=".length).split(",").map((p) => p.trim()).filter(Boolean) : null;
3394
3614
  const catalogFlag = rest.find((a) => a.startsWith("--catalog="));
3395
3615
  const catalog = catalogFlag ? catalogFlag.slice("--catalog=".length).trim() || null : null;
3396
3616
  return {
3397
3617
  command,
3618
+ unknownFlags,
3398
3619
  options: {
3399
- targetRoot: process4.cwd(),
3620
+ targetRoot: process3.cwd(),
3400
3621
  force: flags.has("--force") || flags.has("-f"),
3401
3622
  all: flags.has("--all") || flags.has("--yes") || flags.has("-y"),
3402
3623
  json: flags.has("--json"),
3403
3624
  verbose: flags.has("--verbose") || flags.has("-V"),
3404
3625
  accept: flags.has("--accept"),
3626
+ offline: flags.has("--offline"),
3627
+ dryRun: flags.has("--dry-run"),
3405
3628
  introspectStdio: flags.has("--introspect-stdio"),
3406
3629
  providers: providers2,
3407
3630
  catalog,
@@ -3410,13 +3633,22 @@ var parse2 = (argv) => {
3410
3633
  };
3411
3634
  };
3412
3635
  var run = async () => {
3413
- const { command, options } = parse2(process4.argv.slice(2));
3414
- if (process4.stdin.isTTY && !options.all) {
3636
+ const { command, options, unknownFlags } = parse2(process3.argv.slice(2));
3637
+ if (unknownFlags.length) {
3638
+ await error(
3639
+ `Unknown option${unknownFlags.length === 1 ? "" : "s"}: ${unknownFlags.join(", ")}
3640
+ `
3641
+ );
3642
+ console.log(HELP);
3643
+ process3.exitCode = 1;
3644
+ return;
3645
+ }
3646
+ if (process3.stdin.isTTY && !options.all) {
3415
3647
  const { checkNodeForCommand: checkNodeForCommand2 } = await Promise.resolve().then(() => (init_node_guard(), node_guard_exports));
3416
3648
  const guard = checkNodeForCommand2(command);
3417
3649
  if (!guard.ok) {
3418
3650
  await error(guard.message);
3419
- process4.exitCode = 1;
3651
+ process3.exitCode = 1;
3420
3652
  return;
3421
3653
  }
3422
3654
  }
@@ -3437,6 +3669,12 @@ var run = async () => {
3437
3669
  await remove2(options);
3438
3670
  break;
3439
3671
  }
3672
+ case "enable":
3673
+ case "disable": {
3674
+ const { toggle: toggle2 } = await Promise.resolve().then(() => (init_toggle(), toggle_exports));
3675
+ await toggle2(options, command === "enable");
3676
+ break;
3677
+ }
3440
3678
  case "sync": {
3441
3679
  const { sync: sync2 } = await Promise.resolve().then(() => (init_sync(), sync_exports));
3442
3680
  await sync2(options);
@@ -3458,11 +3696,6 @@ var run = async () => {
3458
3696
  await list3(options);
3459
3697
  break;
3460
3698
  }
3461
- case "status": {
3462
- const { status: status2 } = await Promise.resolve().then(() => (init_status(), status_exports));
3463
- await status2(options);
3464
- break;
3465
- }
3466
3699
  case "check": {
3467
3700
  const { check: check2 } = await Promise.resolve().then(() => (init_check(), check_exports));
3468
3701
  await check2(options);
@@ -3473,16 +3706,6 @@ var run = async () => {
3473
3706
  await upstream2(options);
3474
3707
  break;
3475
3708
  }
3476
- case "login": {
3477
- const { login: login2 } = await Promise.resolve().then(() => (init_login(), login_exports));
3478
- await login2(options);
3479
- break;
3480
- }
3481
- case "logout": {
3482
- const { logout: logout2 } = await Promise.resolve().then(() => (init_logout(), logout_exports));
3483
- await logout2(options);
3484
- break;
3485
- }
3486
3709
  case "help":
3487
3710
  case "--help":
3488
3711
  case "-h":
@@ -3505,7 +3728,7 @@ var run = async () => {
3505
3728
  console.error(`Unknown command: ${command}
3506
3729
  `);
3507
3730
  console.log(HELP);
3508
- process4.exitCode = 1;
3731
+ process3.exitCode = 1;
3509
3732
  }
3510
3733
  if (!["version", "--version", "-v", "help", "--help", "-h"].includes(command)) {
3511
3734
  await maybeNotifyUpdate(options.json);
@@ -3527,7 +3750,14 @@ ${c.yellow("\u25B2")} ${c.bold("update available")} ${c.dim(
3527
3750
  } catch {
3528
3751
  }
3529
3752
  };
3530
- run().catch(async (err) => {
3531
- await error(err instanceof Error ? err.message : String(err));
3532
- process4.exitCode = 1;
3533
- });
3753
+ var main = () => {
3754
+ run().catch(async (err) => {
3755
+ await error(err instanceof Error ? err.message : String(err));
3756
+ process3.exitCode = 1;
3757
+ });
3758
+ };
3759
+ export {
3760
+ main,
3761
+ parse2 as parse,
3762
+ run
3763
+ };