quiver-cli 0.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.
- package/README.md +188 -0
- package/bin/quiver-cli.mjs +2 -0
- package/dist/cli.js +3074 -0
- package/package.json +55 -0
- package/template/.agents/AGENTS.md +25 -0
- package/template/.agents/commands/cp.md +116 -0
- package/template/.agents/commands/next-setup.md +1064 -0
- package/template/.agents/commands/tf-readme.md +38 -0
- package/template/.agents/config.json +60 -0
- package/template/.agents/skills/agent-browser/SKILL.md +55 -0
- package/template/.agents/skills/apps/skybridge/SKILL.md +46 -0
- package/template/.agents/skills/apps/skybridge/references/architecture.md +175 -0
- package/template/.agents/skills/apps/skybridge/references/copy-template.md +24 -0
- package/template/.agents/skills/apps/skybridge/references/csp.md +33 -0
- package/template/.agents/skills/apps/skybridge/references/deploy.md +33 -0
- package/template/.agents/skills/apps/skybridge/references/discover.md +84 -0
- package/template/.agents/skills/apps/skybridge/references/download-file.md +77 -0
- package/template/.agents/skills/apps/skybridge/references/fetch-and-render-data.md +151 -0
- package/template/.agents/skills/apps/skybridge/references/oauth.md +115 -0
- package/template/.agents/skills/apps/skybridge/references/open-external-links.md +71 -0
- package/template/.agents/skills/apps/skybridge/references/prompt-llm.md +20 -0
- package/template/.agents/skills/apps/skybridge/references/publish.md +19 -0
- package/template/.agents/skills/apps/skybridge/references/run-locally.md +51 -0
- package/template/.agents/skills/apps/skybridge/references/state-and-context.md +151 -0
- package/template/.agents/skills/apps/skybridge/references/ui-guidelines.md +205 -0
- package/template/.agents/skills/code/cleanup/SKILL.md +26 -0
- package/template/.agents/skills/code/vercel-react-best-practices/AGENTS.md +3810 -0
- package/template/.agents/skills/code/vercel-react-best-practices/README.md +123 -0
- package/template/.agents/skills/code/vercel-react-best-practices/SKILL.md +149 -0
- package/template/.agents/skills/code/vercel-react-best-practices/metadata.json +15 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/_sections.md +46 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/_template.md +28 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/advanced-effect-event-deps.md +56 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/advanced-event-handler-refs.md +55 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/advanced-init-once.md +42 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/advanced-use-latest.md +39 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/async-api-routes.md +38 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/async-cheap-condition-before-await.md +37 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/async-defer-await.md +82 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/async-dependencies.md +51 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/async-parallel.md +28 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/async-suspense-boundaries.md +99 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/bundle-analyzable-paths.md +63 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/bundle-barrel-imports.md +60 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/bundle-conditional.md +31 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/bundle-defer-third-party.md +49 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/bundle-dynamic-imports.md +35 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/bundle-preload.md +50 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/client-event-listeners.md +74 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/client-localstorage-schema.md +71 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/client-passive-event-listeners.md +48 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/client-swr-dedup.md +56 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/js-batch-dom-css.md +107 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/js-cache-function-results.md +80 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/js-cache-property-access.md +28 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/js-cache-storage.md +70 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/js-combine-iterations.md +32 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/js-early-exit.md +50 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/js-flatmap-filter.md +60 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/js-hoist-regexp.md +45 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/js-index-maps.md +37 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/js-length-check-first.md +49 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/js-min-max-loop.md +82 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/js-request-idle-callback.md +105 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/js-set-map-lookups.md +24 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/js-tosorted-immutable.md +57 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/rendering-activity.md +26 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/rendering-conditional-render.md +40 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/rendering-content-visibility.md +38 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/rendering-hoist-jsx.md +46 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/rendering-hydration-suppress-warning.md +30 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/rendering-resource-hints.md +85 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/rendering-script-defer-async.md +68 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/rendering-svg-precision.md +28 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/rendering-usetransition-loading.md +75 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/rerender-defer-reads.md +39 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/rerender-dependencies.md +45 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/rerender-derived-state-no-effect.md +40 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/rerender-derived-state.md +29 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/rerender-functional-setstate.md +74 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/rerender-lazy-state-init.md +58 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/rerender-memo-with-default-value.md +38 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/rerender-memo.md +44 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/rerender-move-effect-to-event.md +45 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/rerender-no-inline-components.md +82 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/rerender-simple-expression-in-memo.md +35 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/rerender-split-combined-hooks.md +64 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/rerender-transitions.md +40 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/rerender-use-deferred-value.md +59 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/rerender-use-ref-transient-values.md +73 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/server-after-nonblocking.md +73 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/server-auth-actions.md +96 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/server-cache-lru.md +41 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/server-cache-react.md +76 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/server-dedup-props.md +65 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/server-hoist-static-io.md +149 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/server-no-shared-module-state.md +50 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/server-parallel-fetching.md +83 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/server-parallel-nested-fetching.md +34 -0
- package/template/.agents/skills/code/vercel-react-best-practices/rules/server-serialization.md +38 -0
- package/template/.agents/skills/data/prisma-cli/SKILL.md +247 -0
- package/template/.agents/skills/data/prisma-cli/references/db-execute.md +78 -0
- package/template/.agents/skills/data/prisma-cli/references/db-pull.md +185 -0
- package/template/.agents/skills/data/prisma-cli/references/db-push.md +148 -0
- package/template/.agents/skills/data/prisma-cli/references/db-seed.md +188 -0
- package/template/.agents/skills/data/prisma-cli/references/debug.md +46 -0
- package/template/.agents/skills/data/prisma-cli/references/dev.md +157 -0
- package/template/.agents/skills/data/prisma-cli/references/format.md +48 -0
- package/template/.agents/skills/data/prisma-cli/references/generate.md +173 -0
- package/template/.agents/skills/data/prisma-cli/references/init.md +136 -0
- package/template/.agents/skills/data/prisma-cli/references/mcp.md +38 -0
- package/template/.agents/skills/data/prisma-cli/references/migrate-deploy.md +127 -0
- package/template/.agents/skills/data/prisma-cli/references/migrate-dev.md +145 -0
- package/template/.agents/skills/data/prisma-cli/references/migrate-diff.md +89 -0
- package/template/.agents/skills/data/prisma-cli/references/migrate-reset.md +78 -0
- package/template/.agents/skills/data/prisma-cli/references/migrate-resolve.md +57 -0
- package/template/.agents/skills/data/prisma-cli/references/migrate-status.md +65 -0
- package/template/.agents/skills/data/prisma-cli/references/studio.md +137 -0
- package/template/.agents/skills/data/prisma-cli/references/validate.md +53 -0
- package/template/.agents/skills/data/prisma-client-api/SKILL.md +216 -0
- package/template/.agents/skills/data/prisma-client-api/references/client-methods.md +223 -0
- package/template/.agents/skills/data/prisma-client-api/references/constructor.md +208 -0
- package/template/.agents/skills/data/prisma-client-api/references/filters.md +256 -0
- package/template/.agents/skills/data/prisma-client-api/references/model-queries.md +281 -0
- package/template/.agents/skills/data/prisma-client-api/references/query-options.md +276 -0
- package/template/.agents/skills/data/prisma-client-api/references/raw-queries.md +194 -0
- package/template/.agents/skills/data/prisma-client-api/references/relations.md +308 -0
- package/template/.agents/skills/data/prisma-client-api/references/transactions.md +184 -0
- package/template/.agents/skills/design/impeccable/SKILL.md +176 -0
- package/template/.agents/skills/design/impeccable/reference/adapt.md +311 -0
- package/template/.agents/skills/design/impeccable/reference/animate.md +201 -0
- package/template/.agents/skills/design/impeccable/reference/audit.md +133 -0
- package/template/.agents/skills/design/impeccable/reference/bolder.md +113 -0
- package/template/.agents/skills/design/impeccable/reference/brand.md +108 -0
- package/template/.agents/skills/design/impeccable/reference/clarify.md +288 -0
- package/template/.agents/skills/design/impeccable/reference/codex.md +105 -0
- package/template/.agents/skills/design/impeccable/reference/colorize.md +257 -0
- package/template/.agents/skills/design/impeccable/reference/craft.md +123 -0
- package/template/.agents/skills/design/impeccable/reference/critique.md +767 -0
- package/template/.agents/skills/design/impeccable/reference/delight.md +302 -0
- package/template/.agents/skills/design/impeccable/reference/distill.md +111 -0
- package/template/.agents/skills/design/impeccable/reference/document.md +429 -0
- package/template/.agents/skills/design/impeccable/reference/extract.md +69 -0
- package/template/.agents/skills/design/impeccable/reference/harden.md +347 -0
- package/template/.agents/skills/design/impeccable/reference/init.md +172 -0
- package/template/.agents/skills/design/impeccable/reference/interaction-design.md +189 -0
- package/template/.agents/skills/design/impeccable/reference/layout.md +161 -0
- package/template/.agents/skills/design/impeccable/reference/live.md +718 -0
- package/template/.agents/skills/design/impeccable/reference/onboard.md +234 -0
- package/template/.agents/skills/design/impeccable/reference/optimize.md +258 -0
- package/template/.agents/skills/design/impeccable/reference/overdrive.md +130 -0
- package/template/.agents/skills/design/impeccable/reference/polish.md +241 -0
- package/template/.agents/skills/design/impeccable/reference/product.md +60 -0
- package/template/.agents/skills/design/impeccable/reference/quieter.md +99 -0
- package/template/.agents/skills/design/impeccable/reference/shape.md +165 -0
- package/template/.agents/skills/design/impeccable/reference/typeset.md +279 -0
- package/template/.agents/skills/design/impeccable/scripts/cleanup-deprecated.mjs +284 -0
- package/template/.agents/skills/design/impeccable/scripts/command-metadata.json +94 -0
- package/template/.agents/skills/design/impeccable/scripts/context-signals.mjs +225 -0
- package/template/.agents/skills/design/impeccable/scripts/context.mjs +270 -0
- package/template/.agents/skills/design/impeccable/scripts/critique-storage.mjs +242 -0
- package/template/.agents/skills/design/impeccable/scripts/design-parser.mjs +835 -0
- package/template/.agents/skills/design/impeccable/scripts/detect-csp.mjs +198 -0
- package/template/.agents/skills/design/impeccable/scripts/detect.mjs +21 -0
- package/template/.agents/skills/design/impeccable/scripts/detector/browser/injected/index.mjs +1733 -0
- package/template/.agents/skills/design/impeccable/scripts/detector/cli/main.mjs +244 -0
- package/template/.agents/skills/design/impeccable/scripts/detector/detect-antipatterns-browser.js +4551 -0
- package/template/.agents/skills/design/impeccable/scripts/detector/detect-antipatterns.mjs +43 -0
- package/template/.agents/skills/design/impeccable/scripts/detector/engines/browser/detect-url.mjs +252 -0
- package/template/.agents/skills/design/impeccable/scripts/detector/engines/regex/detect-text.mjs +535 -0
- package/template/.agents/skills/design/impeccable/scripts/detector/engines/static-html/css-cascade.mjs +986 -0
- package/template/.agents/skills/design/impeccable/scripts/detector/engines/static-html/detect-html.mjs +208 -0
- package/template/.agents/skills/design/impeccable/scripts/detector/engines/visual/screenshot-contrast.mjs +189 -0
- package/template/.agents/skills/design/impeccable/scripts/detector/findings.mjs +12 -0
- package/template/.agents/skills/design/impeccable/scripts/detector/node/file-system.mjs +198 -0
- package/template/.agents/skills/design/impeccable/scripts/detector/profile/profiler.mjs +166 -0
- package/template/.agents/skills/design/impeccable/scripts/detector/registry/antipatterns.mjs +419 -0
- package/template/.agents/skills/design/impeccable/scripts/detector/rules/checks.mjs +2316 -0
- package/template/.agents/skills/design/impeccable/scripts/detector/shared/color.mjs +124 -0
- package/template/.agents/skills/design/impeccable/scripts/detector/shared/constants.mjs +101 -0
- package/template/.agents/skills/design/impeccable/scripts/detector/shared/page.mjs +7 -0
- package/template/.agents/skills/design/impeccable/scripts/impeccable-paths.mjs +126 -0
- package/template/.agents/skills/design/impeccable/scripts/is-generated.mjs +69 -0
- package/template/.agents/skills/design/impeccable/scripts/live-accept.mjs +812 -0
- package/template/.agents/skills/design/impeccable/scripts/live-browser-session.js +123 -0
- package/template/.agents/skills/design/impeccable/scripts/live-browser.js +10316 -0
- package/template/.agents/skills/design/impeccable/scripts/live-commit-manual-edits.mjs +1241 -0
- package/template/.agents/skills/design/impeccable/scripts/live-complete.mjs +75 -0
- package/template/.agents/skills/design/impeccable/scripts/live-completion.mjs +19 -0
- package/template/.agents/skills/design/impeccable/scripts/live-copy-edit-agent.mjs +683 -0
- package/template/.agents/skills/design/impeccable/scripts/live-discard-manual-edits.mjs +51 -0
- package/template/.agents/skills/design/impeccable/scripts/live-event-validation.mjs +136 -0
- package/template/.agents/skills/design/impeccable/scripts/live-inject.mjs +557 -0
- package/template/.agents/skills/design/impeccable/scripts/live-insert-ui.mjs +458 -0
- package/template/.agents/skills/design/impeccable/scripts/live-insert.mjs +272 -0
- package/template/.agents/skills/design/impeccable/scripts/live-manual-edit-evidence.mjs +363 -0
- package/template/.agents/skills/design/impeccable/scripts/live-manual-edits-buffer.mjs +152 -0
- package/template/.agents/skills/design/impeccable/scripts/live-poll.mjs +379 -0
- package/template/.agents/skills/design/impeccable/scripts/live-resume.mjs +94 -0
- package/template/.agents/skills/design/impeccable/scripts/live-server.mjs +2322 -0
- package/template/.agents/skills/design/impeccable/scripts/live-session-store.mjs +289 -0
- package/template/.agents/skills/design/impeccable/scripts/live-status.mjs +61 -0
- package/template/.agents/skills/design/impeccable/scripts/live-svelte-component.mjs +826 -0
- package/template/.agents/skills/design/impeccable/scripts/live-sveltekit-adapter.mjs +274 -0
- package/template/.agents/skills/design/impeccable/scripts/live-ui-core.mjs +179 -0
- package/template/.agents/skills/design/impeccable/scripts/live-wrap.mjs +894 -0
- package/template/.agents/skills/design/impeccable/scripts/live.mjs +246 -0
- package/template/.agents/skills/design/impeccable/scripts/modern-screenshot.umd.js +14 -0
- package/template/.agents/skills/design/impeccable/scripts/palette.mjs +633 -0
- package/template/.agents/skills/design/impeccable/scripts/pin.mjs +214 -0
- package/template/.agents/skills/design/shadcn/SKILL.md +242 -0
- package/template/.agents/skills/design/shadcn/agents/openai.yml +5 -0
- package/template/.agents/skills/design/shadcn/assets/shadcn-small.png +0 -0
- package/template/.agents/skills/design/shadcn/assets/shadcn.png +0 -0
- package/template/.agents/skills/design/shadcn/cli.md +257 -0
- package/template/.agents/skills/design/shadcn/customization.md +202 -0
- package/template/.agents/skills/design/shadcn/evals/evals.json +47 -0
- package/template/.agents/skills/design/shadcn/mcp.md +94 -0
- package/template/.agents/skills/design/shadcn/rules/base-vs-radix.md +306 -0
- package/template/.agents/skills/design/shadcn/rules/composition.md +195 -0
- package/template/.agents/skills/design/shadcn/rules/forms.md +192 -0
- package/template/.agents/skills/design/shadcn/rules/icons.md +101 -0
- package/template/.agents/skills/design/shadcn/rules/styling.md +162 -0
- package/template/.agents/skills/find-skills/SKILL.md +142 -0
- package/template/.agents/skills/integrations/langfuse/SKILL.md +142 -0
- package/template/.agents/skills/integrations/langfuse/references/cli.md +52 -0
- package/template/.agents/skills/integrations/langfuse/references/error-analysis.md +100 -0
- package/template/.agents/skills/integrations/langfuse/references/instrumentation.md +134 -0
- package/template/.agents/skills/integrations/langfuse/references/judge-calibration.md +288 -0
- package/template/.agents/skills/integrations/langfuse/references/prompt-migration.md +234 -0
- package/template/.agents/skills/integrations/langfuse/references/sdk-upgrade.md +175 -0
- package/template/.agents/skills/integrations/langfuse/references/skill-feedback.md +52 -0
- package/template/.agents/skills/integrations/langfuse/references/user-feedback.md +88 -0
- package/template/.agents/skills/integrations/posthog/SKILL.md +102 -0
- package/template/.agents/skills/integrations/posthog/references/error-tracking-alerts.md +63 -0
- package/template/.agents/skills/integrations/posthog/references/error-tracking-assigning-issues.md +77 -0
- package/template/.agents/skills/integrations/posthog/references/error-tracking-fingerprints.md +57 -0
- package/template/.agents/skills/integrations/posthog/references/error-tracking-monitoring.md +140 -0
- package/template/.agents/skills/integrations/posthog/references/error-tracking-nextjs.md +490 -0
- package/template/.agents/skills/integrations/posthog/references/error-tracking-source-maps.md +45 -0
- package/template/.agents/skills/integrations/posthog/references/feature-flags-best-practices.md +139 -0
- package/template/.agents/skills/integrations/posthog/references/feature-flags-react.md +302 -0
- package/template/.agents/skills/integrations/posthog/references/identify-users.md +202 -0
- package/template/.agents/skills/integrations/posthog/references/integration-example.md +706 -0
- package/template/.agents/skills/integrations/posthog/references/integration-nextjs.md +385 -0
- package/template/.agents/skills/integrations/posthog/references/integration-step-1-begin.md +43 -0
- package/template/.agents/skills/integrations/posthog/references/integration-step-2-edit.md +37 -0
- package/template/.agents/skills/integrations/posthog/references/integration-step-3-revise.md +22 -0
- package/template/.agents/skills/integrations/posthog/references/integration-step-4-conclude.md +38 -0
- package/template/.agents/skills/integrations/posthog/references/llm-analytics-anthropic.md +200 -0
- package/template/.agents/skills/integrations/posthog/references/llm-analytics-basics.md +62 -0
- package/template/.agents/skills/integrations/posthog/references/llm-analytics-costs.md +197 -0
- package/template/.agents/skills/integrations/posthog/references/llm-analytics-manual-capture.md +397 -0
- package/template/.agents/skills/integrations/posthog/references/llm-analytics-traces.md +98 -0
- package/template/.agents/skills/integrations/posthog/references/llm-analytics-vercel-ai.md +120 -0
- package/template/.agents/skills/repo/repo-ci/SKILL.md +265 -0
- package/template/.agents/skills/repo/repo-init-next-js/SKILL.md +129 -0
- package/template/.agents/skills/repo/repo-init-next-js/references/file-contents.md +800 -0
- package/template/.agents/skills/repo/repo-init-next-js/scripts/setup.sh +47 -0
- package/template/.agents/skills/repo/repo-init-node/SKILL.md +196 -0
- package/template/.agents/skills/skill-creator/LICENSE.txt +202 -0
- package/template/.agents/skills/skill-creator/SKILL.md +485 -0
- package/template/.agents/skills/skill-creator/agents/analyzer.md +274 -0
- package/template/.agents/skills/skill-creator/agents/comparator.md +202 -0
- package/template/.agents/skills/skill-creator/agents/grader.md +223 -0
- package/template/.agents/skills/skill-creator/assets/eval_review.html +146 -0
- package/template/.agents/skills/skill-creator/eval-viewer/generate_review.py +471 -0
- package/template/.agents/skills/skill-creator/eval-viewer/viewer.html +1325 -0
- package/template/.agents/skills/skill-creator/references/schemas.md +430 -0
- package/template/.agents/skills/skill-creator/scripts/__init__.py +0 -0
- package/template/.agents/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
- package/template/.agents/skills/skill-creator/scripts/generate_report.py +326 -0
- package/template/.agents/skills/skill-creator/scripts/improve_description.py +247 -0
- package/template/.agents/skills/skill-creator/scripts/package_skill.py +136 -0
- package/template/.agents/skills/skill-creator/scripts/quick_validate.py +103 -0
- package/template/.agents/skills/skill-creator/scripts/run_eval.py +310 -0
- package/template/.agents/skills/skill-creator/scripts/run_loop.py +328 -0
- package/template/.agents/skills/skill-creator/scripts/utils.py +47 -0
- package/template/.agents/upstreams.json +80 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,3074 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
+
var __esm = (fn, res) => function __init() {
|
|
4
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
5
|
+
};
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
// src/ui/prompts.ts
|
|
12
|
+
var clackPromise, loadClack, QUIVER_ART, TAGLINE, colorEnabled, palette, block, banner, outro, step, info, success, warn, error, password, selectGrouped, selectGroupedText;
|
|
13
|
+
var init_prompts = __esm({
|
|
14
|
+
"src/ui/prompts.ts"() {
|
|
15
|
+
"use strict";
|
|
16
|
+
loadClack = async () => {
|
|
17
|
+
if (clackPromise === void 0) {
|
|
18
|
+
clackPromise = import("@clack/prompts").catch(() => null);
|
|
19
|
+
}
|
|
20
|
+
return clackPromise;
|
|
21
|
+
};
|
|
22
|
+
QUIVER_ART = [
|
|
23
|
+
" ___ _ ",
|
|
24
|
+
" / _ \\ _ _(_)_ _____ _ __ ",
|
|
25
|
+
"| | | | | | | \\ \\ / / _ \\ '__| ",
|
|
26
|
+
"| |_| | |_| | |\\ V / __/ | ",
|
|
27
|
+
" \\__\\_\\\\__,_|_| \\_/ \\___|_| "
|
|
28
|
+
];
|
|
29
|
+
TAGLINE = "Compose skills, commands & MCP servers into any repo \u2014 lockfile-based drift awareness.";
|
|
30
|
+
colorEnabled = () => Boolean(process.stdout.isTTY) && !process.env.NO_COLOR;
|
|
31
|
+
palette = () => {
|
|
32
|
+
const on = colorEnabled();
|
|
33
|
+
const wrap = (code) => (s) => on ? `\x1B[${code}m${s}\x1B[0m` : s;
|
|
34
|
+
return {
|
|
35
|
+
green: wrap("32"),
|
|
36
|
+
yellow: wrap("33"),
|
|
37
|
+
cyan: wrap("36"),
|
|
38
|
+
dim: wrap("2"),
|
|
39
|
+
bold: wrap("1"),
|
|
40
|
+
red: wrap("31")
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
block = (lines) => {
|
|
44
|
+
console.log(lines.join("\n"));
|
|
45
|
+
};
|
|
46
|
+
banner = async () => {
|
|
47
|
+
const color = colorEnabled();
|
|
48
|
+
const cyan = color ? "\x1B[36m" : "";
|
|
49
|
+
const dim = color ? "\x1B[2m" : "";
|
|
50
|
+
const reset = color ? "\x1B[0m" : "";
|
|
51
|
+
console.log("");
|
|
52
|
+
for (const line of QUIVER_ART) {
|
|
53
|
+
console.log(`${cyan}${line}${reset}`);
|
|
54
|
+
}
|
|
55
|
+
console.log("");
|
|
56
|
+
console.log(`${dim}${TAGLINE}${reset}`);
|
|
57
|
+
console.log("");
|
|
58
|
+
};
|
|
59
|
+
outro = async (message) => {
|
|
60
|
+
const clack = await loadClack();
|
|
61
|
+
if (clack) clack.outro(message);
|
|
62
|
+
else console.log(`
|
|
63
|
+
${message}
|
|
64
|
+
`);
|
|
65
|
+
};
|
|
66
|
+
step = async (message) => {
|
|
67
|
+
const clack = await loadClack();
|
|
68
|
+
if (clack) clack.log.step(message);
|
|
69
|
+
else console.log(` ${message}`);
|
|
70
|
+
};
|
|
71
|
+
info = async (message) => {
|
|
72
|
+
const clack = await loadClack();
|
|
73
|
+
if (clack) clack.log.info(message);
|
|
74
|
+
else console.log(` ${message}`);
|
|
75
|
+
};
|
|
76
|
+
success = async (message) => {
|
|
77
|
+
const clack = await loadClack();
|
|
78
|
+
if (clack) clack.log.success(message);
|
|
79
|
+
else console.log(` ${message}`);
|
|
80
|
+
};
|
|
81
|
+
warn = async (message) => {
|
|
82
|
+
const clack = await loadClack();
|
|
83
|
+
if (clack) clack.log.warn(message);
|
|
84
|
+
else console.warn(` ${message}`);
|
|
85
|
+
};
|
|
86
|
+
error = async (message) => {
|
|
87
|
+
const clack = await loadClack();
|
|
88
|
+
if (clack) clack.log.error(message);
|
|
89
|
+
else console.error(message);
|
|
90
|
+
};
|
|
91
|
+
password = async (message) => {
|
|
92
|
+
const clack = await loadClack();
|
|
93
|
+
if (clack) {
|
|
94
|
+
const value = await clack.password({ message });
|
|
95
|
+
if (clack.isCancel(value)) return null;
|
|
96
|
+
return value;
|
|
97
|
+
}
|
|
98
|
+
const { createInterface } = await import("readline");
|
|
99
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
100
|
+
const answer = await new Promise((res) => {
|
|
101
|
+
rl.question(`${message}: `, (a) => {
|
|
102
|
+
rl.close();
|
|
103
|
+
res(a);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
return answer.trim() || null;
|
|
107
|
+
};
|
|
108
|
+
selectGrouped = async ({
|
|
109
|
+
message,
|
|
110
|
+
groups,
|
|
111
|
+
initialValues
|
|
112
|
+
}) => {
|
|
113
|
+
const clack = await loadClack();
|
|
114
|
+
if (clack) {
|
|
115
|
+
const options = {};
|
|
116
|
+
for (const group of groups) {
|
|
117
|
+
options[group.name] = group.items.map((i) => ({
|
|
118
|
+
value: i.value,
|
|
119
|
+
label: i.label,
|
|
120
|
+
...i.hint !== void 0 ? { hint: i.hint } : {}
|
|
121
|
+
}));
|
|
122
|
+
}
|
|
123
|
+
const selected = await clack.groupMultiselect({
|
|
124
|
+
message,
|
|
125
|
+
// clack's Option<T> is structurally compatible; the Record index
|
|
126
|
+
// signature is invariant so an explicit cast is required here.
|
|
127
|
+
options,
|
|
128
|
+
initialValues,
|
|
129
|
+
required: false,
|
|
130
|
+
selectableGroups: false
|
|
131
|
+
});
|
|
132
|
+
if (clack.isCancel(selected)) {
|
|
133
|
+
clack.cancel("Cancelled - no changes made.");
|
|
134
|
+
process.exit(0);
|
|
135
|
+
}
|
|
136
|
+
return selected;
|
|
137
|
+
}
|
|
138
|
+
return selectGroupedText({ message, groups, initialValues });
|
|
139
|
+
};
|
|
140
|
+
selectGroupedText = async ({
|
|
141
|
+
message,
|
|
142
|
+
groups,
|
|
143
|
+
initialValues
|
|
144
|
+
}) => {
|
|
145
|
+
const { createInterface } = await import("readline");
|
|
146
|
+
const flat = groups.flatMap((g) => g.items.map((i) => i.value));
|
|
147
|
+
console.log(`
|
|
148
|
+
${message}`);
|
|
149
|
+
let index = 0;
|
|
150
|
+
for (const group of groups) {
|
|
151
|
+
console.log(` ${group.name}`);
|
|
152
|
+
for (const item of group.items) {
|
|
153
|
+
index += 1;
|
|
154
|
+
const mark = initialValues.includes(item.value) ? "*" : " ";
|
|
155
|
+
const hint = item.hint ? ` (${item.hint})` : "";
|
|
156
|
+
console.log(` ${index}) [${mark}] ${item.label}${hint}`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
const preselected = flat.map((v, i) => initialValues.includes(v) ? i + 1 : null).filter((n) => n !== null).join(",");
|
|
160
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
161
|
+
const answer = await new Promise((res) => {
|
|
162
|
+
rl.question(
|
|
163
|
+
`
|
|
164
|
+
Enter numbers (comma-separated), 'all', or 'none' [${preselected || "none"}]: `,
|
|
165
|
+
(a) => {
|
|
166
|
+
rl.close();
|
|
167
|
+
res(a);
|
|
168
|
+
}
|
|
169
|
+
);
|
|
170
|
+
});
|
|
171
|
+
const trimmed = answer.trim().toLowerCase();
|
|
172
|
+
if (trimmed === "all" || trimmed === "a") return flat;
|
|
173
|
+
if (trimmed === "none" || trimmed === "n") return [];
|
|
174
|
+
if (trimmed === "") return flat.filter((v) => initialValues.includes(v));
|
|
175
|
+
const picked = /* @__PURE__ */ new Set();
|
|
176
|
+
for (const token of trimmed.split(/[\s,]+/).filter(Boolean)) {
|
|
177
|
+
const n = Number.parseInt(token, 10);
|
|
178
|
+
if (Number.isInteger(n) && n >= 1 && n <= flat.length) {
|
|
179
|
+
picked.add(flat[n - 1]);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return [...picked];
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
// src/catalog/digest.ts
|
|
188
|
+
import { createHash } from "crypto";
|
|
189
|
+
import { readdirSync, readFileSync, statSync } from "fs";
|
|
190
|
+
import { relative, resolve } from "path";
|
|
191
|
+
var sha256, fileDigest, treeDigest, jsonDigest, canonicalJson;
|
|
192
|
+
var init_digest = __esm({
|
|
193
|
+
"src/catalog/digest.ts"() {
|
|
194
|
+
"use strict";
|
|
195
|
+
sha256 = (data) => "sha256:" + createHash("sha256").update(data).digest("hex");
|
|
196
|
+
fileDigest = (path) => sha256(readFileSync(path));
|
|
197
|
+
treeDigest = (dir) => {
|
|
198
|
+
const files = [];
|
|
199
|
+
const walk = (current) => {
|
|
200
|
+
for (const entry of readdirSync(current, { withFileTypes: true }).sort(
|
|
201
|
+
(a, b) => a.name.localeCompare(b.name)
|
|
202
|
+
)) {
|
|
203
|
+
const full = resolve(current, entry.name);
|
|
204
|
+
if (entry.isDirectory()) walk(full);
|
|
205
|
+
else if (entry.isFile()) files.push(full);
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
walk(dir);
|
|
209
|
+
const hash = createHash("sha256");
|
|
210
|
+
for (const file of files.sort()) {
|
|
211
|
+
const rel = relative(dir, file);
|
|
212
|
+
hash.update(rel);
|
|
213
|
+
hash.update("\0");
|
|
214
|
+
hash.update(createHash("sha256").update(readFileSync(file)).digest());
|
|
215
|
+
hash.update("\0");
|
|
216
|
+
}
|
|
217
|
+
return "sha256:" + hash.digest("hex");
|
|
218
|
+
};
|
|
219
|
+
jsonDigest = (value) => sha256(canonicalJson(value));
|
|
220
|
+
canonicalJson = (value) => {
|
|
221
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
222
|
+
if (Array.isArray(value)) {
|
|
223
|
+
return "[" + value.map(canonicalJson).join(",") + "]";
|
|
224
|
+
}
|
|
225
|
+
const keys = Object.keys(value).sort();
|
|
226
|
+
return "{" + keys.map(
|
|
227
|
+
(k) => JSON.stringify(k) + ":" + canonicalJson(value[k])
|
|
228
|
+
).join(",") + "}";
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
// src/catalog/frontmatter.ts
|
|
234
|
+
var readFrontmatter;
|
|
235
|
+
var init_frontmatter = __esm({
|
|
236
|
+
"src/catalog/frontmatter.ts"() {
|
|
237
|
+
"use strict";
|
|
238
|
+
readFrontmatter = (content) => {
|
|
239
|
+
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
240
|
+
if (!match) return {};
|
|
241
|
+
const fields = {};
|
|
242
|
+
const lines = match[1].split(/\r?\n/);
|
|
243
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
244
|
+
const rawLine = lines[i];
|
|
245
|
+
const line = rawLine.trimEnd();
|
|
246
|
+
if (!line || line.startsWith("#")) continue;
|
|
247
|
+
const colon = line.indexOf(":");
|
|
248
|
+
if (colon === -1) continue;
|
|
249
|
+
if (/^\s/.test(rawLine)) continue;
|
|
250
|
+
const key = line.slice(0, colon).trim();
|
|
251
|
+
let value = line.slice(colon + 1).trim();
|
|
252
|
+
if (value === ">" || value === "|") {
|
|
253
|
+
const folded = value === ">";
|
|
254
|
+
const block2 = [];
|
|
255
|
+
while (i + 1 < lines.length && /^\s+\S/.test(lines[i + 1])) {
|
|
256
|
+
block2.push(lines[i + 1].trim());
|
|
257
|
+
i += 1;
|
|
258
|
+
}
|
|
259
|
+
fields[key] = folded ? block2.join(" ") : block2.join("\n");
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
263
|
+
value = value.slice(1, -1);
|
|
264
|
+
}
|
|
265
|
+
fields[key] = value;
|
|
266
|
+
}
|
|
267
|
+
return fields;
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
// src/catalog/discover.ts
|
|
273
|
+
import { existsSync, readdirSync as readdirSync2, readFileSync as readFileSync2 } from "fs";
|
|
274
|
+
import { relative as relative2, resolve as resolve2 } from "path";
|
|
275
|
+
var readConfig, discoverSkills, discoverCommands, discoverMcp, loadCatalog;
|
|
276
|
+
var init_discover = __esm({
|
|
277
|
+
"src/catalog/discover.ts"() {
|
|
278
|
+
"use strict";
|
|
279
|
+
init_digest();
|
|
280
|
+
init_frontmatter();
|
|
281
|
+
readConfig = (root) => {
|
|
282
|
+
const path = resolve2(root, "config.json");
|
|
283
|
+
if (!existsSync(path)) return { config: {}, path };
|
|
284
|
+
return { config: JSON.parse(readFileSync2(path, "utf8")), path };
|
|
285
|
+
};
|
|
286
|
+
discoverSkills = (root) => {
|
|
287
|
+
const skillsRoot = resolve2(root, "skills");
|
|
288
|
+
if (!existsSync(skillsRoot)) return [];
|
|
289
|
+
const found = [];
|
|
290
|
+
const walk = (dir, group) => {
|
|
291
|
+
for (const entry of readdirSync2(dir, { withFileTypes: true })) {
|
|
292
|
+
if (!entry.isDirectory()) continue;
|
|
293
|
+
const childDir = resolve2(dir, entry.name);
|
|
294
|
+
if (existsSync(resolve2(childDir, "SKILL.md"))) {
|
|
295
|
+
const fm = readFrontmatter(
|
|
296
|
+
readFileSync2(resolve2(childDir, "SKILL.md"), "utf8")
|
|
297
|
+
);
|
|
298
|
+
found.push({
|
|
299
|
+
name: entry.name,
|
|
300
|
+
group: group ?? "general",
|
|
301
|
+
sourcePath: relative2(root, childDir),
|
|
302
|
+
absDir: childDir,
|
|
303
|
+
digest: treeDigest(childDir),
|
|
304
|
+
frontmatter: {
|
|
305
|
+
name: fm["name"] ?? null,
|
|
306
|
+
description: fm["description"] ?? null,
|
|
307
|
+
version: fm["version"] ?? null
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
} else {
|
|
311
|
+
walk(childDir, group ?? entry.name);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
walk(skillsRoot, null);
|
|
316
|
+
const seen = /* @__PURE__ */ new Map();
|
|
317
|
+
for (const skill of found) {
|
|
318
|
+
const prev = seen.get(skill.name);
|
|
319
|
+
if (prev && prev !== skill.absDir) {
|
|
320
|
+
throw new Error(
|
|
321
|
+
`Duplicate skill name "${skill.name}": ${prev} and ${skill.absDir}`
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
seen.set(skill.name, skill.absDir);
|
|
325
|
+
}
|
|
326
|
+
return found.sort((a, b) => a.name.localeCompare(b.name));
|
|
327
|
+
};
|
|
328
|
+
discoverCommands = (root) => {
|
|
329
|
+
const commandsRoot = resolve2(root, "commands");
|
|
330
|
+
if (!existsSync(commandsRoot)) return [];
|
|
331
|
+
return readdirSync2(commandsRoot).filter((f) => f.endsWith(".md")).sort((a, b) => a.localeCompare(b)).map((file) => {
|
|
332
|
+
const absPath = resolve2(commandsRoot, file);
|
|
333
|
+
return {
|
|
334
|
+
name: file.replace(/\.md$/, ""),
|
|
335
|
+
sourcePath: relative2(root, absPath),
|
|
336
|
+
absPath,
|
|
337
|
+
digest: fileDigest(absPath)
|
|
338
|
+
};
|
|
339
|
+
});
|
|
340
|
+
};
|
|
341
|
+
discoverMcp = (config) => {
|
|
342
|
+
const servers = config.mcpServers ?? {};
|
|
343
|
+
return Object.keys(servers).sort((a, b) => a.localeCompare(b)).map((name) => ({
|
|
344
|
+
name,
|
|
345
|
+
server: servers[name],
|
|
346
|
+
configDigest: jsonDigest(servers[name])
|
|
347
|
+
}));
|
|
348
|
+
};
|
|
349
|
+
loadCatalog = (catalog) => {
|
|
350
|
+
const { config, path } = readConfig(catalog.root);
|
|
351
|
+
return {
|
|
352
|
+
config,
|
|
353
|
+
configPath: path,
|
|
354
|
+
skills: discoverSkills(catalog.root),
|
|
355
|
+
commands: discoverCommands(catalog.root),
|
|
356
|
+
mcp: discoverMcp(config)
|
|
357
|
+
};
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
// src/catalog/entries.ts
|
|
363
|
+
var skillToEntry, commandToEntry, mcpToEntry;
|
|
364
|
+
var init_entries = __esm({
|
|
365
|
+
"src/catalog/entries.ts"() {
|
|
366
|
+
"use strict";
|
|
367
|
+
skillToEntry = (skill) => ({
|
|
368
|
+
type: "skill",
|
|
369
|
+
sourcePath: skill.sourcePath,
|
|
370
|
+
digest: skill.digest,
|
|
371
|
+
pin: null,
|
|
372
|
+
frontmatter: skill.frontmatter
|
|
373
|
+
});
|
|
374
|
+
commandToEntry = (command) => ({
|
|
375
|
+
type: "command",
|
|
376
|
+
sourcePath: command.sourcePath,
|
|
377
|
+
digest: command.digest
|
|
378
|
+
});
|
|
379
|
+
mcpToEntry = (mcp) => ({
|
|
380
|
+
type: "mcp",
|
|
381
|
+
transport: mcp.server.transport,
|
|
382
|
+
configDigest: mcp.configDigest,
|
|
383
|
+
tools: null,
|
|
384
|
+
toolsFetchedAt: null
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
// src/secrets/interpolate.ts
|
|
390
|
+
import { existsSync as existsSync2, readFileSync as readFileSync3 } from "fs";
|
|
391
|
+
import { resolve as resolve3 } from "path";
|
|
392
|
+
import process2 from "process";
|
|
393
|
+
var loadEnvLocal, collectEnvVars, interpolateEnvVars;
|
|
394
|
+
var init_interpolate = __esm({
|
|
395
|
+
"src/secrets/interpolate.ts"() {
|
|
396
|
+
"use strict";
|
|
397
|
+
loadEnvLocal = (targetRoot) => {
|
|
398
|
+
const path = resolve3(targetRoot, ".env.local");
|
|
399
|
+
if (!existsSync2(path)) return;
|
|
400
|
+
for (const rawLine of readFileSync3(path, "utf8").split("\n")) {
|
|
401
|
+
const line = rawLine.trim();
|
|
402
|
+
if (!line || line.startsWith("#")) continue;
|
|
403
|
+
const eq = line.indexOf("=");
|
|
404
|
+
if (eq === -1) continue;
|
|
405
|
+
const key = line.slice(0, eq).trim();
|
|
406
|
+
const value = line.slice(eq + 1).trim();
|
|
407
|
+
if (process2.env[key] === void 0) process2.env[key] = value;
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
collectEnvVars = (value) => {
|
|
411
|
+
const found = /* @__PURE__ */ new Set();
|
|
412
|
+
const visit = (v) => {
|
|
413
|
+
if (typeof v === "string") {
|
|
414
|
+
for (const m of v.matchAll(/\$\{([^}]+)\}/g)) found.add(m[1]);
|
|
415
|
+
} else if (Array.isArray(v)) {
|
|
416
|
+
v.forEach(visit);
|
|
417
|
+
} else if (v !== null && typeof v === "object") {
|
|
418
|
+
Object.values(v).forEach(visit);
|
|
419
|
+
}
|
|
420
|
+
};
|
|
421
|
+
visit(value);
|
|
422
|
+
return [...found].sort((a, b) => a.localeCompare(b));
|
|
423
|
+
};
|
|
424
|
+
interpolateEnvVars = (value, onMissing) => {
|
|
425
|
+
if (typeof value === "string") {
|
|
426
|
+
return value.replace(/\$\{([^}]+)\}/g, (match, name) => {
|
|
427
|
+
const env = process2.env[name];
|
|
428
|
+
if (env === void 0) {
|
|
429
|
+
onMissing?.(name);
|
|
430
|
+
return match;
|
|
431
|
+
}
|
|
432
|
+
return env;
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
if (Array.isArray(value)) {
|
|
436
|
+
return value.map((v) => interpolateEnvVars(v, onMissing));
|
|
437
|
+
}
|
|
438
|
+
if (value !== null && typeof value === "object") {
|
|
439
|
+
const out = {};
|
|
440
|
+
for (const [k, v] of Object.entries(value)) {
|
|
441
|
+
out[k] = interpolateEnvVars(v, onMissing);
|
|
442
|
+
}
|
|
443
|
+
return out;
|
|
444
|
+
}
|
|
445
|
+
return value;
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
// src/catalog/materialize.ts
|
|
451
|
+
import { cpSync, existsSync as existsSync3, mkdirSync, rmSync, writeFileSync } from "fs";
|
|
452
|
+
import { dirname, resolve as resolve4 } from "path";
|
|
453
|
+
var materializeCatalog, envExampleContent, removeArtifact;
|
|
454
|
+
var init_materialize = __esm({
|
|
455
|
+
"src/catalog/materialize.ts"() {
|
|
456
|
+
"use strict";
|
|
457
|
+
init_interpolate();
|
|
458
|
+
materializeCatalog = (targetRoot, sourceCatalog, catalog, selection) => {
|
|
459
|
+
const destRoot = resolve4(targetRoot, ".agents");
|
|
460
|
+
mkdirSync(destRoot, { recursive: true });
|
|
461
|
+
const keptSkillDirs = [];
|
|
462
|
+
for (const skill of catalog.skills) {
|
|
463
|
+
if (!selection.skills.includes(skill.name)) continue;
|
|
464
|
+
const dest = resolve4(destRoot, skill.sourcePath);
|
|
465
|
+
mkdirSync(dirname(dest), { recursive: true });
|
|
466
|
+
cpSync(skill.absDir, dest, { recursive: true, force: true });
|
|
467
|
+
keptSkillDirs.push(skill.sourcePath);
|
|
468
|
+
}
|
|
469
|
+
for (const command of catalog.commands) {
|
|
470
|
+
if (!selection.commands.includes(command.name)) continue;
|
|
471
|
+
const dest = resolve4(destRoot, command.sourcePath);
|
|
472
|
+
mkdirSync(dirname(dest), { recursive: true });
|
|
473
|
+
cpSync(command.absPath, dest, { force: true });
|
|
474
|
+
}
|
|
475
|
+
const filteredConfig = {};
|
|
476
|
+
if (catalog.config.shared) filteredConfig["shared"] = catalog.config.shared;
|
|
477
|
+
const mcpServers = {};
|
|
478
|
+
for (const mcp of catalog.mcp) {
|
|
479
|
+
if (selection.mcp.includes(mcp.name)) mcpServers[mcp.name] = mcp.server;
|
|
480
|
+
}
|
|
481
|
+
if (Object.keys(mcpServers).length) filteredConfig["mcpServers"] = mcpServers;
|
|
482
|
+
if (catalog.config.claude) filteredConfig["claude"] = catalog.config.claude;
|
|
483
|
+
writeFileSync(
|
|
484
|
+
resolve4(destRoot, "config.json"),
|
|
485
|
+
JSON.stringify(filteredConfig, null, 2) + "\n"
|
|
486
|
+
);
|
|
487
|
+
for (const file of ["AGENTS.md", "README.md"]) {
|
|
488
|
+
const src = resolve4(sourceCatalog.root, file);
|
|
489
|
+
if (existsSync3(src)) {
|
|
490
|
+
cpSync(src, resolve4(destRoot, file), { force: true });
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
const envVars = collectEnvVars(mcpServers);
|
|
494
|
+
const exampleDest = resolve4(targetRoot, ".env.local.example");
|
|
495
|
+
if (envVars.length && !existsSync3(exampleDest)) {
|
|
496
|
+
writeFileSync(exampleDest, envExampleContent(envVars));
|
|
497
|
+
}
|
|
498
|
+
return { source: sourceCatalog.source, root: destRoot };
|
|
499
|
+
};
|
|
500
|
+
envExampleContent = (vars) => [
|
|
501
|
+
"# Secrets for the selected MCP servers (generated by quiver).",
|
|
502
|
+
"# Copy to `.env.local` (gitignored), fill in the values, then re-run",
|
|
503
|
+
"# `quiver-cli sync`. Never commit `.env.local`.",
|
|
504
|
+
"",
|
|
505
|
+
...vars.map((v) => `${v}=`),
|
|
506
|
+
""
|
|
507
|
+
].join("\n");
|
|
508
|
+
removeArtifact = (targetRoot, sourcePath) => {
|
|
509
|
+
const path = resolve4(targetRoot, ".agents", sourcePath);
|
|
510
|
+
rmSync(path, { recursive: true, force: true });
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
// src/github/auth.ts
|
|
516
|
+
import { execFileSync } from "child_process";
|
|
517
|
+
import {
|
|
518
|
+
chmodSync,
|
|
519
|
+
existsSync as existsSync4,
|
|
520
|
+
mkdirSync as mkdirSync2,
|
|
521
|
+
readFileSync as readFileSync4,
|
|
522
|
+
rmSync as rmSync2,
|
|
523
|
+
writeFileSync as writeFileSync2
|
|
524
|
+
} from "fs";
|
|
525
|
+
import { homedir } from "os";
|
|
526
|
+
import { resolve as resolve5 } from "path";
|
|
527
|
+
var configDir, authFilePath, readAuthFile, readStoredToken, writeStoredToken, deleteStoredToken, cachedToken, ghToken, resolveGithubToken, resetTokenCache, validateToken;
|
|
528
|
+
var init_auth = __esm({
|
|
529
|
+
"src/github/auth.ts"() {
|
|
530
|
+
"use strict";
|
|
531
|
+
configDir = () => {
|
|
532
|
+
const base = process.env["XDG_CONFIG_HOME"] || resolve5(homedir(), ".config");
|
|
533
|
+
return resolve5(base, "quiver");
|
|
534
|
+
};
|
|
535
|
+
authFilePath = () => resolve5(configDir(), "auth.json");
|
|
536
|
+
readAuthFile = () => {
|
|
537
|
+
const path = authFilePath();
|
|
538
|
+
if (!existsSync4(path)) return null;
|
|
539
|
+
try {
|
|
540
|
+
return JSON.parse(readFileSync4(path, "utf8"));
|
|
541
|
+
} catch {
|
|
542
|
+
return null;
|
|
543
|
+
}
|
|
544
|
+
};
|
|
545
|
+
readStoredToken = () => readAuthFile()?.github?.token ?? null;
|
|
546
|
+
writeStoredToken = (token, login2) => {
|
|
547
|
+
mkdirSync2(configDir(), { recursive: true });
|
|
548
|
+
const file = {
|
|
549
|
+
github: { token, login: login2, createdAt: (/* @__PURE__ */ new Date()).toISOString() }
|
|
550
|
+
};
|
|
551
|
+
writeFileSync2(authFilePath(), JSON.stringify(file, null, 2) + "\n", {
|
|
552
|
+
mode: 384
|
|
553
|
+
});
|
|
554
|
+
chmodSync(authFilePath(), 384);
|
|
555
|
+
resetTokenCache();
|
|
556
|
+
};
|
|
557
|
+
deleteStoredToken = () => {
|
|
558
|
+
const path = authFilePath();
|
|
559
|
+
if (!existsSync4(path)) return false;
|
|
560
|
+
rmSync2(path);
|
|
561
|
+
resetTokenCache();
|
|
562
|
+
return true;
|
|
563
|
+
};
|
|
564
|
+
ghToken = () => {
|
|
565
|
+
try {
|
|
566
|
+
const out = execFileSync("gh", ["auth", "token"], {
|
|
567
|
+
encoding: "utf8",
|
|
568
|
+
timeout: 3e3,
|
|
569
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
570
|
+
}).trim();
|
|
571
|
+
return out || null;
|
|
572
|
+
} catch {
|
|
573
|
+
return null;
|
|
574
|
+
}
|
|
575
|
+
};
|
|
576
|
+
resolveGithubToken = () => {
|
|
577
|
+
if (cachedToken !== void 0) return cachedToken;
|
|
578
|
+
cachedToken = process.env["GITHUB_TOKEN"] ?? process.env["GH_TOKEN"] ?? readStoredToken() ?? ghToken();
|
|
579
|
+
return cachedToken;
|
|
580
|
+
};
|
|
581
|
+
resetTokenCache = () => {
|
|
582
|
+
cachedToken = void 0;
|
|
583
|
+
};
|
|
584
|
+
validateToken = async (token) => {
|
|
585
|
+
let res;
|
|
586
|
+
try {
|
|
587
|
+
res = await fetch("https://api.github.com/user", {
|
|
588
|
+
headers: {
|
|
589
|
+
Accept: "application/vnd.github+json",
|
|
590
|
+
"User-Agent": "quiver-cli",
|
|
591
|
+
Authorization: `Bearer ${token}`
|
|
592
|
+
}
|
|
593
|
+
});
|
|
594
|
+
} catch (err) {
|
|
595
|
+
return {
|
|
596
|
+
ok: false,
|
|
597
|
+
reason: err instanceof Error ? err.message : "fetch failed"
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
if (res.status === 401) return { ok: false, reason: "token is invalid or expired" };
|
|
601
|
+
if (!res.ok) return { ok: false, reason: `HTTP ${res.status}` };
|
|
602
|
+
const body = await res.json();
|
|
603
|
+
return { ok: true, login: body.login ?? "unknown" };
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
// src/github/api.ts
|
|
609
|
+
import { mkdirSync as mkdirSync3 } from "fs";
|
|
610
|
+
import { Readable } from "stream";
|
|
611
|
+
import { pipeline } from "stream/promises";
|
|
612
|
+
import * as tar from "tar";
|
|
613
|
+
var headers, failureReason, apiFetch, fetchDefaultBranch, resolveCommitSha, downloadTarball;
|
|
614
|
+
var init_api = __esm({
|
|
615
|
+
"src/github/api.ts"() {
|
|
616
|
+
"use strict";
|
|
617
|
+
init_auth();
|
|
618
|
+
headers = () => {
|
|
619
|
+
const h = {
|
|
620
|
+
Accept: "application/vnd.github+json",
|
|
621
|
+
"User-Agent": "quiver-cli"
|
|
622
|
+
};
|
|
623
|
+
const token = resolveGithubToken();
|
|
624
|
+
if (token) h["Authorization"] = `Bearer ${token}`;
|
|
625
|
+
return h;
|
|
626
|
+
};
|
|
627
|
+
failureReason = (status2) => {
|
|
628
|
+
if (status2 === 401) return "authentication failed - run `quiver-cli login` with a valid token";
|
|
629
|
+
if (status2 === 403 || status2 === 429)
|
|
630
|
+
return "rate-limited or access denied (run `quiver-cli login` or set GITHUB_TOKEN)";
|
|
631
|
+
if (status2 === 404)
|
|
632
|
+
return "repo or ref not found (private repo? run `quiver-cli login`)";
|
|
633
|
+
return `HTTP ${status2}`;
|
|
634
|
+
};
|
|
635
|
+
apiFetch = async (url) => {
|
|
636
|
+
let res;
|
|
637
|
+
try {
|
|
638
|
+
res = await fetch(url, { headers: headers() });
|
|
639
|
+
} catch (err) {
|
|
640
|
+
return {
|
|
641
|
+
ok: false,
|
|
642
|
+
reason: err instanceof Error ? err.message : "fetch failed"
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
if (!res.ok) return { ok: false, reason: failureReason(res.status) };
|
|
646
|
+
return { ok: true, value: res };
|
|
647
|
+
};
|
|
648
|
+
fetchDefaultBranch = async (repo) => {
|
|
649
|
+
const res = await apiFetch(`https://api.github.com/repos/${repo}`);
|
|
650
|
+
if (!res.ok) return res;
|
|
651
|
+
const body = await res.value.json();
|
|
652
|
+
if (!body.default_branch) return { ok: false, reason: "no default branch" };
|
|
653
|
+
return { ok: true, value: body.default_branch };
|
|
654
|
+
};
|
|
655
|
+
resolveCommitSha = async (repo, ref) => {
|
|
656
|
+
const res = await apiFetch(
|
|
657
|
+
`https://api.github.com/repos/${repo}/commits/${encodeURIComponent(ref)}`
|
|
658
|
+
);
|
|
659
|
+
if (!res.ok) return res;
|
|
660
|
+
const body = await res.value.json();
|
|
661
|
+
if (!body.sha) return { ok: false, reason: "no commit sha in response" };
|
|
662
|
+
return { ok: true, value: body.sha };
|
|
663
|
+
};
|
|
664
|
+
downloadTarball = async (repo, sha, destDir) => {
|
|
665
|
+
const res = await apiFetch(
|
|
666
|
+
`https://api.github.com/repos/${repo}/tarball/${sha}`
|
|
667
|
+
);
|
|
668
|
+
if (!res.ok) return res;
|
|
669
|
+
if (!res.value.body) return { ok: false, reason: "empty tarball response" };
|
|
670
|
+
mkdirSync3(destDir, { recursive: true });
|
|
671
|
+
try {
|
|
672
|
+
await pipeline(
|
|
673
|
+
Readable.fromWeb(res.value.body),
|
|
674
|
+
tar.x({ cwd: destDir, strip: 1 })
|
|
675
|
+
);
|
|
676
|
+
} catch (err) {
|
|
677
|
+
return {
|
|
678
|
+
ok: false,
|
|
679
|
+
reason: err instanceof Error ? err.message : "tarball extraction failed"
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
return { ok: true, value: null };
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
// src/catalog/remote.ts
|
|
688
|
+
var remote_exports = {};
|
|
689
|
+
__export(remote_exports, {
|
|
690
|
+
catalogCacheDir: () => catalogCacheDir,
|
|
691
|
+
catalogCacheRoot: () => catalogCacheRoot,
|
|
692
|
+
fetchRemoteCatalog: () => fetchRemoteCatalog,
|
|
693
|
+
parseGithubSource: () => parseGithubSource
|
|
694
|
+
});
|
|
695
|
+
import { existsSync as existsSync5, mkdtempSync, renameSync, rmSync as rmSync3, mkdirSync as mkdirSync4 } from "fs";
|
|
696
|
+
import { homedir as homedir2 } from "os";
|
|
697
|
+
import { dirname as dirname2, resolve as resolve6 } from "path";
|
|
698
|
+
var parseGithubSource, catalogCacheRoot, catalogCacheDir, fetchRemoteCatalog;
|
|
699
|
+
var init_remote = __esm({
|
|
700
|
+
"src/catalog/remote.ts"() {
|
|
701
|
+
"use strict";
|
|
702
|
+
init_api();
|
|
703
|
+
parseGithubSource = (source) => {
|
|
704
|
+
const spec = source.startsWith("github:") ? source.slice("github:".length) : source;
|
|
705
|
+
const [location = "", ...refParts] = spec.split("#");
|
|
706
|
+
const ref = refParts.length ? refParts.join("#") : null;
|
|
707
|
+
const segments = location.split("/").filter(Boolean);
|
|
708
|
+
const [owner, repo, ...pathParts] = segments;
|
|
709
|
+
if (!owner || !repo) {
|
|
710
|
+
throw new Error(
|
|
711
|
+
`Invalid catalog source "${source}". Expected github:owner/repo[/path][#ref].`
|
|
712
|
+
);
|
|
713
|
+
}
|
|
714
|
+
if (ref !== null && !ref) {
|
|
715
|
+
throw new Error(`Invalid catalog source "${source}": empty #ref.`);
|
|
716
|
+
}
|
|
717
|
+
return { repo: `${owner}/${repo}`, path: pathParts.join("/"), ref };
|
|
718
|
+
};
|
|
719
|
+
catalogCacheRoot = () => {
|
|
720
|
+
const base = process.env["XDG_CACHE_HOME"] || resolve6(homedir2(), ".cache");
|
|
721
|
+
return resolve6(base, "quiver", "catalogs");
|
|
722
|
+
};
|
|
723
|
+
catalogCacheDir = (repo, sha) => resolve6(catalogCacheRoot(), `${repo.replace("/", "-")}-${sha.slice(0, 12)}`);
|
|
724
|
+
fetchRemoteCatalog = async (source, options = {}) => {
|
|
725
|
+
const spec = parseGithubSource(source);
|
|
726
|
+
let ref = spec.ref;
|
|
727
|
+
let sha = options.pinnedSha ?? null;
|
|
728
|
+
if (!sha) {
|
|
729
|
+
if (!ref) {
|
|
730
|
+
const branch = await fetchDefaultBranch(spec.repo);
|
|
731
|
+
if (!branch.ok) {
|
|
732
|
+
throw new Error(`Cannot resolve ${spec.repo}: ${branch.reason}`);
|
|
733
|
+
}
|
|
734
|
+
ref = branch.value;
|
|
735
|
+
}
|
|
736
|
+
const commit = await resolveCommitSha(spec.repo, ref);
|
|
737
|
+
if (!commit.ok) {
|
|
738
|
+
throw new Error(`Cannot resolve ${spec.repo}@${ref}: ${commit.reason}`);
|
|
739
|
+
}
|
|
740
|
+
sha = commit.value;
|
|
741
|
+
}
|
|
742
|
+
const cacheDir = catalogCacheDir(spec.repo, sha);
|
|
743
|
+
if (!existsSync5(cacheDir)) {
|
|
744
|
+
mkdirSync4(dirname2(cacheDir), { recursive: true });
|
|
745
|
+
const tmp = mkdtempSync(`${cacheDir}.tmp-`);
|
|
746
|
+
const result = await downloadTarball(spec.repo, sha, tmp);
|
|
747
|
+
if (!result.ok) {
|
|
748
|
+
rmSync3(tmp, { recursive: true, force: true });
|
|
749
|
+
throw new Error(`Cannot download ${spec.repo}@${sha.slice(0, 12)}: ${result.reason}`);
|
|
750
|
+
}
|
|
751
|
+
try {
|
|
752
|
+
renameSync(tmp, cacheDir);
|
|
753
|
+
} catch {
|
|
754
|
+
rmSync3(tmp, { recursive: true, force: true });
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
const root = spec.path ? resolve6(cacheDir, spec.path) : cacheDir;
|
|
758
|
+
if (!existsSync5(root)) {
|
|
759
|
+
throw new Error(
|
|
760
|
+
`Catalog path "${spec.path}" not found in ${spec.repo}@${sha.slice(0, 12)}.`
|
|
761
|
+
);
|
|
762
|
+
}
|
|
763
|
+
return { source, root, ref, resolved: sha, fetchedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
});
|
|
767
|
+
|
|
768
|
+
// src/catalog/resolve.ts
|
|
769
|
+
var resolve_exports = {};
|
|
770
|
+
__export(resolve_exports, {
|
|
771
|
+
DEFAULT_CATALOG_SOURCE: () => DEFAULT_CATALOG_SOURCE,
|
|
772
|
+
packageRoot: () => packageRoot,
|
|
773
|
+
resolveCatalog: () => resolveCatalog
|
|
774
|
+
});
|
|
775
|
+
import { existsSync as existsSync6 } from "fs";
|
|
776
|
+
import { dirname as dirname3, resolve as resolve7 } from "path";
|
|
777
|
+
import { fileURLToPath } from "url";
|
|
778
|
+
var packageRoot, DEFAULT_CATALOG_SOURCE, resolveCatalog;
|
|
779
|
+
var init_resolve = __esm({
|
|
780
|
+
"src/catalog/resolve.ts"() {
|
|
781
|
+
"use strict";
|
|
782
|
+
packageRoot = resolve7(dirname3(fileURLToPath(import.meta.url)), "..");
|
|
783
|
+
DEFAULT_CATALOG_SOURCE = "local:template/.agents";
|
|
784
|
+
resolveCatalog = async (source = DEFAULT_CATALOG_SOURCE, options = {}) => {
|
|
785
|
+
const [scheme, ...rest] = source.split(":");
|
|
786
|
+
const spec = rest.join(":");
|
|
787
|
+
if (scheme === "local") {
|
|
788
|
+
const root = resolve7(packageRoot, spec);
|
|
789
|
+
if (!existsSync6(root)) {
|
|
790
|
+
throw new Error(`Catalog not found at ${root} (source: ${source}).`);
|
|
791
|
+
}
|
|
792
|
+
return { source, root };
|
|
793
|
+
}
|
|
794
|
+
if (scheme === "github") {
|
|
795
|
+
const { fetchRemoteCatalog: fetchRemoteCatalog2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
796
|
+
return fetchRemoteCatalog2(source, options);
|
|
797
|
+
}
|
|
798
|
+
throw new Error(`Unknown catalog source scheme: ${source}`);
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
});
|
|
802
|
+
|
|
803
|
+
// src/lockfile/schema.ts
|
|
804
|
+
var LOCKFILE_VERSION, LOCKFILE_NAME, PROVIDERS, isProvider, entryId, parseEntryId;
|
|
805
|
+
var init_schema = __esm({
|
|
806
|
+
"src/lockfile/schema.ts"() {
|
|
807
|
+
"use strict";
|
|
808
|
+
LOCKFILE_VERSION = 1;
|
|
809
|
+
LOCKFILE_NAME = "quiver.lock";
|
|
810
|
+
PROVIDERS = ["claude", "opencode", "codex"];
|
|
811
|
+
isProvider = (v) => PROVIDERS.includes(v);
|
|
812
|
+
entryId = (type, name) => `${type}:${name}`;
|
|
813
|
+
parseEntryId = (id) => {
|
|
814
|
+
const idx = id.indexOf(":");
|
|
815
|
+
if (idx === -1) return null;
|
|
816
|
+
const type = id.slice(0, idx);
|
|
817
|
+
const name = id.slice(idx + 1);
|
|
818
|
+
if (type !== "skill" && type !== "command" && type !== "mcp") return null;
|
|
819
|
+
if (!name) return null;
|
|
820
|
+
return { type, name };
|
|
821
|
+
};
|
|
822
|
+
}
|
|
823
|
+
});
|
|
824
|
+
|
|
825
|
+
// src/lockfile/io.ts
|
|
826
|
+
import { existsSync as existsSync7, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
|
|
827
|
+
import { resolve as resolve8 } from "path";
|
|
828
|
+
var lockfilePath, lockfileExists, readLockfile, writeLockfile, emptyLockfile;
|
|
829
|
+
var init_io = __esm({
|
|
830
|
+
"src/lockfile/io.ts"() {
|
|
831
|
+
"use strict";
|
|
832
|
+
init_schema();
|
|
833
|
+
lockfilePath = (targetRoot) => resolve8(targetRoot, LOCKFILE_NAME);
|
|
834
|
+
lockfileExists = (targetRoot) => existsSync7(lockfilePath(targetRoot));
|
|
835
|
+
readLockfile = (targetRoot) => {
|
|
836
|
+
const path = lockfilePath(targetRoot);
|
|
837
|
+
if (!existsSync7(path)) return null;
|
|
838
|
+
const parsed = JSON.parse(readFileSync5(path, "utf8"));
|
|
839
|
+
if (parsed.version !== LOCKFILE_VERSION) {
|
|
840
|
+
throw new Error(
|
|
841
|
+
`Unsupported ${LOCKFILE_NAME} version ${parsed.version}; expected ${LOCKFILE_VERSION}.`
|
|
842
|
+
);
|
|
843
|
+
}
|
|
844
|
+
return parsed;
|
|
845
|
+
};
|
|
846
|
+
writeLockfile = (targetRoot, lock) => {
|
|
847
|
+
const sortedEntries = {};
|
|
848
|
+
for (const key of Object.keys(lock.entries).sort()) {
|
|
849
|
+
sortedEntries[key] = lock.entries[key];
|
|
850
|
+
}
|
|
851
|
+
const ordered = {
|
|
852
|
+
version: lock.version,
|
|
853
|
+
catalog: lock.catalog,
|
|
854
|
+
...lock.providers !== void 0 ? { providers: lock.providers } : {},
|
|
855
|
+
entries: sortedEntries
|
|
856
|
+
};
|
|
857
|
+
writeFileSync3(lockfilePath(targetRoot), JSON.stringify(ordered, null, 2) + "\n");
|
|
858
|
+
};
|
|
859
|
+
emptyLockfile = (catalogSource, remote = {}) => ({
|
|
860
|
+
version: LOCKFILE_VERSION,
|
|
861
|
+
catalog: {
|
|
862
|
+
source: catalogSource,
|
|
863
|
+
ref: remote.ref ?? null,
|
|
864
|
+
resolved: remote.resolved ?? null,
|
|
865
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
866
|
+
},
|
|
867
|
+
entries: {}
|
|
868
|
+
});
|
|
869
|
+
}
|
|
870
|
+
});
|
|
871
|
+
|
|
872
|
+
// src/providers/claude.ts
|
|
873
|
+
import { resolve as resolve9 } from "path";
|
|
874
|
+
var formatMcpJson, planClaude;
|
|
875
|
+
var init_claude = __esm({
|
|
876
|
+
"src/providers/claude.ts"() {
|
|
877
|
+
"use strict";
|
|
878
|
+
formatMcpJson = (servers) => {
|
|
879
|
+
if (Object.keys(servers).length === 0) return null;
|
|
880
|
+
return JSON.stringify({ mcpServers: servers }, null, 2) + "\n";
|
|
881
|
+
};
|
|
882
|
+
planClaude = (inputs) => {
|
|
883
|
+
const { targetRoot, selected, mcpServers, claudeSettings } = inputs;
|
|
884
|
+
const files = [];
|
|
885
|
+
const removeFiles = [];
|
|
886
|
+
if (claudeSettings) {
|
|
887
|
+
files.push({
|
|
888
|
+
path: resolve9(targetRoot, ".claude/settings.json"),
|
|
889
|
+
content: JSON.stringify(claudeSettings, null, 2) + "\n"
|
|
890
|
+
});
|
|
891
|
+
}
|
|
892
|
+
const mcp = formatMcpJson(mcpServers);
|
|
893
|
+
const mcpPath = resolve9(targetRoot, ".mcp.json");
|
|
894
|
+
if (mcp) files.push({ path: mcpPath, content: mcp });
|
|
895
|
+
else removeFiles.push(mcpPath);
|
|
896
|
+
const skillNames = selected.skills.map((s) => s.name);
|
|
897
|
+
const commandFiles = selected.commands.map((c) => `${c.name}.md`);
|
|
898
|
+
const symlinks = [
|
|
899
|
+
...selected.skills.map((s) => ({
|
|
900
|
+
path: resolve9(targetRoot, ".claude/skills", s.name),
|
|
901
|
+
target: s.absDir
|
|
902
|
+
})),
|
|
903
|
+
...selected.commands.map((c) => ({
|
|
904
|
+
path: resolve9(targetRoot, ".claude/commands", `${c.name}.md`),
|
|
905
|
+
target: c.absPath
|
|
906
|
+
}))
|
|
907
|
+
];
|
|
908
|
+
const managedDirs = [
|
|
909
|
+
{
|
|
910
|
+
path: resolve9(targetRoot, ".claude/skills"),
|
|
911
|
+
expected: new Set(skillNames)
|
|
912
|
+
},
|
|
913
|
+
{
|
|
914
|
+
path: resolve9(targetRoot, ".claude/commands"),
|
|
915
|
+
expected: new Set(commandFiles)
|
|
916
|
+
}
|
|
917
|
+
];
|
|
918
|
+
return { files, removeFiles, symlinks, managedDirs };
|
|
919
|
+
};
|
|
920
|
+
}
|
|
921
|
+
});
|
|
922
|
+
|
|
923
|
+
// src/providers/codex.ts
|
|
924
|
+
import { resolve as resolve10 } from "path";
|
|
925
|
+
import TOML from "@iarna/toml";
|
|
926
|
+
var buildConfig, planCodex;
|
|
927
|
+
var init_codex = __esm({
|
|
928
|
+
"src/providers/codex.ts"() {
|
|
929
|
+
"use strict";
|
|
930
|
+
buildConfig = (servers) => {
|
|
931
|
+
if (Object.keys(servers).length === 0) return null;
|
|
932
|
+
const mcpServers = {};
|
|
933
|
+
for (const [name, server] of Object.entries(servers)) {
|
|
934
|
+
if (server.transport === "http") {
|
|
935
|
+
const httpHeaders = {};
|
|
936
|
+
const envHeaders = {};
|
|
937
|
+
for (const [key, value] of Object.entries(server.headers ?? {})) {
|
|
938
|
+
const single = value.match(/^\$\{([^}]+)\}$/);
|
|
939
|
+
const embedded = value.match(/\$\{([^}]+)\}/);
|
|
940
|
+
if (single) {
|
|
941
|
+
envHeaders[key] = single[1];
|
|
942
|
+
} else if (embedded) {
|
|
943
|
+
envHeaders[key] = embedded[1];
|
|
944
|
+
} else {
|
|
945
|
+
httpHeaders[key] = value;
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
mcpServers[name] = {
|
|
949
|
+
url: server.url,
|
|
950
|
+
...Object.keys(httpHeaders).length ? { http_headers: httpHeaders } : {},
|
|
951
|
+
...Object.keys(envHeaders).length ? { env_http_headers: envHeaders } : {}
|
|
952
|
+
};
|
|
953
|
+
} else {
|
|
954
|
+
mcpServers[name] = {
|
|
955
|
+
command: server.command,
|
|
956
|
+
...server.args && server.args.length ? { args: server.args } : {},
|
|
957
|
+
...server.env ? { env: server.env } : {}
|
|
958
|
+
};
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
return { mcp_servers: mcpServers };
|
|
962
|
+
};
|
|
963
|
+
planCodex = (inputs) => {
|
|
964
|
+
const { targetRoot, rawMcpServers } = inputs;
|
|
965
|
+
const files = [];
|
|
966
|
+
const removeFiles = [];
|
|
967
|
+
const config = buildConfig(rawMcpServers);
|
|
968
|
+
const path = resolve10(targetRoot, ".codex/config.toml");
|
|
969
|
+
if (config) {
|
|
970
|
+
files.push({
|
|
971
|
+
path,
|
|
972
|
+
content: "# Generated by quiver. Source of truth: .agents/config.json\n" + TOML.stringify(config)
|
|
973
|
+
});
|
|
974
|
+
} else {
|
|
975
|
+
removeFiles.push(path);
|
|
976
|
+
}
|
|
977
|
+
return { files, removeFiles, symlinks: [], managedDirs: [] };
|
|
978
|
+
};
|
|
979
|
+
}
|
|
980
|
+
});
|
|
981
|
+
|
|
982
|
+
// src/providers/fsops.ts
|
|
983
|
+
import {
|
|
984
|
+
existsSync as existsSync8,
|
|
985
|
+
lstatSync,
|
|
986
|
+
mkdirSync as mkdirSync5,
|
|
987
|
+
readdirSync as readdirSync3,
|
|
988
|
+
readFileSync as readFileSync6,
|
|
989
|
+
readlinkSync,
|
|
990
|
+
rmdirSync,
|
|
991
|
+
rmSync as rmSync4,
|
|
992
|
+
symlinkSync,
|
|
993
|
+
unlinkSync,
|
|
994
|
+
writeFileSync as writeFileSync4
|
|
995
|
+
} from "fs";
|
|
996
|
+
import { dirname as dirname4, relative as relative3, resolve as resolve11 } from "path";
|
|
997
|
+
var isENOENT, isMatchingSymlink, removePath, applyOutputs, checkOutputs;
|
|
998
|
+
var init_fsops = __esm({
|
|
999
|
+
"src/providers/fsops.ts"() {
|
|
1000
|
+
"use strict";
|
|
1001
|
+
isENOENT = (e) => !!e && typeof e === "object" && "code" in e && e.code === "ENOENT";
|
|
1002
|
+
isMatchingSymlink = (path, target) => {
|
|
1003
|
+
const stats = lstatSync(path);
|
|
1004
|
+
if (!stats.isSymbolicLink()) return false;
|
|
1005
|
+
return resolve11(dirname4(path), readlinkSync(path)) === target;
|
|
1006
|
+
};
|
|
1007
|
+
removePath = (path) => {
|
|
1008
|
+
let stats;
|
|
1009
|
+
try {
|
|
1010
|
+
stats = lstatSync(path);
|
|
1011
|
+
} catch (e) {
|
|
1012
|
+
if (isENOENT(e)) return;
|
|
1013
|
+
throw e;
|
|
1014
|
+
}
|
|
1015
|
+
if (stats.isSymbolicLink()) unlinkSync(path);
|
|
1016
|
+
else rmSync4(path, { force: true, recursive: true });
|
|
1017
|
+
};
|
|
1018
|
+
applyOutputs = ({
|
|
1019
|
+
files,
|
|
1020
|
+
symlinks,
|
|
1021
|
+
removeFiles,
|
|
1022
|
+
removeDirs = [],
|
|
1023
|
+
managedDirs
|
|
1024
|
+
}) => {
|
|
1025
|
+
const result = { generated: [], linked: [], removed: [] };
|
|
1026
|
+
for (const out of files) {
|
|
1027
|
+
let current = null;
|
|
1028
|
+
try {
|
|
1029
|
+
current = readFileSync6(out.path, "utf8");
|
|
1030
|
+
} catch (e) {
|
|
1031
|
+
if (!isENOENT(e)) throw e;
|
|
1032
|
+
}
|
|
1033
|
+
if (current === out.content) continue;
|
|
1034
|
+
mkdirSync5(dirname4(out.path), { recursive: true });
|
|
1035
|
+
writeFileSync4(out.path, out.content);
|
|
1036
|
+
result.generated.push(out.path);
|
|
1037
|
+
}
|
|
1038
|
+
for (const path of removeFiles) {
|
|
1039
|
+
if (!existsSync8(path)) continue;
|
|
1040
|
+
rmSync4(path, { force: true });
|
|
1041
|
+
result.removed.push(path);
|
|
1042
|
+
}
|
|
1043
|
+
for (const path of removeDirs) {
|
|
1044
|
+
if (!existsSync8(path)) continue;
|
|
1045
|
+
removePath(path);
|
|
1046
|
+
result.removed.push(path);
|
|
1047
|
+
}
|
|
1048
|
+
for (const path of removeDirs) {
|
|
1049
|
+
try {
|
|
1050
|
+
rmdirSync(dirname4(path));
|
|
1051
|
+
} catch {
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
for (const link of symlinks) {
|
|
1055
|
+
mkdirSync5(dirname4(link.path), { recursive: true });
|
|
1056
|
+
try {
|
|
1057
|
+
if (isMatchingSymlink(link.path, link.target)) continue;
|
|
1058
|
+
} catch (e) {
|
|
1059
|
+
if (!isENOENT(e)) throw e;
|
|
1060
|
+
}
|
|
1061
|
+
removePath(link.path);
|
|
1062
|
+
symlinkSync(
|
|
1063
|
+
relative3(dirname4(link.path), link.target),
|
|
1064
|
+
link.path,
|
|
1065
|
+
lstatSync(link.target).isDirectory() ? "dir" : "file"
|
|
1066
|
+
);
|
|
1067
|
+
result.linked.push(link.path);
|
|
1068
|
+
}
|
|
1069
|
+
for (const dir of managedDirs) {
|
|
1070
|
+
if (!existsSync8(dir.path)) continue;
|
|
1071
|
+
for (const child of readdirSync3(dir.path)) {
|
|
1072
|
+
if (dir.expected.has(child)) continue;
|
|
1073
|
+
const childPath = resolve11(dir.path, child);
|
|
1074
|
+
removePath(childPath);
|
|
1075
|
+
result.removed.push(childPath);
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
return result;
|
|
1079
|
+
};
|
|
1080
|
+
checkOutputs = ({
|
|
1081
|
+
files,
|
|
1082
|
+
symlinks,
|
|
1083
|
+
removeFiles,
|
|
1084
|
+
removeDirs = [],
|
|
1085
|
+
managedDirs
|
|
1086
|
+
}) => {
|
|
1087
|
+
const problems = [];
|
|
1088
|
+
for (const out of files) {
|
|
1089
|
+
try {
|
|
1090
|
+
if (readFileSync6(out.path, "utf8") !== out.content) {
|
|
1091
|
+
problems.push(`out of sync: ${out.path}`);
|
|
1092
|
+
}
|
|
1093
|
+
} catch (e) {
|
|
1094
|
+
if (isENOENT(e)) problems.push(`missing: ${out.path}`);
|
|
1095
|
+
else throw e;
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
for (const path of removeFiles) {
|
|
1099
|
+
if (existsSync8(path)) problems.push(`stale (should be removed): ${path}`);
|
|
1100
|
+
}
|
|
1101
|
+
for (const path of removeDirs) {
|
|
1102
|
+
if (existsSync8(path)) problems.push(`stale (should be removed): ${path}`);
|
|
1103
|
+
}
|
|
1104
|
+
for (const link of symlinks) {
|
|
1105
|
+
try {
|
|
1106
|
+
if (!isMatchingSymlink(link.path, link.target)) {
|
|
1107
|
+
problems.push(`out of sync symlink: ${link.path}`);
|
|
1108
|
+
}
|
|
1109
|
+
} catch (e) {
|
|
1110
|
+
if (isENOENT(e)) problems.push(`missing symlink: ${link.path}`);
|
|
1111
|
+
else throw e;
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
for (const dir of managedDirs) {
|
|
1115
|
+
if (!existsSync8(dir.path)) continue;
|
|
1116
|
+
for (const child of readdirSync3(dir.path)) {
|
|
1117
|
+
if (!dir.expected.has(child)) {
|
|
1118
|
+
problems.push(`unexpected shim: ${resolve11(dir.path, child)}`);
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
return problems;
|
|
1123
|
+
};
|
|
1124
|
+
}
|
|
1125
|
+
});
|
|
1126
|
+
|
|
1127
|
+
// src/providers/opencode.ts
|
|
1128
|
+
import { resolve as resolve12 } from "path";
|
|
1129
|
+
var formatOpenCodeJson, planOpenCode;
|
|
1130
|
+
var init_opencode = __esm({
|
|
1131
|
+
"src/providers/opencode.ts"() {
|
|
1132
|
+
"use strict";
|
|
1133
|
+
formatOpenCodeJson = (servers) => {
|
|
1134
|
+
if (Object.keys(servers).length === 0) return null;
|
|
1135
|
+
const mcp = {};
|
|
1136
|
+
for (const [name, server] of Object.entries(servers)) {
|
|
1137
|
+
if (server.transport === "http") {
|
|
1138
|
+
mcp[name] = {
|
|
1139
|
+
type: "remote",
|
|
1140
|
+
url: server.url,
|
|
1141
|
+
...server.headers ? { headers: server.headers } : {}
|
|
1142
|
+
};
|
|
1143
|
+
} else {
|
|
1144
|
+
mcp[name] = {
|
|
1145
|
+
type: "local",
|
|
1146
|
+
command: [server.command, ...server.args ?? []],
|
|
1147
|
+
...server.env ? { environment: server.env } : {}
|
|
1148
|
+
};
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
return JSON.stringify(
|
|
1152
|
+
{ $schema: "https://opencode.ai/config.json", mcp },
|
|
1153
|
+
null,
|
|
1154
|
+
2
|
|
1155
|
+
) + "\n";
|
|
1156
|
+
};
|
|
1157
|
+
planOpenCode = (inputs) => {
|
|
1158
|
+
const { targetRoot, selected, mcpServers } = inputs;
|
|
1159
|
+
const files = [];
|
|
1160
|
+
const removeFiles = [];
|
|
1161
|
+
const json = formatOpenCodeJson(mcpServers);
|
|
1162
|
+
const jsonPath = resolve12(targetRoot, "opencode.json");
|
|
1163
|
+
if (json) files.push({ path: jsonPath, content: json });
|
|
1164
|
+
else removeFiles.push(jsonPath);
|
|
1165
|
+
const symlinks = [
|
|
1166
|
+
...selected.skills.map((s) => ({
|
|
1167
|
+
path: resolve12(targetRoot, ".opencode/skills", s.name),
|
|
1168
|
+
target: s.absDir
|
|
1169
|
+
})),
|
|
1170
|
+
...selected.commands.map((c) => ({
|
|
1171
|
+
path: resolve12(targetRoot, ".opencode/commands", `${c.name}.md`),
|
|
1172
|
+
target: c.absPath
|
|
1173
|
+
}))
|
|
1174
|
+
];
|
|
1175
|
+
const managedDirs = [
|
|
1176
|
+
{
|
|
1177
|
+
path: resolve12(targetRoot, ".opencode/skills"),
|
|
1178
|
+
expected: new Set(selected.skills.map((s) => s.name))
|
|
1179
|
+
},
|
|
1180
|
+
{
|
|
1181
|
+
path: resolve12(targetRoot, ".opencode/commands"),
|
|
1182
|
+
expected: new Set(selected.commands.map((c) => `${c.name}.md`))
|
|
1183
|
+
}
|
|
1184
|
+
];
|
|
1185
|
+
return { files, removeFiles, symlinks, managedDirs };
|
|
1186
|
+
};
|
|
1187
|
+
}
|
|
1188
|
+
});
|
|
1189
|
+
|
|
1190
|
+
// src/providers/selection.ts
|
|
1191
|
+
var resolveSelection;
|
|
1192
|
+
var init_selection = __esm({
|
|
1193
|
+
"src/providers/selection.ts"() {
|
|
1194
|
+
"use strict";
|
|
1195
|
+
init_schema();
|
|
1196
|
+
resolveSelection = (catalog, lock) => {
|
|
1197
|
+
const skillNames = /* @__PURE__ */ new Set();
|
|
1198
|
+
const commandNames = /* @__PURE__ */ new Set();
|
|
1199
|
+
const mcpNames = /* @__PURE__ */ new Set();
|
|
1200
|
+
for (const id of Object.keys(lock.entries)) {
|
|
1201
|
+
const parsed = parseEntryId(id);
|
|
1202
|
+
if (!parsed) continue;
|
|
1203
|
+
if (parsed.type === "skill") skillNames.add(parsed.name);
|
|
1204
|
+
else if (parsed.type === "command") commandNames.add(parsed.name);
|
|
1205
|
+
else if (parsed.type === "mcp") mcpNames.add(parsed.name);
|
|
1206
|
+
}
|
|
1207
|
+
return {
|
|
1208
|
+
skills: catalog.skills.filter((s) => skillNames.has(s.name)),
|
|
1209
|
+
commands: catalog.commands.filter((c) => commandNames.has(c.name)),
|
|
1210
|
+
mcp: catalog.mcp.filter((m) => mcpNames.has(m.name))
|
|
1211
|
+
};
|
|
1212
|
+
};
|
|
1213
|
+
}
|
|
1214
|
+
});
|
|
1215
|
+
|
|
1216
|
+
// src/providers/write.ts
|
|
1217
|
+
import { existsSync as existsSync9, lstatSync as lstatSync2 } from "fs";
|
|
1218
|
+
import { basename, resolve as resolve13 } from "path";
|
|
1219
|
+
var isLinkable, buildPlan, writeProviders, checkProviders;
|
|
1220
|
+
var init_write = __esm({
|
|
1221
|
+
"src/providers/write.ts"() {
|
|
1222
|
+
"use strict";
|
|
1223
|
+
init_schema();
|
|
1224
|
+
init_interpolate();
|
|
1225
|
+
init_claude();
|
|
1226
|
+
init_codex();
|
|
1227
|
+
init_fsops();
|
|
1228
|
+
init_opencode();
|
|
1229
|
+
init_selection();
|
|
1230
|
+
isLinkable = (path) => {
|
|
1231
|
+
try {
|
|
1232
|
+
return lstatSync2(path).isSymbolicLink();
|
|
1233
|
+
} catch {
|
|
1234
|
+
return true;
|
|
1235
|
+
}
|
|
1236
|
+
};
|
|
1237
|
+
buildPlan = (targetRoot, catalog, lock, onMissingEnv, onSkippedRootFile) => {
|
|
1238
|
+
loadEnvLocal(targetRoot);
|
|
1239
|
+
const selected = resolveSelection(catalog, lock);
|
|
1240
|
+
const rawMcpServers = {};
|
|
1241
|
+
for (const mcp of selected.mcp) rawMcpServers[mcp.name] = mcp.server;
|
|
1242
|
+
const mcpServers = interpolateEnvVars(rawMcpServers, onMissingEnv);
|
|
1243
|
+
const agentsRoot = resolve13(targetRoot, ".agents");
|
|
1244
|
+
const inputs = {
|
|
1245
|
+
targetRoot,
|
|
1246
|
+
agentsRoot,
|
|
1247
|
+
selected,
|
|
1248
|
+
mcpServers,
|
|
1249
|
+
rawMcpServers,
|
|
1250
|
+
claudeSettings: catalog.config.claude?.settings ?? null
|
|
1251
|
+
};
|
|
1252
|
+
const active = lock.providers?.length ? lock.providers : [...PROVIDERS];
|
|
1253
|
+
const planByProvider = {
|
|
1254
|
+
claude: planClaude(inputs),
|
|
1255
|
+
opencode: planOpenCode(inputs),
|
|
1256
|
+
codex: planCodex(inputs)
|
|
1257
|
+
};
|
|
1258
|
+
const plans = [];
|
|
1259
|
+
const removeDirs = [];
|
|
1260
|
+
const removedOutputs = [];
|
|
1261
|
+
for (const provider of PROVIDERS) {
|
|
1262
|
+
const plan = planByProvider[provider];
|
|
1263
|
+
if (active.includes(provider)) {
|
|
1264
|
+
plans.push(plan);
|
|
1265
|
+
} else {
|
|
1266
|
+
removedOutputs.push(...plan.files.map((f) => f.path), ...plan.removeFiles);
|
|
1267
|
+
removeDirs.push(
|
|
1268
|
+
...plan.symlinks.map((s) => s.path),
|
|
1269
|
+
...plan.managedDirs.map((d) => d.path)
|
|
1270
|
+
);
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
const rootSymlinks = [];
|
|
1274
|
+
const agentsMd = resolve13(agentsRoot, "AGENTS.md");
|
|
1275
|
+
if (existsSync9(agentsMd)) {
|
|
1276
|
+
const rootAgents = resolve13(targetRoot, "AGENTS.md");
|
|
1277
|
+
for (const link of [
|
|
1278
|
+
{ path: rootAgents, target: agentsMd },
|
|
1279
|
+
{ path: resolve13(targetRoot, "CLAUDE.md"), target: rootAgents }
|
|
1280
|
+
]) {
|
|
1281
|
+
if (isLinkable(link.path)) rootSymlinks.push(link);
|
|
1282
|
+
else onSkippedRootFile?.(basename(link.path));
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
return {
|
|
1286
|
+
files: plans.flatMap((p) => p.files),
|
|
1287
|
+
removeFiles: [...plans.flatMap((p) => p.removeFiles), ...removedOutputs],
|
|
1288
|
+
removeDirs,
|
|
1289
|
+
symlinks: [...rootSymlinks, ...plans.flatMap((p) => p.symlinks)],
|
|
1290
|
+
managedDirs: plans.flatMap((p) => p.managedDirs)
|
|
1291
|
+
};
|
|
1292
|
+
};
|
|
1293
|
+
writeProviders = (targetRoot, catalog, lock) => {
|
|
1294
|
+
const missing = /* @__PURE__ */ new Set();
|
|
1295
|
+
const skipped = /* @__PURE__ */ new Set();
|
|
1296
|
+
const plan = buildPlan(
|
|
1297
|
+
targetRoot,
|
|
1298
|
+
catalog,
|
|
1299
|
+
lock,
|
|
1300
|
+
(n) => missing.add(n),
|
|
1301
|
+
(n) => skipped.add(n)
|
|
1302
|
+
);
|
|
1303
|
+
const result = applyOutputs(plan);
|
|
1304
|
+
if (missing.size) {
|
|
1305
|
+
console.warn(
|
|
1306
|
+
`Warning: unset env vars left un-interpolated: ${[...missing].join(", ")}. Set them in .env.local and re-run quiver-cli sync.`
|
|
1307
|
+
);
|
|
1308
|
+
}
|
|
1309
|
+
if (skipped.size) {
|
|
1310
|
+
console.warn(
|
|
1311
|
+
`Warning: ${[...skipped].join(", ")} already exist(s) and is not managed by quiver - left untouched. Merge .agents/AGENTS.md manually if desired.`
|
|
1312
|
+
);
|
|
1313
|
+
}
|
|
1314
|
+
return result;
|
|
1315
|
+
};
|
|
1316
|
+
checkProviders = (targetRoot, catalog, lock) => {
|
|
1317
|
+
const plan = buildPlan(targetRoot, catalog, lock, () => {
|
|
1318
|
+
});
|
|
1319
|
+
return checkOutputs(plan);
|
|
1320
|
+
};
|
|
1321
|
+
}
|
|
1322
|
+
});
|
|
1323
|
+
|
|
1324
|
+
// src/commands/gitignore.ts
|
|
1325
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
1326
|
+
import { existsSync as existsSync10, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
|
|
1327
|
+
import { resolve as resolve14 } from "path";
|
|
1328
|
+
var HEADER, SECRETS_COMMENT, PROVIDER_ENTRIES, SHARED_ENTRIES, SECRET_ENTRIES, patchGitignore, ignoredSourcePaths;
|
|
1329
|
+
var init_gitignore = __esm({
|
|
1330
|
+
"src/commands/gitignore.ts"() {
|
|
1331
|
+
"use strict";
|
|
1332
|
+
init_schema();
|
|
1333
|
+
HEADER = "# Generated by quiver (source of truth: .agents/ + quiver.lock)";
|
|
1334
|
+
SECRETS_COMMENT = "# Local secrets for MCP env-var interpolation";
|
|
1335
|
+
PROVIDER_ENTRIES = {
|
|
1336
|
+
claude: [".claude/", ".mcp.json"],
|
|
1337
|
+
opencode: [".opencode/", "opencode.json"],
|
|
1338
|
+
codex: [".codex/"]
|
|
1339
|
+
};
|
|
1340
|
+
SHARED_ENTRIES = ["AGENTS.md", "CLAUDE.md"];
|
|
1341
|
+
SECRET_ENTRIES = [".env.local"];
|
|
1342
|
+
patchGitignore = (targetRoot, providers) => {
|
|
1343
|
+
const path = resolve14(targetRoot, ".gitignore");
|
|
1344
|
+
const current = existsSync10(path) ? readFileSync7(path, "utf8") : "";
|
|
1345
|
+
const lines = new Set(current.split("\n").map((l) => l.trim()));
|
|
1346
|
+
const active = providers?.length ? providers : [...PROVIDERS];
|
|
1347
|
+
const shimEntries = [
|
|
1348
|
+
...active.flatMap((p) => PROVIDER_ENTRIES[p]),
|
|
1349
|
+
...SHARED_ENTRIES
|
|
1350
|
+
];
|
|
1351
|
+
const missingShims = shimEntries.filter((e) => !lines.has(e));
|
|
1352
|
+
const missingSecrets = SECRET_ENTRIES.filter((e) => !lines.has(e));
|
|
1353
|
+
if (missingShims.length === 0 && missingSecrets.length === 0) return false;
|
|
1354
|
+
const block2 = [];
|
|
1355
|
+
if (missingShims.length) block2.push(HEADER, ...missingShims);
|
|
1356
|
+
if (missingSecrets.length) block2.push(SECRETS_COMMENT, ...missingSecrets);
|
|
1357
|
+
const prefix = current && !current.endsWith("\n") ? "\n" : "";
|
|
1358
|
+
writeFileSync5(
|
|
1359
|
+
path,
|
|
1360
|
+
current + prefix + (current ? "\n" : "") + block2.join("\n") + "\n"
|
|
1361
|
+
);
|
|
1362
|
+
return true;
|
|
1363
|
+
};
|
|
1364
|
+
ignoredSourcePaths = (targetRoot) => {
|
|
1365
|
+
try {
|
|
1366
|
+
const out = execFileSync2(
|
|
1367
|
+
"git",
|
|
1368
|
+
["check-ignore", ".agents", "quiver.lock"],
|
|
1369
|
+
{ cwd: targetRoot, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }
|
|
1370
|
+
);
|
|
1371
|
+
return out.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
1372
|
+
} catch {
|
|
1373
|
+
return [];
|
|
1374
|
+
}
|
|
1375
|
+
};
|
|
1376
|
+
}
|
|
1377
|
+
});
|
|
1378
|
+
|
|
1379
|
+
// src/commands/select.ts
|
|
1380
|
+
var DEFAULT_SKILLS, DEFAULT_COMMANDS, skillHint, serverDetail, selectFromCatalog;
|
|
1381
|
+
var init_select = __esm({
|
|
1382
|
+
"src/commands/select.ts"() {
|
|
1383
|
+
"use strict";
|
|
1384
|
+
init_prompts();
|
|
1385
|
+
DEFAULT_SKILLS = ["find-skills", "skill-creator"];
|
|
1386
|
+
DEFAULT_COMMANDS = ["cp"];
|
|
1387
|
+
skillHint = (fm) => {
|
|
1388
|
+
const parts = [];
|
|
1389
|
+
if (fm.version) parts.push(`v${fm.version}`);
|
|
1390
|
+
if (fm.description) {
|
|
1391
|
+
const flat = fm.description.replace(/\s+/g, " ").trim();
|
|
1392
|
+
const max = 70;
|
|
1393
|
+
parts.push(flat.length > max ? `${flat.slice(0, max - 1)}\u2026` : flat);
|
|
1394
|
+
}
|
|
1395
|
+
return parts.join(" \xB7 ");
|
|
1396
|
+
};
|
|
1397
|
+
serverDetail = (catalog, name) => {
|
|
1398
|
+
const entry = catalog.mcp.find((m) => m.name === name);
|
|
1399
|
+
if (!entry) return "";
|
|
1400
|
+
const s = entry.server;
|
|
1401
|
+
return s.transport === "http" ? s.url : [s.command, ...s.args ?? []].filter(Boolean).join(" ");
|
|
1402
|
+
};
|
|
1403
|
+
selectFromCatalog = async (catalog, { interactive }) => {
|
|
1404
|
+
const allSkills = catalog.skills.map((s) => s.name);
|
|
1405
|
+
const allCommands = catalog.commands.map((c) => c.name);
|
|
1406
|
+
const allMcp = catalog.mcp.map((m) => m.name);
|
|
1407
|
+
if (!interactive || !process.stdin.isTTY) {
|
|
1408
|
+
return { skills: allSkills, commands: allCommands, mcp: allMcp };
|
|
1409
|
+
}
|
|
1410
|
+
const groupNames = [...new Set(catalog.skills.map((s) => s.group))].sort(
|
|
1411
|
+
(a, b) => a === "general" ? -1 : b === "general" ? 1 : a.localeCompare(b)
|
|
1412
|
+
);
|
|
1413
|
+
const skillGroups = groupNames.map((name) => ({
|
|
1414
|
+
name,
|
|
1415
|
+
items: catalog.skills.filter((s) => s.group === name).sort((a, b) => {
|
|
1416
|
+
if (name === "general") {
|
|
1417
|
+
const ad = DEFAULT_SKILLS.includes(a.name);
|
|
1418
|
+
const bd = DEFAULT_SKILLS.includes(b.name);
|
|
1419
|
+
if (ad !== bd) return ad ? -1 : 1;
|
|
1420
|
+
}
|
|
1421
|
+
return a.name.localeCompare(b.name);
|
|
1422
|
+
}).map((s) => {
|
|
1423
|
+
const hint = skillHint(s.frontmatter);
|
|
1424
|
+
return { value: s.name, label: s.name, ...hint ? { hint } : {} };
|
|
1425
|
+
})
|
|
1426
|
+
}));
|
|
1427
|
+
const skills = catalog.skills.length ? await selectGrouped({
|
|
1428
|
+
message: "Select skills (space toggles, a all, enter confirms)",
|
|
1429
|
+
groups: skillGroups,
|
|
1430
|
+
initialValues: allSkills.filter((s) => DEFAULT_SKILLS.includes(s))
|
|
1431
|
+
}) : [];
|
|
1432
|
+
const commands = catalog.commands.length ? await selectGrouped({
|
|
1433
|
+
message: "Select commands (space toggles, a all, enter confirms)",
|
|
1434
|
+
groups: [
|
|
1435
|
+
{
|
|
1436
|
+
name: "commands",
|
|
1437
|
+
items: catalog.commands.map((c) => ({
|
|
1438
|
+
value: c.name,
|
|
1439
|
+
label: `/${c.name}`
|
|
1440
|
+
}))
|
|
1441
|
+
}
|
|
1442
|
+
],
|
|
1443
|
+
initialValues: allCommands.filter((c) => DEFAULT_COMMANDS.includes(c))
|
|
1444
|
+
}) : [];
|
|
1445
|
+
const remote = catalog.mcp.filter((m) => m.server.transport === "http");
|
|
1446
|
+
const local = catalog.mcp.filter((m) => m.server.transport !== "http");
|
|
1447
|
+
const mcpGroups = [
|
|
1448
|
+
{ name: "remote", items: remote },
|
|
1449
|
+
{ name: "local", items: local }
|
|
1450
|
+
].filter((g) => g.items.length).map((g) => ({
|
|
1451
|
+
name: g.name,
|
|
1452
|
+
items: g.items.map((m) => ({
|
|
1453
|
+
value: m.name,
|
|
1454
|
+
label: m.name,
|
|
1455
|
+
hint: serverDetail(catalog, m.name)
|
|
1456
|
+
}))
|
|
1457
|
+
}));
|
|
1458
|
+
const mcp = catalog.mcp.length ? await selectGrouped({
|
|
1459
|
+
message: "Select MCP servers (space toggles, a all, enter confirms)",
|
|
1460
|
+
groups: mcpGroups,
|
|
1461
|
+
initialValues: []
|
|
1462
|
+
}) : [];
|
|
1463
|
+
return { skills, commands, mcp };
|
|
1464
|
+
};
|
|
1465
|
+
}
|
|
1466
|
+
});
|
|
1467
|
+
|
|
1468
|
+
// src/commands/init.ts
|
|
1469
|
+
var init_exports = {};
|
|
1470
|
+
__export(init_exports, {
|
|
1471
|
+
init: () => init
|
|
1472
|
+
});
|
|
1473
|
+
var init, resolveProviders;
|
|
1474
|
+
var init_init = __esm({
|
|
1475
|
+
"src/commands/init.ts"() {
|
|
1476
|
+
"use strict";
|
|
1477
|
+
init_discover();
|
|
1478
|
+
init_entries();
|
|
1479
|
+
init_materialize();
|
|
1480
|
+
init_resolve();
|
|
1481
|
+
init_io();
|
|
1482
|
+
init_schema();
|
|
1483
|
+
init_write();
|
|
1484
|
+
init_interpolate();
|
|
1485
|
+
init_prompts();
|
|
1486
|
+
init_gitignore();
|
|
1487
|
+
init_select();
|
|
1488
|
+
init = async (options) => {
|
|
1489
|
+
await banner();
|
|
1490
|
+
if (lockfileExists(options.targetRoot) && !options.force) {
|
|
1491
|
+
await warn(
|
|
1492
|
+
"quiver.lock already exists. Use `quiver-cli add/remove` to change it, or --force to re-init."
|
|
1493
|
+
);
|
|
1494
|
+
return;
|
|
1495
|
+
}
|
|
1496
|
+
const catalogSource = options.catalog ?? DEFAULT_CATALOG_SOURCE;
|
|
1497
|
+
const source = await resolveCatalog(catalogSource);
|
|
1498
|
+
if (source.resolved) {
|
|
1499
|
+
await step(
|
|
1500
|
+
`Catalog: ${source.source} @ ${source.resolved.slice(0, 12)}${source.ref ? ` (${source.ref})` : ""}`
|
|
1501
|
+
);
|
|
1502
|
+
}
|
|
1503
|
+
const sourceCatalog = loadCatalog(source);
|
|
1504
|
+
const selection = await selectFromCatalog(sourceCatalog, {
|
|
1505
|
+
interactive: !options.all
|
|
1506
|
+
});
|
|
1507
|
+
const providers = await resolveProviders(options);
|
|
1508
|
+
if (providers === null) return;
|
|
1509
|
+
const resolved = materializeCatalog(
|
|
1510
|
+
options.targetRoot,
|
|
1511
|
+
source,
|
|
1512
|
+
sourceCatalog,
|
|
1513
|
+
selection
|
|
1514
|
+
);
|
|
1515
|
+
const catalog = loadCatalog(resolved);
|
|
1516
|
+
const lock = emptyLockfile(resolved.source, {
|
|
1517
|
+
ref: source.ref ?? null,
|
|
1518
|
+
resolved: source.resolved ?? null
|
|
1519
|
+
});
|
|
1520
|
+
lock.providers = providers;
|
|
1521
|
+
for (const skill of catalog.skills) {
|
|
1522
|
+
if (selection.skills.includes(skill.name)) {
|
|
1523
|
+
lock.entries[entryId("skill", skill.name)] = skillToEntry(skill);
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
for (const command of catalog.commands) {
|
|
1527
|
+
if (selection.commands.includes(command.name)) {
|
|
1528
|
+
lock.entries[entryId("command", command.name)] = commandToEntry(command);
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
for (const mcp of catalog.mcp) {
|
|
1532
|
+
if (selection.mcp.includes(mcp.name)) {
|
|
1533
|
+
lock.entries[entryId("mcp", mcp.name)] = mcpToEntry(mcp);
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
writeLockfile(options.targetRoot, lock);
|
|
1537
|
+
await step(
|
|
1538
|
+
`Wrote quiver.lock (${selection.skills.length} skills, ${selection.commands.length} commands, ${selection.mcp.length} MCP servers)`
|
|
1539
|
+
);
|
|
1540
|
+
const result = writeProviders(options.targetRoot, catalog, lock);
|
|
1541
|
+
if (result.generated.length || result.linked.length) {
|
|
1542
|
+
await step(
|
|
1543
|
+
`Provider configs: ${result.generated.length} generated, ${result.linked.length} linked`
|
|
1544
|
+
);
|
|
1545
|
+
}
|
|
1546
|
+
if (patchGitignore(options.targetRoot, providers)) {
|
|
1547
|
+
await step("Updated .gitignore (generated shims + .env.local ignored)");
|
|
1548
|
+
}
|
|
1549
|
+
const ignored = ignoredSourcePaths(options.targetRoot);
|
|
1550
|
+
if (ignored.length) {
|
|
1551
|
+
await warn(
|
|
1552
|
+
`Source of truth is gitignored: ${ignored.join(", ")}. Remove those .gitignore entries - a fresh clone would miss them.`
|
|
1553
|
+
);
|
|
1554
|
+
}
|
|
1555
|
+
const envVars = collectEnvVars(catalog.config.mcpServers ?? {});
|
|
1556
|
+
if (envVars.length) {
|
|
1557
|
+
await info(
|
|
1558
|
+
`MCP secrets needed: ${envVars.join(", ")} - fill them in .env.local (template: .env.local.example)`
|
|
1559
|
+
);
|
|
1560
|
+
}
|
|
1561
|
+
await outro(
|
|
1562
|
+
"Done. Commit .agents/ and quiver.lock; restart your AI tool to load the config."
|
|
1563
|
+
);
|
|
1564
|
+
};
|
|
1565
|
+
resolveProviders = async (options) => {
|
|
1566
|
+
if (options.providers) {
|
|
1567
|
+
const invalid = options.providers.filter((p) => !isProvider(p));
|
|
1568
|
+
if (invalid.length) {
|
|
1569
|
+
await error(
|
|
1570
|
+
`Unknown provider(s): ${invalid.join(", ")}. Valid: ${PROVIDERS.join(", ")}.`
|
|
1571
|
+
);
|
|
1572
|
+
process.exitCode = 1;
|
|
1573
|
+
return null;
|
|
1574
|
+
}
|
|
1575
|
+
return options.providers.filter(isProvider);
|
|
1576
|
+
}
|
|
1577
|
+
if (options.all || !process.stdin.isTTY) return [...PROVIDERS];
|
|
1578
|
+
const picked = await selectGrouped({
|
|
1579
|
+
message: "Generate configs for (space toggles, enter confirms)",
|
|
1580
|
+
groups: [
|
|
1581
|
+
{
|
|
1582
|
+
name: "providers",
|
|
1583
|
+
items: [
|
|
1584
|
+
{ value: "claude", label: "Claude Code" },
|
|
1585
|
+
{ value: "opencode", label: "opencode" },
|
|
1586
|
+
{ value: "codex", label: "Codex" }
|
|
1587
|
+
]
|
|
1588
|
+
}
|
|
1589
|
+
],
|
|
1590
|
+
initialValues: [...PROVIDERS]
|
|
1591
|
+
});
|
|
1592
|
+
return picked.length ? picked : [...PROVIDERS];
|
|
1593
|
+
};
|
|
1594
|
+
}
|
|
1595
|
+
});
|
|
1596
|
+
|
|
1597
|
+
// src/catalog/repo.ts
|
|
1598
|
+
import { existsSync as existsSync11 } from "fs";
|
|
1599
|
+
import { resolve as resolve15 } from "path";
|
|
1600
|
+
var repoCatalogRoot, repoCatalogExists, loadRepoCatalog;
|
|
1601
|
+
var init_repo = __esm({
|
|
1602
|
+
"src/catalog/repo.ts"() {
|
|
1603
|
+
"use strict";
|
|
1604
|
+
init_discover();
|
|
1605
|
+
repoCatalogRoot = (targetRoot) => resolve15(targetRoot, ".agents");
|
|
1606
|
+
repoCatalogExists = (targetRoot) => existsSync11(repoCatalogRoot(targetRoot));
|
|
1607
|
+
loadRepoCatalog = (targetRoot, source) => {
|
|
1608
|
+
const resolved = {
|
|
1609
|
+
source,
|
|
1610
|
+
root: repoCatalogRoot(targetRoot)
|
|
1611
|
+
};
|
|
1612
|
+
return { resolved, catalog: loadCatalog(resolved) };
|
|
1613
|
+
};
|
|
1614
|
+
}
|
|
1615
|
+
});
|
|
1616
|
+
|
|
1617
|
+
// src/commands/add.ts
|
|
1618
|
+
var add_exports = {};
|
|
1619
|
+
__export(add_exports, {
|
|
1620
|
+
add: () => add
|
|
1621
|
+
});
|
|
1622
|
+
var add, selectionFromLock;
|
|
1623
|
+
var init_add = __esm({
|
|
1624
|
+
"src/commands/add.ts"() {
|
|
1625
|
+
"use strict";
|
|
1626
|
+
init_discover();
|
|
1627
|
+
init_entries();
|
|
1628
|
+
init_materialize();
|
|
1629
|
+
init_repo();
|
|
1630
|
+
init_resolve();
|
|
1631
|
+
init_io();
|
|
1632
|
+
init_schema();
|
|
1633
|
+
init_write();
|
|
1634
|
+
init_prompts();
|
|
1635
|
+
add = async (options) => {
|
|
1636
|
+
const id = options.positionals[0];
|
|
1637
|
+
if (!id) {
|
|
1638
|
+
await error("Usage: quiver-cli add <skill:name|command:name|mcp:name>");
|
|
1639
|
+
process.exitCode = 1;
|
|
1640
|
+
return;
|
|
1641
|
+
}
|
|
1642
|
+
const parsed = parseEntryId(id);
|
|
1643
|
+
if (!parsed) {
|
|
1644
|
+
await error(`Invalid id "${id}". Expected skill:<name>, command:<name> or mcp:<name>.`);
|
|
1645
|
+
process.exitCode = 1;
|
|
1646
|
+
return;
|
|
1647
|
+
}
|
|
1648
|
+
const lock = readLockfile(options.targetRoot);
|
|
1649
|
+
if (!lock) {
|
|
1650
|
+
await error("No quiver.lock found. Run `quiver-cli init` first.");
|
|
1651
|
+
process.exitCode = 1;
|
|
1652
|
+
return;
|
|
1653
|
+
}
|
|
1654
|
+
if (lock.entries[id]) {
|
|
1655
|
+
await info(`${id} is already installed.`);
|
|
1656
|
+
return;
|
|
1657
|
+
}
|
|
1658
|
+
const source = await resolveCatalog(lock.catalog.source, {
|
|
1659
|
+
pinnedSha: lock.catalog.resolved
|
|
1660
|
+
});
|
|
1661
|
+
const sourceCatalog = loadCatalog(source);
|
|
1662
|
+
const skill = sourceCatalog.skills.find((s) => s.name === parsed.name);
|
|
1663
|
+
const command = sourceCatalog.commands.find((c) => c.name === parsed.name);
|
|
1664
|
+
const mcp = sourceCatalog.mcp.find((m) => m.name === parsed.name);
|
|
1665
|
+
const entry = parsed.type === "skill" && skill ? skillToEntry(skill) : parsed.type === "command" && command ? commandToEntry(command) : parsed.type === "mcp" && mcp ? mcpToEntry(mcp) : null;
|
|
1666
|
+
if (!entry) {
|
|
1667
|
+
await error(`${id} not found in catalog.`);
|
|
1668
|
+
process.exitCode = 1;
|
|
1669
|
+
return;
|
|
1670
|
+
}
|
|
1671
|
+
const selection = selectionFromLock(lock);
|
|
1672
|
+
if (parsed.type === "skill") selection.skills.push(parsed.name);
|
|
1673
|
+
if (parsed.type === "command") selection.commands.push(parsed.name);
|
|
1674
|
+
if (parsed.type === "mcp") selection.mcp.push(parsed.name);
|
|
1675
|
+
materializeCatalog(options.targetRoot, source, sourceCatalog, selection);
|
|
1676
|
+
lock.entries[id] = entry;
|
|
1677
|
+
writeLockfile(options.targetRoot, lock);
|
|
1678
|
+
const { catalog } = loadRepoCatalog(options.targetRoot, lock.catalog.source);
|
|
1679
|
+
writeProviders(options.targetRoot, catalog, lock);
|
|
1680
|
+
await success(`Added ${id}.`);
|
|
1681
|
+
};
|
|
1682
|
+
selectionFromLock = (lock) => {
|
|
1683
|
+
const selection = { skills: [], commands: [], mcp: [] };
|
|
1684
|
+
for (const lid of Object.keys(lock.entries)) {
|
|
1685
|
+
const p = parseEntryId(lid);
|
|
1686
|
+
if (!p) continue;
|
|
1687
|
+
if (p.type === "skill") selection.skills.push(p.name);
|
|
1688
|
+
else if (p.type === "command") selection.commands.push(p.name);
|
|
1689
|
+
else if (p.type === "mcp") selection.mcp.push(p.name);
|
|
1690
|
+
}
|
|
1691
|
+
return selection;
|
|
1692
|
+
};
|
|
1693
|
+
}
|
|
1694
|
+
});
|
|
1695
|
+
|
|
1696
|
+
// src/commands/remove.ts
|
|
1697
|
+
var remove_exports = {};
|
|
1698
|
+
__export(remove_exports, {
|
|
1699
|
+
remove: () => remove
|
|
1700
|
+
});
|
|
1701
|
+
import { existsSync as existsSync12, readdirSync as readdirSync4, readFileSync as readFileSync8, rmSync as rmSync5, writeFileSync as writeFileSync6 } from "fs";
|
|
1702
|
+
import { resolve as resolve16 } from "path";
|
|
1703
|
+
var remove, cleanupEmptyGroups, rewriteRepoMcp;
|
|
1704
|
+
var init_remove = __esm({
|
|
1705
|
+
"src/commands/remove.ts"() {
|
|
1706
|
+
"use strict";
|
|
1707
|
+
init_materialize();
|
|
1708
|
+
init_repo();
|
|
1709
|
+
init_io();
|
|
1710
|
+
init_schema();
|
|
1711
|
+
init_write();
|
|
1712
|
+
init_prompts();
|
|
1713
|
+
remove = async (options) => {
|
|
1714
|
+
const id = options.positionals[0];
|
|
1715
|
+
if (!id) {
|
|
1716
|
+
await error("Usage: quiver remove <skill:name|command:name|mcp:name>");
|
|
1717
|
+
process.exitCode = 1;
|
|
1718
|
+
return;
|
|
1719
|
+
}
|
|
1720
|
+
const parsed = parseEntryId(id);
|
|
1721
|
+
if (!parsed) {
|
|
1722
|
+
await error(`Invalid id "${id}".`);
|
|
1723
|
+
process.exitCode = 1;
|
|
1724
|
+
return;
|
|
1725
|
+
}
|
|
1726
|
+
const lock = readLockfile(options.targetRoot);
|
|
1727
|
+
if (!lock) {
|
|
1728
|
+
await error("No quiver.lock found. Run `quiver-cli init` first.");
|
|
1729
|
+
process.exitCode = 1;
|
|
1730
|
+
return;
|
|
1731
|
+
}
|
|
1732
|
+
const entry = lock.entries[id];
|
|
1733
|
+
if (!entry) {
|
|
1734
|
+
await info(`${id} is not installed.`);
|
|
1735
|
+
return;
|
|
1736
|
+
}
|
|
1737
|
+
if (entry.type === "skill" || entry.type === "command") {
|
|
1738
|
+
removeArtifact(options.targetRoot, entry.sourcePath);
|
|
1739
|
+
cleanupEmptyGroups(options.targetRoot);
|
|
1740
|
+
}
|
|
1741
|
+
delete lock.entries[id];
|
|
1742
|
+
writeLockfile(options.targetRoot, lock);
|
|
1743
|
+
rewriteRepoMcp(options.targetRoot, lock);
|
|
1744
|
+
const { catalog } = loadRepoCatalog(options.targetRoot, lock.catalog.source);
|
|
1745
|
+
writeProviders(options.targetRoot, catalog, lock);
|
|
1746
|
+
await success(`Removed ${id}.`);
|
|
1747
|
+
};
|
|
1748
|
+
cleanupEmptyGroups = (targetRoot) => {
|
|
1749
|
+
const skillsRoot = resolve16(targetRoot, ".agents/skills");
|
|
1750
|
+
if (!existsSync12(skillsRoot)) return;
|
|
1751
|
+
for (const entry of readdirSync4(skillsRoot, { withFileTypes: true })) {
|
|
1752
|
+
if (!entry.isDirectory()) continue;
|
|
1753
|
+
const dir = resolve16(skillsRoot, entry.name);
|
|
1754
|
+
if (existsSync12(resolve16(dir, "SKILL.md"))) continue;
|
|
1755
|
+
if (readdirSync4(dir).length === 0) rmSync5(dir, { recursive: true, force: true });
|
|
1756
|
+
}
|
|
1757
|
+
};
|
|
1758
|
+
rewriteRepoMcp = (targetRoot, lock) => {
|
|
1759
|
+
const configPath = resolve16(targetRoot, ".agents/config.json");
|
|
1760
|
+
if (!existsSync12(configPath)) return;
|
|
1761
|
+
const config = JSON.parse(readFileSync8(configPath, "utf8"));
|
|
1762
|
+
if (!config.mcpServers) return;
|
|
1763
|
+
const keep = new Set(
|
|
1764
|
+
Object.keys(lock.entries).map(parseEntryId).filter((p) => p?.type === "mcp").map((p) => p.name)
|
|
1765
|
+
);
|
|
1766
|
+
const kept = {};
|
|
1767
|
+
for (const [name, server] of Object.entries(config.mcpServers)) {
|
|
1768
|
+
if (keep.has(name)) kept[name] = server;
|
|
1769
|
+
}
|
|
1770
|
+
if (Object.keys(kept).length) config.mcpServers = kept;
|
|
1771
|
+
else delete config.mcpServers;
|
|
1772
|
+
writeFileSync6(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
1773
|
+
};
|
|
1774
|
+
}
|
|
1775
|
+
});
|
|
1776
|
+
|
|
1777
|
+
// src/commands/locksync.ts
|
|
1778
|
+
var refreshLockDigests;
|
|
1779
|
+
var init_locksync = __esm({
|
|
1780
|
+
"src/commands/locksync.ts"() {
|
|
1781
|
+
"use strict";
|
|
1782
|
+
refreshLockDigests = (catalog, lock) => {
|
|
1783
|
+
const drift = [];
|
|
1784
|
+
const skillByName = new Map(catalog.skills.map((s) => [s.name, s]));
|
|
1785
|
+
const commandByName = new Map(catalog.commands.map((c) => [c.name, c]));
|
|
1786
|
+
const mcpByName = new Map(catalog.mcp.map((m) => [m.name, m]));
|
|
1787
|
+
for (const [id, entry] of Object.entries(lock.entries)) {
|
|
1788
|
+
if (entry.type === "skill") {
|
|
1789
|
+
const cat = skillByName.get(id.slice("skill:".length));
|
|
1790
|
+
if (!cat) continue;
|
|
1791
|
+
if (cat.digest !== entry.digest) {
|
|
1792
|
+
drift.push(`${id}: skill content changed`);
|
|
1793
|
+
entry.digest = cat.digest;
|
|
1794
|
+
entry.frontmatter = cat.frontmatter;
|
|
1795
|
+
}
|
|
1796
|
+
} else if (entry.type === "command") {
|
|
1797
|
+
const cat = commandByName.get(id.slice("command:".length));
|
|
1798
|
+
if (!cat) continue;
|
|
1799
|
+
if (cat.digest !== entry.digest) {
|
|
1800
|
+
drift.push(`${id}: command content changed`);
|
|
1801
|
+
entry.digest = cat.digest;
|
|
1802
|
+
}
|
|
1803
|
+
} else if (entry.type === "mcp") {
|
|
1804
|
+
const cat = mcpByName.get(id.slice("mcp:".length));
|
|
1805
|
+
if (!cat) continue;
|
|
1806
|
+
if (cat.configDigest !== entry.configDigest) {
|
|
1807
|
+
drift.push(`${id}: MCP server definition changed`);
|
|
1808
|
+
entry.configDigest = cat.configDigest;
|
|
1809
|
+
entry.transport = cat.server.transport;
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
return drift;
|
|
1814
|
+
};
|
|
1815
|
+
}
|
|
1816
|
+
});
|
|
1817
|
+
|
|
1818
|
+
// src/commands/sync.ts
|
|
1819
|
+
var sync_exports = {};
|
|
1820
|
+
__export(sync_exports, {
|
|
1821
|
+
sync: () => sync
|
|
1822
|
+
});
|
|
1823
|
+
var sync;
|
|
1824
|
+
var init_sync = __esm({
|
|
1825
|
+
"src/commands/sync.ts"() {
|
|
1826
|
+
"use strict";
|
|
1827
|
+
init_repo();
|
|
1828
|
+
init_io();
|
|
1829
|
+
init_schema();
|
|
1830
|
+
init_write();
|
|
1831
|
+
init_prompts();
|
|
1832
|
+
init_gitignore();
|
|
1833
|
+
init_locksync();
|
|
1834
|
+
sync = async (options) => {
|
|
1835
|
+
const lock = readLockfile(options.targetRoot);
|
|
1836
|
+
if (!lock) {
|
|
1837
|
+
await error("No quiver.lock found. Run `quiver-cli init` first.");
|
|
1838
|
+
process.exitCode = 1;
|
|
1839
|
+
return;
|
|
1840
|
+
}
|
|
1841
|
+
if (!repoCatalogExists(options.targetRoot)) {
|
|
1842
|
+
await error("No .agents/ directory found. Run `quiver-cli init` first.");
|
|
1843
|
+
process.exitCode = 1;
|
|
1844
|
+
return;
|
|
1845
|
+
}
|
|
1846
|
+
const { catalog } = loadRepoCatalog(options.targetRoot, lock.catalog.source);
|
|
1847
|
+
const haveSkills = new Set(catalog.skills.map((s) => s.name));
|
|
1848
|
+
const haveCommands = new Set(catalog.commands.map((c) => c.name));
|
|
1849
|
+
const haveMcp = new Set(catalog.mcp.map((m) => m.name));
|
|
1850
|
+
const orphans = [];
|
|
1851
|
+
for (const id of Object.keys(lock.entries)) {
|
|
1852
|
+
const p = parseEntryId(id);
|
|
1853
|
+
if (!p) continue;
|
|
1854
|
+
const present = p.type === "skill" && haveSkills.has(p.name) || p.type === "command" && haveCommands.has(p.name) || p.type === "mcp" && haveMcp.has(p.name);
|
|
1855
|
+
if (!present) orphans.push(id);
|
|
1856
|
+
}
|
|
1857
|
+
if (orphans.length) {
|
|
1858
|
+
await warn(
|
|
1859
|
+
`Lockfile references missing catalog entries: ${orphans.join(", ")}. Run \`quiver-cli remove <id>\` to drop them.`
|
|
1860
|
+
);
|
|
1861
|
+
}
|
|
1862
|
+
const drift = refreshLockDigests(catalog, lock);
|
|
1863
|
+
if (drift.length) {
|
|
1864
|
+
await warn(`Catalog drift detected:
|
|
1865
|
+
- ${drift.join("\n - ")}`);
|
|
1866
|
+
}
|
|
1867
|
+
const ignored = ignoredSourcePaths(options.targetRoot);
|
|
1868
|
+
if (ignored.length) {
|
|
1869
|
+
await warn(
|
|
1870
|
+
`Source of truth is gitignored: ${ignored.join(", ")}. Remove those .gitignore entries - a fresh clone would miss them.`
|
|
1871
|
+
);
|
|
1872
|
+
}
|
|
1873
|
+
writeLockfile(options.targetRoot, lock);
|
|
1874
|
+
const result = writeProviders(options.targetRoot, catalog, lock);
|
|
1875
|
+
await success(
|
|
1876
|
+
`Synced: ${result.generated.length} generated, ${result.linked.length} linked, ${result.removed.length} removed`
|
|
1877
|
+
);
|
|
1878
|
+
};
|
|
1879
|
+
}
|
|
1880
|
+
});
|
|
1881
|
+
|
|
1882
|
+
// src/commands/update.ts
|
|
1883
|
+
var update_exports = {};
|
|
1884
|
+
__export(update_exports, {
|
|
1885
|
+
update: () => update
|
|
1886
|
+
});
|
|
1887
|
+
import { cpSync as cpSync2, mkdirSync as mkdirSync6, readFileSync as readFileSync9, rmSync as rmSync6, writeFileSync as writeFileSync7 } from "fs";
|
|
1888
|
+
import { dirname as dirname5, resolve as resolve17 } from "path";
|
|
1889
|
+
var update, applyUpdate, report;
|
|
1890
|
+
var init_update = __esm({
|
|
1891
|
+
"src/commands/update.ts"() {
|
|
1892
|
+
"use strict";
|
|
1893
|
+
init_discover();
|
|
1894
|
+
init_repo();
|
|
1895
|
+
init_resolve();
|
|
1896
|
+
init_io();
|
|
1897
|
+
init_schema();
|
|
1898
|
+
init_write();
|
|
1899
|
+
init_prompts();
|
|
1900
|
+
update = async (options) => {
|
|
1901
|
+
const lock = readLockfile(options.targetRoot);
|
|
1902
|
+
if (!lock) {
|
|
1903
|
+
await error("No quiver.lock found. Run `quiver-cli init` first.");
|
|
1904
|
+
process.exitCode = 1;
|
|
1905
|
+
return;
|
|
1906
|
+
}
|
|
1907
|
+
if (!repoCatalogExists(options.targetRoot)) {
|
|
1908
|
+
await error("No .agents/ directory found. Run `quiver-cli init` first.");
|
|
1909
|
+
process.exitCode = 1;
|
|
1910
|
+
return;
|
|
1911
|
+
}
|
|
1912
|
+
const onlyId = options.positionals[0];
|
|
1913
|
+
if (onlyId && !lock.entries[onlyId]) {
|
|
1914
|
+
await error(`${onlyId} is not installed (not found in quiver.lock).`);
|
|
1915
|
+
process.exitCode = 1;
|
|
1916
|
+
return;
|
|
1917
|
+
}
|
|
1918
|
+
const source = await resolveCatalog(lock.catalog.source);
|
|
1919
|
+
const sourceCatalog = loadCatalog(source);
|
|
1920
|
+
const catalogMoved = source.resolved != null && source.resolved !== lock.catalog.resolved;
|
|
1921
|
+
if (catalogMoved) {
|
|
1922
|
+
lock.catalog.ref = source.ref ?? lock.catalog.ref;
|
|
1923
|
+
lock.catalog.resolved = source.resolved ?? null;
|
|
1924
|
+
lock.catalog.fetchedAt = source.fetchedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
1925
|
+
}
|
|
1926
|
+
const { catalog: repoCatalog } = loadRepoCatalog(
|
|
1927
|
+
options.targetRoot,
|
|
1928
|
+
lock.catalog.source
|
|
1929
|
+
);
|
|
1930
|
+
const ids = onlyId ? [onlyId] : Object.keys(lock.entries).sort();
|
|
1931
|
+
const reports = [];
|
|
1932
|
+
let changed = false;
|
|
1933
|
+
for (const id of ids) {
|
|
1934
|
+
const parsed = parseEntryId(id);
|
|
1935
|
+
const entry = lock.entries[id];
|
|
1936
|
+
if (!parsed || !entry) continue;
|
|
1937
|
+
const report4 = applyUpdate(
|
|
1938
|
+
options.targetRoot,
|
|
1939
|
+
parsed,
|
|
1940
|
+
entry,
|
|
1941
|
+
sourceCatalog,
|
|
1942
|
+
repoCatalog,
|
|
1943
|
+
lock,
|
|
1944
|
+
options.force
|
|
1945
|
+
);
|
|
1946
|
+
if (report4.status === "updated") changed = true;
|
|
1947
|
+
reports.push(report4);
|
|
1948
|
+
}
|
|
1949
|
+
if (changed || catalogMoved) {
|
|
1950
|
+
writeLockfile(options.targetRoot, lock);
|
|
1951
|
+
}
|
|
1952
|
+
if (changed) {
|
|
1953
|
+
const { catalog } = loadRepoCatalog(options.targetRoot, lock.catalog.source);
|
|
1954
|
+
writeProviders(options.targetRoot, catalog, lock);
|
|
1955
|
+
}
|
|
1956
|
+
report(reports, options);
|
|
1957
|
+
};
|
|
1958
|
+
applyUpdate = (targetRoot, parsed, entry, sourceCatalog, repoCatalog, lock, force) => {
|
|
1959
|
+
const id = `${parsed.type}:${parsed.name}`;
|
|
1960
|
+
if (entry.type === "skill") {
|
|
1961
|
+
const src2 = sourceCatalog.skills.find((s) => s.name === parsed.name);
|
|
1962
|
+
if (!src2) return { id, status: "not-in-catalog" };
|
|
1963
|
+
if (src2.digest === entry.digest) return { id, status: "up-to-date" };
|
|
1964
|
+
const repo2 = repoCatalog.skills.find((s) => s.name === parsed.name);
|
|
1965
|
+
if (repo2 && repo2.digest !== entry.digest && !force) {
|
|
1966
|
+
return { id, status: "local-changes" };
|
|
1967
|
+
}
|
|
1968
|
+
const dest = resolve17(targetRoot, ".agents", src2.sourcePath);
|
|
1969
|
+
rmSync6(dest, { recursive: true, force: true });
|
|
1970
|
+
mkdirSync6(dirname5(dest), { recursive: true });
|
|
1971
|
+
cpSync2(src2.absDir, dest, { recursive: true });
|
|
1972
|
+
entry.digest = src2.digest;
|
|
1973
|
+
entry.frontmatter = src2.frontmatter;
|
|
1974
|
+
entry.sourcePath = src2.sourcePath;
|
|
1975
|
+
return { id, status: "updated" };
|
|
1976
|
+
}
|
|
1977
|
+
if (entry.type === "command") {
|
|
1978
|
+
const src2 = sourceCatalog.commands.find((c) => c.name === parsed.name);
|
|
1979
|
+
if (!src2) return { id, status: "not-in-catalog" };
|
|
1980
|
+
if (src2.digest === entry.digest) return { id, status: "up-to-date" };
|
|
1981
|
+
const repo2 = repoCatalog.commands.find((c) => c.name === parsed.name);
|
|
1982
|
+
if (repo2 && repo2.digest !== entry.digest && !force) {
|
|
1983
|
+
return { id, status: "local-changes" };
|
|
1984
|
+
}
|
|
1985
|
+
const dest = resolve17(targetRoot, ".agents", src2.sourcePath);
|
|
1986
|
+
mkdirSync6(dirname5(dest), { recursive: true });
|
|
1987
|
+
cpSync2(src2.absPath, dest, { force: true });
|
|
1988
|
+
entry.digest = src2.digest;
|
|
1989
|
+
entry.sourcePath = src2.sourcePath;
|
|
1990
|
+
return { id, status: "updated" };
|
|
1991
|
+
}
|
|
1992
|
+
const src = sourceCatalog.mcp.find((m) => m.name === parsed.name);
|
|
1993
|
+
if (!src) return { id, status: "not-in-catalog" };
|
|
1994
|
+
if (src.configDigest === entry.configDigest) return { id, status: "up-to-date" };
|
|
1995
|
+
const repo = repoCatalog.mcp.find((m) => m.name === parsed.name);
|
|
1996
|
+
if (repo && repo.configDigest !== entry.configDigest && !force) {
|
|
1997
|
+
return { id, status: "local-changes" };
|
|
1998
|
+
}
|
|
1999
|
+
const configPath = resolve17(targetRoot, ".agents", "config.json");
|
|
2000
|
+
const config = JSON.parse(readFileSync9(configPath, "utf8"));
|
|
2001
|
+
config.mcpServers = { ...config.mcpServers, [parsed.name]: src.server };
|
|
2002
|
+
writeFileSync7(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
2003
|
+
entry.configDigest = src.configDigest;
|
|
2004
|
+
entry.transport = src.server.transport;
|
|
2005
|
+
entry.tools = null;
|
|
2006
|
+
entry.toolsFetchedAt = null;
|
|
2007
|
+
return { id, status: "updated" };
|
|
2008
|
+
};
|
|
2009
|
+
report = (reports, options) => {
|
|
2010
|
+
const by = (s) => reports.filter((r) => r.status === s);
|
|
2011
|
+
if (options.json) {
|
|
2012
|
+
console.log(
|
|
2013
|
+
JSON.stringify(
|
|
2014
|
+
{
|
|
2015
|
+
ok: true,
|
|
2016
|
+
updated: by("updated").map((r) => r.id),
|
|
2017
|
+
upToDate: by("up-to-date").map((r) => r.id),
|
|
2018
|
+
localChanges: by("local-changes").map((r) => r.id),
|
|
2019
|
+
notInCatalog: by("not-in-catalog").map((r) => r.id)
|
|
2020
|
+
},
|
|
2021
|
+
null,
|
|
2022
|
+
2
|
|
2023
|
+
)
|
|
2024
|
+
);
|
|
2025
|
+
return;
|
|
2026
|
+
}
|
|
2027
|
+
const c = palette();
|
|
2028
|
+
const lines = [""];
|
|
2029
|
+
for (const r of by("updated")) lines.push(` ${c.cyan("\u2191")} ${r.id} updated`);
|
|
2030
|
+
for (const r of by("local-changes"))
|
|
2031
|
+
lines.push(
|
|
2032
|
+
` ${c.yellow("\u25B2")} ${r.id} ${c.yellow("local changes - skipped (use --force)")}`
|
|
2033
|
+
);
|
|
2034
|
+
for (const r of by("not-in-catalog"))
|
|
2035
|
+
lines.push(` ${c.dim("\u2022")} ${r.id} ${c.dim("not in catalog")}`);
|
|
2036
|
+
for (const r of by("up-to-date"))
|
|
2037
|
+
lines.push(` ${c.green("\u2713")} ${r.id} ${c.dim("up to date")}`);
|
|
2038
|
+
const updated = by("updated").length;
|
|
2039
|
+
lines.push(
|
|
2040
|
+
"",
|
|
2041
|
+
` ${updated ? c.cyan(`\u2191 ${updated} updated - review and commit .agents/ + quiver.lock`) : c.green("\u2713 everything up to date with the catalog")}`,
|
|
2042
|
+
""
|
|
2043
|
+
);
|
|
2044
|
+
block(lines);
|
|
2045
|
+
};
|
|
2046
|
+
}
|
|
2047
|
+
});
|
|
2048
|
+
|
|
2049
|
+
// src/commands/list.ts
|
|
2050
|
+
var list_exports = {};
|
|
2051
|
+
__export(list_exports, {
|
|
2052
|
+
list: () => list
|
|
2053
|
+
});
|
|
2054
|
+
var truncate, list;
|
|
2055
|
+
var init_list = __esm({
|
|
2056
|
+
"src/commands/list.ts"() {
|
|
2057
|
+
"use strict";
|
|
2058
|
+
init_repo();
|
|
2059
|
+
init_io();
|
|
2060
|
+
init_schema();
|
|
2061
|
+
init_prompts();
|
|
2062
|
+
truncate = (s, max = 60) => {
|
|
2063
|
+
const flat = s.replace(/\s+/g, " ").trim();
|
|
2064
|
+
return flat.length > max ? flat.slice(0, max - 1) + "\u2026" : flat;
|
|
2065
|
+
};
|
|
2066
|
+
list = async (options) => {
|
|
2067
|
+
const lock = readLockfile(options.targetRoot);
|
|
2068
|
+
if (!lock) {
|
|
2069
|
+
if (options.json) console.log(JSON.stringify({ ok: false, error: "no-lockfile" }));
|
|
2070
|
+
else await error("No quiver.lock found. Run `quiver-cli init` first.");
|
|
2071
|
+
process.exitCode = 1;
|
|
2072
|
+
return;
|
|
2073
|
+
}
|
|
2074
|
+
const serverDetail2 = /* @__PURE__ */ new Map();
|
|
2075
|
+
if (repoCatalogExists(options.targetRoot)) {
|
|
2076
|
+
const { catalog } = loadRepoCatalog(options.targetRoot, lock.catalog.source);
|
|
2077
|
+
for (const mcp2 of catalog.mcp) {
|
|
2078
|
+
serverDetail2.set(
|
|
2079
|
+
mcp2.name,
|
|
2080
|
+
mcp2.server.transport === "http" ? mcp2.server.url : [mcp2.server.command, ...mcp2.server.args ?? []].join(" ")
|
|
2081
|
+
);
|
|
2082
|
+
}
|
|
2083
|
+
}
|
|
2084
|
+
const skills = [];
|
|
2085
|
+
const commands = [];
|
|
2086
|
+
const mcp = [];
|
|
2087
|
+
for (const [id, entry] of Object.entries(lock.entries)) {
|
|
2088
|
+
const p = parseEntryId(id);
|
|
2089
|
+
if (!p) continue;
|
|
2090
|
+
if (entry.type === "skill") skills.push({ name: p.name, entry });
|
|
2091
|
+
else if (entry.type === "command") commands.push({ name: p.name, entry });
|
|
2092
|
+
else if (entry.type === "mcp") mcp.push({ name: p.name, entry });
|
|
2093
|
+
}
|
|
2094
|
+
for (const group of [skills, commands, mcp]) {
|
|
2095
|
+
group.sort((a, b) => a.name.localeCompare(b.name));
|
|
2096
|
+
}
|
|
2097
|
+
if (options.json) {
|
|
2098
|
+
console.log(
|
|
2099
|
+
JSON.stringify(
|
|
2100
|
+
{
|
|
2101
|
+
ok: true,
|
|
2102
|
+
skills: skills.map(({ name, entry }) => ({
|
|
2103
|
+
name,
|
|
2104
|
+
version: entry.frontmatter.version,
|
|
2105
|
+
description: entry.frontmatter.description
|
|
2106
|
+
})),
|
|
2107
|
+
commands: commands.map(({ name }) => ({ name })),
|
|
2108
|
+
mcp: mcp.map(({ name, entry }) => ({
|
|
2109
|
+
name,
|
|
2110
|
+
transport: entry.transport,
|
|
2111
|
+
detail: serverDetail2.get(name) ?? null,
|
|
2112
|
+
toolCount: entry.tools ? Object.keys(entry.tools).length : null
|
|
2113
|
+
}))
|
|
2114
|
+
},
|
|
2115
|
+
null,
|
|
2116
|
+
2
|
|
2117
|
+
)
|
|
2118
|
+
);
|
|
2119
|
+
return;
|
|
2120
|
+
}
|
|
2121
|
+
const c = palette();
|
|
2122
|
+
const lines = [""];
|
|
2123
|
+
const width = Math.max(
|
|
2124
|
+
0,
|
|
2125
|
+
...[...skills, ...commands, ...mcp].map((e) => e.name.length + 1)
|
|
2126
|
+
);
|
|
2127
|
+
if (skills.length) {
|
|
2128
|
+
lines.push(` ${c.bold("skills")}`);
|
|
2129
|
+
for (const { name, entry } of skills) {
|
|
2130
|
+
const version = entry.frontmatter.version ? c.cyan(`v${entry.frontmatter.version} `) : "";
|
|
2131
|
+
const desc = entry.frontmatter.description ? c.dim(truncate(entry.frontmatter.description)) : "";
|
|
2132
|
+
lines.push(` ${name.padEnd(width)} ${version}${desc}`);
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2135
|
+
if (commands.length) {
|
|
2136
|
+
lines.push("", ` ${c.bold("commands")}`);
|
|
2137
|
+
for (const { name } of commands) {
|
|
2138
|
+
lines.push(` /${name}`);
|
|
2139
|
+
}
|
|
2140
|
+
}
|
|
2141
|
+
if (mcp.length) {
|
|
2142
|
+
lines.push("", ` ${c.bold("mcp servers")}`);
|
|
2143
|
+
for (const { name, entry } of mcp) {
|
|
2144
|
+
const tools = entry.tools ? c.green(`${Object.keys(entry.tools).length} tools`) : c.dim("tools: ? (run check)");
|
|
2145
|
+
const detail = serverDetail2.get(name);
|
|
2146
|
+
lines.push(
|
|
2147
|
+
` ${name.padEnd(width)} ${entry.transport.padEnd(5)} ${tools}` + (detail ? ` ${c.dim(detail)}` : "")
|
|
2148
|
+
);
|
|
2149
|
+
}
|
|
2150
|
+
}
|
|
2151
|
+
const providers = lock.providers?.length ? lock.providers.join(", ") : "claude, opencode, codex";
|
|
2152
|
+
lines.push(
|
|
2153
|
+
"",
|
|
2154
|
+
` ${c.bold(
|
|
2155
|
+
`${skills.length} skills \xB7 ${commands.length} commands \xB7 ${mcp.length} MCP servers`
|
|
2156
|
+
)} ${c.dim(`providers: ${providers}`)}`,
|
|
2157
|
+
""
|
|
2158
|
+
);
|
|
2159
|
+
block(lines);
|
|
2160
|
+
};
|
|
2161
|
+
}
|
|
2162
|
+
});
|
|
2163
|
+
|
|
2164
|
+
// src/commands/status.ts
|
|
2165
|
+
var status_exports = {};
|
|
2166
|
+
__export(status_exports, {
|
|
2167
|
+
status: () => status
|
|
2168
|
+
});
|
|
2169
|
+
var status;
|
|
2170
|
+
var init_status = __esm({
|
|
2171
|
+
"src/commands/status.ts"() {
|
|
2172
|
+
"use strict";
|
|
2173
|
+
init_repo();
|
|
2174
|
+
init_io();
|
|
2175
|
+
init_schema();
|
|
2176
|
+
init_write();
|
|
2177
|
+
init_prompts();
|
|
2178
|
+
status = async (options) => {
|
|
2179
|
+
const lock = readLockfile(options.targetRoot);
|
|
2180
|
+
if (!lock) {
|
|
2181
|
+
if (options.json) console.log(JSON.stringify({ ok: false, error: "no-lockfile" }));
|
|
2182
|
+
else await error("No quiver.lock found. Run `quiver-cli init` first.");
|
|
2183
|
+
process.exitCode = 1;
|
|
2184
|
+
return;
|
|
2185
|
+
}
|
|
2186
|
+
if (!repoCatalogExists(options.targetRoot)) {
|
|
2187
|
+
if (options.json) console.log(JSON.stringify({ ok: false, error: "no-agents" }));
|
|
2188
|
+
else await error("No .agents/ directory found. Run `quiver-cli init` first.");
|
|
2189
|
+
process.exitCode = 1;
|
|
2190
|
+
return;
|
|
2191
|
+
}
|
|
2192
|
+
const { catalog } = loadRepoCatalog(options.targetRoot, lock.catalog.source);
|
|
2193
|
+
const haveSkills = new Set(catalog.skills.map((s) => s.name));
|
|
2194
|
+
const haveCommands = new Set(catalog.commands.map((c) => c.name));
|
|
2195
|
+
const haveMcp = new Set(catalog.mcp.map((m) => m.name));
|
|
2196
|
+
const missing = [];
|
|
2197
|
+
const changed = [];
|
|
2198
|
+
const skillByName = new Map(catalog.skills.map((s) => [s.name, s]));
|
|
2199
|
+
const commandByName = new Map(catalog.commands.map((c) => [c.name, c]));
|
|
2200
|
+
const mcpByName = new Map(catalog.mcp.map((m) => [m.name, m]));
|
|
2201
|
+
for (const [id, entry] of Object.entries(lock.entries)) {
|
|
2202
|
+
const p = parseEntryId(id);
|
|
2203
|
+
if (!p) continue;
|
|
2204
|
+
if (p.type === "skill") {
|
|
2205
|
+
if (!haveSkills.has(p.name)) missing.push(id);
|
|
2206
|
+
else if (entry.type === "skill" && skillByName.get(p.name).digest !== entry.digest)
|
|
2207
|
+
changed.push(id);
|
|
2208
|
+
} else if (p.type === "command") {
|
|
2209
|
+
if (!haveCommands.has(p.name)) missing.push(id);
|
|
2210
|
+
else if (entry.type === "command" && commandByName.get(p.name).digest !== entry.digest)
|
|
2211
|
+
changed.push(id);
|
|
2212
|
+
} else if (p.type === "mcp") {
|
|
2213
|
+
if (!haveMcp.has(p.name)) missing.push(id);
|
|
2214
|
+
else if (entry.type === "mcp" && mcpByName.get(p.name).configDigest !== entry.configDigest)
|
|
2215
|
+
changed.push(id);
|
|
2216
|
+
}
|
|
2217
|
+
}
|
|
2218
|
+
const shimProblems = checkProviders(options.targetRoot, catalog, lock);
|
|
2219
|
+
const ok = missing.length === 0 && changed.length === 0 && shimProblems.length === 0;
|
|
2220
|
+
if (options.json) {
|
|
2221
|
+
console.log(JSON.stringify({ ok, missing, changed, shims: shimProblems }, null, 2));
|
|
2222
|
+
if (!ok) process.exitCode = 1;
|
|
2223
|
+
return;
|
|
2224
|
+
}
|
|
2225
|
+
if (ok) {
|
|
2226
|
+
await success("In sync: lockfile, .agents/ and provider shims agree.");
|
|
2227
|
+
return;
|
|
2228
|
+
}
|
|
2229
|
+
if (missing.length)
|
|
2230
|
+
await warn(`Missing in .agents/: ${missing.join(", ")}`);
|
|
2231
|
+
if (changed.length)
|
|
2232
|
+
await warn(`Content changed since lockfile: ${changed.join(", ")}`);
|
|
2233
|
+
if (shimProblems.length)
|
|
2234
|
+
await warn(`Provider shims out of date:
|
|
2235
|
+
- ${shimProblems.join("\n - ")}`);
|
|
2236
|
+
await info("Run `quiver-cli sync` to regenerate provider shims and refresh digests.");
|
|
2237
|
+
process.exitCode = 1;
|
|
2238
|
+
};
|
|
2239
|
+
}
|
|
2240
|
+
});
|
|
2241
|
+
|
|
2242
|
+
// src/mcp/diff.ts
|
|
2243
|
+
var diffSnapshots, isEmptyDiff;
|
|
2244
|
+
var init_diff = __esm({
|
|
2245
|
+
"src/mcp/diff.ts"() {
|
|
2246
|
+
"use strict";
|
|
2247
|
+
diffSnapshots = (before, after) => {
|
|
2248
|
+
const diff = {
|
|
2249
|
+
added: [],
|
|
2250
|
+
removed: [],
|
|
2251
|
+
schemaChanged: [],
|
|
2252
|
+
descriptionChanged: []
|
|
2253
|
+
};
|
|
2254
|
+
for (const name of Object.keys(after)) {
|
|
2255
|
+
if (!(name in before)) diff.added.push(name);
|
|
2256
|
+
}
|
|
2257
|
+
for (const name of Object.keys(before)) {
|
|
2258
|
+
if (!(name in after)) {
|
|
2259
|
+
diff.removed.push(name);
|
|
2260
|
+
continue;
|
|
2261
|
+
}
|
|
2262
|
+
const b = before[name];
|
|
2263
|
+
const a = after[name];
|
|
2264
|
+
if (b.inputSchemaHash !== a.inputSchemaHash) diff.schemaChanged.push(name);
|
|
2265
|
+
if (b.description !== a.description) {
|
|
2266
|
+
diff.descriptionChanged.push({
|
|
2267
|
+
name,
|
|
2268
|
+
before: b.description,
|
|
2269
|
+
after: a.description
|
|
2270
|
+
});
|
|
2271
|
+
}
|
|
2272
|
+
}
|
|
2273
|
+
diff.added.sort();
|
|
2274
|
+
diff.removed.sort();
|
|
2275
|
+
diff.schemaChanged.sort();
|
|
2276
|
+
diff.descriptionChanged.sort((x2, y) => x2.name.localeCompare(y.name));
|
|
2277
|
+
return diff;
|
|
2278
|
+
};
|
|
2279
|
+
isEmptyDiff = (d) => d.added.length === 0 && d.removed.length === 0 && d.schemaChanged.length === 0 && d.descriptionChanged.length === 0;
|
|
2280
|
+
}
|
|
2281
|
+
});
|
|
2282
|
+
|
|
2283
|
+
// src/mcp/introspect.ts
|
|
2284
|
+
var CONNECT_TIMEOUT_MS, withTimeout, introspect, errMsg;
|
|
2285
|
+
var init_introspect = __esm({
|
|
2286
|
+
"src/mcp/introspect.ts"() {
|
|
2287
|
+
"use strict";
|
|
2288
|
+
CONNECT_TIMEOUT_MS = 15e3;
|
|
2289
|
+
withTimeout = async (p, ms) => {
|
|
2290
|
+
let timer;
|
|
2291
|
+
const timeout = new Promise((_, reject) => {
|
|
2292
|
+
timer = setTimeout(() => reject(new Error(`timed out after ${ms}ms`)), ms);
|
|
2293
|
+
});
|
|
2294
|
+
try {
|
|
2295
|
+
return await Promise.race([p, timeout]);
|
|
2296
|
+
} finally {
|
|
2297
|
+
clearTimeout(timer);
|
|
2298
|
+
}
|
|
2299
|
+
};
|
|
2300
|
+
introspect = async (server, { allowStdio }) => {
|
|
2301
|
+
const { Client } = await import("@modelcontextprotocol/sdk/client/index.js");
|
|
2302
|
+
let transport;
|
|
2303
|
+
try {
|
|
2304
|
+
if (server.transport === "http") {
|
|
2305
|
+
const { StreamableHTTPClientTransport } = await import("@modelcontextprotocol/sdk/client/streamableHttp.js");
|
|
2306
|
+
const requestInit = server.headers ? { headers: server.headers } : {};
|
|
2307
|
+
transport = new StreamableHTTPClientTransport(new URL(server.url), {
|
|
2308
|
+
requestInit
|
|
2309
|
+
});
|
|
2310
|
+
} else {
|
|
2311
|
+
if (!allowStdio) {
|
|
2312
|
+
return {
|
|
2313
|
+
ok: false,
|
|
2314
|
+
reason: "stdio server skipped (pass --introspect-stdio to run it)"
|
|
2315
|
+
};
|
|
2316
|
+
}
|
|
2317
|
+
const { StdioClientTransport } = await import("@modelcontextprotocol/sdk/client/stdio.js");
|
|
2318
|
+
transport = new StdioClientTransport({
|
|
2319
|
+
command: server.command,
|
|
2320
|
+
args: server.args ?? [],
|
|
2321
|
+
env: { ...process.env, ...server.env ?? {} }
|
|
2322
|
+
});
|
|
2323
|
+
}
|
|
2324
|
+
} catch (e) {
|
|
2325
|
+
return { ok: false, reason: `transport error: ${errMsg(e)}` };
|
|
2326
|
+
}
|
|
2327
|
+
const client = new Client(
|
|
2328
|
+
{ name: "quiver", version: "1.0.0" },
|
|
2329
|
+
{ capabilities: {} }
|
|
2330
|
+
);
|
|
2331
|
+
try {
|
|
2332
|
+
await withTimeout(client.connect(transport), CONNECT_TIMEOUT_MS);
|
|
2333
|
+
const res = await withTimeout(client.listTools(), CONNECT_TIMEOUT_MS);
|
|
2334
|
+
const tools = (res.tools ?? []).map((t) => ({
|
|
2335
|
+
name: t.name,
|
|
2336
|
+
description: t.description ?? "",
|
|
2337
|
+
inputSchema: t.inputSchema
|
|
2338
|
+
}));
|
|
2339
|
+
return { ok: true, tools };
|
|
2340
|
+
} catch (e) {
|
|
2341
|
+
return { ok: false, reason: errMsg(e) };
|
|
2342
|
+
} finally {
|
|
2343
|
+
try {
|
|
2344
|
+
await client.close();
|
|
2345
|
+
} catch {
|
|
2346
|
+
}
|
|
2347
|
+
}
|
|
2348
|
+
};
|
|
2349
|
+
errMsg = (e) => e instanceof Error ? e.message : String(e);
|
|
2350
|
+
}
|
|
2351
|
+
});
|
|
2352
|
+
|
|
2353
|
+
// src/mcp/snapshot.ts
|
|
2354
|
+
var toSnapshot;
|
|
2355
|
+
var init_snapshot = __esm({
|
|
2356
|
+
"src/mcp/snapshot.ts"() {
|
|
2357
|
+
"use strict";
|
|
2358
|
+
init_digest();
|
|
2359
|
+
toSnapshot = (tools) => {
|
|
2360
|
+
const snapshot = {};
|
|
2361
|
+
for (const tool of tools) {
|
|
2362
|
+
snapshot[tool.name] = {
|
|
2363
|
+
description: tool.description,
|
|
2364
|
+
inputSchemaHash: jsonDigest(tool.inputSchema)
|
|
2365
|
+
};
|
|
2366
|
+
}
|
|
2367
|
+
return snapshot;
|
|
2368
|
+
};
|
|
2369
|
+
}
|
|
2370
|
+
});
|
|
2371
|
+
|
|
2372
|
+
// src/commands/check.ts
|
|
2373
|
+
var check_exports = {};
|
|
2374
|
+
__export(check_exports, {
|
|
2375
|
+
check: () => check
|
|
2376
|
+
});
|
|
2377
|
+
var check, report2, truncate2, fail;
|
|
2378
|
+
var init_check = __esm({
|
|
2379
|
+
"src/commands/check.ts"() {
|
|
2380
|
+
"use strict";
|
|
2381
|
+
init_repo();
|
|
2382
|
+
init_io();
|
|
2383
|
+
init_schema();
|
|
2384
|
+
init_diff();
|
|
2385
|
+
init_introspect();
|
|
2386
|
+
init_snapshot();
|
|
2387
|
+
init_interpolate();
|
|
2388
|
+
init_prompts();
|
|
2389
|
+
check = async (options) => {
|
|
2390
|
+
const lock = readLockfile(options.targetRoot);
|
|
2391
|
+
if (!lock) {
|
|
2392
|
+
return fail(options, "no-lockfile", "No quiver.lock found. Run `quiver-cli init` first.");
|
|
2393
|
+
}
|
|
2394
|
+
if (!repoCatalogExists(options.targetRoot)) {
|
|
2395
|
+
return fail(options, "no-agents", "No .agents/ directory found. Run `quiver-cli init` first.");
|
|
2396
|
+
}
|
|
2397
|
+
loadEnvLocal(options.targetRoot);
|
|
2398
|
+
const { catalog } = loadRepoCatalog(options.targetRoot, lock.catalog.source);
|
|
2399
|
+
const skillByName = new Map(catalog.skills.map((s) => [s.name, s]));
|
|
2400
|
+
const commandByName = new Map(catalog.commands.map((c) => [c.name, c]));
|
|
2401
|
+
const skillDrift = [];
|
|
2402
|
+
for (const [id, entry] of Object.entries(lock.entries)) {
|
|
2403
|
+
const p = parseEntryId(id);
|
|
2404
|
+
if (!p) continue;
|
|
2405
|
+
if (entry.type === "skill") {
|
|
2406
|
+
const cat = skillByName.get(p.name);
|
|
2407
|
+
if (cat && cat.digest !== entry.digest) skillDrift.push({ id, kind: "content" });
|
|
2408
|
+
} else if (entry.type === "command") {
|
|
2409
|
+
const cat = commandByName.get(p.name);
|
|
2410
|
+
if (cat && cat.digest !== entry.digest) skillDrift.push({ id, kind: "content" });
|
|
2411
|
+
}
|
|
2412
|
+
}
|
|
2413
|
+
const mcpReports = [];
|
|
2414
|
+
let lockChanged = false;
|
|
2415
|
+
for (const [id, entry] of Object.entries(lock.entries)) {
|
|
2416
|
+
if (entry.type !== "mcp") continue;
|
|
2417
|
+
const p = parseEntryId(id);
|
|
2418
|
+
const catMcp = catalog.mcp.find((m) => m.name === p.name);
|
|
2419
|
+
if (!catMcp) continue;
|
|
2420
|
+
const server = interpolateEnvVars(catMcp.server);
|
|
2421
|
+
const res = await introspect(server, { allowStdio: options.introspectStdio });
|
|
2422
|
+
if (!res.ok) {
|
|
2423
|
+
mcpReports.push({ id, status: "skipped", reason: res.reason });
|
|
2424
|
+
continue;
|
|
2425
|
+
}
|
|
2426
|
+
const current = toSnapshot(res.tools);
|
|
2427
|
+
const mcpEntry = entry;
|
|
2428
|
+
if (!mcpEntry.tools) {
|
|
2429
|
+
mcpEntry.tools = current;
|
|
2430
|
+
mcpEntry.toolsFetchedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2431
|
+
lockChanged = true;
|
|
2432
|
+
mcpReports.push({ id, status: "baseline" });
|
|
2433
|
+
continue;
|
|
2434
|
+
}
|
|
2435
|
+
const diff = diffSnapshots(mcpEntry.tools, current);
|
|
2436
|
+
if (isEmptyDiff(diff)) {
|
|
2437
|
+
mcpReports.push({ id, status: "ok" });
|
|
2438
|
+
} else {
|
|
2439
|
+
mcpReports.push({ id, status: "drift", diff });
|
|
2440
|
+
}
|
|
2441
|
+
}
|
|
2442
|
+
if (lockChanged) writeLockfile(options.targetRoot, lock);
|
|
2443
|
+
const hasDrift = skillDrift.length > 0 || mcpReports.some((r) => r.status === "drift");
|
|
2444
|
+
if (options.json) {
|
|
2445
|
+
console.log(
|
|
2446
|
+
JSON.stringify(
|
|
2447
|
+
{ ok: !hasDrift, skillDrift, mcp: mcpReports },
|
|
2448
|
+
null,
|
|
2449
|
+
2
|
|
2450
|
+
)
|
|
2451
|
+
);
|
|
2452
|
+
if (hasDrift) process.exitCode = 1;
|
|
2453
|
+
return;
|
|
2454
|
+
}
|
|
2455
|
+
await report2(skillDrift, mcpReports);
|
|
2456
|
+
if (hasDrift) process.exitCode = 1;
|
|
2457
|
+
};
|
|
2458
|
+
report2 = async (skillDrift, mcpReports) => {
|
|
2459
|
+
if (skillDrift.length) {
|
|
2460
|
+
await warn(
|
|
2461
|
+
`Skill/command content changed since lockfile:
|
|
2462
|
+
- ${skillDrift.map((s) => s.id).join("\n - ")}`
|
|
2463
|
+
);
|
|
2464
|
+
}
|
|
2465
|
+
for (const r of mcpReports) {
|
|
2466
|
+
if (r.status === "ok") {
|
|
2467
|
+
await success(`${r.id}: no tool changes`);
|
|
2468
|
+
} else if (r.status === "baseline") {
|
|
2469
|
+
await info(`${r.id}: recorded tool baseline`);
|
|
2470
|
+
} else if (r.status === "skipped") {
|
|
2471
|
+
await info(`${r.id}: skipped (${r.reason})`);
|
|
2472
|
+
} else if (r.status === "drift" && r.diff) {
|
|
2473
|
+
const lines = [];
|
|
2474
|
+
if (r.diff.added.length) lines.push(`new tools: ${r.diff.added.join(", ")}`);
|
|
2475
|
+
if (r.diff.removed.length)
|
|
2476
|
+
lines.push(`removed tools: ${r.diff.removed.join(", ")}`);
|
|
2477
|
+
if (r.diff.schemaChanged.length)
|
|
2478
|
+
lines.push(`schema changed: ${r.diff.schemaChanged.join(", ")}`);
|
|
2479
|
+
for (const dc of r.diff.descriptionChanged) {
|
|
2480
|
+
lines.push(
|
|
2481
|
+
`DESCRIPTION CHANGED (possible poisoning) "${dc.name}":
|
|
2482
|
+
before: ${truncate2(dc.before)}
|
|
2483
|
+
after: ${truncate2(dc.after)}`
|
|
2484
|
+
);
|
|
2485
|
+
}
|
|
2486
|
+
await warn(`${r.id}: tool drift
|
|
2487
|
+
- ${lines.join("\n - ")}`);
|
|
2488
|
+
}
|
|
2489
|
+
}
|
|
2490
|
+
if (!skillDrift.length && !mcpReports.some((r) => r.status === "drift")) {
|
|
2491
|
+
await success("check passed: no upstream drift detected.");
|
|
2492
|
+
}
|
|
2493
|
+
};
|
|
2494
|
+
truncate2 = (s, max = 120) => s.length > max ? s.slice(0, max) + "\u2026" : s;
|
|
2495
|
+
fail = async (options, code, message) => {
|
|
2496
|
+
if (options.json) console.log(JSON.stringify({ ok: false, error: code }));
|
|
2497
|
+
else await error(message);
|
|
2498
|
+
process.exitCode = 1;
|
|
2499
|
+
};
|
|
2500
|
+
}
|
|
2501
|
+
});
|
|
2502
|
+
|
|
2503
|
+
// src/catalog/upstreams.ts
|
|
2504
|
+
import { execFileSync as execFileSync3 } from "child_process";
|
|
2505
|
+
import {
|
|
2506
|
+
existsSync as existsSync13,
|
|
2507
|
+
mkdtempSync as mkdtempSync2,
|
|
2508
|
+
readFileSync as readFileSync10,
|
|
2509
|
+
rmSync as rmSync7,
|
|
2510
|
+
writeFileSync as writeFileSync8
|
|
2511
|
+
} from "fs";
|
|
2512
|
+
import { tmpdir } from "os";
|
|
2513
|
+
import { join, resolve as resolve18 } from "path";
|
|
2514
|
+
var UPSTREAMS_FILE, upstreamsPath, loadUpstreams, writeUpstreams, fetchLatestCommit, fetchUpstreamDir, short, evaluateOrigin;
|
|
2515
|
+
var init_upstreams = __esm({
|
|
2516
|
+
"src/catalog/upstreams.ts"() {
|
|
2517
|
+
"use strict";
|
|
2518
|
+
init_auth();
|
|
2519
|
+
init_auth();
|
|
2520
|
+
UPSTREAMS_FILE = "upstreams.json";
|
|
2521
|
+
upstreamsPath = (catalog) => resolve18(catalog.root, UPSTREAMS_FILE);
|
|
2522
|
+
loadUpstreams = (catalog) => {
|
|
2523
|
+
const path = upstreamsPath(catalog);
|
|
2524
|
+
if (!existsSync13(path)) return {};
|
|
2525
|
+
return JSON.parse(readFileSync10(path, "utf8"));
|
|
2526
|
+
};
|
|
2527
|
+
writeUpstreams = (catalog, map) => {
|
|
2528
|
+
writeFileSync8(upstreamsPath(catalog), JSON.stringify(map, null, 2) + "\n");
|
|
2529
|
+
};
|
|
2530
|
+
fetchLatestCommit = async (origin) => {
|
|
2531
|
+
const url = `https://api.github.com/repos/${origin.repo}/commits?path=${encodeURIComponent(origin.path)}&sha=${encodeURIComponent(origin.ref)}&per_page=1`;
|
|
2532
|
+
const headers2 = {
|
|
2533
|
+
Accept: "application/vnd.github+json",
|
|
2534
|
+
"User-Agent": "quiver-cli"
|
|
2535
|
+
};
|
|
2536
|
+
const token = resolveGithubToken();
|
|
2537
|
+
if (token) headers2["Authorization"] = `Bearer ${token}`;
|
|
2538
|
+
let res;
|
|
2539
|
+
try {
|
|
2540
|
+
res = await fetch(url, { headers: headers2 });
|
|
2541
|
+
} catch (err) {
|
|
2542
|
+
return { ok: false, reason: err instanceof Error ? err.message : "fetch failed" };
|
|
2543
|
+
}
|
|
2544
|
+
if (res.status === 403 || res.status === 429) {
|
|
2545
|
+
return {
|
|
2546
|
+
ok: false,
|
|
2547
|
+
reason: "rate-limited (set GITHUB_TOKEN or log in with the gh CLI)"
|
|
2548
|
+
};
|
|
2549
|
+
}
|
|
2550
|
+
if (res.status === 404) {
|
|
2551
|
+
return { ok: false, reason: "repo or path not found" };
|
|
2552
|
+
}
|
|
2553
|
+
if (!res.ok) {
|
|
2554
|
+
return { ok: false, reason: `HTTP ${res.status}` };
|
|
2555
|
+
}
|
|
2556
|
+
const body = await res.json();
|
|
2557
|
+
const head = Array.isArray(body) ? body[0] : void 0;
|
|
2558
|
+
if (!head?.sha) return { ok: false, reason: "no commits for path" };
|
|
2559
|
+
return {
|
|
2560
|
+
ok: true,
|
|
2561
|
+
sha: head.sha,
|
|
2562
|
+
date: head.commit?.committer?.date ?? ""
|
|
2563
|
+
};
|
|
2564
|
+
};
|
|
2565
|
+
fetchUpstreamDir = (origin) => {
|
|
2566
|
+
const tmp = mkdtempSync2(join(tmpdir(), "quiver-pull-"));
|
|
2567
|
+
const cleanup = () => rmSync7(tmp, { recursive: true, force: true });
|
|
2568
|
+
const git = (args, cwd) => {
|
|
2569
|
+
execFileSync3("git", args, {
|
|
2570
|
+
cwd,
|
|
2571
|
+
stdio: ["ignore", "ignore", "pipe"],
|
|
2572
|
+
timeout: 12e4
|
|
2573
|
+
});
|
|
2574
|
+
};
|
|
2575
|
+
try {
|
|
2576
|
+
git([
|
|
2577
|
+
"clone",
|
|
2578
|
+
"--depth",
|
|
2579
|
+
"1",
|
|
2580
|
+
"--filter=blob:none",
|
|
2581
|
+
"--sparse",
|
|
2582
|
+
"--branch",
|
|
2583
|
+
origin.ref,
|
|
2584
|
+
`https://github.com/${origin.repo}.git`,
|
|
2585
|
+
tmp
|
|
2586
|
+
]);
|
|
2587
|
+
git(["sparse-checkout", "set", origin.path], tmp);
|
|
2588
|
+
} catch (err) {
|
|
2589
|
+
cleanup();
|
|
2590
|
+
const msg = err instanceof Error && "stderr" in err ? String(err.stderr).trim().split("\n").pop() : err instanceof Error ? err.message : "git clone failed";
|
|
2591
|
+
return { ok: false, reason: msg || "git clone failed" };
|
|
2592
|
+
}
|
|
2593
|
+
const dir = resolve18(tmp, origin.path);
|
|
2594
|
+
if (!existsSync13(resolve18(dir, "SKILL.md"))) {
|
|
2595
|
+
cleanup();
|
|
2596
|
+
return { ok: false, reason: `no SKILL.md at ${origin.path} in ${origin.repo}` };
|
|
2597
|
+
}
|
|
2598
|
+
return { ok: true, dir, cleanup };
|
|
2599
|
+
};
|
|
2600
|
+
short = (sha) => sha.slice(0, 10);
|
|
2601
|
+
evaluateOrigin = (name, origin, result) => {
|
|
2602
|
+
const base = { name, repo: origin.repo, path: origin.path };
|
|
2603
|
+
if (!result.ok) {
|
|
2604
|
+
return {
|
|
2605
|
+
report: { ...base, status: "skipped", reason: result.reason },
|
|
2606
|
+
changed: false
|
|
2607
|
+
};
|
|
2608
|
+
}
|
|
2609
|
+
if (!origin.commit) {
|
|
2610
|
+
origin.commit = result.sha;
|
|
2611
|
+
origin.fetchedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2612
|
+
return {
|
|
2613
|
+
report: { ...base, status: "baseline", to: short(result.sha), date: result.date },
|
|
2614
|
+
changed: true
|
|
2615
|
+
};
|
|
2616
|
+
}
|
|
2617
|
+
if (origin.commit === result.sha) {
|
|
2618
|
+
return {
|
|
2619
|
+
report: { ...base, status: "ok", from: short(origin.commit) },
|
|
2620
|
+
changed: false
|
|
2621
|
+
};
|
|
2622
|
+
}
|
|
2623
|
+
return {
|
|
2624
|
+
report: {
|
|
2625
|
+
...base,
|
|
2626
|
+
status: origin.curated ? "drift-curated" : "drift",
|
|
2627
|
+
from: short(origin.commit),
|
|
2628
|
+
to: short(result.sha),
|
|
2629
|
+
date: result.date
|
|
2630
|
+
},
|
|
2631
|
+
changed: false
|
|
2632
|
+
};
|
|
2633
|
+
};
|
|
2634
|
+
}
|
|
2635
|
+
});
|
|
2636
|
+
|
|
2637
|
+
// src/commands/upstream.ts
|
|
2638
|
+
var upstream_exports = {};
|
|
2639
|
+
__export(upstream_exports, {
|
|
2640
|
+
upstream: () => upstream
|
|
2641
|
+
});
|
|
2642
|
+
import { cpSync as cpSync3, rmSync as rmSync8 } from "fs";
|
|
2643
|
+
var upstream, STATUS_ORDER, pull, report3, countByStatus;
|
|
2644
|
+
var init_upstream = __esm({
|
|
2645
|
+
"src/commands/upstream.ts"() {
|
|
2646
|
+
"use strict";
|
|
2647
|
+
init_discover();
|
|
2648
|
+
init_resolve();
|
|
2649
|
+
init_upstreams();
|
|
2650
|
+
init_interpolate();
|
|
2651
|
+
init_prompts();
|
|
2652
|
+
upstream = async (options) => {
|
|
2653
|
+
loadEnvLocal(options.targetRoot);
|
|
2654
|
+
if (options.positionals[0] === "pull") {
|
|
2655
|
+
await pull(options);
|
|
2656
|
+
return;
|
|
2657
|
+
}
|
|
2658
|
+
const resolved = await resolveCatalog();
|
|
2659
|
+
const catalog = loadCatalog(resolved);
|
|
2660
|
+
const upstreams = loadUpstreams(resolved);
|
|
2661
|
+
const trackedNames = Object.keys(upstreams);
|
|
2662
|
+
const catalogSkillNames = new Set(catalog.skills.map((s) => s.name));
|
|
2663
|
+
const untracked = catalog.skills.map((s) => s.name).filter((n) => !trackedNames.includes(n)).sort((a, b) => a.localeCompare(b));
|
|
2664
|
+
const reports = [];
|
|
2665
|
+
let mapChanged = false;
|
|
2666
|
+
for (const name of trackedNames.sort((a, b) => a.localeCompare(b))) {
|
|
2667
|
+
const origin = upstreams[name];
|
|
2668
|
+
const result = await fetchLatestCommit(origin);
|
|
2669
|
+
const { report: report4, changed } = evaluateOrigin(name, origin, result);
|
|
2670
|
+
if (changed) mapChanged = true;
|
|
2671
|
+
reports.push(report4);
|
|
2672
|
+
}
|
|
2673
|
+
if (mapChanged) writeUpstreams(resolved, upstreams);
|
|
2674
|
+
const hasDrift = reports.some(
|
|
2675
|
+
(r) => r.status === "drift" || r.status === "drift-curated"
|
|
2676
|
+
);
|
|
2677
|
+
const stale = trackedNames.filter((n) => !catalogSkillNames.has(n));
|
|
2678
|
+
if (options.json) {
|
|
2679
|
+
console.log(
|
|
2680
|
+
JSON.stringify({ ok: !hasDrift, skills: reports, untracked, stale }, null, 2)
|
|
2681
|
+
);
|
|
2682
|
+
if (hasDrift) process.exitCode = 1;
|
|
2683
|
+
return;
|
|
2684
|
+
}
|
|
2685
|
+
await report3(reports, untracked, stale);
|
|
2686
|
+
if (hasDrift) process.exitCode = 1;
|
|
2687
|
+
};
|
|
2688
|
+
STATUS_ORDER = {
|
|
2689
|
+
drift: 0,
|
|
2690
|
+
"drift-curated": 1,
|
|
2691
|
+
skipped: 2,
|
|
2692
|
+
baseline: 3,
|
|
2693
|
+
ok: 4
|
|
2694
|
+
};
|
|
2695
|
+
pull = async (options) => {
|
|
2696
|
+
const resolved = await resolveCatalog();
|
|
2697
|
+
const catalog = loadCatalog(resolved);
|
|
2698
|
+
const upstreams = loadUpstreams(resolved);
|
|
2699
|
+
const only = options.positionals[1];
|
|
2700
|
+
if (only && !upstreams[only]) {
|
|
2701
|
+
await error(`${only} is not tracked in upstreams.json.`);
|
|
2702
|
+
process.exitCode = 1;
|
|
2703
|
+
return;
|
|
2704
|
+
}
|
|
2705
|
+
const names = only ? [only] : Object.keys(upstreams).sort((a, b) => a.localeCompare(b));
|
|
2706
|
+
const reports = [];
|
|
2707
|
+
let mapChanged = false;
|
|
2708
|
+
for (const name of names) {
|
|
2709
|
+
const origin = upstreams[name];
|
|
2710
|
+
const skill = catalog.skills.find((s) => s.name === name);
|
|
2711
|
+
if (!skill) {
|
|
2712
|
+
reports.push({ name, status: "not-in-catalog" });
|
|
2713
|
+
continue;
|
|
2714
|
+
}
|
|
2715
|
+
if (origin.curated && !options.force) {
|
|
2716
|
+
reports.push({
|
|
2717
|
+
name,
|
|
2718
|
+
status: "curated-skipped",
|
|
2719
|
+
detail: "modified after import - use --force to overwrite"
|
|
2720
|
+
});
|
|
2721
|
+
continue;
|
|
2722
|
+
}
|
|
2723
|
+
const latest = await fetchLatestCommit(origin);
|
|
2724
|
+
if (!latest.ok) {
|
|
2725
|
+
reports.push({ name, status: "error", detail: latest.reason });
|
|
2726
|
+
continue;
|
|
2727
|
+
}
|
|
2728
|
+
if (origin.commit === latest.sha && !options.force) {
|
|
2729
|
+
reports.push({ name, status: "up-to-date" });
|
|
2730
|
+
continue;
|
|
2731
|
+
}
|
|
2732
|
+
const fetched = fetchUpstreamDir(origin);
|
|
2733
|
+
if (!fetched.ok) {
|
|
2734
|
+
reports.push({ name, status: "error", detail: fetched.reason });
|
|
2735
|
+
continue;
|
|
2736
|
+
}
|
|
2737
|
+
try {
|
|
2738
|
+
rmSync8(skill.absDir, { recursive: true, force: true });
|
|
2739
|
+
cpSync3(fetched.dir, skill.absDir, { recursive: true, dereference: true });
|
|
2740
|
+
} finally {
|
|
2741
|
+
fetched.cleanup();
|
|
2742
|
+
}
|
|
2743
|
+
origin.commit = latest.sha;
|
|
2744
|
+
origin.fetchedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2745
|
+
mapChanged = true;
|
|
2746
|
+
reports.push({ name, status: "pulled", detail: latest.sha.slice(0, 10) });
|
|
2747
|
+
}
|
|
2748
|
+
if (mapChanged) writeUpstreams(resolved, upstreams);
|
|
2749
|
+
if (options.json) {
|
|
2750
|
+
console.log(JSON.stringify({ ok: true, skills: reports }, null, 2));
|
|
2751
|
+
return;
|
|
2752
|
+
}
|
|
2753
|
+
const c = palette();
|
|
2754
|
+
const lines = [""];
|
|
2755
|
+
for (const r of reports) {
|
|
2756
|
+
if (r.status === "pulled")
|
|
2757
|
+
lines.push(` ${c.cyan("\u2193")} ${r.name} pulled @ ${r.detail}`);
|
|
2758
|
+
else if (r.status === "up-to-date")
|
|
2759
|
+
lines.push(` ${c.green("\u2713")} ${r.name} ${c.dim("up to date")}`);
|
|
2760
|
+
else if (r.status === "curated-skipped")
|
|
2761
|
+
lines.push(` ${c.yellow("\u25B2")} ${r.name} ${c.yellow(r.detail ?? "curated")}`);
|
|
2762
|
+
else if (r.status === "not-in-catalog")
|
|
2763
|
+
lines.push(` ${c.dim("\u2022")} ${r.name} ${c.dim("not in catalog")}`);
|
|
2764
|
+
else lines.push(` ${c.red("\u2717")} ${r.name} ${c.red(r.detail ?? "error")}`);
|
|
2765
|
+
}
|
|
2766
|
+
const pulled = reports.filter((r) => r.status === "pulled").length;
|
|
2767
|
+
lines.push(
|
|
2768
|
+
"",
|
|
2769
|
+
` ${pulled ? c.cyan(`\u2193 ${pulled} pulled - review, rebuild and commit the catalog`) : c.green("\u2713 nothing to pull")}`,
|
|
2770
|
+
""
|
|
2771
|
+
);
|
|
2772
|
+
block(lines);
|
|
2773
|
+
};
|
|
2774
|
+
report3 = async (reports, untracked, stale) => {
|
|
2775
|
+
const c = palette();
|
|
2776
|
+
const sorted = [...reports].sort(
|
|
2777
|
+
(a, b) => STATUS_ORDER[a.status] - STATUS_ORDER[b.status] || a.name.localeCompare(b.name)
|
|
2778
|
+
);
|
|
2779
|
+
const mark = {
|
|
2780
|
+
drift: c.yellow("\u25B2"),
|
|
2781
|
+
"drift-curated": c.yellow("\u25B2"),
|
|
2782
|
+
skipped: c.dim("\u2022"),
|
|
2783
|
+
baseline: c.cyan("+"),
|
|
2784
|
+
ok: c.green("\u2713")
|
|
2785
|
+
};
|
|
2786
|
+
const nameWidth = Math.max(0, ...reports.map((r) => r.name.length));
|
|
2787
|
+
const lines = [""];
|
|
2788
|
+
for (const r of sorted) {
|
|
2789
|
+
const name = r.name.padEnd(nameWidth);
|
|
2790
|
+
const where = c.dim(`${r.repo}/${r.path}`);
|
|
2791
|
+
let detail;
|
|
2792
|
+
switch (r.status) {
|
|
2793
|
+
case "ok":
|
|
2794
|
+
detail = `up to date ${c.dim(r.from ?? "")}`;
|
|
2795
|
+
break;
|
|
2796
|
+
case "baseline":
|
|
2797
|
+
detail = `baseline ${c.dim(r.to ?? "")}`;
|
|
2798
|
+
break;
|
|
2799
|
+
case "skipped":
|
|
2800
|
+
detail = c.dim(`skipped ${r.reason ?? ""}`);
|
|
2801
|
+
break;
|
|
2802
|
+
case "drift":
|
|
2803
|
+
detail = c.yellow(`UPDATED ${r.from} \u2192 ${r.to}`);
|
|
2804
|
+
break;
|
|
2805
|
+
case "drift-curated":
|
|
2806
|
+
detail = c.yellow(`UPDATED ${r.from} \u2192 ${r.to} (curated)`);
|
|
2807
|
+
break;
|
|
2808
|
+
}
|
|
2809
|
+
lines.push(` ${mark[r.status]} ${c.bold(name)} ${detail} ${where}`);
|
|
2810
|
+
}
|
|
2811
|
+
block(lines);
|
|
2812
|
+
const drifted = sorted.filter(
|
|
2813
|
+
(r) => r.status === "drift" || r.status === "drift-curated"
|
|
2814
|
+
);
|
|
2815
|
+
if (drifted.length) {
|
|
2816
|
+
const hint = [""];
|
|
2817
|
+
for (const r of drifted) {
|
|
2818
|
+
const note = r.status === "drift-curated" ? "curated \u2014 reconcile changes by hand" : "re-fetch with the skills CLI, then copy into the catalog";
|
|
2819
|
+
hint.push(` ${c.yellow(r.name)}: ${c.dim(note)}`);
|
|
2820
|
+
}
|
|
2821
|
+
block(hint);
|
|
2822
|
+
}
|
|
2823
|
+
const counts = countByStatus(reports);
|
|
2824
|
+
const summaryParts = [];
|
|
2825
|
+
if (counts.drift) summaryParts.push(c.yellow(`${counts.drift} updated`));
|
|
2826
|
+
if (counts.baseline) summaryParts.push(c.cyan(`${counts.baseline} new`));
|
|
2827
|
+
if (counts.skipped) summaryParts.push(c.dim(`${counts.skipped} skipped`));
|
|
2828
|
+
if (counts.ok) summaryParts.push(c.green(`${counts.ok} up to date`));
|
|
2829
|
+
const lead = counts.drift === 0 ? c.green("\u2713") : c.yellow("\u25B2");
|
|
2830
|
+
const footer = [""];
|
|
2831
|
+
footer.push(
|
|
2832
|
+
` ${lead} ${c.bold(`${reports.length} tracked`)}` + (summaryParts.length ? ` ${summaryParts.join(c.dim(" \xB7 "))}` : "")
|
|
2833
|
+
);
|
|
2834
|
+
if (untracked.length) {
|
|
2835
|
+
footer.push(` ${c.dim(`untracked: ${untracked.join(", ")}`)}`);
|
|
2836
|
+
}
|
|
2837
|
+
if (stale.length) {
|
|
2838
|
+
footer.push(` ${c.yellow(`stale origins: ${stale.join(", ")}`)}`);
|
|
2839
|
+
}
|
|
2840
|
+
block([...footer, ""]);
|
|
2841
|
+
};
|
|
2842
|
+
countByStatus = (reports) => {
|
|
2843
|
+
const counts = { drift: 0, baseline: 0, skipped: 0, ok: 0 };
|
|
2844
|
+
for (const r of reports) {
|
|
2845
|
+
if (r.status === "drift" || r.status === "drift-curated") counts.drift += 1;
|
|
2846
|
+
else if (r.status === "baseline") counts.baseline += 1;
|
|
2847
|
+
else if (r.status === "skipped") counts.skipped += 1;
|
|
2848
|
+
else if (r.status === "ok") counts.ok += 1;
|
|
2849
|
+
}
|
|
2850
|
+
return counts;
|
|
2851
|
+
};
|
|
2852
|
+
}
|
|
2853
|
+
});
|
|
2854
|
+
|
|
2855
|
+
// src/commands/login.ts
|
|
2856
|
+
var login_exports = {};
|
|
2857
|
+
__export(login_exports, {
|
|
2858
|
+
login: () => login
|
|
2859
|
+
});
|
|
2860
|
+
import process3 from "process";
|
|
2861
|
+
var login, promptToken, readStdin;
|
|
2862
|
+
var init_login = __esm({
|
|
2863
|
+
"src/commands/login.ts"() {
|
|
2864
|
+
"use strict";
|
|
2865
|
+
init_auth();
|
|
2866
|
+
init_prompts();
|
|
2867
|
+
login = async (options) => {
|
|
2868
|
+
const token = process3.stdin.isTTY ? await promptToken() : await readStdin();
|
|
2869
|
+
if (!token) {
|
|
2870
|
+
await error(
|
|
2871
|
+
"No token provided. Create a GitHub personal access token with `repo` read access and try again."
|
|
2872
|
+
);
|
|
2873
|
+
process3.exitCode = 1;
|
|
2874
|
+
return;
|
|
2875
|
+
}
|
|
2876
|
+
const validation = await validateToken(token);
|
|
2877
|
+
if (!validation.ok) {
|
|
2878
|
+
await error(`Token validation failed: ${validation.reason}`);
|
|
2879
|
+
process3.exitCode = 1;
|
|
2880
|
+
return;
|
|
2881
|
+
}
|
|
2882
|
+
writeStoredToken(token, validation.login);
|
|
2883
|
+
if (options.json) {
|
|
2884
|
+
console.log(JSON.stringify({ ok: true, login: validation.login }, null, 2));
|
|
2885
|
+
return;
|
|
2886
|
+
}
|
|
2887
|
+
await success(
|
|
2888
|
+
`Logged in as ${validation.login}. Token stored in ${authFilePath()}.`
|
|
2889
|
+
);
|
|
2890
|
+
};
|
|
2891
|
+
promptToken = async () => {
|
|
2892
|
+
await info(
|
|
2893
|
+
"Paste a GitHub personal access token (used for github: catalogs; needs read access to the catalog repo)."
|
|
2894
|
+
);
|
|
2895
|
+
const value = await password("GitHub token");
|
|
2896
|
+
return value?.trim() || null;
|
|
2897
|
+
};
|
|
2898
|
+
readStdin = async () => {
|
|
2899
|
+
let data = "";
|
|
2900
|
+
for await (const chunk of process3.stdin) data += chunk;
|
|
2901
|
+
return data.trim() || null;
|
|
2902
|
+
};
|
|
2903
|
+
}
|
|
2904
|
+
});
|
|
2905
|
+
|
|
2906
|
+
// src/commands/logout.ts
|
|
2907
|
+
var logout_exports = {};
|
|
2908
|
+
__export(logout_exports, {
|
|
2909
|
+
logout: () => logout
|
|
2910
|
+
});
|
|
2911
|
+
var logout;
|
|
2912
|
+
var init_logout = __esm({
|
|
2913
|
+
"src/commands/logout.ts"() {
|
|
2914
|
+
"use strict";
|
|
2915
|
+
init_auth();
|
|
2916
|
+
init_prompts();
|
|
2917
|
+
logout = async (options) => {
|
|
2918
|
+
const removed = deleteStoredToken();
|
|
2919
|
+
if (options.json) {
|
|
2920
|
+
console.log(JSON.stringify({ ok: true, removed }, null, 2));
|
|
2921
|
+
return;
|
|
2922
|
+
}
|
|
2923
|
+
if (removed) {
|
|
2924
|
+
await success(`Logged out. Removed ${authFilePath()}.`);
|
|
2925
|
+
} else {
|
|
2926
|
+
await info("Not logged in - nothing to remove.");
|
|
2927
|
+
}
|
|
2928
|
+
};
|
|
2929
|
+
}
|
|
2930
|
+
});
|
|
2931
|
+
|
|
2932
|
+
// src/cli.ts
|
|
2933
|
+
init_prompts();
|
|
2934
|
+
import process4 from "process";
|
|
2935
|
+
var HELP = `quiver-cli - compose agent skills, commands & MCP servers into any repo
|
|
2936
|
+
|
|
2937
|
+
Usage:
|
|
2938
|
+
quiver-cli <command> [options]
|
|
2939
|
+
|
|
2940
|
+
Commands:
|
|
2941
|
+
init Interactive picker over the catalog; write native configs + quiver.lock
|
|
2942
|
+
add <id> Add a single catalog entry (skill:<name>, command:<name>, mcp:<name>)
|
|
2943
|
+
remove <id> Remove a single entry; keep lockfile + configs consistent
|
|
2944
|
+
sync Regenerate provider configs from .agents/ (warns on drift)
|
|
2945
|
+
update [id] Pull newer catalog content into .agents/ (all or one entry)
|
|
2946
|
+
list Show installed entries (skills, commands, MCP tool counts)
|
|
2947
|
+
status Diff the lockfile against what is actually in the repo
|
|
2948
|
+
check Detect upstream drift (skill digests, MCP tool snapshots)
|
|
2949
|
+
upstream Check source repos for skill updates (catalog maintenance)
|
|
2950
|
+
upstream pull Pull latest upstream content into the catalog [skill]
|
|
2951
|
+
login Store a GitHub token for remote (github:) catalogs
|
|
2952
|
+
logout Remove the stored GitHub token
|
|
2953
|
+
help Show this help
|
|
2954
|
+
version Show the quiver-cli version
|
|
2955
|
+
|
|
2956
|
+
Options:
|
|
2957
|
+
-f, --force Overwrite existing files
|
|
2958
|
+
--all, -y Keep everything without prompting (non-interactive)
|
|
2959
|
+
--json Machine-readable output (status/check/upstream/list)
|
|
2960
|
+
--providers=a,b Generate configs only for these tools (claude, opencode, codex)
|
|
2961
|
+
--catalog=<source> Catalog source for init (e.g. github:owner/repo[/path][#ref])
|
|
2962
|
+
--introspect-stdio Allow introspecting stdio MCP servers (runs foreign code)
|
|
2963
|
+
-v, --version Show the quiver-cli version
|
|
2964
|
+
`;
|
|
2965
|
+
var parse = (argv) => {
|
|
2966
|
+
const [command = "init", ...rest] = argv;
|
|
2967
|
+
const flags = new Set(rest.filter((a) => a.startsWith("-")));
|
|
2968
|
+
const positionals = rest.filter((a) => !a.startsWith("-"));
|
|
2969
|
+
const providersFlag = rest.find((a) => a.startsWith("--providers="));
|
|
2970
|
+
const providers = providersFlag ? providersFlag.slice("--providers=".length).split(",").map((p) => p.trim()).filter(Boolean) : null;
|
|
2971
|
+
const catalogFlag = rest.find((a) => a.startsWith("--catalog="));
|
|
2972
|
+
const catalog = catalogFlag ? catalogFlag.slice("--catalog=".length).trim() || null : null;
|
|
2973
|
+
return {
|
|
2974
|
+
command,
|
|
2975
|
+
options: {
|
|
2976
|
+
targetRoot: process4.cwd(),
|
|
2977
|
+
force: flags.has("--force") || flags.has("-f"),
|
|
2978
|
+
all: flags.has("--all") || flags.has("--yes") || flags.has("-y"),
|
|
2979
|
+
json: flags.has("--json"),
|
|
2980
|
+
introspectStdio: flags.has("--introspect-stdio"),
|
|
2981
|
+
providers,
|
|
2982
|
+
catalog,
|
|
2983
|
+
positionals
|
|
2984
|
+
}
|
|
2985
|
+
};
|
|
2986
|
+
};
|
|
2987
|
+
var run = async () => {
|
|
2988
|
+
const { command, options } = parse(process4.argv.slice(2));
|
|
2989
|
+
switch (command) {
|
|
2990
|
+
case "init": {
|
|
2991
|
+
const { init: init2 } = await Promise.resolve().then(() => (init_init(), init_exports));
|
|
2992
|
+
await init2(options);
|
|
2993
|
+
break;
|
|
2994
|
+
}
|
|
2995
|
+
case "add": {
|
|
2996
|
+
const { add: add2 } = await Promise.resolve().then(() => (init_add(), add_exports));
|
|
2997
|
+
await add2(options);
|
|
2998
|
+
break;
|
|
2999
|
+
}
|
|
3000
|
+
case "remove":
|
|
3001
|
+
case "rm": {
|
|
3002
|
+
const { remove: remove2 } = await Promise.resolve().then(() => (init_remove(), remove_exports));
|
|
3003
|
+
await remove2(options);
|
|
3004
|
+
break;
|
|
3005
|
+
}
|
|
3006
|
+
case "sync": {
|
|
3007
|
+
const { sync: sync2 } = await Promise.resolve().then(() => (init_sync(), sync_exports));
|
|
3008
|
+
await sync2(options);
|
|
3009
|
+
break;
|
|
3010
|
+
}
|
|
3011
|
+
case "update": {
|
|
3012
|
+
const { update: update2 } = await Promise.resolve().then(() => (init_update(), update_exports));
|
|
3013
|
+
await update2(options);
|
|
3014
|
+
break;
|
|
3015
|
+
}
|
|
3016
|
+
case "list":
|
|
3017
|
+
case "ls": {
|
|
3018
|
+
const { list: list2 } = await Promise.resolve().then(() => (init_list(), list_exports));
|
|
3019
|
+
await list2(options);
|
|
3020
|
+
break;
|
|
3021
|
+
}
|
|
3022
|
+
case "status": {
|
|
3023
|
+
const { status: status2 } = await Promise.resolve().then(() => (init_status(), status_exports));
|
|
3024
|
+
await status2(options);
|
|
3025
|
+
break;
|
|
3026
|
+
}
|
|
3027
|
+
case "check": {
|
|
3028
|
+
const { check: check2 } = await Promise.resolve().then(() => (init_check(), check_exports));
|
|
3029
|
+
await check2(options);
|
|
3030
|
+
break;
|
|
3031
|
+
}
|
|
3032
|
+
case "upstream": {
|
|
3033
|
+
const { upstream: upstream2 } = await Promise.resolve().then(() => (init_upstream(), upstream_exports));
|
|
3034
|
+
await upstream2(options);
|
|
3035
|
+
break;
|
|
3036
|
+
}
|
|
3037
|
+
case "login": {
|
|
3038
|
+
const { login: login2 } = await Promise.resolve().then(() => (init_login(), login_exports));
|
|
3039
|
+
await login2(options);
|
|
3040
|
+
break;
|
|
3041
|
+
}
|
|
3042
|
+
case "logout": {
|
|
3043
|
+
const { logout: logout2 } = await Promise.resolve().then(() => (init_logout(), logout_exports));
|
|
3044
|
+
await logout2(options);
|
|
3045
|
+
break;
|
|
3046
|
+
}
|
|
3047
|
+
case "help":
|
|
3048
|
+
case "--help":
|
|
3049
|
+
case "-h":
|
|
3050
|
+
console.log(HELP);
|
|
3051
|
+
break;
|
|
3052
|
+
case "version":
|
|
3053
|
+
case "--version":
|
|
3054
|
+
case "-v": {
|
|
3055
|
+
const { readFileSync: readFileSync11 } = await import("fs");
|
|
3056
|
+
const { resolve: resolve19 } = await import("path");
|
|
3057
|
+
const { packageRoot: packageRoot2 } = await Promise.resolve().then(() => (init_resolve(), resolve_exports));
|
|
3058
|
+
const pkg = JSON.parse(
|
|
3059
|
+
readFileSync11(resolve19(packageRoot2, "package.json"), "utf8")
|
|
3060
|
+
);
|
|
3061
|
+
console.log(pkg.version);
|
|
3062
|
+
break;
|
|
3063
|
+
}
|
|
3064
|
+
default:
|
|
3065
|
+
console.error(`Unknown command: ${command}
|
|
3066
|
+
`);
|
|
3067
|
+
console.log(HELP);
|
|
3068
|
+
process4.exitCode = 1;
|
|
3069
|
+
}
|
|
3070
|
+
};
|
|
3071
|
+
run().catch(async (err) => {
|
|
3072
|
+
await error(err instanceof Error ? err.message : String(err));
|
|
3073
|
+
process4.exitCode = 1;
|
|
3074
|
+
});
|