skilldrop-cli 0.16.3__py3-none-any.whl
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.
- skilldrop_cli/__init__.py +16 -0
- skilldrop_cli/__main__.py +3 -0
- skilldrop_cli/data/LICENSE +9 -0
- skilldrop_cli/data/LICENSE-APACHE +202 -0
- skilldrop_cli/data/LICENSE-MIT +21 -0
- skilldrop_cli/data/agents/README.md +127 -0
- skilldrop_cli/data/agents/code-quality.md +40 -0
- skilldrop_cli/data/agents/devils-advocate.md +40 -0
- skilldrop_cli/data/agents/security-reviewer.md +44 -0
- skilldrop_cli/data/bin/skilldrop.js +2110 -0
- skilldrop_cli/data/catalogue.json +187 -0
- skilldrop_cli/data/contracts/agent.schema.json +28 -0
- skilldrop_cli/data/contracts/catalogue.schema.json +33 -0
- skilldrop_cli/data/contracts/guide.schema.json +18 -0
- skilldrop_cli/data/contracts/loop.schema.json +87 -0
- skilldrop_cli/data/contracts/pack.schema.json +75 -0
- skilldrop_cli/data/contracts/skill.schema.json +110 -0
- skilldrop_cli/data/contracts/terminals.json +33 -0
- skilldrop_cli/data/model-routing.json +474 -0
- skilldrop_cli/data/package.json +46 -0
- skilldrop_cli/data/packs/ai-engineering/pack.json +57 -0
- skilldrop_cli/data/packs/ai-engineering/skills/agent-adoption-stage/SKILL.md +90 -0
- skilldrop_cli/data/packs/ai-engineering/skills/agent-adoption-stage/evals/eval_queries.json +34 -0
- skilldrop_cli/data/packs/ai-engineering/skills/agent-adoption-stage/evals/evals.json +19 -0
- skilldrop_cli/data/packs/ai-engineering/skills/agent-adoption-stage/manifest.json +39 -0
- skilldrop_cli/data/packs/ai-engineering/skills/agent-budget/SKILL.md +63 -0
- skilldrop_cli/data/packs/ai-engineering/skills/agent-budget/evals/eval_queries.json +11 -0
- skilldrop_cli/data/packs/ai-engineering/skills/agent-budget/evals/evals.json +28 -0
- skilldrop_cli/data/packs/ai-engineering/skills/agent-budget/manifest.json +11 -0
- skilldrop_cli/data/packs/ai-engineering/skills/agent-budget/templates/budget-spec.md +30 -0
- skilldrop_cli/data/packs/ai-engineering/skills/agent-loop-design/SKILL.md +70 -0
- skilldrop_cli/data/packs/ai-engineering/skills/agent-loop-design/evals/eval_queries.json +11 -0
- skilldrop_cli/data/packs/ai-engineering/skills/agent-loop-design/evals/evals.json +28 -0
- skilldrop_cli/data/packs/ai-engineering/skills/agent-loop-design/manifest.json +11 -0
- skilldrop_cli/data/packs/ai-engineering/skills/agent-loop-design/templates/loop-spec.md +46 -0
- skilldrop_cli/data/packs/ai-engineering/skills/agent-threat-model/SKILL.md +89 -0
- skilldrop_cli/data/packs/ai-engineering/skills/agent-threat-model/evals/eval_queries.json +15 -0
- skilldrop_cli/data/packs/ai-engineering/skills/agent-threat-model/evals/evals.json +41 -0
- skilldrop_cli/data/packs/ai-engineering/skills/agent-threat-model/examples/support-triage-agent.md +117 -0
- skilldrop_cli/data/packs/ai-engineering/skills/agent-threat-model/manifest.json +11 -0
- skilldrop_cli/data/packs/ai-engineering/skills/agent-threat-model/reference.md +101 -0
- skilldrop_cli/data/packs/ai-engineering/skills/agent-threat-model/templates/agent-threat-model.md +71 -0
- skilldrop_cli/data/packs/ai-engineering/skills/ai-adoption-rollout/SKILL.md +61 -0
- skilldrop_cli/data/packs/ai-engineering/skills/ai-adoption-rollout/evals/eval_queries.json +34 -0
- skilldrop_cli/data/packs/ai-engineering/skills/ai-adoption-rollout/evals/evals.json +19 -0
- skilldrop_cli/data/packs/ai-engineering/skills/ai-adoption-rollout/manifest.json +33 -0
- skilldrop_cli/data/packs/ai-engineering/skills/ai-readiness-assessment/SKILL.md +68 -0
- skilldrop_cli/data/packs/ai-engineering/skills/ai-readiness-assessment/evals/eval_queries.json +34 -0
- skilldrop_cli/data/packs/ai-engineering/skills/ai-readiness-assessment/evals/evals.json +17 -0
- skilldrop_cli/data/packs/ai-engineering/skills/ai-readiness-assessment/manifest.json +35 -0
- skilldrop_cli/data/packs/ai-engineering/skills/ai-usage-policy/SKILL.md +67 -0
- skilldrop_cli/data/packs/ai-engineering/skills/ai-usage-policy/evals/eval_queries.json +34 -0
- skilldrop_cli/data/packs/ai-engineering/skills/ai-usage-policy/evals/evals.json +19 -0
- skilldrop_cli/data/packs/ai-engineering/skills/ai-usage-policy/manifest.json +33 -0
- skilldrop_cli/data/packs/ai-engineering/skills/ai-usage-report/SKILL.md +111 -0
- skilldrop_cli/data/packs/ai-engineering/skills/ai-usage-report/evals/eval_queries.json +30 -0
- skilldrop_cli/data/packs/ai-engineering/skills/ai-usage-report/evals/evals.json +19 -0
- skilldrop_cli/data/packs/ai-engineering/skills/ai-usage-report/evals/files/1/northwind-ai-events.csv +4213 -0
- skilldrop_cli/data/packs/ai-engineering/skills/ai-usage-report/examples/weekly-engineering-team.md +133 -0
- skilldrop_cli/data/packs/ai-engineering/skills/ai-usage-report/manifest.json +12 -0
- skilldrop_cli/data/packs/ai-engineering/skills/ai-usage-report/scripts/build_report.py +436 -0
- skilldrop_cli/data/packs/ai-engineering/skills/ai-usage-report/templates/report-per-user.md +42 -0
- skilldrop_cli/data/packs/ai-engineering/skills/ai-usage-report/templates/report-team-rollup.md +49 -0
- skilldrop_cli/data/packs/ai-engineering/skills/ai-usage-report/templates/usage-event-schema.md +76 -0
- skilldrop_cli/data/packs/ai-engineering/skills/ai-use-case-triage/SKILL.md +59 -0
- skilldrop_cli/data/packs/ai-engineering/skills/ai-use-case-triage/evals/eval_queries.json +34 -0
- skilldrop_cli/data/packs/ai-engineering/skills/ai-use-case-triage/evals/evals.json +18 -0
- skilldrop_cli/data/packs/ai-engineering/skills/ai-use-case-triage/manifest.json +34 -0
- skilldrop_cli/data/packs/ai-engineering/skills/llm-eval-harness/SKILL.md +73 -0
- skilldrop_cli/data/packs/ai-engineering/skills/llm-eval-harness/evals/eval_queries.json +34 -0
- skilldrop_cli/data/packs/ai-engineering/skills/llm-eval-harness/evals/evals.json +19 -0
- skilldrop_cli/data/packs/ai-engineering/skills/llm-eval-harness/manifest.json +11 -0
- skilldrop_cli/data/packs/ai-engineering/skills/llm-eval-harness/reference.md +58 -0
- skilldrop_cli/data/packs/ai-engineering/skills/llm-eval-harness/templates/eval-plan.md +63 -0
- skilldrop_cli/data/packs/ai-engineering/skills/llm-eval-harness/templates/golden-case.jsonl +5 -0
- skilldrop_cli/data/packs/ai-engineering/skills/subagent-design/SKILL.md +69 -0
- skilldrop_cli/data/packs/ai-engineering/skills/subagent-design/evals/eval_queries.json +11 -0
- skilldrop_cli/data/packs/ai-engineering/skills/subagent-design/evals/evals.json +29 -0
- skilldrop_cli/data/packs/ai-engineering/skills/subagent-design/manifest.json +11 -0
- skilldrop_cli/data/packs/ai-engineering/skills/subagent-design/templates/orchestration-plan.md +47 -0
- skilldrop_cli/data/packs/api-builder/pack.json +48 -0
- skilldrop_cli/data/packs/api-builder/skills/eval-harness-generator/SKILL.md +84 -0
- skilldrop_cli/data/packs/api-builder/skills/eval-harness-generator/evals/eval_queries.json +30 -0
- skilldrop_cli/data/packs/api-builder/skills/eval-harness-generator/evals/evals.json +19 -0
- skilldrop_cli/data/packs/api-builder/skills/eval-harness-generator/evals/files/1/SKILL.md +31 -0
- skilldrop_cli/data/packs/api-builder/skills/eval-harness-generator/manifest.json +11 -0
- skilldrop_cli/data/packs/api-builder/skills/prompt-caching-advisor/SKILL.md +90 -0
- skilldrop_cli/data/packs/api-builder/skills/prompt-caching-advisor/evals/eval_queries.json +30 -0
- skilldrop_cli/data/packs/api-builder/skills/prompt-caching-advisor/evals/evals.json +19 -0
- skilldrop_cli/data/packs/api-builder/skills/prompt-caching-advisor/manifest.json +11 -0
- skilldrop_cli/data/packs/api-builder/skills/token-budget-estimator/SKILL.md +75 -0
- skilldrop_cli/data/packs/api-builder/skills/token-budget-estimator/evals/eval_queries.json +30 -0
- skilldrop_cli/data/packs/api-builder/skills/token-budget-estimator/evals/evals.json +18 -0
- skilldrop_cli/data/packs/api-builder/skills/token-budget-estimator/manifest.json +11 -0
- skilldrop_cli/data/packs/api-builder/skills/tool-use-schema-writer/SKILL.md +111 -0
- skilldrop_cli/data/packs/api-builder/skills/tool-use-schema-writer/evals/eval_queries.json +30 -0
- skilldrop_cli/data/packs/api-builder/skills/tool-use-schema-writer/evals/evals.json +18 -0
- skilldrop_cli/data/packs/api-builder/skills/tool-use-schema-writer/manifest.json +11 -0
- skilldrop_cli/data/packs/converters/pack.json +66 -0
- skilldrop_cli/data/packs/converters/skills/file-to-markdown/SKILL.md +103 -0
- skilldrop_cli/data/packs/converters/skills/file-to-markdown/evals/eval_queries.json +34 -0
- skilldrop_cli/data/packs/converters/skills/file-to-markdown/evals/evals.json +27 -0
- skilldrop_cli/data/packs/converters/skills/file-to-markdown/evals/files/1/northwind-q3.pptx +0 -0
- skilldrop_cli/data/packs/converters/skills/file-to-markdown/evals/files/2/acme-contract-summary.pdf +93 -0
- skilldrop_cli/data/packs/converters/skills/file-to-markdown/examples/inputs/accounts.json +1 -0
- skilldrop_cli/data/packs/converters/skills/file-to-markdown/examples/inputs/fabrikam-status.html +14 -0
- skilldrop_cli/data/packs/converters/skills/file-to-markdown/examples/mixed-inputs.md +306 -0
- skilldrop_cli/data/packs/converters/skills/file-to-markdown/manifest.json +49 -0
- skilldrop_cli/data/packs/converters/skills/file-to-markdown/scripts/to_markdown.py +1299 -0
- skilldrop_cli/data/packs/converters/skills/md-to-docx/SKILL.md +124 -0
- skilldrop_cli/data/packs/converters/skills/md-to-docx/evals/eval_queries.json +10 -0
- skilldrop_cli/data/packs/converters/skills/md-to-docx/evals/evals.json +29 -0
- skilldrop_cli/data/packs/converters/skills/md-to-docx/evals/files/1/brand/brand.json +80 -0
- skilldrop_cli/data/packs/converters/skills/md-to-docx/evals/files/1/brand/logo-white.png +0 -0
- skilldrop_cli/data/packs/converters/skills/md-to-docx/evals/files/1/brand/logo.png +0 -0
- skilldrop_cli/data/packs/converters/skills/md-to-docx/evals/files/1/brand/mark.png +0 -0
- skilldrop_cli/data/packs/converters/skills/md-to-docx/evals/files/1/docs/vendor-onboarding.md +42 -0
- skilldrop_cli/data/packs/converters/skills/md-to-docx/examples/q3-support-review-input.md +54 -0
- skilldrop_cli/data/packs/converters/skills/md-to-docx/examples/q3-support-review.md +80 -0
- skilldrop_cli/data/packs/converters/skills/md-to-docx/examples/tickets-by-region.png +0 -0
- skilldrop_cli/data/packs/converters/skills/md-to-docx/manifest.json +52 -0
- skilldrop_cli/data/packs/converters/skills/md-to-docx/reference.md +89 -0
- skilldrop_cli/data/packs/converters/skills/md-to-docx/scripts/md_to_docx.py +1114 -0
- skilldrop_cli/data/packs/converters/skills/md-to-html/SKILL.md +100 -0
- skilldrop_cli/data/packs/converters/skills/md-to-html/evals/eval_queries.json +34 -0
- skilldrop_cli/data/packs/converters/skills/md-to-html/evals/evals.json +18 -0
- skilldrop_cli/data/packs/converters/skills/md-to-html/evals/files/1/brand/brand.json +80 -0
- skilldrop_cli/data/packs/converters/skills/md-to-html/evals/files/1/brand/logo-white.png +0 -0
- skilldrop_cli/data/packs/converters/skills/md-to-html/evals/files/1/brand/logo.png +0 -0
- skilldrop_cli/data/packs/converters/skills/md-to-html/evals/files/1/brand/mark.png +0 -0
- skilldrop_cli/data/packs/converters/skills/md-to-html/evals/files/1/docs/q3-review.md +35 -0
- skilldrop_cli/data/packs/converters/skills/md-to-html/evals/files/1/img/dashboard.png +0 -0
- skilldrop_cli/data/packs/converters/skills/md-to-html/examples/inputs/chart.svg +8 -0
- skilldrop_cli/data/packs/converters/skills/md-to-html/examples/inputs/ops-review.md +48 -0
- skilldrop_cli/data/packs/converters/skills/md-to-html/examples/ops-review.md +83 -0
- skilldrop_cli/data/packs/converters/skills/md-to-html/manifest.json +43 -0
- skilldrop_cli/data/packs/converters/skills/md-to-html/reference.md +70 -0
- skilldrop_cli/data/packs/converters/skills/md-to-html/scripts/md_to_html.py +652 -0
- skilldrop_cli/data/packs/converters/skills/md-to-xlsx/SKILL.md +120 -0
- skilldrop_cli/data/packs/converters/skills/md-to-xlsx/evals/eval_queries.json +9 -0
- skilldrop_cli/data/packs/converters/skills/md-to-xlsx/evals/evals.json +28 -0
- skilldrop_cli/data/packs/converters/skills/md-to-xlsx/evals/files/1/exports/northwind-invoices.csv +25 -0
- skilldrop_cli/data/packs/converters/skills/md-to-xlsx/evals/files/2/reports/q3-ops-review.md +32 -0
- skilldrop_cli/data/packs/converters/skills/md-to-xlsx/examples/vendor-review-input.md +28 -0
- skilldrop_cli/data/packs/converters/skills/md-to-xlsx/examples/vendor-review.md +94 -0
- skilldrop_cli/data/packs/converters/skills/md-to-xlsx/manifest.json +33 -0
- skilldrop_cli/data/packs/converters/skills/md-to-xlsx/reference.md +94 -0
- skilldrop_cli/data/packs/converters/skills/md-to-xlsx/scripts/md_to_xlsx.py +667 -0
- skilldrop_cli/data/packs/converters/skills/mermaid-render/SKILL.md +109 -0
- skilldrop_cli/data/packs/converters/skills/mermaid-render/evals/eval_queries.json +30 -0
- skilldrop_cli/data/packs/converters/skills/mermaid-render/evals/evals.json +17 -0
- skilldrop_cli/data/packs/converters/skills/mermaid-render/evals/files/1/docs/contoso-orders.md +23 -0
- skilldrop_cli/data/packs/converters/skills/mermaid-render/examples/broken-diagrams.md +147 -0
- skilldrop_cli/data/packs/converters/skills/mermaid-render/examples/inputs/broken-flowchart.mmd +8 -0
- skilldrop_cli/data/packs/converters/skills/mermaid-render/examples/inputs/broken-sequence.mmd +11 -0
- skilldrop_cli/data/packs/converters/skills/mermaid-render/examples/inputs/checkout-flow.mmd +9 -0
- skilldrop_cli/data/packs/converters/skills/mermaid-render/examples/inputs/design-notes.md +35 -0
- skilldrop_cli/data/packs/converters/skills/mermaid-render/examples/inputs/fixed-flowchart.mmd +9 -0
- skilldrop_cli/data/packs/converters/skills/mermaid-render/examples/inputs/typo.mmd +2 -0
- skilldrop_cli/data/packs/converters/skills/mermaid-render/manifest.json +40 -0
- skilldrop_cli/data/packs/converters/skills/mermaid-render/reference.md +70 -0
- skilldrop_cli/data/packs/converters/skills/mermaid-render/scripts/render_mermaid.py +486 -0
- skilldrop_cli/data/packs/core/loops/ship-a-draft/LOOP.md +77 -0
- skilldrop_cli/data/packs/core/loops/ship-a-draft/evals/eval_queries.json +30 -0
- skilldrop_cli/data/packs/core/loops/ship-a-draft/evals/evals.json +25 -0
- skilldrop_cli/data/packs/core/loops/ship-a-draft/loop.json +41 -0
- skilldrop_cli/data/packs/core/pack.json +50 -0
- skilldrop_cli/data/packs/core/skills/brief-intake/SKILL.md +79 -0
- skilldrop_cli/data/packs/core/skills/brief-intake/evals/eval_queries.json +34 -0
- skilldrop_cli/data/packs/core/skills/brief-intake/evals/evals.json +16 -0
- skilldrop_cli/data/packs/core/skills/brief-intake/manifest.json +11 -0
- skilldrop_cli/data/packs/core/skills/brief-intake/templates/brief-adr.md +41 -0
- skilldrop_cli/data/packs/core/skills/brief-intake/templates/brief-decision-log.md +35 -0
- skilldrop_cli/data/packs/core/skills/brief-intake/templates/brief-deck.md +43 -0
- skilldrop_cli/data/packs/core/skills/brief-intake/templates/brief-design-doc.md +48 -0
- skilldrop_cli/data/packs/core/skills/brief-intake/templates/brief-exec-summary.md +47 -0
- skilldrop_cli/data/packs/core/skills/brief-intake/templates/brief-generic.md +45 -0
- skilldrop_cli/data/packs/core/skills/brief-intake/templates/brief-runbook.md +44 -0
- skilldrop_cli/data/packs/core/skills/brief-intake/templates/brief-tech-comparison.md +38 -0
- skilldrop_cli/data/packs/core/skills/council-review/SKILL.md +106 -0
- skilldrop_cli/data/packs/core/skills/council-review/evals/eval_queries.json +38 -0
- skilldrop_cli/data/packs/core/skills/council-review/evals/evals.json +18 -0
- skilldrop_cli/data/packs/core/skills/council-review/examples/redis-cache-decision.md +75 -0
- skilldrop_cli/data/packs/core/skills/council-review/manifest.json +11 -0
- skilldrop_cli/data/packs/core/skills/council-review/reference.md +76 -0
- skilldrop_cli/data/packs/core/skills/council-review/seats/architect.md +29 -0
- skilldrop_cli/data/packs/core/skills/council-review/seats/bench.md +39 -0
- skilldrop_cli/data/packs/core/skills/council-review/seats/operator.md +27 -0
- skilldrop_cli/data/packs/core/skills/council-review/seats/pragmatist.md +29 -0
- skilldrop_cli/data/packs/core/skills/council-review/seats/security-engineer.md +29 -0
- skilldrop_cli/data/packs/core/skills/council-review/seats/user-advocate.md +27 -0
- skilldrop_cli/data/packs/core/skills/council-review/templates/council-review.md +59 -0
- skilldrop_cli/data/packs/core/skills/doc-critique/SKILL.md +119 -0
- skilldrop_cli/data/packs/core/skills/doc-critique/evals/eval_queries.json +38 -0
- skilldrop_cli/data/packs/core/skills/doc-critique/evals/evals.json +17 -0
- skilldrop_cli/data/packs/core/skills/doc-critique/examples/design-doc-critique.md +34 -0
- skilldrop_cli/data/packs/core/skills/doc-critique/manifest.json +11 -0
- skilldrop_cli/data/packs/core/skills/doc-critique/rubrics/adr.md +36 -0
- skilldrop_cli/data/packs/core/skills/doc-critique/rubrics/decision-log.md +39 -0
- skilldrop_cli/data/packs/core/skills/doc-critique/rubrics/deck.md +45 -0
- skilldrop_cli/data/packs/core/skills/doc-critique/rubrics/design-doc.md +42 -0
- skilldrop_cli/data/packs/core/skills/doc-critique/rubrics/exec-summary.md +41 -0
- skilldrop_cli/data/packs/core/skills/doc-critique/rubrics/generic.md +40 -0
- skilldrop_cli/data/packs/core/skills/doc-critique/rubrics/runbook.md +42 -0
- skilldrop_cli/data/packs/core/skills/doc-critique/rubrics/tech-comparison.md +40 -0
- skilldrop_cli/data/packs/core/skills/doc-critique/templates/critique.md +49 -0
- skilldrop_cli/data/packs/core/skills/output-hygiene/SKILL.md +97 -0
- skilldrop_cli/data/packs/core/skills/output-hygiene/evals/eval_queries.json +11 -0
- skilldrop_cli/data/packs/core/skills/output-hygiene/evals/evals.json +30 -0
- skilldrop_cli/data/packs/core/skills/output-hygiene/manifest.json +34 -0
- skilldrop_cli/data/packs/core/skills/output-hygiene/reference.md +106 -0
- skilldrop_cli/data/packs/core/skills/output-hygiene/scripts/scrub.py +315 -0
- skilldrop_cli/data/packs/data-analytics/pack.json +57 -0
- skilldrop_cli/data/packs/data-analytics/skills/dashboard-spec/SKILL.md +83 -0
- skilldrop_cli/data/packs/data-analytics/skills/dashboard-spec/evals/eval_queries.json +34 -0
- skilldrop_cli/data/packs/data-analytics/skills/dashboard-spec/evals/evals.json +19 -0
- skilldrop_cli/data/packs/data-analytics/skills/dashboard-spec/examples/support-staffing.md +84 -0
- skilldrop_cli/data/packs/data-analytics/skills/dashboard-spec/manifest.json +40 -0
- skilldrop_cli/data/packs/data-analytics/skills/dashboard-spec/reference.md +45 -0
- skilldrop_cli/data/packs/data-analytics/skills/dashboard-spec/templates/dashboard-spec.md +55 -0
- skilldrop_cli/data/packs/data-analytics/skills/metric-definition/SKILL.md +84 -0
- skilldrop_cli/data/packs/data-analytics/skills/metric-definition/evals/eval_queries.json +34 -0
- skilldrop_cli/data/packs/data-analytics/skills/metric-definition/evals/evals.json +19 -0
- skilldrop_cli/data/packs/data-analytics/skills/metric-definition/examples/refund-rate.md +104 -0
- skilldrop_cli/data/packs/data-analytics/skills/metric-definition/manifest.json +41 -0
- skilldrop_cli/data/packs/data-analytics/skills/metric-definition/reference.md +67 -0
- skilldrop_cli/data/packs/data-analytics/skills/metric-definition/templates/metric-spec.md +70 -0
- skilldrop_cli/data/packs/data-analytics/skills/sql-review/SKILL.md +83 -0
- skilldrop_cli/data/packs/data-analytics/skills/sql-review/evals/eval_queries.json +34 -0
- skilldrop_cli/data/packs/data-analytics/skills/sql-review/evals/evals.json +19 -0
- skilldrop_cli/data/packs/data-analytics/skills/sql-review/examples/revenue-by-region.md +174 -0
- skilldrop_cli/data/packs/data-analytics/skills/sql-review/manifest.json +42 -0
- skilldrop_cli/data/packs/data-analytics/skills/sql-review/reference.md +84 -0
- skilldrop_cli/data/packs/data-analytics/skills/sql-review/scripts/sql_lint.py +461 -0
- skilldrop_cli/data/packs/design/pack.json +60 -0
- skilldrop_cli/data/packs/design/skills/brand-kit/SKILL.md +105 -0
- skilldrop_cli/data/packs/design/skills/brand-kit/evals/eval_queries.json +30 -0
- skilldrop_cli/data/packs/design/skills/brand-kit/evals/evals.json +18 -0
- skilldrop_cli/data/packs/design/skills/brand-kit/evals/files/1/brand/northwind-logo-white.png +0 -0
- skilldrop_cli/data/packs/design/skills/brand-kit/evals/files/1/brand/northwind-logo.png +0 -0
- skilldrop_cli/data/packs/design/skills/brand-kit/examples/northwind-brand.md +48 -0
- skilldrop_cli/data/packs/design/skills/brand-kit/manifest.json +45 -0
- skilldrop_cli/data/packs/design/skills/brand-kit/scripts/check_brand.py +114 -0
- skilldrop_cli/data/packs/design/skills/brand-kit/templates/brand.json +54 -0
- skilldrop_cli/data/packs/design/skills/deck-builder/SKILL.md +201 -0
- skilldrop_cli/data/packs/design/skills/deck-builder/evals/eval_queries.json +14 -0
- skilldrop_cli/data/packs/design/skills/deck-builder/evals/evals.json +40 -0
- skilldrop_cli/data/packs/design/skills/deck-builder/examples/exec-board-update.md +108 -0
- skilldrop_cli/data/packs/design/skills/deck-builder/examples/templated-brand-deck.md +148 -0
- skilldrop_cli/data/packs/design/skills/deck-builder/manifest.json +18 -0
- skilldrop_cli/data/packs/design/skills/deck-builder/reference.md +180 -0
- skilldrop_cli/data/packs/design/skills/deck-builder/requirements.txt +3 -0
- skilldrop_cli/data/packs/design/skills/deck-builder/scripts/build_deck.py +1134 -0
- skilldrop_cli/data/packs/design/skills/deck-builder/templates/deck-spec.json +168 -0
- skilldrop_cli/data/packs/design/skills/deck-builder/templates/palettes.json +59 -0
- skilldrop_cli/data/packs/design/skills/marketing-flyer/SKILL.md +113 -0
- skilldrop_cli/data/packs/design/skills/marketing-flyer/evals/eval_queries.json +30 -0
- skilldrop_cli/data/packs/design/skills/marketing-flyer/evals/evals.json +18 -0
- skilldrop_cli/data/packs/design/skills/marketing-flyer/evals/files/1/brand/brand.json +80 -0
- skilldrop_cli/data/packs/design/skills/marketing-flyer/evals/files/1/brand/logo-white.png +0 -0
- skilldrop_cli/data/packs/design/skills/marketing-flyer/evals/files/1/brand/logo.png +0 -0
- skilldrop_cli/data/packs/design/skills/marketing-flyer/evals/files/1/brand/mark.png +0 -0
- skilldrop_cli/data/packs/design/skills/marketing-flyer/examples/community-health-fair.md +68 -0
- skilldrop_cli/data/packs/design/skills/marketing-flyer/manifest.json +40 -0
- skilldrop_cli/data/packs/design/skills/marketing-flyer/reference.md +54 -0
- skilldrop_cli/data/packs/design/skills/marketing-flyer/scripts/build_flyer.py +323 -0
- skilldrop_cli/data/packs/design/skills/marketing-flyer/templates/flyer-spec.json +26 -0
- skilldrop_cli/data/packs/design/skills/slide-outliner/SKILL.md +58 -0
- skilldrop_cli/data/packs/design/skills/slide-outliner/evals/eval_queries.json +34 -0
- skilldrop_cli/data/packs/design/skills/slide-outliner/evals/evals.json +16 -0
- skilldrop_cli/data/packs/design/skills/slide-outliner/manifest.json +11 -0
- skilldrop_cli/data/packs/design/skills/slide-outliner/templates/deck-outline.md +131 -0
- skilldrop_cli/data/packs/dev-team/loops/build/LOOP.md +80 -0
- skilldrop_cli/data/packs/dev-team/loops/build/evals/eval_queries.json +30 -0
- skilldrop_cli/data/packs/dev-team/loops/build/evals/evals.json +27 -0
- skilldrop_cli/data/packs/dev-team/loops/build/loop.json +48 -0
- skilldrop_cli/data/packs/dev-team/loops/release/LOOP.md +91 -0
- skilldrop_cli/data/packs/dev-team/loops/release/loop.json +47 -0
- skilldrop_cli/data/packs/dev-team/pack.json +56 -0
- skilldrop_cli/data/packs/dev-team/skills/accessibility-audit/SKILL.md +74 -0
- skilldrop_cli/data/packs/dev-team/skills/accessibility-audit/evals/eval_queries.json +30 -0
- skilldrop_cli/data/packs/dev-team/skills/accessibility-audit/evals/evals.json +19 -0
- skilldrop_cli/data/packs/dev-team/skills/accessibility-audit/examples/login-form.md +76 -0
- skilldrop_cli/data/packs/dev-team/skills/accessibility-audit/manifest.json +11 -0
- skilldrop_cli/data/packs/dev-team/skills/accessibility-audit/reference.md +69 -0
- skilldrop_cli/data/packs/dev-team/skills/agents-md-generator/SKILL.md +82 -0
- skilldrop_cli/data/packs/dev-team/skills/agents-md-generator/evals/eval_queries.json +15 -0
- skilldrop_cli/data/packs/dev-team/skills/agents-md-generator/evals/evals.json +50 -0
- skilldrop_cli/data/packs/dev-team/skills/agents-md-generator/evals/files/1/Makefile +5 -0
- skilldrop_cli/data/packs/dev-team/skills/agents-md-generator/evals/files/1/alembic/versions/0001_create_bookings.py +10 -0
- skilldrop_cli/data/packs/dev-team/skills/agents-md-generator/evals/files/1/booking/__init__.py +0 -0
- skilldrop_cli/data/packs/dev-team/skills/agents-md-generator/evals/files/1/booking/app.py +8 -0
- skilldrop_cli/data/packs/dev-team/skills/agents-md-generator/evals/files/1/pyproject.toml +11 -0
- skilldrop_cli/data/packs/dev-team/skills/agents-md-generator/evals/files/1/tests/test_health.py +5 -0
- skilldrop_cli/data/packs/dev-team/skills/agents-md-generator/evals/files/2/AGENTS.md +3 -0
- skilldrop_cli/data/packs/dev-team/skills/agents-md-generator/evals/files/2/package.json +12 -0
- skilldrop_cli/data/packs/dev-team/skills/agents-md-generator/evals/files/2/src/index.ts +1 -0
- skilldrop_cli/data/packs/dev-team/skills/agents-md-generator/manifest.json +11 -0
- skilldrop_cli/data/packs/dev-team/skills/agents-md-generator/reference.md +89 -0
- skilldrop_cli/data/packs/dev-team/skills/agents-md-generator/templates/agents-md.md +48 -0
- skilldrop_cli/data/packs/dev-team/skills/bug-triage/SKILL.md +67 -0
- skilldrop_cli/data/packs/dev-team/skills/bug-triage/evals/eval_queries.json +34 -0
- skilldrop_cli/data/packs/dev-team/skills/bug-triage/evals/evals.json +17 -0
- skilldrop_cli/data/packs/dev-team/skills/bug-triage/manifest.json +14 -0
- skilldrop_cli/data/packs/dev-team/skills/bug-triage/templates/bug-ticket.md +86 -0
- skilldrop_cli/data/packs/dev-team/skills/contribution-wizard/SKILL.md +134 -0
- skilldrop_cli/data/packs/dev-team/skills/contribution-wizard/evals/eval_queries.json +30 -0
- skilldrop_cli/data/packs/dev-team/skills/contribution-wizard/evals/evals.json +19 -0
- skilldrop_cli/data/packs/dev-team/skills/contribution-wizard/manifest.json +11 -0
- skilldrop_cli/data/packs/dev-team/skills/devils-advocate/SKILL.md +143 -0
- skilldrop_cli/data/packs/dev-team/skills/devils-advocate/evals/eval_queries.json +38 -0
- skilldrop_cli/data/packs/dev-team/skills/devils-advocate/evals/evals.json +19 -0
- skilldrop_cli/data/packs/dev-team/skills/devils-advocate/examples/rate-limiter-review.md +46 -0
- skilldrop_cli/data/packs/dev-team/skills/devils-advocate/lenses/adversarial-review.md +88 -0
- skilldrop_cli/data/packs/dev-team/skills/devils-advocate/lenses/edge-cases.md +95 -0
- skilldrop_cli/data/packs/dev-team/skills/devils-advocate/lenses/future-proofing.md +75 -0
- skilldrop_cli/data/packs/dev-team/skills/devils-advocate/lenses/test-coverage.md +90 -0
- skilldrop_cli/data/packs/dev-team/skills/devils-advocate/manifest.json +14 -0
- skilldrop_cli/data/packs/dev-team/skills/devils-advocate/templates/challenge.md +118 -0
- skilldrop_cli/data/packs/dev-team/skills/feature-implement-loop/SKILL.md +88 -0
- skilldrop_cli/data/packs/dev-team/skills/feature-implement-loop/evals/eval_queries.json +30 -0
- skilldrop_cli/data/packs/dev-team/skills/feature-implement-loop/evals/evals.json +19 -0
- skilldrop_cli/data/packs/dev-team/skills/feature-implement-loop/manifest.json +14 -0
- skilldrop_cli/data/packs/dev-team/skills/launch-readiness/SKILL.md +95 -0
- skilldrop_cli/data/packs/dev-team/skills/launch-readiness/evals/eval_queries.json +10 -0
- skilldrop_cli/data/packs/dev-team/skills/launch-readiness/evals/evals.json +19 -0
- skilldrop_cli/data/packs/dev-team/skills/launch-readiness/examples/schema-change-revise.md +54 -0
- skilldrop_cli/data/packs/dev-team/skills/launch-readiness/manifest.json +24 -0
- skilldrop_cli/data/packs/dev-team/skills/launch-readiness/templates/readiness-report.md +41 -0
- skilldrop_cli/data/packs/dev-team/skills/migration-plan/SKILL.md +68 -0
- skilldrop_cli/data/packs/dev-team/skills/migration-plan/evals/eval_queries.json +34 -0
- skilldrop_cli/data/packs/dev-team/skills/migration-plan/evals/evals.json +16 -0
- skilldrop_cli/data/packs/dev-team/skills/migration-plan/manifest.json +11 -0
- skilldrop_cli/data/packs/dev-team/skills/migration-plan/templates/migration-plan.md +68 -0
- skilldrop_cli/data/packs/dev-team/skills/pre-merge-review/SKILL.md +85 -0
- skilldrop_cli/data/packs/dev-team/skills/pre-merge-review/evals/eval_queries.json +11 -0
- skilldrop_cli/data/packs/dev-team/skills/pre-merge-review/evals/evals.json +26 -0
- skilldrop_cli/data/packs/dev-team/skills/pre-merge-review/examples/pre-merge-verdict.md +43 -0
- skilldrop_cli/data/packs/dev-team/skills/pre-merge-review/manifest.json +15 -0
- skilldrop_cli/data/packs/dev-team/skills/pre-merge-review/scripts/gate.py +189 -0
- skilldrop_cli/data/packs/dev-team/skills/release-notes/SKILL.md +61 -0
- skilldrop_cli/data/packs/dev-team/skills/release-notes/evals/eval_queries.json +30 -0
- skilldrop_cli/data/packs/dev-team/skills/release-notes/evals/evals.json +19 -0
- skilldrop_cli/data/packs/dev-team/skills/release-notes/manifest.json +11 -0
- skilldrop_cli/data/packs/dev-team/skills/release-notes/templates/release-notes.md +83 -0
- skilldrop_cli/data/packs/dev-team/skills/sonar-onboard/SKILL.md +170 -0
- skilldrop_cli/data/packs/dev-team/skills/sonar-onboard/evals/eval_queries.json +34 -0
- skilldrop_cli/data/packs/dev-team/skills/sonar-onboard/evals/evals.json +16 -0
- skilldrop_cli/data/packs/dev-team/skills/sonar-onboard/manifest.json +11 -0
- skilldrop_cli/data/packs/dev-team/skills/sonar-onboard/reference.md +151 -0
- skilldrop_cli/data/packs/dev-team/skills/sonar-onboard/templates/github-actions-sonar-server.yml.tmpl +50 -0
- skilldrop_cli/data/packs/dev-team/skills/sonar-onboard/templates/github-actions-sonarcloud.yml.tmpl +53 -0
- skilldrop_cli/data/packs/dev-team/skills/sonar-onboard/templates/readme-snippet.md +38 -0
- skilldrop_cli/data/packs/dev-team/skills/sonar-onboard/templates/sonar-project.properties.tmpl +31 -0
- skilldrop_cli/data/packs/dev-team/skills/sonar-review/SKILL.md +220 -0
- skilldrop_cli/data/packs/dev-team/skills/sonar-review/evals/eval_queries.json +34 -0
- skilldrop_cli/data/packs/dev-team/skills/sonar-review/evals/evals.json +16 -0
- skilldrop_cli/data/packs/dev-team/skills/sonar-review/lenses/bugs.md +48 -0
- skilldrop_cli/data/packs/dev-team/skills/sonar-review/lenses/code-smells.md +64 -0
- skilldrop_cli/data/packs/dev-team/skills/sonar-review/lenses/coverage-and-duplication.md +74 -0
- skilldrop_cli/data/packs/dev-team/skills/sonar-review/lenses/security-hotspots.md +56 -0
- skilldrop_cli/data/packs/dev-team/skills/sonar-review/lenses/vulnerabilities.md +52 -0
- skilldrop_cli/data/packs/dev-team/skills/sonar-review/manifest.json +37 -0
- skilldrop_cli/data/packs/dev-team/skills/sonar-review/reference.md +167 -0
- skilldrop_cli/data/packs/dev-team/skills/sonar-review/templates/report.md +84 -0
- skilldrop_cli/data/packs/dev-team/skills/test-plan-generator/SKILL.md +59 -0
- skilldrop_cli/data/packs/dev-team/skills/test-plan-generator/evals/eval_queries.json +34 -0
- skilldrop_cli/data/packs/dev-team/skills/test-plan-generator/evals/evals.json +16 -0
- skilldrop_cli/data/packs/dev-team/skills/test-plan-generator/manifest.json +11 -0
- skilldrop_cli/data/packs/dev-team/skills/test-plan-generator/templates/test-plan.md +83 -0
- skilldrop_cli/data/packs/dev-team/skills/user-story-splitter/SKILL.md +88 -0
- skilldrop_cli/data/packs/dev-team/skills/user-story-splitter/evals/eval_queries.json +34 -0
- skilldrop_cli/data/packs/dev-team/skills/user-story-splitter/evals/evals.json +16 -0
- skilldrop_cli/data/packs/dev-team/skills/user-story-splitter/examples/notifications-epic.md +123 -0
- skilldrop_cli/data/packs/dev-team/skills/user-story-splitter/manifest.json +14 -0
- skilldrop_cli/data/packs/dev-team/skills/user-story-splitter/templates/story.md +40 -0
- skilldrop_cli/data/packs/experience-design/pack.json +68 -0
- skilldrop_cli/data/packs/experience-design/skills/content-design/SKILL.md +77 -0
- skilldrop_cli/data/packs/experience-design/skills/content-design/evals/eval_queries.json +34 -0
- skilldrop_cli/data/packs/experience-design/skills/content-design/evals/evals.json +18 -0
- skilldrop_cli/data/packs/experience-design/skills/content-design/examples/parking-permit-page.md +102 -0
- skilldrop_cli/data/packs/experience-design/skills/content-design/manifest.json +41 -0
- skilldrop_cli/data/packs/experience-design/skills/content-design/scripts/readability.py +115 -0
- skilldrop_cli/data/packs/experience-design/skills/content-design/templates/content-spec.md +45 -0
- skilldrop_cli/data/packs/experience-design/skills/design-system-spec/SKILL.md +79 -0
- skilldrop_cli/data/packs/experience-design/skills/design-system-spec/evals/eval_queries.json +34 -0
- skilldrop_cli/data/packs/experience-design/skills/design-system-spec/evals/evals.json +19 -0
- skilldrop_cli/data/packs/experience-design/skills/design-system-spec/examples/text-field-spec.md +121 -0
- skilldrop_cli/data/packs/experience-design/skills/design-system-spec/manifest.json +46 -0
- skilldrop_cli/data/packs/experience-design/skills/design-system-spec/reference.md +66 -0
- skilldrop_cli/data/packs/experience-design/skills/design-system-spec/templates/component-spec.md +74 -0
- skilldrop_cli/data/packs/experience-design/skills/information-architecture/SKILL.md +76 -0
- skilldrop_cli/data/packs/experience-design/skills/information-architecture/evals/eval_queries.json +34 -0
- skilldrop_cli/data/packs/experience-design/skills/information-architecture/evals/evals.json +19 -0
- skilldrop_cli/data/packs/experience-design/skills/information-architecture/examples/payroll-app-nav.md +127 -0
- skilldrop_cli/data/packs/experience-design/skills/information-architecture/manifest.json +46 -0
- skilldrop_cli/data/packs/experience-design/skills/information-architecture/reference.md +54 -0
- skilldrop_cli/data/packs/experience-design/skills/information-architecture/templates/ia-spec.md +77 -0
- skilldrop_cli/data/packs/experience-design/skills/service-blueprint/SKILL.md +76 -0
- skilldrop_cli/data/packs/experience-design/skills/service-blueprint/evals/eval_queries.json +30 -0
- skilldrop_cli/data/packs/experience-design/skills/service-blueprint/evals/evals.json +19 -0
- skilldrop_cli/data/packs/experience-design/skills/service-blueprint/examples/insurance-claim-blueprint.md +107 -0
- skilldrop_cli/data/packs/experience-design/skills/service-blueprint/manifest.json +45 -0
- skilldrop_cli/data/packs/experience-design/skills/service-blueprint/templates/blueprint.md +72 -0
- skilldrop_cli/data/packs/experience-design/skills/ux-writing/SKILL.md +77 -0
- skilldrop_cli/data/packs/experience-design/skills/ux-writing/evals/eval_queries.json +34 -0
- skilldrop_cli/data/packs/experience-design/skills/ux-writing/evals/evals.json +19 -0
- skilldrop_cli/data/packs/experience-design/skills/ux-writing/examples/team-invite-flow.md +66 -0
- skilldrop_cli/data/packs/experience-design/skills/ux-writing/manifest.json +41 -0
- skilldrop_cli/data/packs/experience-design/skills/ux-writing/templates/string-table.md +22 -0
- skilldrop_cli/data/packs/grc/pack.json +57 -0
- skilldrop_cli/data/packs/grc/skills/dpia/SKILL.md +123 -0
- skilldrop_cli/data/packs/grc/skills/dpia/evals/eval_queries.json +34 -0
- skilldrop_cli/data/packs/grc/skills/dpia/evals/evals.json +19 -0
- skilldrop_cli/data/packs/grc/skills/dpia/examples/acme-warehouse-performance.md +159 -0
- skilldrop_cli/data/packs/grc/skills/dpia/manifest.json +47 -0
- skilldrop_cli/data/packs/grc/skills/dpia/reference.md +118 -0
- skilldrop_cli/data/packs/grc/skills/dpia/templates/dpia.md +103 -0
- skilldrop_cli/data/packs/grc/skills/risk-register/SKILL.md +139 -0
- skilldrop_cli/data/packs/grc/skills/risk-register/evals/eval_queries.json +34 -0
- skilldrop_cli/data/packs/grc/skills/risk-register/evals/evals.json +19 -0
- skilldrop_cli/data/packs/grc/skills/risk-register/examples/northwind-register.csv +9 -0
- skilldrop_cli/data/packs/grc/skills/risk-register/examples/northwind-register.md +127 -0
- skilldrop_cli/data/packs/grc/skills/risk-register/manifest.json +43 -0
- skilldrop_cli/data/packs/grc/skills/risk-register/reference.md +99 -0
- skilldrop_cli/data/packs/grc/skills/risk-register/scripts/check_register.py +338 -0
- skilldrop_cli/data/packs/grc/skills/risk-register/templates/risk-register.csv +2 -0
- skilldrop_cli/data/packs/grc/skills/soc2-evidence-map/SKILL.md +117 -0
- skilldrop_cli/data/packs/grc/skills/soc2-evidence-map/evals/eval_queries.json +34 -0
- skilldrop_cli/data/packs/grc/skills/soc2-evidence-map/evals/evals.json +19 -0
- skilldrop_cli/data/packs/grc/skills/soc2-evidence-map/examples/northwind-evidence-map.csv +17 -0
- skilldrop_cli/data/packs/grc/skills/soc2-evidence-map/examples/northwind-type2-security.md +127 -0
- skilldrop_cli/data/packs/grc/skills/soc2-evidence-map/manifest.json +41 -0
- skilldrop_cli/data/packs/grc/skills/soc2-evidence-map/reference.md +156 -0
- skilldrop_cli/data/packs/grc/skills/soc2-evidence-map/templates/evidence-map.csv +2 -0
- skilldrop_cli/data/packs/infra-as-code/pack.json +51 -0
- skilldrop_cli/data/packs/infra-as-code/skills/terraform-module/SKILL.md +85 -0
- skilldrop_cli/data/packs/infra-as-code/skills/terraform-module/evals/eval_queries.json +34 -0
- skilldrop_cli/data/packs/infra-as-code/skills/terraform-module/evals/evals.json +31 -0
- skilldrop_cli/data/packs/infra-as-code/skills/terraform-module/examples/s3-log-bucket/README.md +71 -0
- skilldrop_cli/data/packs/infra-as-code/skills/terraform-module/examples/s3-log-bucket/examples/basic/main.tf +30 -0
- skilldrop_cli/data/packs/infra-as-code/skills/terraform-module/examples/s3-log-bucket/main.tf +153 -0
- skilldrop_cli/data/packs/infra-as-code/skills/terraform-module/examples/s3-log-bucket/outputs.tf +14 -0
- skilldrop_cli/data/packs/infra-as-code/skills/terraform-module/examples/s3-log-bucket/variables.tf +71 -0
- skilldrop_cli/data/packs/infra-as-code/skills/terraform-module/examples/s3-log-bucket/versions.tf +10 -0
- skilldrop_cli/data/packs/infra-as-code/skills/terraform-module/examples/s3-log-bucket.md +50 -0
- skilldrop_cli/data/packs/infra-as-code/skills/terraform-module/manifest.json +41 -0
- skilldrop_cli/data/packs/infra-as-code/skills/terraform-module/reference.md +105 -0
- skilldrop_cli/data/packs/infra-as-code/skills/terraform-module/templates/module-readme.md +55 -0
- skilldrop_cli/data/packs/infra-as-code/skills/terraform-plan-review/SKILL.md +87 -0
- skilldrop_cli/data/packs/infra-as-code/skills/terraform-plan-review/evals/eval_queries.json +34 -0
- skilldrop_cli/data/packs/infra-as-code/skills/terraform-plan-review/evals/evals.json +30 -0
- skilldrop_cli/data/packs/infra-as-code/skills/terraform-plan-review/evals/files/2/plan.json +133 -0
- skilldrop_cli/data/packs/infra-as-code/skills/terraform-plan-review/examples/northwind-orders-plan.json +174 -0
- skilldrop_cli/data/packs/infra-as-code/skills/terraform-plan-review/examples/northwind-orders.md +87 -0
- skilldrop_cli/data/packs/infra-as-code/skills/terraform-plan-review/manifest.json +48 -0
- skilldrop_cli/data/packs/infra-as-code/skills/terraform-plan-review/reference.md +77 -0
- skilldrop_cli/data/packs/infra-as-code/skills/terraform-plan-review/scripts/plan_summary.py +361 -0
- skilldrop_cli/data/packs/product-manager/loops/discover/LOOP.md +81 -0
- skilldrop_cli/data/packs/product-manager/loops/discover/evals/eval_queries.json +30 -0
- skilldrop_cli/data/packs/product-manager/loops/discover/evals/evals.json +26 -0
- skilldrop_cli/data/packs/product-manager/loops/discover/loop.json +41 -0
- skilldrop_cli/data/packs/product-manager/pack.json +61 -0
- skilldrop_cli/data/packs/product-manager/skills/business-case/SKILL.md +64 -0
- skilldrop_cli/data/packs/product-manager/skills/business-case/evals/eval_queries.json +30 -0
- skilldrop_cli/data/packs/product-manager/skills/business-case/evals/evals.json +19 -0
- skilldrop_cli/data/packs/product-manager/skills/business-case/examples/build-vs-buy-flags.md +42 -0
- skilldrop_cli/data/packs/product-manager/skills/business-case/manifest.json +11 -0
- skilldrop_cli/data/packs/product-manager/skills/business-case/templates/business-case.md +60 -0
- skilldrop_cli/data/packs/product-manager/skills/okr-cascade/SKILL.md +59 -0
- skilldrop_cli/data/packs/product-manager/skills/okr-cascade/evals/eval_queries.json +12 -0
- skilldrop_cli/data/packs/product-manager/skills/okr-cascade/evals/evals.json +28 -0
- skilldrop_cli/data/packs/product-manager/skills/okr-cascade/manifest.json +11 -0
- skilldrop_cli/data/packs/product-manager/skills/okr-cascade/templates/okr-cascade.md +46 -0
- skilldrop_cli/data/packs/product-manager/skills/prd-draft/SKILL.md +62 -0
- skilldrop_cli/data/packs/product-manager/skills/prd-draft/evals/eval_queries.json +34 -0
- skilldrop_cli/data/packs/product-manager/skills/prd-draft/evals/evals.json +17 -0
- skilldrop_cli/data/packs/product-manager/skills/prd-draft/manifest.json +16 -0
- skilldrop_cli/data/packs/product-manager/skills/prd-draft/templates/prd.md +66 -0
- skilldrop_cli/data/packs/product-manager/skills/prfaq/SKILL.md +76 -0
- skilldrop_cli/data/packs/product-manager/skills/prfaq/evals/eval_queries.json +13 -0
- skilldrop_cli/data/packs/product-manager/skills/prfaq/evals/evals.json +32 -0
- skilldrop_cli/data/packs/product-manager/skills/prfaq/manifest.json +11 -0
- skilldrop_cli/data/packs/product-manager/skills/prfaq/templates/prfaq.md +60 -0
- skilldrop_cli/data/packs/product-manager/skills/requirements-interview/SKILL.md +61 -0
- skilldrop_cli/data/packs/product-manager/skills/requirements-interview/evals/eval_queries.json +34 -0
- skilldrop_cli/data/packs/product-manager/skills/requirements-interview/evals/evals.json +16 -0
- skilldrop_cli/data/packs/product-manager/skills/requirements-interview/manifest.json +14 -0
- skilldrop_cli/data/packs/product-manager/skills/requirements-interview/templates/interview-kit.md +63 -0
- skilldrop_cli/data/packs/product-manager/skills/strategy-analysis/SKILL.md +63 -0
- skilldrop_cli/data/packs/product-manager/skills/strategy-analysis/evals/eval_queries.json +13 -0
- skilldrop_cli/data/packs/product-manager/skills/strategy-analysis/evals/evals.json +36 -0
- skilldrop_cli/data/packs/product-manager/skills/strategy-analysis/examples/market-entry-tows.md +42 -0
- skilldrop_cli/data/packs/product-manager/skills/strategy-analysis/manifest.json +15 -0
- skilldrop_cli/data/packs/product-manager/skills/success-metrics/SKILL.md +65 -0
- skilldrop_cli/data/packs/product-manager/skills/success-metrics/evals/eval_queries.json +11 -0
- skilldrop_cli/data/packs/product-manager/skills/success-metrics/evals/evals.json +27 -0
- skilldrop_cli/data/packs/product-manager/skills/success-metrics/manifest.json +11 -0
- skilldrop_cli/data/packs/product-manager/skills/success-metrics/templates/metrics-plan.md +55 -0
- skilldrop_cli/data/packs/product-manager/skills/user-journey-map/SKILL.md +65 -0
- skilldrop_cli/data/packs/product-manager/skills/user-journey-map/evals/eval_queries.json +12 -0
- skilldrop_cli/data/packs/product-manager/skills/user-journey-map/evals/evals.json +30 -0
- skilldrop_cli/data/packs/product-manager/skills/user-journey-map/examples/b2b-saas-onboarding.md +69 -0
- skilldrop_cli/data/packs/product-manager/skills/user-journey-map/manifest.json +15 -0
- skilldrop_cli/data/packs/product-manager/skills/user-journey-map/templates/journey-map.md +47 -0
- skilldrop_cli/data/packs/research/pack.json +52 -0
- skilldrop_cli/data/packs/research/skills/hypothesis-comparison/SKILL.md +131 -0
- skilldrop_cli/data/packs/research/skills/hypothesis-comparison/evals/eval_queries.json +34 -0
- skilldrop_cli/data/packs/research/skills/hypothesis-comparison/evals/evals.json +19 -0
- skilldrop_cli/data/packs/research/skills/hypothesis-comparison/examples/conversion-drop.md +132 -0
- skilldrop_cli/data/packs/research/skills/hypothesis-comparison/manifest.json +43 -0
- skilldrop_cli/data/packs/research/skills/hypothesis-comparison/reference.md +107 -0
- skilldrop_cli/data/packs/research/skills/hypothesis-comparison/scripts/ach_matrix.py +204 -0
- skilldrop_cli/data/packs/research/skills/hypothesis-comparison/templates/hypothesis-comparison.md +61 -0
- skilldrop_cli/data/packs/research/skills/hypothesis-comparison/templates/matrix.csv +4 -0
- skilldrop_cli/data/packs/research/skills/research-plan/SKILL.md +122 -0
- skilldrop_cli/data/packs/research/skills/research-plan/evals/eval_queries.json +34 -0
- skilldrop_cli/data/packs/research/skills/research-plan/evals/evals.json +28 -0
- skilldrop_cli/data/packs/research/skills/research-plan/examples/four-day-week-plan.md +97 -0
- skilldrop_cli/data/packs/research/skills/research-plan/manifest.json +47 -0
- skilldrop_cli/data/packs/research/skills/research-plan/reference.md +88 -0
- skilldrop_cli/data/packs/research/skills/research-plan/templates/research-plan.md +54 -0
- skilldrop_cli/data/packs/research/skills/source-synthesis/SKILL.md +123 -0
- skilldrop_cli/data/packs/research/skills/source-synthesis/evals/eval_queries.json +34 -0
- skilldrop_cli/data/packs/research/skills/source-synthesis/evals/evals.json +19 -0
- skilldrop_cli/data/packs/research/skills/source-synthesis/examples/four-day-week.md +146 -0
- skilldrop_cli/data/packs/research/skills/source-synthesis/manifest.json +42 -0
- skilldrop_cli/data/packs/research/skills/source-synthesis/reference.md +92 -0
- skilldrop_cli/data/packs/research/skills/source-synthesis/scripts/citations.py +314 -0
- skilldrop_cli/data/packs/research/skills/source-synthesis/templates/synthesis.md +46 -0
- skilldrop_cli/data/packs/skill-engineering/pack.json +46 -0
- skilldrop_cli/data/packs/skill-engineering/skills/skill-author/SKILL.md +139 -0
- skilldrop_cli/data/packs/skill-engineering/skills/skill-author/evals/eval_queries.json +10 -0
- skilldrop_cli/data/packs/skill-engineering/skills/skill-author/evals/evals.json +19 -0
- skilldrop_cli/data/packs/skill-engineering/skills/skill-author/examples/release-notes-skill.md +185 -0
- skilldrop_cli/data/packs/skill-engineering/skills/skill-author/manifest.json +42 -0
- skilldrop_cli/data/packs/skill-engineering/skills/skill-author/reference.md +144 -0
- skilldrop_cli/data/packs/skill-engineering/skills/skill-author/templates/eval_queries.json +9 -0
- skilldrop_cli/data/packs/skill-engineering/skills/skill-author/templates/evals.json +16 -0
- skilldrop_cli/data/packs/skill-engineering/skills/skill-author/templates/skill-template.md +52 -0
- skilldrop_cli/data/packs/skill-engineering/skills/skill-review/SKILL.md +131 -0
- skilldrop_cli/data/packs/skill-engineering/skills/skill-review/evals/eval_queries.json +11 -0
- skilldrop_cli/data/packs/skill-engineering/skills/skill-review/evals/evals.json +19 -0
- skilldrop_cli/data/packs/skill-engineering/skills/skill-review/evals/files/1/skills/changelog-writer/SKILL.md +10 -0
- skilldrop_cli/data/packs/skill-engineering/skills/skill-review/evals/files/1/skills/pr-summary/SKILL.md +12 -0
- skilldrop_cli/data/packs/skill-engineering/skills/skill-review/evals/files/1/skills/pr-summary/references/tone.md +3 -0
- skilldrop_cli/data/packs/skill-engineering/skills/skill-review/evals/files/1/skills/pr-summary/scripts/collect.py +6 -0
- skilldrop_cli/data/packs/skill-engineering/skills/skill-review/examples/meeting-notes-review.md +147 -0
- skilldrop_cli/data/packs/skill-engineering/skills/skill-review/manifest.json +50 -0
- skilldrop_cli/data/packs/skill-engineering/skills/skill-review/reference.md +135 -0
- skilldrop_cli/data/packs/skill-engineering/skills/skill-review/scripts/lint_skill.py +481 -0
- skilldrop_cli/data/packs/skill-engineering/skills/skill-review/templates/review.md +43 -0
- skilldrop_cli/data/packs/solution-architect/loops/design/LOOP.md +83 -0
- skilldrop_cli/data/packs/solution-architect/loops/design/evals/eval_queries.json +30 -0
- skilldrop_cli/data/packs/solution-architect/loops/design/evals/evals.json +26 -0
- skilldrop_cli/data/packs/solution-architect/loops/design/loop.json +47 -0
- skilldrop_cli/data/packs/solution-architect/pack.json +65 -0
- skilldrop_cli/data/packs/solution-architect/skills/adr-generator/SKILL.md +50 -0
- skilldrop_cli/data/packs/solution-architect/skills/adr-generator/evals/eval_queries.json +34 -0
- skilldrop_cli/data/packs/solution-architect/skills/adr-generator/evals/evals.json +17 -0
- skilldrop_cli/data/packs/solution-architect/skills/adr-generator/manifest.json +11 -0
- skilldrop_cli/data/packs/solution-architect/skills/adr-generator/templates/madr.md +56 -0
- skilldrop_cli/data/packs/solution-architect/skills/adr-generator/templates/nygard.md +19 -0
- skilldrop_cli/data/packs/solution-architect/skills/api-contract-draft/SKILL.md +69 -0
- skilldrop_cli/data/packs/solution-architect/skills/api-contract-draft/evals/eval_queries.json +34 -0
- skilldrop_cli/data/packs/solution-architect/skills/api-contract-draft/evals/evals.json +18 -0
- skilldrop_cli/data/packs/solution-architect/skills/api-contract-draft/manifest.json +11 -0
- skilldrop_cli/data/packs/solution-architect/skills/api-contract-draft/reference.md +73 -0
- skilldrop_cli/data/packs/solution-architect/skills/api-contract-draft/templates/openapi-skeleton.yaml +148 -0
- skilldrop_cli/data/packs/solution-architect/skills/architecture-diagrams/SKILL.md +66 -0
- skilldrop_cli/data/packs/solution-architect/skills/architecture-diagrams/evals/eval_queries.json +34 -0
- skilldrop_cli/data/packs/solution-architect/skills/architecture-diagrams/evals/evals.json +16 -0
- skilldrop_cli/data/packs/solution-architect/skills/architecture-diagrams/examples/aws-three-tier.md +44 -0
- skilldrop_cli/data/packs/solution-architect/skills/architecture-diagrams/examples/c4-container-saas.md +51 -0
- skilldrop_cli/data/packs/solution-architect/skills/architecture-diagrams/examples/microservices-sequence.md +49 -0
- skilldrop_cli/data/packs/solution-architect/skills/architecture-diagrams/manifest.json +31 -0
- skilldrop_cli/data/packs/solution-architect/skills/architecture-diagrams/templates/c4-container.puml +31 -0
- skilldrop_cli/data/packs/solution-architect/skills/architecture-diagrams/templates/mermaid-aws.md +52 -0
- skilldrop_cli/data/packs/solution-architect/skills/architecture-diagrams/templates/mermaid-flowchart.md +37 -0
- skilldrop_cli/data/packs/solution-architect/skills/architecture-diagrams/templates/mermaid-sequence.md +39 -0
- skilldrop_cli/data/packs/solution-architect/skills/data-contract/SKILL.md +67 -0
- skilldrop_cli/data/packs/solution-architect/skills/data-contract/evals/eval_queries.json +34 -0
- skilldrop_cli/data/packs/solution-architect/skills/data-contract/evals/evals.json +16 -0
- skilldrop_cli/data/packs/solution-architect/skills/data-contract/manifest.json +11 -0
- skilldrop_cli/data/packs/solution-architect/skills/data-contract/reference.md +46 -0
- skilldrop_cli/data/packs/solution-architect/skills/data-contract/templates/data-contract.md +69 -0
- skilldrop_cli/data/packs/solution-architect/skills/db-schema-design/SKILL.md +68 -0
- skilldrop_cli/data/packs/solution-architect/skills/db-schema-design/evals/eval_queries.json +34 -0
- skilldrop_cli/data/packs/solution-architect/skills/db-schema-design/evals/evals.json +16 -0
- skilldrop_cli/data/packs/solution-architect/skills/db-schema-design/manifest.json +11 -0
- skilldrop_cli/data/packs/solution-architect/skills/db-schema-design/reference.md +56 -0
- skilldrop_cli/data/packs/solution-architect/skills/db-schema-design/templates/schema-design.md +75 -0
- skilldrop_cli/data/packs/solution-architect/skills/design-doc/SKILL.md +49 -0
- skilldrop_cli/data/packs/solution-architect/skills/design-doc/evals/eval_queries.json +34 -0
- skilldrop_cli/data/packs/solution-architect/skills/design-doc/evals/evals.json +17 -0
- skilldrop_cli/data/packs/solution-architect/skills/design-doc/manifest.json +14 -0
- skilldrop_cli/data/packs/solution-architect/skills/design-doc/templates/design-doc.md +98 -0
- skilldrop_cli/data/packs/solution-architect/skills/figma-diagrams/SKILL.md +87 -0
- skilldrop_cli/data/packs/solution-architect/skills/figma-diagrams/evals/eval_queries.json +34 -0
- skilldrop_cli/data/packs/solution-architect/skills/figma-diagrams/evals/evals.json +15 -0
- skilldrop_cli/data/packs/solution-architect/skills/figma-diagrams/manifest.json +36 -0
- skilldrop_cli/data/packs/solution-architect/skills/figma-diagrams/reference.md +91 -0
- skilldrop_cli/data/packs/solution-architect/skills/figma-diagrams/requirements.txt +3 -0
- skilldrop_cli/data/packs/solution-architect/skills/figma-diagrams/scripts/_figma_client.py +71 -0
- skilldrop_cli/data/packs/solution-architect/skills/figma-diagrams/scripts/frame_to_mermaid.py +102 -0
- skilldrop_cli/data/packs/solution-architect/skills/figma-diagrams/scripts/inspect_file.py +46 -0
- skilldrop_cli/data/packs/solution-architect/skills/figma-diagrams/scripts/list_comments.py +35 -0
- skilldrop_cli/data/packs/solution-architect/skills/figma-diagrams/scripts/post_comment.py +52 -0
- skilldrop_cli/data/packs/solution-architect/skills/figma-diagrams/templates/figjam-import.json +20 -0
- skilldrop_cli/data/packs/solution-architect/skills/nfr-spec/SKILL.md +63 -0
- skilldrop_cli/data/packs/solution-architect/skills/nfr-spec/evals/eval_queries.json +34 -0
- skilldrop_cli/data/packs/solution-architect/skills/nfr-spec/evals/evals.json +16 -0
- skilldrop_cli/data/packs/solution-architect/skills/nfr-spec/manifest.json +16 -0
- skilldrop_cli/data/packs/solution-architect/skills/nfr-spec/reference.md +74 -0
- skilldrop_cli/data/packs/solution-architect/skills/nfr-spec/templates/nfr-spec.md +56 -0
- skilldrop_cli/data/packs/solution-architect/skills/reverse-architecture/SKILL.md +103 -0
- skilldrop_cli/data/packs/solution-architect/skills/reverse-architecture/evals/eval_queries.json +34 -0
- skilldrop_cli/data/packs/solution-architect/skills/reverse-architecture/evals/evals.json +16 -0
- skilldrop_cli/data/packs/solution-architect/skills/reverse-architecture/manifest.json +11 -0
- skilldrop_cli/data/packs/solution-architect/skills/reverse-architecture/reference.md +150 -0
- skilldrop_cli/data/packs/solution-architect/skills/reverse-architecture/templates/description.md +52 -0
- skilldrop_cli/data/packs/solution-architect/skills/reverse-architecture/templates/extraction.md +55 -0
- skilldrop_cli/data/packs/solution-architect/skills/tech-comparison-matrix/SKILL.md +56 -0
- skilldrop_cli/data/packs/solution-architect/skills/tech-comparison-matrix/evals/eval_queries.json +30 -0
- skilldrop_cli/data/packs/solution-architect/skills/tech-comparison-matrix/evals/evals.json +18 -0
- skilldrop_cli/data/packs/solution-architect/skills/tech-comparison-matrix/examples/queue-selection.md +41 -0
- skilldrop_cli/data/packs/solution-architect/skills/tech-comparison-matrix/manifest.json +11 -0
- skilldrop_cli/data/packs/solution-architect/skills/tech-comparison-matrix/templates/matrix.md +45 -0
- skilldrop_cli/data/packs/solution-architect/skills/threat-model/SKILL.md +65 -0
- skilldrop_cli/data/packs/solution-architect/skills/threat-model/evals/eval_queries.json +34 -0
- skilldrop_cli/data/packs/solution-architect/skills/threat-model/evals/evals.json +18 -0
- skilldrop_cli/data/packs/solution-architect/skills/threat-model/examples/document-upload.md +56 -0
- skilldrop_cli/data/packs/solution-architect/skills/threat-model/manifest.json +11 -0
- skilldrop_cli/data/packs/solution-architect/skills/threat-model/reference.md +69 -0
- skilldrop_cli/data/packs/sre-oncall/loops/operate/LOOP.md +84 -0
- skilldrop_cli/data/packs/sre-oncall/loops/operate/evals/eval_queries.json +30 -0
- skilldrop_cli/data/packs/sre-oncall/loops/operate/evals/evals.json +25 -0
- skilldrop_cli/data/packs/sre-oncall/loops/operate/loop.json +41 -0
- skilldrop_cli/data/packs/sre-oncall/pack.json +59 -0
- skilldrop_cli/data/packs/sre-oncall/skills/capacity-cost-model/SKILL.md +65 -0
- skilldrop_cli/data/packs/sre-oncall/skills/capacity-cost-model/evals/eval_queries.json +34 -0
- skilldrop_cli/data/packs/sre-oncall/skills/capacity-cost-model/evals/evals.json +19 -0
- skilldrop_cli/data/packs/sre-oncall/skills/capacity-cost-model/manifest.json +11 -0
- skilldrop_cli/data/packs/sre-oncall/skills/capacity-cost-model/reference.md +54 -0
- skilldrop_cli/data/packs/sre-oncall/skills/capacity-cost-model/templates/cost-model.md +68 -0
- skilldrop_cli/data/packs/sre-oncall/skills/cloud-cost-review/SKILL.md +121 -0
- skilldrop_cli/data/packs/sre-oncall/skills/cloud-cost-review/evals/eval_queries.json +34 -0
- skilldrop_cli/data/packs/sre-oncall/skills/cloud-cost-review/evals/evals.json +29 -0
- skilldrop_cli/data/packs/sre-oncall/skills/cloud-cost-review/examples/acme-aws-september.md +247 -0
- skilldrop_cli/data/packs/sre-oncall/skills/cloud-cost-review/examples/acme-cur-2026-08-09.csv +1085 -0
- skilldrop_cli/data/packs/sre-oncall/skills/cloud-cost-review/manifest.json +49 -0
- skilldrop_cli/data/packs/sre-oncall/skills/cloud-cost-review/reference.md +100 -0
- skilldrop_cli/data/packs/sre-oncall/skills/cloud-cost-review/scripts/cost_summary.py +630 -0
- skilldrop_cli/data/packs/sre-oncall/skills/cloud-cost-review/templates/cost-review.md +36 -0
- skilldrop_cli/data/packs/sre-oncall/skills/delivery-metrics-report/SKILL.md +114 -0
- skilldrop_cli/data/packs/sre-oncall/skills/delivery-metrics-report/evals/eval_queries.json +34 -0
- skilldrop_cli/data/packs/sre-oncall/skills/delivery-metrics-report/evals/evals.json +28 -0
- skilldrop_cli/data/packs/sre-oncall/skills/delivery-metrics-report/evals/files/1/deploys.csv +49 -0
- skilldrop_cli/data/packs/sre-oncall/skills/delivery-metrics-report/evals/files/1/incidents.csv +7 -0
- skilldrop_cli/data/packs/sre-oncall/skills/delivery-metrics-report/evals/files/1/prs.csv +84 -0
- skilldrop_cli/data/packs/sre-oncall/skills/delivery-metrics-report/examples/checkout-api-8-weeks.md +123 -0
- skilldrop_cli/data/packs/sre-oncall/skills/delivery-metrics-report/examples/deployments.csv +49 -0
- skilldrop_cli/data/packs/sre-oncall/skills/delivery-metrics-report/examples/incidents.csv +7 -0
- skilldrop_cli/data/packs/sre-oncall/skills/delivery-metrics-report/examples/prs.csv +84 -0
- skilldrop_cli/data/packs/sre-oncall/skills/delivery-metrics-report/manifest.json +48 -0
- skilldrop_cli/data/packs/sre-oncall/skills/delivery-metrics-report/reference.md +83 -0
- skilldrop_cli/data/packs/sre-oncall/skills/delivery-metrics-report/scripts/delivery_metrics.py +566 -0
- skilldrop_cli/data/packs/sre-oncall/skills/delivery-metrics-report/templates/delivery-report.md +31 -0
- skilldrop_cli/data/packs/sre-oncall/skills/incident-comms/SKILL.md +68 -0
- skilldrop_cli/data/packs/sre-oncall/skills/incident-comms/evals/eval_queries.json +34 -0
- skilldrop_cli/data/packs/sre-oncall/skills/incident-comms/evals/evals.json +16 -0
- skilldrop_cli/data/packs/sre-oncall/skills/incident-comms/manifest.json +14 -0
- skilldrop_cli/data/packs/sre-oncall/skills/incident-comms/reference.md +43 -0
- skilldrop_cli/data/packs/sre-oncall/skills/incident-comms/templates/messages.md +82 -0
- skilldrop_cli/data/packs/sre-oncall/skills/observability-plan/SKILL.md +70 -0
- skilldrop_cli/data/packs/sre-oncall/skills/observability-plan/evals/eval_queries.json +34 -0
- skilldrop_cli/data/packs/sre-oncall/skills/observability-plan/evals/evals.json +16 -0
- skilldrop_cli/data/packs/sre-oncall/skills/observability-plan/manifest.json +15 -0
- skilldrop_cli/data/packs/sre-oncall/skills/observability-plan/reference.md +67 -0
- skilldrop_cli/data/packs/sre-oncall/skills/observability-plan/templates/observability-plan.md +62 -0
- skilldrop_cli/data/packs/sre-oncall/skills/postmortem-generator/SKILL.md +66 -0
- skilldrop_cli/data/packs/sre-oncall/skills/postmortem-generator/evals/eval_queries.json +34 -0
- skilldrop_cli/data/packs/sre-oncall/skills/postmortem-generator/evals/evals.json +17 -0
- skilldrop_cli/data/packs/sre-oncall/skills/postmortem-generator/manifest.json +14 -0
- skilldrop_cli/data/packs/sre-oncall/skills/postmortem-generator/templates/postmortem.md +67 -0
- skilldrop_cli/data/packs/sre-oncall/skills/runbook-generator/SKILL.md +42 -0
- skilldrop_cli/data/packs/sre-oncall/skills/runbook-generator/evals/eval_queries.json +34 -0
- skilldrop_cli/data/packs/sre-oncall/skills/runbook-generator/evals/evals.json +17 -0
- skilldrop_cli/data/packs/sre-oncall/skills/runbook-generator/manifest.json +11 -0
- skilldrop_cli/data/packs/sre-oncall/skills/runbook-generator/templates/runbook.md +109 -0
- skilldrop_cli/data/packs/stakeholder-comms/pack.json +60 -0
- skilldrop_cli/data/packs/stakeholder-comms/skills/audience-profile/SKILL.md +74 -0
- skilldrop_cli/data/packs/stakeholder-comms/skills/audience-profile/evals/eval_queries.json +34 -0
- skilldrop_cli/data/packs/stakeholder-comms/skills/audience-profile/evals/evals.json +15 -0
- skilldrop_cli/data/packs/stakeholder-comms/skills/audience-profile/manifest.json +11 -0
- skilldrop_cli/data/packs/stakeholder-comms/skills/audience-profile/reference.md +175 -0
- skilldrop_cli/data/packs/stakeholder-comms/skills/decision-log/SKILL.md +68 -0
- skilldrop_cli/data/packs/stakeholder-comms/skills/decision-log/evals/eval_queries.json +34 -0
- skilldrop_cli/data/packs/stakeholder-comms/skills/decision-log/evals/evals.json +17 -0
- skilldrop_cli/data/packs/stakeholder-comms/skills/decision-log/manifest.json +11 -0
- skilldrop_cli/data/packs/stakeholder-comms/skills/exec-summary/SKILL.md +45 -0
- skilldrop_cli/data/packs/stakeholder-comms/skills/exec-summary/evals/eval_queries.json +34 -0
- skilldrop_cli/data/packs/stakeholder-comms/skills/exec-summary/evals/evals.json +17 -0
- skilldrop_cli/data/packs/stakeholder-comms/skills/exec-summary/manifest.json +11 -0
- skilldrop_cli/data/packs/stakeholder-comms/skills/exec-summary/templates/exec-summary.md +48 -0
- skilldrop_cli/data/packs/stakeholder-comms/skills/guide-builder/SKILL.md +77 -0
- skilldrop_cli/data/packs/stakeholder-comms/skills/guide-builder/evals/eval_queries.json +34 -0
- skilldrop_cli/data/packs/stakeholder-comms/skills/guide-builder/evals/evals.json +18 -0
- skilldrop_cli/data/packs/stakeholder-comms/skills/guide-builder/examples/local-setup-guide.md +127 -0
- skilldrop_cli/data/packs/stakeholder-comms/skills/guide-builder/manifest.json +11 -0
- skilldrop_cli/data/packs/stakeholder-comms/skills/guide-builder/templates/api-reference.md +120 -0
- skilldrop_cli/data/packs/stakeholder-comms/skills/guide-builder/templates/design-walkthrough.md +72 -0
- skilldrop_cli/data/packs/stakeholder-comms/skills/guide-builder/templates/setup-guide.md +109 -0
- skilldrop_cli/data/packs/trackers/pack.json +61 -0
- skilldrop_cli/data/packs/trackers/skills/backlog-triage/SKILL.md +116 -0
- skilldrop_cli/data/packs/trackers/skills/backlog-triage/evals/eval_queries.json +34 -0
- skilldrop_cli/data/packs/trackers/skills/backlog-triage/evals/evals.json +30 -0
- skilldrop_cli/data/packs/trackers/skills/backlog-triage/evals/files/1/examples/northwind-backlog.csv +28 -0
- skilldrop_cli/data/packs/trackers/skills/backlog-triage/evals/files/2/issues.json +6052 -0
- skilldrop_cli/data/packs/trackers/skills/backlog-triage/examples/northwind-backlog.csv +28 -0
- skilldrop_cli/data/packs/trackers/skills/backlog-triage/examples/northwind-checkout-triage.md +202 -0
- skilldrop_cli/data/packs/trackers/skills/backlog-triage/manifest.json +42 -0
- skilldrop_cli/data/packs/trackers/skills/backlog-triage/reference.md +106 -0
- skilldrop_cli/data/packs/trackers/skills/backlog-triage/scripts/triage_backlog.py +461 -0
- skilldrop_cli/data/packs/trackers/skills/backlog-triage/templates/triage-report.md +42 -0
- skilldrop_cli/data/packs/trackers/skills/team-status-report/SKILL.md +111 -0
- skilldrop_cli/data/packs/trackers/skills/team-status-report/evals/eval_queries.json +30 -0
- skilldrop_cli/data/packs/trackers/skills/team-status-report/evals/evals.json +30 -0
- skilldrop_cli/data/packs/trackers/skills/team-status-report/evals/files/1/examples/northwind-week-40.csv +16 -0
- skilldrop_cli/data/packs/trackers/skills/team-status-report/evals/files/2/issues.json +1212 -0
- skilldrop_cli/data/packs/trackers/skills/team-status-report/examples/northwind-week-40.csv +16 -0
- skilldrop_cli/data/packs/trackers/skills/team-status-report/examples/northwind-weekly-status.md +160 -0
- skilldrop_cli/data/packs/trackers/skills/team-status-report/manifest.json +33 -0
- skilldrop_cli/data/packs/trackers/skills/team-status-report/reference.md +92 -0
- skilldrop_cli/data/packs/trackers/skills/team-status-report/scripts/status_counts.py +486 -0
- skilldrop_cli/data/packs/trackers/skills/team-status-report/templates/status-report.md +42 -0
- skilldrop_cli/data/packs/trackers/skills/tracker-brief-sync/SKILL.md +116 -0
- skilldrop_cli/data/packs/trackers/skills/tracker-brief-sync/evals/eval_queries.json +34 -0
- skilldrop_cli/data/packs/trackers/skills/tracker-brief-sync/evals/evals.json +31 -0
- skilldrop_cli/data/packs/trackers/skills/tracker-brief-sync/evals/files/1/examples/prd-guest-checkout.md +53 -0
- skilldrop_cli/data/packs/trackers/skills/tracker-brief-sync/evals/files/2/examples/prd-guest-checkout.md +53 -0
- skilldrop_cli/data/packs/trackers/skills/tracker-brief-sync/evals/files/2/examples/tracker-after-two-weeks.csv +30 -0
- skilldrop_cli/data/packs/trackers/skills/tracker-brief-sync/examples/guest-checkout-spec.json +98 -0
- skilldrop_cli/data/packs/trackers/skills/tracker-brief-sync/examples/guest-checkout-sync.md +151 -0
- skilldrop_cli/data/packs/trackers/skills/tracker-brief-sync/examples/prd-guest-checkout.md +53 -0
- skilldrop_cli/data/packs/trackers/skills/tracker-brief-sync/examples/tracker-after-two-weeks.csv +30 -0
- skilldrop_cli/data/packs/trackers/skills/tracker-brief-sync/manifest.json +41 -0
- skilldrop_cli/data/packs/trackers/skills/tracker-brief-sync/reference.md +101 -0
- skilldrop_cli/data/packs/trackers/skills/tracker-brief-sync/scripts/brief_sync.py +501 -0
- skilldrop_cli/data/packs/trackers/skills/tracker-brief-sync/templates/issues-spec.json +26 -0
- skilldrop_cli/data/profiles.json +24 -0
- skilldrop_cli-0.16.3.dist-info/METADATA +91 -0
- skilldrop_cli-0.16.3.dist-info/RECORD +757 -0
- skilldrop_cli-0.16.3.dist-info/WHEEL +5 -0
- skilldrop_cli-0.16.3.dist-info/entry_points.txt +2 -0
- skilldrop_cli-0.16.3.dist-info/licenses/LICENSE +9 -0
- skilldrop_cli-0.16.3.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,2110 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/* skilldrop — install portable AI-agent skills into Claude Code, Cursor, Kiro,
|
|
3
|
+
* or any directory, from the bundled catalog or any third-party catalog
|
|
4
|
+
* (--from <path | git-url[#ref]>). Zero dependencies; copy-only, never executes
|
|
5
|
+
* catalog content at install time.
|
|
6
|
+
* Reads three catalog shapes: skilldrop pack folders (packs/<p>/pack.json + packs/<p>/skills/),
|
|
7
|
+
* flat skilldrop (skills/ + packs.json), and agentbundle (packs/<p>/pack.toml + .apm/skills).
|
|
8
|
+
* Design: skilldrop-cli-design/skilldrop-cli-design.md
|
|
9
|
+
* Scope: docs/rfcs/0002-skilldrop-cli.md, docs/rfcs/0003-third-party-catalogs.md,
|
|
10
|
+
* docs/rfcs/0014-agentbundle-interop.md (the agentbundle reader)
|
|
11
|
+
*/
|
|
12
|
+
"use strict";
|
|
13
|
+
const fs = require("fs");
|
|
14
|
+
const path = require("path");
|
|
15
|
+
const os = require("os");
|
|
16
|
+
const { execFileSync } = require("child_process");
|
|
17
|
+
const crypto = require("crypto");
|
|
18
|
+
|
|
19
|
+
const ROOT = path.resolve(__dirname, "..");
|
|
20
|
+
const LEDGER = ".skilldrop.json";
|
|
21
|
+
const BUNDLED = "bundled";
|
|
22
|
+
// Neutral hook vocabulary (RFC-0006). Kept in sync with validate.py's HOOK_EVENTS.
|
|
23
|
+
const HOOK_EVENTS = ["session-start", "pre-commit-review", "on-demand"];
|
|
24
|
+
|
|
25
|
+
// Panels (RFC-0020): a named bundle of reviewer subagents + the orchestrator skill that fires
|
|
26
|
+
// them in parallel. `install --panel <name>` installs the whole fleet in one command.
|
|
27
|
+
const PANELS = {
|
|
28
|
+
review: {
|
|
29
|
+
agents: ["devils-advocate", "security-reviewer", "code-quality"],
|
|
30
|
+
skill: "pre-merge-review",
|
|
31
|
+
blurb: "correctness + security + craft reviewers, fired in parallel by pre-merge-review",
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const HELP = `skilldrop — portable AI-agent skills for Claude Code, Cursor, Kiro, and more
|
|
36
|
+
|
|
37
|
+
Usage:
|
|
38
|
+
skilldrop list [--from <src>] all skills in a catalog (name, version, tier)
|
|
39
|
+
skilldrop info <skill> [--from <src>] description, related, packs, deps
|
|
40
|
+
skilldrop info --pack <name> a pack's skills, loops, and what to try first (RFC-0032)
|
|
41
|
+
skilldrop packs [--from <src>] role-based packs
|
|
42
|
+
skilldrop agents [--from <src>] reviewer subagents in a catalog
|
|
43
|
+
skilldrop loops [--from <src>] loops — sequenced stages over skills (RFC-0028)
|
|
44
|
+
skilldrop install <skill...> install skills (default: Claude Code, user scope)
|
|
45
|
+
skilldrop install --pack <name> install a whole pack
|
|
46
|
+
skilldrop install --profile <name> install a named bundle of packs, agents, and loops
|
|
47
|
+
skilldrop profiles [--from <src>] list available profiles
|
|
48
|
+
skilldrop install --all install every skill in the catalog
|
|
49
|
+
skilldrop install --agent <name...> install reviewer subagents (RFC-0012)
|
|
50
|
+
skilldrop install --loop <name...> install loops + the stage skills they sequence
|
|
51
|
+
(--no-skills for the loop alone) (RFC-0028)
|
|
52
|
+
skilldrop install --panel review install the review panel — the 3 reviewer subagents +
|
|
53
|
+
the pre-merge-review orchestrator that fires them (RFC-0020)
|
|
54
|
+
skilldrop update [--force] [--changed] re-copy installed skills whose version changed; files
|
|
55
|
+
you edited are kept and the new copy lands beside them
|
|
56
|
+
as <file>.upstream (--force overwrites) (RFC-0032).
|
|
57
|
+
Files changed upstream under the same version are named,
|
|
58
|
+
not taken, unless you add --changed (RFC-0039)
|
|
59
|
+
skilldrop outdated show installed vs current versions, change nothing
|
|
60
|
+
skilldrop diff <skill> [--stat] what differs between your installed copy and the catalog's —
|
|
61
|
+
your edits, upstream changes, or both
|
|
62
|
+
skilldrop doctor check a target against its ledger: missing folders, leftover
|
|
63
|
+
.upstream files, stale wiring and hooks. Changes nothing.
|
|
64
|
+
skilldrop uninstall <skill...> remove skills (and wiring files this tool wrote)
|
|
65
|
+
skilldrop uninstall --agent <name...> remove subagents
|
|
66
|
+
skilldrop uninstall --loop <name...> remove loops (stage skills are left in place)
|
|
67
|
+
skilldrop validate [--from <src>] structural check of a catalog (for catalog authors)
|
|
68
|
+
skilldrop scan [<skill...>] [--from <src>] supply-chain scan — flag network/exec/credential
|
|
69
|
+
patterns in scripts and injection-shaped instructions
|
|
70
|
+
in SKILL.md before you trust a catalog (RFC-0022)
|
|
71
|
+
skilldrop new-skill <name> --pack <p> scaffold a skill in the catalog you're in (for catalog authors;
|
|
72
|
+
--tier light|standard|heavy, default standard)
|
|
73
|
+
skilldrop loop-stats [<file>] [--days N] summarise the opt-in loop run log (SKILLDROP_LOOP_LOG):
|
|
74
|
+
which gates pass first time, loop back, or block. Local only.
|
|
75
|
+
skilldrop init-catalogue <dir> [--pack <name>] start a private catalogue for your team's skills
|
|
76
|
+
skilldrop package <dir> [--pack a,b] [--skills x,y] [--agents] copy a vetted subset of a catalogue
|
|
77
|
+
into a standalone one to host internally (writes MIRROR.json)
|
|
78
|
+
skilldrop --version print the CLI version
|
|
79
|
+
skilldrop bootstrap add the skilldrop marketplace to ~/.claude/settings.json
|
|
80
|
+
(idempotent — safe to run in onboarding scripts)
|
|
81
|
+
|
|
82
|
+
Catalogs:
|
|
83
|
+
(default) the catalog bundled with this package
|
|
84
|
+
--from <dir|url> a skilldrop catalog (packs/<pack>/skills/<name>/ or flat skills/<name>/) OR
|
|
85
|
+
an agentbundle one (packs/<pack>/.apm/skills/<name>/SKILL.md); a git URL
|
|
86
|
+
works for either. Append #<commit-sha> to pin exactly (a branch or tag can
|
|
87
|
+
move after you review it); installs record the commit they got
|
|
88
|
+
|
|
89
|
+
Install/update/uninstall targets (pick one):
|
|
90
|
+
(default) ~/.claude/skills Claude Code, user scope
|
|
91
|
+
--project ./.claude/skills Claude Code + GitHub Copilot CLI, project scope
|
|
92
|
+
--ide cursor ./.cursor/skills + writes .cursor/rules/<skill>.mdc
|
|
93
|
+
--ide kiro ./.kiro/skills Kiro IDE + Kiro CLI (discovered natively)
|
|
94
|
+
--ide codex ~/.codex/skills Codex (--project: ./.agents/skills)
|
|
95
|
+
--ide antigravity ~/.gemini/antigravity-cli/skills Antigravity CLI (--project: ./.agents/skills)
|
|
96
|
+
--ide copilot ~/.copilot/skills GitHub Copilot (--project: ./.github/skills)
|
|
97
|
+
--dest <dir> any directory e.g. Continue / Cline / Aider
|
|
98
|
+
--local project scope, hidden from git via .git/info/exclude — try skills in a
|
|
99
|
+
repo you don't own without touching its tracked files or .gitignore
|
|
100
|
+
|
|
101
|
+
Options:
|
|
102
|
+
--dry-run install | update | uninstall | new-skill: print what would change, change nothing
|
|
103
|
+
--yes skip the confirmation uninstall and update --force ask for at a terminal
|
|
104
|
+
(scripts and CI are never asked)
|
|
105
|
+
--agent operate on subagents instead of skills. Targets:
|
|
106
|
+
(default) ~/.claude/agents Claude Code (--project for repo scope)
|
|
107
|
+
--ide kiro ./.kiro/agents generated JSON, tools mapped
|
|
108
|
+
--ide copilot ./.github/agents <name>.agent.md
|
|
109
|
+
--ide codex ~/.codex/agents generated TOML (--project for repo)
|
|
110
|
+
--ide antigravity ~/.gemini/config/agents markdown, subagent: true
|
|
111
|
+
--dest <dir> any directory copied as-is
|
|
112
|
+
Cursor has no agent format — use a custom mode (agents/README.md).
|
|
113
|
+
--json machine-readable output for list | info | packs | agents | outdated | doctor —
|
|
114
|
+
one JSON object on stdout, nothing else (for agents, scripts, CI)
|
|
115
|
+
--with-related also install each skill's related companions (one level)
|
|
116
|
+
--with-hooks also wire any hooks a skill declares (RFC-0006) — git pre-commit
|
|
117
|
+
reminders and Claude Code session-start context; degrades where the
|
|
118
|
+
target has no hook mechanism. Off by default so installs never touch
|
|
119
|
+
your git repo or settings unasked.
|
|
120
|
+
`;
|
|
121
|
+
|
|
122
|
+
function die(msg) { console.error("error: " + msg); process.exit(1); }
|
|
123
|
+
function readJSON(p) { return JSON.parse(fs.readFileSync(p, "utf8")); }
|
|
124
|
+
|
|
125
|
+
/* --json (RFC-0021): the read commands emit one machine-readable object on stdout and nothing
|
|
126
|
+
else, so an agent or script can consume them without scraping aligned columns. Human output is
|
|
127
|
+
unchanged by default — this is additive. Errors still go to stderr and exit non-zero. */
|
|
128
|
+
function emitJSON(obj) { console.log(JSON.stringify(obj, null, 2)); }
|
|
129
|
+
|
|
130
|
+
function parseArgs(argv) {
|
|
131
|
+
const out = { _: [], flags: {} };
|
|
132
|
+
for (let i = 0; i < argv.length; i++) {
|
|
133
|
+
const a = argv[i];
|
|
134
|
+
if (a === "--pack" || a === "--ide" || a === "--dest" || a === "--from" || a === "--panel" || a === "--profile" || a === "--tier" || a === "--skills" || a === "--days") out.flags[a.slice(2)] = argv[++i];
|
|
135
|
+
else if (a.startsWith("--")) out.flags[a.slice(2)] = true;
|
|
136
|
+
else out._.push(a);
|
|
137
|
+
}
|
|
138
|
+
return out;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/* ---------- catalogs ----------
|
|
142
|
+
Three on-disk shapes are read (RFC-0003, RFC-0014, RFC-0034):
|
|
143
|
+
"packs" — skilldrop's own layout since RFC-0034: packs/<pack>/{pack.json,
|
|
144
|
+
skills/<name>/{SKILL.md,manifest.json}, loops/<name>/} + catalogue.json
|
|
145
|
+
"skilldrop" — flat skills/<name>/{SKILL.md,manifest.json} + optional packs.json; what
|
|
146
|
+
skilldrop shipped before RFC-0034, and what most third-party catalogs use
|
|
147
|
+
"apm" — agentbundle (agent-ready-repo): packs/<pack>/{pack.toml,
|
|
148
|
+
.apm/skills/<name>/SKILL.md, .apm/agents/<name>.md}
|
|
149
|
+
The apm reader normalizes into the same accessors the native path uses — skills keyed by
|
|
150
|
+
folder name, a per-skill manifest synthesized from the SKILL.md frontmatter + the pack.toml
|
|
151
|
+
version, each pack.toml's [pack] table a virtual pack — so `--from git+https://…/agent-ready-repo`
|
|
152
|
+
installs his packs through this same CLI. Skill accessors: skillsIn / skillDir / skillExists /
|
|
153
|
+
manifestOf; never touch cat.skillsDir directly (it exists only on the flat skilldrop shape). */
|
|
154
|
+
|
|
155
|
+
const catalogCache = {};
|
|
156
|
+
/* Commit pinning (RFC-0039, OWASP AST02/AST07). A tag or branch can be moved to point at
|
|
157
|
+
different code after you reviewed it; a commit SHA cannot. `--from <url>#<sha>` fetches
|
|
158
|
+
exactly that commit, and every install records the commit it actually got, so the ledger
|
|
159
|
+
says what is on disk and an unpinned install can tell you how to pin it. */
|
|
160
|
+
const SHA_RE = /^[0-9a-f]{7,40}$/i;
|
|
161
|
+
function gitHead(dir) {
|
|
162
|
+
try {
|
|
163
|
+
return execFileSync("git", ["rev-parse", "HEAD"], { cwd: dir, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim() || null;
|
|
164
|
+
} catch (e) { return null; }
|
|
165
|
+
}
|
|
166
|
+
function splitSource(source) {
|
|
167
|
+
const i = source.lastIndexOf("#");
|
|
168
|
+
return i > 0 ? [source.slice(0, i), source.slice(i + 1)] : [source, ""];
|
|
169
|
+
}
|
|
170
|
+
function isPinned(source) { return !!source && SHA_RE.test(splitSource(source)[1]); }
|
|
171
|
+
|
|
172
|
+
function fetchCatalog(source) {
|
|
173
|
+
const [url, ref] = splitSource(source);
|
|
174
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "skilldrop-cat-"));
|
|
175
|
+
const git = (args, cwd) => execFileSync("git", args, { cwd, stdio: ["ignore", "ignore", "pipe"] });
|
|
176
|
+
try {
|
|
177
|
+
if (SHA_RE.test(ref)) {
|
|
178
|
+
// `git clone --branch` takes branches and tags, not commits: fetch the commit itself.
|
|
179
|
+
git(["init", "-q"], dir);
|
|
180
|
+
git(["remote", "add", "origin", url], dir);
|
|
181
|
+
let direct = ref.length === 40;
|
|
182
|
+
if (direct) {
|
|
183
|
+
try { git(["fetch", "-q", "--depth", "1", "origin", ref], dir); }
|
|
184
|
+
catch (e) { direct = false; } // a host that won't serve a commit by itself
|
|
185
|
+
}
|
|
186
|
+
if (!direct) git(["fetch", "-q", "origin", "+refs/heads/*:refs/remotes/origin/*", "+refs/tags/*:refs/tags/*"], dir);
|
|
187
|
+
try { git(["checkout", "-q", direct ? "FETCH_HEAD" : ref], dir); }
|
|
188
|
+
catch (e) { die(`catalog '${source}': commit ${ref} isn't in that repository (check the SHA, or that it was pushed)`); }
|
|
189
|
+
} else {
|
|
190
|
+
git(["clone", "-q", "--depth", "1", ...(ref ? ["--branch", ref] : []), url, dir]);
|
|
191
|
+
}
|
|
192
|
+
} catch (e) {
|
|
193
|
+
die(`could not fetch catalog '${source}' — not a local path, and git ${SHA_RE.test(ref) ? "could not fetch that commit" : "clone failed"}`);
|
|
194
|
+
}
|
|
195
|
+
const head = gitHead(dir);
|
|
196
|
+
if (SHA_RE.test(ref) && (!head || !head.startsWith(ref.toLowerCase())))
|
|
197
|
+
die(`catalog '${source}' resolved to ${head || "nothing"}, not the pinned commit ${ref}`);
|
|
198
|
+
return dir;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function resolveCatalog(source) {
|
|
202
|
+
const key = source || BUNDLED;
|
|
203
|
+
if (catalogCache[key]) return catalogCache[key];
|
|
204
|
+
let dir;
|
|
205
|
+
if (!source) dir = ROOT;
|
|
206
|
+
else if (fs.existsSync(source)) dir = path.resolve(source);
|
|
207
|
+
else dir = fetchCatalog(source);
|
|
208
|
+
const src = source || BUNDLED;
|
|
209
|
+
// The bundled catalog is versioned by the npm package; anything else by its git commit.
|
|
210
|
+
const meta = { commit: source ? gitHead(dir) : null, pinned: isPinned(source) };
|
|
211
|
+
const packsDir = path.join(dir, "packs");
|
|
212
|
+
if (fs.existsSync(packsDir) && fs.readdirSync(packsDir).some((p) => fs.existsSync(path.join(packsDir, p, "pack.json"))))
|
|
213
|
+
return (catalogCache[key] = Object.assign(readPacksCatalog(dir, src), meta));
|
|
214
|
+
if (fs.existsSync(path.join(dir, "skills")))
|
|
215
|
+
return (catalogCache[key] = Object.assign({ dir, source: src, shape: "skilldrop", skillsDir: path.join(dir, "skills") }, meta));
|
|
216
|
+
if (fs.existsSync(path.join(dir, "packs")))
|
|
217
|
+
return (catalogCache[key] = Object.assign(readApmCatalog(dir, src), meta));
|
|
218
|
+
die(`'${src}' is not a catalog — expected packs/<pack>/pack.json, a skills/ directory, or packs/<pack>/pack.toml (agentbundle)`);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/* RFC-0034: membership is the folder. A skill is in a pack because it sits at
|
|
222
|
+
packs/<pack>/skills/<name>/; catalogue.json only sets the order packs are listed in. */
|
|
223
|
+
function readPacksCatalog(dir, source) {
|
|
224
|
+
const packsDir = path.join(dir, "packs");
|
|
225
|
+
const children = (d, marker) => fs.existsSync(d)
|
|
226
|
+
? fs.readdirSync(d).filter((x) => fs.existsSync(path.join(d, x, marker))).sort() : [];
|
|
227
|
+
const onDisk = children(packsDir, "pack.json");
|
|
228
|
+
const idx = path.join(dir, "catalogue.json");
|
|
229
|
+
const listed = fs.existsSync(idx) ? (readJSON(idx).packs || []).filter((p) => onDisk.includes(p)) : [];
|
|
230
|
+
const order = [...listed, ...onDisk.filter((p) => !listed.includes(p))];
|
|
231
|
+
const skillIndex = {}, loopIndex = {}, packsData = {};
|
|
232
|
+
for (const pack of order) {
|
|
233
|
+
const pdir = path.join(packsDir, pack);
|
|
234
|
+
const skills = children(path.join(pdir, "skills"), "SKILL.md");
|
|
235
|
+
const loops = children(path.join(pdir, "loops"), "loop.json");
|
|
236
|
+
for (const s of skills) if (!skillIndex[s]) skillIndex[s] = { dir: path.join(pdir, "skills", s) };
|
|
237
|
+
for (const l of loops) if (!loopIndex[l]) loopIndex[l] = path.join(pdir, "loops", l);
|
|
238
|
+
packsData[pack] = Object.assign({}, readJSON(path.join(pdir, "pack.json")), { skills, loops });
|
|
239
|
+
}
|
|
240
|
+
return { dir, source, shape: "packs", skillIndex, loopIndex, packsData };
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/* Minimal TOML read — the [pack] table's basic-string scalars (name, version, description).
|
|
244
|
+
pack.toml is small and regular; a full TOML parser would be dead weight in a zero-dep CLI. */
|
|
245
|
+
function packMeta(tomlText) {
|
|
246
|
+
const out = {};
|
|
247
|
+
let inPack = false;
|
|
248
|
+
for (const raw of tomlText.split(/\r?\n/)) {
|
|
249
|
+
const t = raw.trim();
|
|
250
|
+
if (t.startsWith("[")) { inPack = t === "[pack]"; continue; }
|
|
251
|
+
if (!inPack || !t || t.startsWith("#")) continue;
|
|
252
|
+
const m = t.match(/^([A-Za-z0-9_.-]+)\s*=\s*"((?:[^"\\]|\\.)*)"/);
|
|
253
|
+
if (m) out[m[1]] = m[2].replace(/\\"/g, '"').replace(/\\\\/g, "\\");
|
|
254
|
+
}
|
|
255
|
+
return out;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/* SKILL.md frontmatter (agentskills.io: name optional, description carries the trigger). */
|
|
259
|
+
function frontmatter(mdPath) {
|
|
260
|
+
let fm = "";
|
|
261
|
+
try { fm = fs.readFileSync(mdPath, "utf8").split("---")[1] || ""; } catch (e) { return {}; }
|
|
262
|
+
const get = (k) => { const m = fm.match(new RegExp("^" + k + ":\\s*(.+)$", "m")); return m ? m[1].trim() : ""; };
|
|
263
|
+
return { name: get("name"), description: get("description") };
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/* Read an agentbundle catalog into the native accessor shape. First-wins on a skill name
|
|
267
|
+
(skilldrop's own generated catalogue duplicates a multi-pack skill into each pack — the
|
|
268
|
+
flat skill list dedups, while packsData still lists it under every pack it belongs to). */
|
|
269
|
+
function readApmCatalog(dir, source) {
|
|
270
|
+
const packsDir = path.join(dir, "packs");
|
|
271
|
+
const skillIndex = {}, agentIndex = {}, packsData = {};
|
|
272
|
+
for (const pack of fs.readdirSync(packsDir)) {
|
|
273
|
+
const pdir = path.join(packsDir, pack);
|
|
274
|
+
if (!fs.statSync(pdir).isDirectory()) continue;
|
|
275
|
+
const ptoml = path.join(pdir, "pack.toml");
|
|
276
|
+
const pm = fs.existsSync(ptoml) ? packMeta(fs.readFileSync(ptoml, "utf8")) : {};
|
|
277
|
+
const version = pm.version || "0.0.0";
|
|
278
|
+
const skills = [];
|
|
279
|
+
const skillsRoot = path.join(pdir, ".apm", "skills");
|
|
280
|
+
if (fs.existsSync(skillsRoot))
|
|
281
|
+
for (const s of fs.readdirSync(skillsRoot)) {
|
|
282
|
+
const sdir = path.join(skillsRoot, s);
|
|
283
|
+
if (!fs.statSync(sdir).isDirectory()) continue;
|
|
284
|
+
skills.push(s);
|
|
285
|
+
if (!skillIndex[s])
|
|
286
|
+
skillIndex[s] = { dir: sdir, manifest: {
|
|
287
|
+
name: s, description: frontmatter(path.join(sdir, "SKILL.md")).description || "",
|
|
288
|
+
version, related: [], tags: [],
|
|
289
|
+
deps: { pip: [], npm: [] }, env: { required: [], optional: [] },
|
|
290
|
+
} };
|
|
291
|
+
}
|
|
292
|
+
const agentsRoot = path.join(pdir, ".apm", "agents");
|
|
293
|
+
if (fs.existsSync(agentsRoot))
|
|
294
|
+
for (const f of fs.readdirSync(agentsRoot))
|
|
295
|
+
if (f.endsWith(".md") && f !== "README.md") agentIndex[f.slice(0, -3)] = path.join(agentsRoot, f);
|
|
296
|
+
packsData[pack] = { description: pm.description || "", skills };
|
|
297
|
+
}
|
|
298
|
+
return { dir, source, shape: "apm", skillIndex, agentIndex, packsData };
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function skillsIn(cat) {
|
|
302
|
+
if (cat.skillIndex) return Object.keys(cat.skillIndex).sort();
|
|
303
|
+
return fs.readdirSync(cat.skillsDir).filter((d) => fs.statSync(path.join(cat.skillsDir, d)).isDirectory()).sort();
|
|
304
|
+
}
|
|
305
|
+
function skillDir(cat, s) {
|
|
306
|
+
if (cat.skillIndex) return cat.skillIndex[s] && cat.skillIndex[s].dir;
|
|
307
|
+
return path.join(cat.skillsDir, s);
|
|
308
|
+
}
|
|
309
|
+
// Skill names reach RegExp when unwiring hooks. skillExists() proves a directory exists; it
|
|
310
|
+
// does not prove the name is regex-safe, and a third-party catalog (RFC-0003) may name a
|
|
311
|
+
// skill anything a filesystem accepts. Escape before interpolating.
|
|
312
|
+
// Read a file if it is there. Checking existsSync first then reading is a race: the file can
|
|
313
|
+
// vanish in between and the read throws. Handle the absence, do not predict it.
|
|
314
|
+
function readIfPresent(p, absent = "") {
|
|
315
|
+
try { return fs.readFileSync(p, "utf8"); }
|
|
316
|
+
catch (e) { if (e.code === "ENOENT" || e.code === "ENOTDIR") return absent; throw e; }
|
|
317
|
+
}
|
|
318
|
+
function reEscape(s) { return String(s).replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); }
|
|
319
|
+
function skillExists(cat, s) { const d = skillDir(cat, s); return !!d && fs.existsSync(d); }
|
|
320
|
+
function manifestOf(cat, s) {
|
|
321
|
+
if (cat.shape === "apm") {
|
|
322
|
+
if (!cat.skillIndex[s]) throw new Error(`no such skill '${s}'`);
|
|
323
|
+
return cat.skillIndex[s].manifest;
|
|
324
|
+
}
|
|
325
|
+
return readJSON(path.join(skillDir(cat, s), "manifest.json"));
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/* Agents (RFC-0012): single markdown files, frontmatter is already Claude Code's format.
|
|
329
|
+
Optional in a catalog — a third-party catalog with no agents is still valid. */
|
|
330
|
+
function agentsIn(cat) {
|
|
331
|
+
if (cat.shape === "apm") return Object.keys(cat.agentIndex).sort();
|
|
332
|
+
const d = path.join(cat.dir, "agents");
|
|
333
|
+
if (!fs.existsSync(d)) return [];
|
|
334
|
+
return fs.readdirSync(d).filter((f) => f.endsWith(".md") && f !== "README.md").map((f) => f.slice(0, -3)).sort();
|
|
335
|
+
}
|
|
336
|
+
function agentPath(cat, a) {
|
|
337
|
+
if (cat.shape === "apm") return cat.agentIndex[a];
|
|
338
|
+
return path.join(cat.dir, "agents", `${a}.md`);
|
|
339
|
+
}
|
|
340
|
+
function agentMeta(cat, a) {
|
|
341
|
+
const fm = (fs.readFileSync(agentPath(cat, a), "utf8").split("---")[1] || "");
|
|
342
|
+
const get = (k) => { const m = fm.match(new RegExp("^" + k + ":\\s*(.+)$", "m")); return m ? m[1].trim() : ""; };
|
|
343
|
+
return { name: get("name"), description: get("description"), tools: get("tools"), model: get("model") };
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/* Same gate the skills get, applied to agents before anything is copied. */
|
|
347
|
+
function checkAgent(cat, a) {
|
|
348
|
+
const problems = [];
|
|
349
|
+
const p = agentPath(cat, a);
|
|
350
|
+
if (!p || !fs.existsSync(p)) return [`agent '${a}' missing`];
|
|
351
|
+
const m = agentMeta(cat, a);
|
|
352
|
+
if (m.name !== a) problems.push(`frontmatter name '${m.name}' != filename '${a}'`);
|
|
353
|
+
if (!m.description) problems.push("frontmatter missing description");
|
|
354
|
+
return problems;
|
|
355
|
+
}
|
|
356
|
+
function packsOf(cat) {
|
|
357
|
+
if (cat.packsData) return Object.keys(cat.packsData).length ? cat.packsData : null;
|
|
358
|
+
const p = path.join(cat.dir, "packs.json");
|
|
359
|
+
return fs.existsSync(p) ? readJSON(p).packs : null;
|
|
360
|
+
}
|
|
361
|
+
/* RFC-0033: a pack installs together with the packs it `requires` (in practice, core), so
|
|
362
|
+
one --pack still delivers a role's whole toolkit now that shared skills have one home. */
|
|
363
|
+
function packMembers(ps, name, key) {
|
|
364
|
+
const req = (ps[name].requires || []).filter((r) => ps[r]);
|
|
365
|
+
const out = [];
|
|
366
|
+
for (const n of [...req, name]) for (const x of ps[n][key] || []) if (!out.includes(x)) out.push(x);
|
|
367
|
+
if (req.length) console.log(`pack '${name}' includes ${req.join(", ")}`);
|
|
368
|
+
return out;
|
|
369
|
+
}
|
|
370
|
+
function profilesOf(cat) {
|
|
371
|
+
const p = path.join(cat.dir, "profiles.json");
|
|
372
|
+
return fs.existsSync(p) ? readJSON(p).profiles : null;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/* Structural gate (RFC-0003): a skill must pass before it is copied anywhere. */
|
|
376
|
+
function checkSkill(cat, s) {
|
|
377
|
+
const problems = [];
|
|
378
|
+
const dir = skillDir(cat, s);
|
|
379
|
+
if (!dir || !fs.existsSync(path.join(dir, "SKILL.md"))) problems.push("SKILL.md missing");
|
|
380
|
+
if (cat.shape === "apm") {
|
|
381
|
+
// agentbundle skills carry name/description in SKILL.md frontmatter (agentskills.io);
|
|
382
|
+
// version lives in pack.toml and there is no model tier — gate only the portable fields.
|
|
383
|
+
if (!problems.length && !manifestOf(cat, s).description) problems.push("SKILL.md frontmatter missing description");
|
|
384
|
+
return problems;
|
|
385
|
+
}
|
|
386
|
+
let m = null;
|
|
387
|
+
try { m = manifestOf(cat, s); } catch (e) { problems.push("manifest.json missing or invalid JSON"); }
|
|
388
|
+
if (m) {
|
|
389
|
+
if (m.name !== s) problems.push(`manifest name '${m.name}' != folder '${s}'`);
|
|
390
|
+
if (!m.description) problems.push("manifest missing description");
|
|
391
|
+
if (!m.version) problems.push("manifest missing version");
|
|
392
|
+
if (!m.model || !m.model.tier) problems.push("manifest missing model.tier");
|
|
393
|
+
const fmName = (() => {
|
|
394
|
+
try {
|
|
395
|
+
const fm = fs.readFileSync(path.join(dir, "SKILL.md"), "utf8").split("---")[1] || "";
|
|
396
|
+
const match = fm.match(/^name:\s*(\S+)/m);
|
|
397
|
+
return match && match[1];
|
|
398
|
+
} catch (e) { return null; }
|
|
399
|
+
})();
|
|
400
|
+
if (fmName && fmName !== s) problems.push(`SKILL.md frontmatter name '${fmName}' != folder '${s}'`);
|
|
401
|
+
}
|
|
402
|
+
return problems;
|
|
403
|
+
}
|
|
404
|
+
function gate(cat, names) {
|
|
405
|
+
let bad = 0;
|
|
406
|
+
for (const s of names) {
|
|
407
|
+
const problems = checkSkill(cat, s);
|
|
408
|
+
for (const p of problems) console.error(`refused ${s}: ${p}`);
|
|
409
|
+
if (problems.length) bad++;
|
|
410
|
+
}
|
|
411
|
+
if (bad) die(`${bad} skill(s) failed the structural check — nothing was installed`);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/* ---------- supply-chain scan (RFC-0022) ----------
|
|
415
|
+
A skill is instruction prose an agent will obey, and its scripts are code that runs on the
|
|
416
|
+
user's machine — so installing a third-party catalog is a supply-chain event. Anthropic's own
|
|
417
|
+
Skills guidance says to audit a skill's files for "unexpected network calls, file access
|
|
418
|
+
patterns, or operations that don't match the Skill's stated purpose"; this automates that first
|
|
419
|
+
pass. It is a HEURISTIC that reports — it never blocks and never replaces reading the skill.
|
|
420
|
+
|
|
421
|
+
Two rule sets, deliberately different:
|
|
422
|
+
- SCRIPT_RULES run over executable files (real code): network, exec, credential, obfuscation.
|
|
423
|
+
- PROSE_RULES run over every markdown file (SKILL.md, reference.md, templates, examples) and only match *instructions to misbehave* — not security
|
|
424
|
+
vocabulary, so a skill that legitimately discusses injection (threat-model) isn't flagged. */
|
|
425
|
+
|
|
426
|
+
const SCRIPT_EXT = new Set([".py", ".js", ".mjs", ".cjs", ".sh", ".bash", ".zsh", ".rb", ".pl", ".ps1"]);
|
|
427
|
+
|
|
428
|
+
const SCRIPT_RULES = [
|
|
429
|
+
{ id: "exec-remote", sev: "🟥", owasp: ["AST01", "AST02"], why: "downloads and executes remote content — the classic supply-chain payload",
|
|
430
|
+
re: /(curl|wget)[^\n|]*\|\s*(sudo\s+)?(ba|z|)sh|eval\s*\(\s*(requests|urllib|fetch)|base64\s+(-d|--decode)[^\n|]*\|\s*(ba|z|)sh/i },
|
|
431
|
+
{ id: "shell-exec", sev: "🟧", owasp: ["AST03", "LLM06"], why: "executes shell commands",
|
|
432
|
+
re: /\b(os\.system|subprocess\.(run|call|Popen|check_output)|child_process|execSync|spawnSync|shell_exec|`[^`\n]*\$\()/ },
|
|
433
|
+
{ id: "network", sev: "🟧", owasp: ["AST03", "LLM02"], why: "makes outbound network calls",
|
|
434
|
+
re: /\b(requests\.(get|post|put|patch|delete|head|request|Session)\b|urllib\.request|urlopen\s*\(|http\.client|httpx\.|aiohttp\.|axios\.|node-fetch|\bfetch\s*\(\s*["'`]https?:|curl\s+https?:|wget\s+https?:)/i },
|
|
435
|
+
{ id: "credentials", sev: "🟧", owasp: ["AST01", "LLM02"], why: "reads credentials or secret material",
|
|
436
|
+
re: /\b(os\.environ|process\.env)\b[^\n]{0,40}(TOKEN|KEY|SECRET|PASSWORD|CREDENTIAL)|~\/\.(aws|ssh|npmrc|netrc)|\.env\b|id_rsa/i },
|
|
437
|
+
{ id: "broad-fs", sev: "🟨", owasp: ["AST03", "LLM06"], why: "writes outside the skill's own directory",
|
|
438
|
+
re: /\b(shutil\.rmtree|rm\s+-rf\s+[~/]|open\s*\(\s*["'`]\/(etc|usr|bin)|fs\.(unlink|rmSync)\s*\([^)]*\.\.\/)/ },
|
|
439
|
+
];
|
|
440
|
+
|
|
441
|
+
const PROSE_RULES = [
|
|
442
|
+
{ id: "instruction-override", sev: "🟥", owasp: ["AST01", "LLM01"], why: "tells the agent to ignore its prior instructions",
|
|
443
|
+
re: /ignore\s+(all\s+)?(previous|prior|earlier|above|preceding)\s+(instructions|prompts|rules)/i },
|
|
444
|
+
{ id: "conceal-from-user", sev: "🟥", owasp: ["AST01", "LLM01"], why: "tells the agent to hide actions from the user",
|
|
445
|
+
re: /(do\s*n[o']?t|never|without)\s+(tell|telling|inform|informing|notify|notifying|mention|mentioning)\s+(the\s+)?(user|human)/i },
|
|
446
|
+
{ id: "memory-overwrite", sev: "🟥", owasp: ["AST01", "LLM01"], why: "instructs the agent to rewrite its own persistent memory/identity",
|
|
447
|
+
re: /(update|overwrite|append\s+to|modify)\s+your\s+(own\s+)?(SOUL|MEMORY|CLAUDE|AGENTS)\.md/i },
|
|
448
|
+
{ id: "prose-exfil", sev: "🟧", owasp: ["AST01", "LLM02"], why: "instructs the agent to send data to an external endpoint",
|
|
449
|
+
re: /\b(POST|send|upload|transmit)\b[^\n.]{0,60}\bto\s+https?:\/\//i },
|
|
450
|
+
{ id: "remote-instructions", sev: "🟧", owasp: ["AST05", "LLM01"],
|
|
451
|
+
why: "tells the agent to fetch instructions from a URL at run time — what it obeys can change after you reviewed it",
|
|
452
|
+
re: /\b(fetch|download|load|read|follow|pull)\b[^\n.]{0,40}\b(instructions|rules|prompt|system prompt|skill|directives)\b[^\n.]{0,30}\bfrom\s+(https?:\/\/|<?url)/i },
|
|
453
|
+
];
|
|
454
|
+
|
|
455
|
+
function walkFiles(dir, out = []) {
|
|
456
|
+
for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
457
|
+
const p = path.join(dir, e.name);
|
|
458
|
+
if (e.isDirectory()) walkFiles(p, out);
|
|
459
|
+
else out.push(p);
|
|
460
|
+
}
|
|
461
|
+
return out;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/* Scan one skill. Returns [{sev, id, why, file, line, text}] — worst first. */
|
|
465
|
+
function scanSkill(cat, s) {
|
|
466
|
+
const dir = skillDir(cat, s);
|
|
467
|
+
const findings = [];
|
|
468
|
+
if (!dir || !fs.existsSync(dir)) return findings;
|
|
469
|
+
for (const file of walkFiles(dir)) {
|
|
470
|
+
const rel = path.relative(dir, file);
|
|
471
|
+
const ext = path.extname(file).toLowerCase();
|
|
472
|
+
const isScript = SCRIPT_EXT.has(ext);
|
|
473
|
+
const isProse = ext === ".md";
|
|
474
|
+
if (!isScript && !isProse) continue;
|
|
475
|
+
let body = "";
|
|
476
|
+
try { body = fs.readFileSync(file, "utf8"); } catch (e) { continue; }
|
|
477
|
+
const rules = isScript ? SCRIPT_RULES : PROSE_RULES;
|
|
478
|
+
body.split(/\r?\n/).forEach((line, i) => {
|
|
479
|
+
if (line.length > 400) return; // minified/data line — not reviewable prose or code
|
|
480
|
+
for (const r of rules) {
|
|
481
|
+
if (r.re.test(line))
|
|
482
|
+
findings.push({ sev: r.sev, id: r.id, why: r.why, owasp: r.owasp, file: rel, line: i + 1, text: line.trim().slice(0, 120) });
|
|
483
|
+
}
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
findings.push(...permissionGaps(cat, s, findings));
|
|
487
|
+
const order = { "🟥": 0, "🟧": 1, "🟨": 2, "⚪": 3 };
|
|
488
|
+
return findings.sort((a, b) => order[a.sev] - order[b.sev]);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/* RFC-0039: compare what a skill's scripts do (the scan's script findings) with what its
|
|
492
|
+
manifest says they do. A capability the code shows and the manifest doesn't declare is the
|
|
493
|
+
finding that matters most: it is either an honest omission or something hiding. */
|
|
494
|
+
function permissionsOf(cat, s) {
|
|
495
|
+
if (cat.shape === "apm") return null;
|
|
496
|
+
try { return manifestOf(cat, s).permissions || null; } catch (e) { return null; }
|
|
497
|
+
}
|
|
498
|
+
function permissionGaps(cat, s, findings) {
|
|
499
|
+
const dir = skillDir(cat, s);
|
|
500
|
+
if (!dir || !fs.existsSync(path.join(dir, "scripts"))) return [];
|
|
501
|
+
const perm = permissionsOf(cat, s);
|
|
502
|
+
if (!perm)
|
|
503
|
+
return [{ sev: "🟨", id: "no-permissions", owasp: ["AST03", "AST10"], file: "manifest.json", line: 0,
|
|
504
|
+
why: "ships scripts but declares no permissions (network, commands, files) — nothing to check them against",
|
|
505
|
+
text: "add a `permissions` block to manifest.json (RFC-0039)" }];
|
|
506
|
+
const code = findings.filter((f) => SCRIPT_EXT.has(path.extname(f.file).toLowerCase()));
|
|
507
|
+
const gaps = [];
|
|
508
|
+
const gap = (id, f, why) => gaps.push({ sev: "🟥", id, owasp: ["AST03", "AST01"], why, file: f.file, line: f.line, text: f.text });
|
|
509
|
+
for (const f of code) {
|
|
510
|
+
if ((f.id === "network" || f.id === "exec-remote") && !(perm.network || []).length)
|
|
511
|
+
gap("undeclared-network", f, "makes network calls, but the manifest declares no network hosts");
|
|
512
|
+
if ((f.id === "shell-exec" || f.id === "exec-remote") && !(perm.commands || []).length)
|
|
513
|
+
gap("undeclared-commands", f, "runs commands, but the manifest declares none");
|
|
514
|
+
if (f.id === "broad-fs" && perm.files !== "anywhere")
|
|
515
|
+
gap("undeclared-files", f, `writes outside its own paths, but the manifest declares files: "${perm.files}"`);
|
|
516
|
+
}
|
|
517
|
+
return gaps;
|
|
518
|
+
}
|
|
519
|
+
function permissionSummary(perm) {
|
|
520
|
+
if (!perm) return "no permissions declared";
|
|
521
|
+
const net = (perm.network || []).length ? `contacts ${perm.network.join(", ")}` : "no network";
|
|
522
|
+
const cmds = (perm.commands || []).length ? `runs ${perm.commands.map((c) => c === "*" ? "commands you configure" : c).join(", ")}` : "runs no commands";
|
|
523
|
+
const files = { none: "writes nothing", "named-paths": "writes only paths you name", project: "writes inside the project", anywhere: "writes anywhere" }[perm.files] || `files: ${perm.files}`;
|
|
524
|
+
return `${net} · ${cmds} · ${files}`;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
function printScan(cat, names, { compact = false } = {}) {
|
|
528
|
+
let total = 0, worst = null;
|
|
529
|
+
for (const s of names) {
|
|
530
|
+
const f = scanSkill(cat, s);
|
|
531
|
+
if (!f.length) continue;
|
|
532
|
+
total += f.length;
|
|
533
|
+
if (!worst || f[0].sev === "🟥") worst = worst === "🟥" ? worst : f[0].sev;
|
|
534
|
+
console.log(`\n${s}`);
|
|
535
|
+
for (const x of (compact ? f.slice(0, 3) : f))
|
|
536
|
+
console.log(` ${x.sev} ${x.id} — ${x.why} [${x.owasp.join(", ")}]\n ${x.file}:${x.line} ${x.text}`);
|
|
537
|
+
if (compact && f.length > 3) console.log(` … ${f.length - 3} more (skilldrop scan ${s} --from ${cat.source})`);
|
|
538
|
+
}
|
|
539
|
+
return { total, worst };
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
function scan(args) {
|
|
543
|
+
const cat = resolveCatalog(args.flags.from);
|
|
544
|
+
const names = args._.length ? args._.slice() : skillsIn(cat);
|
|
545
|
+
for (const s of names) if (!skillExists(cat, s)) die(`unknown skill '${s}' in catalog '${cat.source}'`);
|
|
546
|
+
|
|
547
|
+
if (args.flags.json) {
|
|
548
|
+
const out = names.map((s) => ({ skill: s, findings: scanSkill(cat, s) })).filter((r) => r.findings.length);
|
|
549
|
+
return emitJSON({
|
|
550
|
+
catalog: cat.source, scanned: names.length,
|
|
551
|
+
flagged: out.length, findingCount: out.reduce((n, r) => n + r.findings.length, 0), skills: out,
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
console.log(`Scanning ${names.length} skill(s) in catalog '${cat.source}' for supply-chain patterns…`);
|
|
555
|
+
const { total } = printScan(cat, names);
|
|
556
|
+
if (!total) {
|
|
557
|
+
console.log("\nNo flagged patterns found.");
|
|
558
|
+
} else {
|
|
559
|
+
console.log(`\n${total} flagged pattern(s). These are HEURISTICS, not verdicts — a match can be` +
|
|
560
|
+
` entirely legitimate (a skill that is *about* security, or a script that genuinely needs the network).`);
|
|
561
|
+
console.log("Read the cited lines before installing. Nothing here is executed by skilldrop; installs copy files only.");
|
|
562
|
+
console.log("Tags are OWASP Agentic Skills Top 10 (AST) and LLM Top 10 2025 (LLM) IDs — guides/reference/owasp-mapping.md.");
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/* ---------- install targets & ledger ---------- */
|
|
567
|
+
|
|
568
|
+
/* Skill directories per tool: [project path, user path]. A null user path means the tool only
|
|
569
|
+
reads skills from the repo. Paths are the ones each tool's own docs name, recorded with sources
|
|
570
|
+
in docs/designs/ide-primitive-coverage.md. .agents/skills is shared: Codex, Antigravity and
|
|
571
|
+
Copilot CLI all read it, so one project install reaches all three. */
|
|
572
|
+
const SKILL_DIRS = {
|
|
573
|
+
claude: [[".claude", "skills"], [".claude", "skills"]],
|
|
574
|
+
cursor: [[".cursor", "skills"], null],
|
|
575
|
+
kiro: [[".kiro", "skills"], null],
|
|
576
|
+
codex: [[".agents", "skills"], [".codex", "skills"]],
|
|
577
|
+
antigravity: [[".agents", "skills"], [".gemini", "antigravity-cli", "skills"]],
|
|
578
|
+
copilot: [[".github", "skills"], [".copilot", "skills"]],
|
|
579
|
+
};
|
|
580
|
+
|
|
581
|
+
function target(flags) {
|
|
582
|
+
if (flags.dest) return { dest: path.resolve(flags.dest), ide: "generic" };
|
|
583
|
+
const ide = flags.ide || "claude";
|
|
584
|
+
const dirs = SKILL_DIRS[ide];
|
|
585
|
+
if (!dirs) die(`unknown --ide '${ide}' (${Object.keys(SKILL_DIRS).join(" | ")}; use --dest for anything else)`);
|
|
586
|
+
const [proj, user] = dirs;
|
|
587
|
+
// --local is a project install the repo never sees (see localExclude), so it implies --project.
|
|
588
|
+
if (flags.project || flags.local || !user) return { dest: path.resolve(...proj), ide };
|
|
589
|
+
return { dest: path.join(os.homedir(), ...user), ide };
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/* Claude Code tool name -> Kiro built-in tool name. Confirmed against
|
|
593
|
+
kiro.dev/docs/cli/reference/built-in-tools/ — not inferred. Agent configs use these
|
|
594
|
+
simplified names; Kiro's *hook* matchers use internal names (fs_read, execute_bash)
|
|
595
|
+
instead, and mixing the two silently grants nothing. */
|
|
596
|
+
const KIRO_TOOLS = {
|
|
597
|
+
Read: "read", Grep: "grep", Glob: "glob", Bash: "shell",
|
|
598
|
+
Write: "write", Edit: "write", WebSearch: "web_search", WebFetch: "web_fetch",
|
|
599
|
+
};
|
|
600
|
+
|
|
601
|
+
function kiroAgentJSON(meta, body) {
|
|
602
|
+
const declared = meta.tools ? meta.tools.split(",").map((s) => s.trim()).filter(Boolean) : [];
|
|
603
|
+
const tools = [], unmapped = [];
|
|
604
|
+
for (const d of declared) (KIRO_TOOLS[d] ? tools : unmapped).push(KIRO_TOOLS[d] || d);
|
|
605
|
+
// The prompt is inlined rather than a file:// reference: the docs' example is
|
|
606
|
+
// relative, but whether it resolves against the workspace root or the JSON file is
|
|
607
|
+
// not stated, and a wrong path fails silently. One self-contained file cannot.
|
|
608
|
+
const agent = { name: meta.name, description: meta.description, prompt: body, tools };
|
|
609
|
+
// allowedTools is deliberately omitted — it is auto-approval, so leaving it out means
|
|
610
|
+
// the user is prompted per tool call. Also sidesteps kirodotdev/Kiro#6714, where
|
|
611
|
+
// allowedTools does not load as configured.
|
|
612
|
+
return { json: JSON.stringify(agent, null, 2) + "\n", unmapped: [...new Set(unmapped)] };
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
/* Antigravity agent markdown (RFC-0013). Only mappings confirmed in Antigravity's own
|
|
616
|
+
subagents doc are listed. There is no glob-style tool in its file tooling
|
|
617
|
+
(view_file / list_dir / grep_search / write_to_file), so Glob is dropped and named
|
|
618
|
+
rather than bent into list_dir — a different operation. */
|
|
619
|
+
const ANTIGRAVITY_TOOLS = { Read: "view_file", Grep: "grep_search", Bash: "run_command" };
|
|
620
|
+
const ANTIGRAVITY_MODELS = new Set(["inherit", "flash", "pro"]);
|
|
621
|
+
|
|
622
|
+
function antigravityAgentMD(meta, body) {
|
|
623
|
+
const declared = meta.tools ? meta.tools.split(",").map((s) => s.trim()).filter(Boolean) : [];
|
|
624
|
+
const tools = [], unmapped = [];
|
|
625
|
+
for (const d of declared) (ANTIGRAVITY_TOOLS[d] ? tools : unmapped).push(ANTIGRAVITY_TOOLS[d] || d);
|
|
626
|
+
// YAML double-quoted scalars are a superset of JSON strings, so JSON.stringify is a
|
|
627
|
+
// correct quoter here — descriptions contain em-dashes and embedded quotes.
|
|
628
|
+
const lines = [`name: ${JSON.stringify(meta.name)}`, `description: ${JSON.stringify(meta.description)}`];
|
|
629
|
+
if (tools.length) lines.push(`tools: [${tools.join(", ")}]`);
|
|
630
|
+
lines.push("subagent: true"); // required, or invoke_subagent cannot reach it
|
|
631
|
+
if (ANTIGRAVITY_MODELS.has(meta.model)) lines.push(`model: ${meta.model}`);
|
|
632
|
+
return { md: `---\n${lines.join("\n")}\n---\n\n${body}\n`, unmapped: [...new Set(unmapped)] };
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/* Codex agent TOML. Schema confirmed at learn.chatgpt.com/docs/agent-configuration/subagents:
|
|
636
|
+
name, description and developer_instructions are required; unknown fields are rejected.
|
|
637
|
+
sandbox_mode is deliberately NOT emitted — Codex has no `tools` field, permissions come
|
|
638
|
+
from sandbox_mode, and its permitted values are unconfirmed. Omitting it inherits the
|
|
639
|
+
parent session, which is the only behaviour that can neither break the agent nor
|
|
640
|
+
silently widen what it may do. */
|
|
641
|
+
function codexAgentTOML(meta, body) {
|
|
642
|
+
const esc = (s) => s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
643
|
+
const safe = body.includes('"""') ? body.replace(/"""/g, '\\"\\"\\"') : body;
|
|
644
|
+
return `name = "${esc(meta.name)}"\n` +
|
|
645
|
+
`description = "${esc(meta.description)}"\n` +
|
|
646
|
+
`developer_instructions = """\n${safe}\n"""\n`;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
/* Only plain-copy targets ship in RFC-0012. Everything else needs a projection the
|
|
650
|
+
install-target model (RFC-0010) has not settled — so say so instead of guessing a path. */
|
|
651
|
+
const AGENT_MANUAL = {
|
|
652
|
+
cursor: "a custom mode — Cursor has no agent file format",
|
|
653
|
+
};
|
|
654
|
+
function agentTarget(flags) {
|
|
655
|
+
if (flags.dest) return { dest: path.resolve(flags.dest), ide: "generic" };
|
|
656
|
+
const ide = flags.ide || "claude";
|
|
657
|
+
if (ide === "claude")
|
|
658
|
+
return flags.project
|
|
659
|
+
? { dest: path.resolve(".claude", "agents"), ide }
|
|
660
|
+
: { dest: path.join(os.homedir(), ".claude", "agents"), ide };
|
|
661
|
+
// Kiro and Copilot agent dirs are repo-relative by convention, so --project is implied.
|
|
662
|
+
if (ide === "kiro") return { dest: path.resolve(".kiro", "agents"), ide };
|
|
663
|
+
if (ide === "copilot") return { dest: path.resolve(".github", "agents"), ide };
|
|
664
|
+
if (ide === "codex")
|
|
665
|
+
return flags.project
|
|
666
|
+
? { dest: path.resolve(".codex", "agents"), ide }
|
|
667
|
+
: { dest: path.join(os.homedir(), ".codex", "agents"), ide };
|
|
668
|
+
if (ide === "antigravity")
|
|
669
|
+
return flags.project
|
|
670
|
+
? { dest: path.resolve(".agents", "agents"), ide }
|
|
671
|
+
: { dest: path.join(os.homedir(), ".gemini", "config", "agents"), ide };
|
|
672
|
+
const hint = AGENT_MANUAL[ide];
|
|
673
|
+
die(`--agent has no target for '${ide}'${hint ? ` — it needs ${hint}` : ""}.\n` +
|
|
674
|
+
` Copy it by hand (see agents/README.md), or use --dest <dir>. Tracked in RFC-0012.`);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
/* Write one agent into a target, projecting only where the target's format demands it.
|
|
678
|
+
Returns the filename written plus any warning worth printing. */
|
|
679
|
+
function writeAgent(cat, a, dest, ide) {
|
|
680
|
+
const src = agentPath(cat, a);
|
|
681
|
+
if (ide === "kiro") {
|
|
682
|
+
const raw = fs.readFileSync(src, "utf8");
|
|
683
|
+
const body = raw.split("---").slice(2).join("---").trim();
|
|
684
|
+
const { json, unmapped } = kiroAgentJSON(agentMeta(cat, a), body);
|
|
685
|
+
fs.writeFileSync(path.join(dest, `${a}.json`), json);
|
|
686
|
+
return { file: `${a}.json`, warn: unmapped.length
|
|
687
|
+
? ` ${a}: no Kiro equivalent for ${unmapped.join(", ")} — dropped from tools, add by hand if needed`
|
|
688
|
+
: null };
|
|
689
|
+
}
|
|
690
|
+
if (ide === "antigravity") {
|
|
691
|
+
const raw = fs.readFileSync(src, "utf8");
|
|
692
|
+
const body = raw.split("---").slice(2).join("---").trim();
|
|
693
|
+
const { md, unmapped } = antigravityAgentMD(agentMeta(cat, a), body);
|
|
694
|
+
fs.writeFileSync(path.join(dest, `${a}.md`), md);
|
|
695
|
+
return { file: `${a}.md`, warn: unmapped.length
|
|
696
|
+
? ` ${a}: no Antigravity equivalent for ${unmapped.join(", ")} — dropped from tools, add by hand if needed`
|
|
697
|
+
: null };
|
|
698
|
+
}
|
|
699
|
+
if (ide === "codex") {
|
|
700
|
+
const raw = fs.readFileSync(src, "utf8");
|
|
701
|
+
const body = raw.split("---").slice(2).join("---").trim();
|
|
702
|
+
fs.writeFileSync(path.join(dest, `${a}.toml`), codexAgentTOML(agentMeta(cat, a), body));
|
|
703
|
+
return { file: `${a}.toml`, warn: null };
|
|
704
|
+
}
|
|
705
|
+
const name = ide === "copilot" ? `${a}.agent.md` : `${a}.md`;
|
|
706
|
+
fs.copyFileSync(src, path.join(dest, name));
|
|
707
|
+
return { file: name, warn: null };
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
function ledger(dest) {
|
|
711
|
+
const p = path.join(dest, LEDGER);
|
|
712
|
+
return { path: p, data: fs.existsSync(p) ? readJSON(p) : {} };
|
|
713
|
+
}
|
|
714
|
+
// An empty ledger is removed rather than left behind, so uninstalling the last skill leaves no trace.
|
|
715
|
+
function saveLedger(l) {
|
|
716
|
+
if (!Object.keys(l.data).length) return fs.rmSync(l.path, { force: true });
|
|
717
|
+
fs.writeFileSync(l.path, JSON.stringify(l.data, null, 2) + "\n");
|
|
718
|
+
}
|
|
719
|
+
/* Ledger values: {version, source}; legacy plain strings mean a bundled install. */
|
|
720
|
+
function lver(v) { return typeof v === "string" ? v : v.version; }
|
|
721
|
+
function lsrc(v) { return typeof v === "string" ? BUNDLED : v.source || BUNDLED; }
|
|
722
|
+
|
|
723
|
+
/* Ask before a destructive step. Only a person at a terminal is asked: scripts, CI and pipes
|
|
724
|
+
(stdin not a TTY) proceed as they always did, and --yes skips the question. */
|
|
725
|
+
function confirm(question, flags) {
|
|
726
|
+
if (flags.yes || flags["dry-run"] || !process.stdin.isTTY) return true;
|
|
727
|
+
process.stdout.write(`${question} [y/N] `);
|
|
728
|
+
const buf = Buffer.alloc(256);
|
|
729
|
+
let n = 0;
|
|
730
|
+
try { n = fs.readSync(0, buf, 0, buf.length, null); } catch (e) { return false; }
|
|
731
|
+
return /^y(es)?$/i.test(buf.toString("utf8", 0, n).trim());
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
/* Files in an installed skill that differ from the baseline the ledger recorded at copy time:
|
|
735
|
+
the user's edits. Empty when there is no baseline (installed before RFC-0032). */
|
|
736
|
+
function editedFiles(dest, s, entry) {
|
|
737
|
+
if (!entry || typeof entry !== "object" || !entry.files) return [];
|
|
738
|
+
const out = [];
|
|
739
|
+
for (const [rel, h] of Object.entries(entry.files)) {
|
|
740
|
+
let mine = null;
|
|
741
|
+
try { mine = sha256(fs.readFileSync(path.join(dest, s, ...rel.split("/")))); } catch (e) { /* deleted */ }
|
|
742
|
+
if (mine !== null && mine !== h) out.push(rel);
|
|
743
|
+
}
|
|
744
|
+
return out;
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
/* --local: try skills in a repo you don't own. The install is a normal project install, and
|
|
748
|
+
every path it writes is listed in the repo's own exclude file (.git/info/exclude, which git
|
|
749
|
+
reads like .gitignore but never commits), so `git status` stays clean and nothing can be
|
|
750
|
+
committed by accident. The entries live in one marker-fenced block this tool owns. */
|
|
751
|
+
const EXCLUDE_OPEN = "# >>> skilldrop --local >>>", EXCLUDE_CLOSE = "# <<< skilldrop --local <<<";
|
|
752
|
+
function excludePath(root) {
|
|
753
|
+
try {
|
|
754
|
+
const out = execFileSync("git", ["rev-parse", "--git-path", "info/exclude"],
|
|
755
|
+
{ cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
756
|
+
if (out) return path.resolve(root, out);
|
|
757
|
+
} catch (e) { /* no git binary */ }
|
|
758
|
+
return path.join(root, ".git", "info", "exclude");
|
|
759
|
+
}
|
|
760
|
+
function readExclude(root) {
|
|
761
|
+
const p = excludePath(root);
|
|
762
|
+
const body = readIfPresent(p);
|
|
763
|
+
const a = body.indexOf(EXCLUDE_OPEN), b = body.indexOf(EXCLUDE_CLOSE);
|
|
764
|
+
const entries = a >= 0 && b > a
|
|
765
|
+
? body.slice(a + EXCLUDE_OPEN.length, b).split("\n").map((x) => x.trim()).filter(Boolean) : [];
|
|
766
|
+
const rest = a >= 0 && b > a ? body.slice(0, a) + body.slice(b + EXCLUDE_CLOSE.length) : body;
|
|
767
|
+
return { p, entries, rest: rest.replace(/\n{3,}/g, "\n\n") };
|
|
768
|
+
}
|
|
769
|
+
function writeExclude(root, entries) {
|
|
770
|
+
const { p, rest } = readExclude(root);
|
|
771
|
+
const sorted = [...new Set(entries)].sort();
|
|
772
|
+
let body = rest.replace(/\s+$/, "");
|
|
773
|
+
if (sorted.length) body += `${body ? "\n\n" : ""}${EXCLUDE_OPEN}\n${sorted.join("\n")}\n${EXCLUDE_CLOSE}`;
|
|
774
|
+
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
775
|
+
fs.writeFileSync(p, body ? body + "\n" : "");
|
|
776
|
+
return p;
|
|
777
|
+
}
|
|
778
|
+
// Exclude patterns are repo-root-relative and anchored with a leading slash.
|
|
779
|
+
function excludeEntry(root, abs, dir) { return "/" + path.relative(root, abs).split(path.sep).join("/") + (dir ? "/" : ""); }
|
|
780
|
+
function localPaths(root, dest, ide, s) {
|
|
781
|
+
const out = [excludeEntry(root, path.join(dest, s), true)];
|
|
782
|
+
const w = wiringPath(ide, dest, s);
|
|
783
|
+
if (w && ide === "cursor") out.push(excludeEntry(root, w, false));
|
|
784
|
+
return out;
|
|
785
|
+
}
|
|
786
|
+
function addLocal(dest, ide, names) {
|
|
787
|
+
const root = gitRoot(process.cwd());
|
|
788
|
+
const { entries } = readExclude(root);
|
|
789
|
+
const next = entries.concat(excludeEntry(root, path.join(dest, LEDGER), false));
|
|
790
|
+
for (const s of names) next.push(...localPaths(root, dest, ide, s));
|
|
791
|
+
return writeExclude(root, next);
|
|
792
|
+
}
|
|
793
|
+
/* Called on every uninstall, --local or not: dropping an entry that isn't there is a no-op, and
|
|
794
|
+
the ledger's own entry goes once no skill in that folder is excluded any more. */
|
|
795
|
+
function removeLocal(dest, ide, names) {
|
|
796
|
+
const root = gitRoot(process.cwd());
|
|
797
|
+
if (!root) return null;
|
|
798
|
+
const { entries } = readExclude(root);
|
|
799
|
+
if (!entries.length) return null;
|
|
800
|
+
const drop = new Set(names.flatMap((s) => localPaths(root, dest, ide, s)));
|
|
801
|
+
let next = entries.filter((e) => !drop.has(e));
|
|
802
|
+
const destPrefix = excludeEntry(root, dest, true);
|
|
803
|
+
const ledgerEntry = excludeEntry(root, path.join(dest, LEDGER), false);
|
|
804
|
+
if (!next.some((e) => e !== ledgerEntry && e.startsWith(destPrefix))) next = next.filter((e) => e !== ledgerEntry);
|
|
805
|
+
if (next.length === entries.length) return null;
|
|
806
|
+
return writeExclude(root, next);
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
/* Wiring = the pointer file a target needs to *find* a skill.
|
|
810
|
+
Cursor needs one: .cursor/skills/ is not a discovery path, so the .mdc rule is what
|
|
811
|
+
makes the skill reachable (and it carries alwaysApply:false, so it stays inert until matched).
|
|
812
|
+
Kiro no longer does: Kiro discovers .kiro/skills/ natively (Agent Skills, 2026-02-05), and a
|
|
813
|
+
steering file with no frontmatter is always-included — so the old shim pinned one description
|
|
814
|
+
per installed skill into every session's context to point at a folder Kiro already reads.
|
|
815
|
+
wiringPath still resolves the Kiro path so uninstall and install can clean up legacy shims. */
|
|
816
|
+
const KIRO_SHIM_PREFIX = "When the user requests the following, defer to the instructions in .kiro/skills/";
|
|
817
|
+
|
|
818
|
+
function wiringPath(ide, dest, s) {
|
|
819
|
+
const base = path.dirname(dest); // .cursor/ or .kiro/
|
|
820
|
+
if (ide === "cursor") return path.join(base, "rules", `${s}.mdc`);
|
|
821
|
+
if (ide === "kiro") return path.join(base, "steering", `${s}.md`);
|
|
822
|
+
return null;
|
|
823
|
+
}
|
|
824
|
+
function writeWiring(ide, dest, s, desc) {
|
|
825
|
+
if (ide === "kiro") return clearKiroShim(dest, s);
|
|
826
|
+
const p = wiringPath(ide, dest, s);
|
|
827
|
+
if (!p) return null;
|
|
828
|
+
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
829
|
+
fs.writeFileSync(p, `---\ndescription: ${desc.replace(/\n/g, " ")}\nglobs:\nalwaysApply: false\n---\nFollow the instructions in .cursor/skills/${s}/SKILL.md when the user requests this task.\n`);
|
|
830
|
+
return null;
|
|
831
|
+
}
|
|
832
|
+
/* Remove a steering file this CLI wrote in an earlier version, so upgrading drops the context
|
|
833
|
+
leak. Content-matched on purpose: a hand-written .kiro/steering/<skill>.md is the user's file
|
|
834
|
+
and is left alone (with a note) rather than deleted. */
|
|
835
|
+
function clearKiroShim(dest, s) {
|
|
836
|
+
const p = wiringPath("kiro", dest, s);
|
|
837
|
+
if (!fs.existsSync(p)) return null;
|
|
838
|
+
let body = "";
|
|
839
|
+
try { body = fs.readFileSync(p, "utf8"); } catch (e) { return null; }
|
|
840
|
+
if (!body.startsWith(KIRO_SHIM_PREFIX))
|
|
841
|
+
return ` kept ${path.relative(process.cwd(), p)} — not written by skilldrop, left for you to review`;
|
|
842
|
+
fs.rmSync(p, { force: true });
|
|
843
|
+
return ` removed stale steering shim ${path.relative(process.cwd(), p)} — Kiro discovers .kiro/skills/ natively`;
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
/* ---------- hooks (RFC-0006: emit per target, degrade where unsupported) ---------- */
|
|
847
|
+
|
|
848
|
+
function gitRoot(startDir) {
|
|
849
|
+
let d = path.resolve(startDir);
|
|
850
|
+
for (;;) {
|
|
851
|
+
if (fs.existsSync(path.join(d, ".git"))) return d;
|
|
852
|
+
const up = path.dirname(d);
|
|
853
|
+
if (up === d) return null;
|
|
854
|
+
d = up;
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
/* Where git actually reads the pre-commit hook. In a worktree or a submodule .git is a file
|
|
859
|
+
("gitdir: ..."), not a folder, and core.hooksPath can move hooks anywhere — so ask git, and
|
|
860
|
+
only fall back to .git/hooks when git isn't on the PATH. */
|
|
861
|
+
function preCommitPath(root) {
|
|
862
|
+
try {
|
|
863
|
+
const out = execFileSync("git", ["rev-parse", "--git-path", "hooks/pre-commit"],
|
|
864
|
+
{ cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
865
|
+
if (out) return path.resolve(root, out);
|
|
866
|
+
} catch (e) { /* no git binary, or not a repo after all */ }
|
|
867
|
+
return path.join(root, ".git", "hooks", "pre-commit");
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
// Append an idempotent, marker-fenced reminder to the repo's pre-commit hook. IDE-agnostic.
|
|
871
|
+
function writeGitPreCommitHook(root, skill, hook) {
|
|
872
|
+
const p = preCommitPath(root);
|
|
873
|
+
const marker = `skilldrop-hook:${skill}:pre-commit-review`;
|
|
874
|
+
let body = readIfPresent(p); // read-and-handle, not check-then-read
|
|
875
|
+
if (!body.startsWith("#!")) body = "#!/bin/sh\n" + body;
|
|
876
|
+
if (body.includes(marker)) return p; // already wired
|
|
877
|
+
const line = `echo "skilldrop: run /${hook.action} on your staged changes before committing — ${hook.description}"`;
|
|
878
|
+
body += `\n# >>> ${marker} >>>\n${line}\n# <<< ${marker} <<<\n`;
|
|
879
|
+
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
880
|
+
fs.writeFileSync(p, body);
|
|
881
|
+
fs.chmodSync(p, 0o755);
|
|
882
|
+
return p;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
// Merge a SessionStart command into Claude Code settings.json. Returns the path,
|
|
886
|
+
// or null if the file exists but is malformed (we never clobber unparseable JSON).
|
|
887
|
+
function writeClaudeSessionHook(settingsPath, skill, hook) {
|
|
888
|
+
let data = {};
|
|
889
|
+
const raw = readIfPresent(settingsPath, null);
|
|
890
|
+
if (raw !== null) {
|
|
891
|
+
try { data = JSON.parse(raw); }
|
|
892
|
+
catch (e) { return null; }
|
|
893
|
+
}
|
|
894
|
+
data.hooks = data.hooks || {};
|
|
895
|
+
data.hooks.SessionStart = data.hooks.SessionStart || [];
|
|
896
|
+
const marker = `skilldrop-hook:${skill}:session-start`;
|
|
897
|
+
if (!JSON.stringify(data.hooks.SessionStart).includes(marker)) {
|
|
898
|
+
const cmd = `echo "skilldrop: /${hook.action} is available — ${hook.description}" # ${marker}`;
|
|
899
|
+
data.hooks.SessionStart.push({ hooks: [{ type: "command", command: cmd }] });
|
|
900
|
+
fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
|
|
901
|
+
fs.writeFileSync(settingsPath, JSON.stringify(data, null, 2) + "\n");
|
|
902
|
+
}
|
|
903
|
+
return settingsPath;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
// Wire one skill's declared hooks; returns human-readable status lines.
|
|
907
|
+
/* Claude Code reads settings.local.json beside settings.json and never expects it committed, so a
|
|
908
|
+
--local install puts its session-start hook there instead of in the repo's shared settings. */
|
|
909
|
+
function claudeSettings(dest, local) { return path.join(path.dirname(dest), local ? "settings.local.json" : "settings.json"); }
|
|
910
|
+
|
|
911
|
+
function emitHooks(cat, skill, dest, ide, local) {
|
|
912
|
+
let hooks = [];
|
|
913
|
+
try { hooks = manifestOf(cat, skill).hooks || []; } catch (e) { return []; }
|
|
914
|
+
const lines = [];
|
|
915
|
+
for (const h of hooks) {
|
|
916
|
+
if (h.event === "pre-commit-review") {
|
|
917
|
+
const root = gitRoot(process.cwd());
|
|
918
|
+
if (root) lines.push(` ${skill}: pre-commit reminder -> ${preCommitPath(root)}`);
|
|
919
|
+
else lines.push(` ${skill}: pre-commit-review skipped — no git repo at ${process.cwd()}`);
|
|
920
|
+
if (root) writeGitPreCommitHook(root, skill, h);
|
|
921
|
+
} else if (h.event === "session-start") {
|
|
922
|
+
if (ide === "claude") {
|
|
923
|
+
const sp = claudeSettings(dest, local);
|
|
924
|
+
const written = writeClaudeSessionHook(sp, skill, h);
|
|
925
|
+
lines.push(written ? ` ${skill}: session-start context -> ${sp}`
|
|
926
|
+
: ` ${skill}: session-start skipped — ${sp} is not valid JSON`);
|
|
927
|
+
} else {
|
|
928
|
+
lines.push(` ${skill}: session-start skipped — no hook mechanism for ${ide}`);
|
|
929
|
+
}
|
|
930
|
+
} else if (h.event === "on-demand") {
|
|
931
|
+
lines.push(` ${skill}: on-demand — invoke /${h.action} manually (no artifact needed)`);
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
return lines;
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
function removeHooksFor(skill, dest, ide) {
|
|
938
|
+
const root = gitRoot(process.cwd());
|
|
939
|
+
if (root) {
|
|
940
|
+
const p = preCommitPath(root);
|
|
941
|
+
const body = readIfPresent(p, null);
|
|
942
|
+
if (body !== null) {
|
|
943
|
+
const q = reEscape(skill);
|
|
944
|
+
const re = new RegExp(`\\n?# >>> skilldrop-hook:${q}:[^\\n]* >>>[\\s\\S]*?# <<< skilldrop-hook:${q}:[^\\n]* <<<\\n?`, "g");
|
|
945
|
+
const next = body.replace(re, "\n");
|
|
946
|
+
if (next !== body) fs.writeFileSync(p, next);
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
if (ide === "claude") for (const sp of [claudeSettings(dest, false), claudeSettings(dest, true)]) {
|
|
950
|
+
const raw = readIfPresent(sp, null);
|
|
951
|
+
if (raw !== null) {
|
|
952
|
+
try {
|
|
953
|
+
const data = JSON.parse(raw);
|
|
954
|
+
const arr = data.hooks && data.hooks.SessionStart;
|
|
955
|
+
if (Array.isArray(arr)) {
|
|
956
|
+
const kept = arr.filter((e) => !JSON.stringify(e).includes(`skilldrop-hook:${skill}:`));
|
|
957
|
+
if (kept.length !== arr.length) { data.hooks.SessionStart = kept; fs.writeFileSync(sp, JSON.stringify(data, null, 2) + "\n"); }
|
|
958
|
+
}
|
|
959
|
+
} catch (e) { /* leave malformed settings untouched */ }
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
/* ---------- commands ---------- */
|
|
965
|
+
|
|
966
|
+
/* Packs that were renamed, so an old command or script keeps working. claude-api became
|
|
967
|
+
api-builder in 0.13.9: Claude Code reserves plugin names that start with "claude-". */
|
|
968
|
+
const PACK_ALIASES = { "claude-api": "api-builder" };
|
|
969
|
+
function packAlias(flags, ps) {
|
|
970
|
+
const n = flags.pack;
|
|
971
|
+
if (n && ps && !ps[n] && PACK_ALIASES[n] && ps[PACK_ALIASES[n]]) {
|
|
972
|
+
console.error(`note: pack '${n}' is now '${PACK_ALIASES[n]}' — use --pack ${PACK_ALIASES[n]}`);
|
|
973
|
+
flags.pack = PACK_ALIASES[n];
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
function expandNames(args, cat) {
|
|
978
|
+
if (args.flags.all) return skillsIn(cat);
|
|
979
|
+
if (args.flags.pack) {
|
|
980
|
+
const ps = packsOf(cat);
|
|
981
|
+
if (!ps) die(`catalog '${cat.source}' defines no packs — install skills by name`);
|
|
982
|
+
packAlias(args.flags, ps);
|
|
983
|
+
const p = ps[args.flags.pack];
|
|
984
|
+
if (!p) die(`unknown pack '${args.flags.pack}' in catalog '${cat.source}'`);
|
|
985
|
+
return packMembers(ps, args.flags.pack, "skills");
|
|
986
|
+
}
|
|
987
|
+
if (!args._.length) die("nothing to install — pass skill names, --pack <name>, or --all");
|
|
988
|
+
for (const s of args._) if (!skillExists(cat, s)) die(`unknown skill '${s}' in catalog '${cat.source}'`);
|
|
989
|
+
return args._.slice();
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
function copyOne(cat, s, dest, ide, l, notes) {
|
|
993
|
+
const m = manifestOf(cat, s);
|
|
994
|
+
fs.cpSync(skillDir(cat, s), path.join(dest, s), { recursive: true });
|
|
995
|
+
const note = writeWiring(ide, dest, s, m.description);
|
|
996
|
+
if (note && notes) notes.push(note);
|
|
997
|
+
l.data[s] = { version: m.version, source: cat.source, files: fileHashes(skillDir(cat, s)) };
|
|
998
|
+
if (cat.commit) l.data[s].commit = cat.commit;
|
|
999
|
+
return m;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
/* RFC-0032: per-file SHA-256 of a skill as the catalog shipped it, recorded in the ledger at
|
|
1003
|
+
copy time. It is the baseline `update` compares against, so a file the user edited is never
|
|
1004
|
+
overwritten. Paths are forward-slash relative; *.upstream files are this tool's output. */
|
|
1005
|
+
function sha256(buf) { return crypto.createHash("sha256").update(buf).digest("hex"); }
|
|
1006
|
+
function fileHashes(dir) {
|
|
1007
|
+
const out = {};
|
|
1008
|
+
(function walk(d, rel) {
|
|
1009
|
+
for (const e of fs.readdirSync(d, { withFileTypes: true })) {
|
|
1010
|
+
const r = rel ? `${rel}/${e.name}` : e.name;
|
|
1011
|
+
if (e.isDirectory()) walk(path.join(d, e.name), r);
|
|
1012
|
+
else if (e.isFile() && !e.name.endsWith(".upstream")) out[r] = sha256(fs.readFileSync(path.join(d, e.name)));
|
|
1013
|
+
}
|
|
1014
|
+
})(dir, "");
|
|
1015
|
+
return out;
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
/* Update one skill file by file against the ledger baseline (RFC-0032):
|
|
1019
|
+
upstream unchanged -> leave the local file alone, edited or not
|
|
1020
|
+
local untouched, or new file -> take the new version
|
|
1021
|
+
local edited, upstream moved -> keep the local file, write the new one as <file>.upstream
|
|
1022
|
+
A file the catalog dropped is removed only if the user never touched it. The new baseline is
|
|
1023
|
+
what the catalog ships now, so a kept edit stays "edited" until the user merges it. */
|
|
1024
|
+
function mergeOne(cat, s, dest, ide, l, notes, dry = false) {
|
|
1025
|
+
const m = manifestOf(cat, s);
|
|
1026
|
+
const src = skillDir(cat, s), dst = path.join(dest, s);
|
|
1027
|
+
const base = l.data[s].files, next = fileHashes(src);
|
|
1028
|
+
const kept = [], orphaned = [];
|
|
1029
|
+
const at = (root, rel) => path.join(root, ...rel.split("/"));
|
|
1030
|
+
const localHash = (rel) => { try { return sha256(fs.readFileSync(at(dst, rel))); } catch (e) { return null; } };
|
|
1031
|
+
for (const [rel, h] of Object.entries(next)) {
|
|
1032
|
+
const mine = localHash(rel), to = at(dst, rel);
|
|
1033
|
+
if (mine === h) { if (!dry) fs.rmSync(to + ".upstream", { force: true }); continue; }
|
|
1034
|
+
if (h === base[rel]) continue;
|
|
1035
|
+
if (mine === null ? !(rel in base) : mine === base[rel]) {
|
|
1036
|
+
if (dry) continue;
|
|
1037
|
+
fs.mkdirSync(path.dirname(to), { recursive: true });
|
|
1038
|
+
fs.copyFileSync(at(src, rel), to);
|
|
1039
|
+
fs.rmSync(to + ".upstream", { force: true });
|
|
1040
|
+
} else {
|
|
1041
|
+
kept.push(rel);
|
|
1042
|
+
if (dry) continue;
|
|
1043
|
+
fs.mkdirSync(path.dirname(to), { recursive: true });
|
|
1044
|
+
fs.copyFileSync(at(src, rel), to + ".upstream");
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
for (const rel of Object.keys(base)) {
|
|
1048
|
+
if (rel in next) continue;
|
|
1049
|
+
const mine = localHash(rel);
|
|
1050
|
+
if (mine === base[rel]) { if (!dry) fs.rmSync(at(dst, rel), { force: true }); }
|
|
1051
|
+
else if (mine !== null) orphaned.push(rel);
|
|
1052
|
+
}
|
|
1053
|
+
if (dry) return { m, kept, orphaned };
|
|
1054
|
+
const note = writeWiring(ide, dest, s, m.description);
|
|
1055
|
+
if (note && notes) notes.push(note);
|
|
1056
|
+
l.data[s] = { version: m.version, source: cat.source, files: next };
|
|
1057
|
+
if (cat.commit) l.data[s].commit = cat.commit;
|
|
1058
|
+
return { m, kept, orphaned };
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
/* Install a whole review panel — the reviewer subagents + the orchestrator skill — in one
|
|
1062
|
+
command (RFC-0020). Needs a target with BOTH a subagent and a skill format, so it accepts
|
|
1063
|
+
the default (Claude Code), --project, --ide kiro, or --dest; other IDEs install the halves
|
|
1064
|
+
separately. Delegates to installAgents + install so every existing safety/craft gate applies. */
|
|
1065
|
+
const PANEL_IDES = ["kiro", "codex", "antigravity", "copilot"];
|
|
1066
|
+
function installPanel(args) {
|
|
1067
|
+
const name = args.flags.panel;
|
|
1068
|
+
const p = PANELS[name];
|
|
1069
|
+
if (!p) die(`unknown panel '${name}' — available: ${Object.keys(PANELS).join(", ")}`);
|
|
1070
|
+
const ide = args.flags.ide;
|
|
1071
|
+
if (ide && !PANEL_IDES.includes(ide) && !args.flags.dest)
|
|
1072
|
+
die(`--panel installs subagents + the orchestrator skill together, so it needs a target with both formats:\n` +
|
|
1073
|
+
` (default | --project) Claude Code, --ide ${PANEL_IDES.join(" | ")}, or --dest <dir>.\n` +
|
|
1074
|
+
` For ${ide}: install the agents (skilldrop install --agent ${p.agents.join(" ")} --ide ${ide}) ` +
|
|
1075
|
+
`and the skill (skilldrop install ${p.skill} --ide ${ide}) separately.`);
|
|
1076
|
+
const flags = Object.assign({}, args.flags);
|
|
1077
|
+
delete flags.panel;
|
|
1078
|
+
console.log(`Installing the '${name}' panel — ${p.agents.length} reviewer subagents + the ${p.skill} orchestrator skill.\n`);
|
|
1079
|
+
installAgents({ _: p.agents.slice(), flags: Object.assign({}, flags, { agent: true }) });
|
|
1080
|
+
console.log("");
|
|
1081
|
+
install({ _: [p.skill], flags });
|
|
1082
|
+
console.log(`\nPanel installed. Fire it with the ${p.skill} skill — e.g. "run a pre-merge review on this change".`);
|
|
1083
|
+
console.log(`It dispatches the three subagents in parallel where your tool has native subagents (Claude Code today;`);
|
|
1084
|
+
console.log(`Codex / Antigravity multi-agent runners), and sweeps the lenses inline otherwise. Per-tool: agents/README.md.`);
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
function listProfiles(args) {
|
|
1088
|
+
const cat = resolveCatalog(args.flags.from);
|
|
1089
|
+
const ps = profilesOf(cat);
|
|
1090
|
+
if (args.flags.json)
|
|
1091
|
+
return emitJSON({
|
|
1092
|
+
catalog: cat.source,
|
|
1093
|
+
count: ps ? Object.keys(ps).length : 0,
|
|
1094
|
+
profiles: Object.entries(ps || {}).map(([name, p]) => ({
|
|
1095
|
+
name, description: p.description || null,
|
|
1096
|
+
packs: p.packs || [], agents: p.agents || [], loops: p.loops || [],
|
|
1097
|
+
})),
|
|
1098
|
+
});
|
|
1099
|
+
if (!ps) return console.log(`catalog '${cat.source}' defines no profiles.`);
|
|
1100
|
+
const w = Math.max(...Object.keys(ps).map((n) => n.length));
|
|
1101
|
+
for (const [n, p] of Object.entries(ps))
|
|
1102
|
+
console.log(`${n.padEnd(w)} ${p.description}`);
|
|
1103
|
+
console.log("\nInstall one: skilldrop install --profile <name>");
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
function installProfile(args) {
|
|
1107
|
+
const name = args.flags.profile;
|
|
1108
|
+
const cat = resolveCatalog(args.flags.from);
|
|
1109
|
+
const profiles = profilesOf(cat);
|
|
1110
|
+
if (!profiles) die(`catalog '${cat.source}' has no profiles.json`);
|
|
1111
|
+
const p = profiles[name];
|
|
1112
|
+
if (!p) die(`unknown profile '${name}' — available: ${Object.keys(profiles).join(", ")}`);
|
|
1113
|
+
|
|
1114
|
+
console.log(`Installing profile '${name}': ${p.description}\n`);
|
|
1115
|
+
const flags = Object.assign({}, args.flags);
|
|
1116
|
+
delete flags.profile;
|
|
1117
|
+
|
|
1118
|
+
for (const pack of (p.packs || [])) {
|
|
1119
|
+
console.log(`\n── pack: ${pack} ──`);
|
|
1120
|
+
install({ _: [], flags: Object.assign({}, flags, { pack }) });
|
|
1121
|
+
}
|
|
1122
|
+
for (const loop of (p.loops || [])) {
|
|
1123
|
+
if (loopsIn(cat).includes(loop)) {
|
|
1124
|
+
console.log(`\n── loop: ${loop} ──`);
|
|
1125
|
+
installLoops({ _: [loop], flags: Object.assign({}, flags, { loop: true }) });
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
if ((p.agents || []).length) {
|
|
1129
|
+
console.log(`\n── agents: ${p.agents.join(", ")} ──`);
|
|
1130
|
+
installAgents({ _: p.agents.slice(), flags: Object.assign({}, flags, { agent: true }) });
|
|
1131
|
+
}
|
|
1132
|
+
console.log(`\nProfile '${name}' installed.`);
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
function install(args) {
|
|
1136
|
+
if (args.flags["dry-run"] && (args.flags.panel || args.flags.profile || args.flags.loop || args.flags.agent))
|
|
1137
|
+
die("--dry-run covers skill installs, update and uninstall. To preview a panel, profile, loop or agent,\n" +
|
|
1138
|
+
" read what it contains first: skilldrop profiles | loops | agents --json");
|
|
1139
|
+
if (args.flags.local && !gitRoot(process.cwd()))
|
|
1140
|
+
die("--local hides the install from git, so it needs a git repo — run it from inside one");
|
|
1141
|
+
if (args.flags.local && (args.flags.panel || args.flags.profile || args.flags.agent))
|
|
1142
|
+
die("--local covers skills and loops. Agents have their own folders; install them with --dest into a path you exclude yourself");
|
|
1143
|
+
if (args.flags.panel) return installPanel(args);
|
|
1144
|
+
if (args.flags.profile) return installProfile(args);
|
|
1145
|
+
if (args.flags.loop) return installLoops(args);
|
|
1146
|
+
if (args.flags.agent) return installAgents(args);
|
|
1147
|
+
const cat = resolveCatalog(args.flags.from);
|
|
1148
|
+
let names = expandNames(args, cat);
|
|
1149
|
+
if (args.flags["with-related"]) {
|
|
1150
|
+
const seen = new Set(names);
|
|
1151
|
+
for (const s of names.slice()) {
|
|
1152
|
+
let related = [];
|
|
1153
|
+
try { related = manifestOf(cat, s).related || []; } catch (e) { /* gated below */ }
|
|
1154
|
+
for (const r of related) if (!seen.has(r) && skillExists(cat, r)) { seen.add(r); names.push(r); }
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
gate(cat, names);
|
|
1158
|
+
const { dest, ide } = target(args.flags);
|
|
1159
|
+
if (args.flags["dry-run"]) return planInstall(cat, names, dest, ide, args.flags);
|
|
1160
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
1161
|
+
const l = ledger(dest);
|
|
1162
|
+
const pipDeps = [], suggestions = new Set(), notes = [];
|
|
1163
|
+
for (const s of names) {
|
|
1164
|
+
const m = copyOne(cat, s, dest, ide, l, notes);
|
|
1165
|
+
if (fs.existsSync(path.join(skillDir(cat, s), "requirements.txt"))) pipDeps.push(s);
|
|
1166
|
+
for (const r of m.related || []) if (!names.includes(r) && !l.data[r]) suggestions.add(r);
|
|
1167
|
+
console.log(`installed ${s}@${m.version} -> ${dest}`);
|
|
1168
|
+
}
|
|
1169
|
+
saveLedger(l);
|
|
1170
|
+
console.log(`\n${names.length} skill(s) installed (${ide}).`);
|
|
1171
|
+
if (notes.length) console.log(`\nCleanup:\n${notes.join("\n")}`);
|
|
1172
|
+
const local = !!args.flags.local;
|
|
1173
|
+
if (local) {
|
|
1174
|
+
const ex = addLocal(dest, ide, names);
|
|
1175
|
+
console.log(`\n--local: listed in ${path.relative(process.cwd(), ex) || ex}, so git won't see these files.` +
|
|
1176
|
+
`\n Nothing in the repo's tracked files changed. Remove with: skilldrop uninstall <skill> --local`);
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
const withHooks = names.filter((s) => { try { return (manifestOf(cat, s).hooks || []).length; } catch (e) { return false; } });
|
|
1180
|
+
if (withHooks.length && args.flags["with-hooks"]) {
|
|
1181
|
+
const lines = withHooks.flatMap((s) => emitHooks(cat, s, dest, ide, local));
|
|
1182
|
+
if (local && ide === "claude") {
|
|
1183
|
+
const root = gitRoot(process.cwd());
|
|
1184
|
+
writeExclude(root, readExclude(root).entries.concat(excludeEntry(root, claudeSettings(dest, true), false)));
|
|
1185
|
+
}
|
|
1186
|
+
console.log(`\nHooks wired (RFC-0006):\n${lines.join("\n")}`);
|
|
1187
|
+
if (cat.source !== BUNDLED)
|
|
1188
|
+
console.log(` NOTE: these hooks run commands from third-party catalog '${cat.source}' — read them before trusting them.`);
|
|
1189
|
+
console.log(` Undo any of these with: skilldrop uninstall <skill> ${ide === "claude" ? "" : "--ide " + ide}`.trimEnd());
|
|
1190
|
+
} else if (withHooks.length) {
|
|
1191
|
+
console.log(`\n${withHooks.join(", ")} declare hooks — re-run with --with-hooks to wire them (git pre-commit reminders / session-start context).`);
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
if (cat.source !== BUNDLED && cat.commit) {
|
|
1195
|
+
if (cat.pinned) console.log(`\nPinned: these skills came from commit ${cat.commit.slice(0, 12)}, as requested.`);
|
|
1196
|
+
else console.log(`\nNot pinned: '${cat.source}' can change after you review it. These skills came from commit ${cat.commit.slice(0, 12)};` +
|
|
1197
|
+
`\n to get exactly these files again, install with --from ${splitSource(cat.source)[0]}#${cat.commit}`);
|
|
1198
|
+
}
|
|
1199
|
+
if (cat.source !== BUNDLED) {
|
|
1200
|
+
console.log(`\nWARNING: third-party catalog '${cat.source}'. Skills are instructions your AI agent will follow — review each installed SKILL.md under ${dest} before first use. Install copied files only; nothing was executed.`);
|
|
1201
|
+
// Supply-chain scan (RFC-0022): surface risky patterns at the moment of trust. Reports only —
|
|
1202
|
+
// the files are already copied, and a match is a prompt to read, not a verdict.
|
|
1203
|
+
const scripted = names.filter((s) => fs.existsSync(path.join(skillDir(cat, s), "scripts")));
|
|
1204
|
+
if (scripted.length) {
|
|
1205
|
+
console.log(`\nWhat the scripts declare they do (RFC-0039):`);
|
|
1206
|
+
for (const s of scripted) console.log(` ${s}: ${permissionSummary(permissionsOf(cat, s))}`);
|
|
1207
|
+
}
|
|
1208
|
+
const { total } = printScan(cat, names, { compact: true });
|
|
1209
|
+
console.log(total
|
|
1210
|
+
? `\n${total} pattern(s) worth reading before you trust these skills. Full detail: skilldrop scan --from ${cat.source}`
|
|
1211
|
+
: "\nSupply-chain scan: no flagged patterns.");
|
|
1212
|
+
}
|
|
1213
|
+
if (ide === "generic")
|
|
1214
|
+
console.log("wiring: attach each skill's SKILL.md to your agent (Continue/Cline: @file, Aider: /add) — see the repo README's per-IDE steps.");
|
|
1215
|
+
for (const s of pipDeps)
|
|
1216
|
+
console.log(`deps: ${s} needs Python packages — run: cd ${path.join(dest, s)} && python3 -m pip install -r requirements.txt`);
|
|
1217
|
+
if (suggestions.size)
|
|
1218
|
+
console.log(`related (not installed): ${[...suggestions].sort().join(", ")} — add --with-related or install by name.`);
|
|
1219
|
+
const pk = args.flags.pack && (packsOf(cat) || {})[args.flags.pack];
|
|
1220
|
+
if (pk && pk["first-value"]) console.log("\n" + firstValueLines(pk).join("\n"));
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
/* install --dry-run: everything install would write, and nothing written. */
|
|
1224
|
+
function planInstall(cat, names, dest, ide, flags) {
|
|
1225
|
+
const l = ledger(dest);
|
|
1226
|
+
console.log(`dry run — nothing will be written. Target: ${dest} (${ide})\n`);
|
|
1227
|
+
for (const s of names) {
|
|
1228
|
+
const m = manifestOf(cat, s);
|
|
1229
|
+
const have = l.data[s];
|
|
1230
|
+
const verb = !have ? "install" : lver(have) === m.version ? "reinstall" : `replace ${lver(have)} with`;
|
|
1231
|
+
const edits = editedFiles(dest, s, have);
|
|
1232
|
+
console.log(`would ${verb} ${s}@${m.version} -> ${path.join(dest, s)}`);
|
|
1233
|
+
if (edits.length) console.log(` overwrites your edits in: ${edits.join(", ")} (update keeps them; install does not)`);
|
|
1234
|
+
const w = wiringPath(ide, dest, s);
|
|
1235
|
+
if (w && ide === "cursor") console.log(` and write ${path.relative(process.cwd(), w)}`);
|
|
1236
|
+
}
|
|
1237
|
+
if (flags.local) {
|
|
1238
|
+
const root = gitRoot(process.cwd());
|
|
1239
|
+
console.log(`\n--local: would list ${names.length} folder(s) and the ledger in ${path.relative(process.cwd(), excludePath(root))}`);
|
|
1240
|
+
}
|
|
1241
|
+
if (flags["with-hooks"]) {
|
|
1242
|
+
const hooked = names.filter((s) => { try { return (manifestOf(cat, s).hooks || []).length; } catch (e) { return false; } });
|
|
1243
|
+
if (hooked.length) console.log(`\nwould wire hooks for: ${hooked.join(", ")}`);
|
|
1244
|
+
}
|
|
1245
|
+
console.log(`\n${names.length} skill(s). Rerun without --dry-run to install.`);
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
function installedRows(flags) {
|
|
1249
|
+
const { dest, ide } = target(flags);
|
|
1250
|
+
const l = ledger(dest);
|
|
1251
|
+
const rows = Object.keys(l.data).sort().map((s) => {
|
|
1252
|
+
const src = lsrc(l.data[s]);
|
|
1253
|
+
let current = null, cat = null, changed = [];
|
|
1254
|
+
const entry = l.data[s];
|
|
1255
|
+
try {
|
|
1256
|
+
cat = resolveCatalog(src === BUNDLED ? undefined : src);
|
|
1257
|
+
if (skillExists(cat, s)) {
|
|
1258
|
+
current = manifestOf(cat, s).version;
|
|
1259
|
+
// Same version, different files: the catalog changed the skill without bumping it.
|
|
1260
|
+
// The version check alone would never see that (OWASP AST07, update drift).
|
|
1261
|
+
if (current === lver(entry) && typeof entry === "object" && entry.files) {
|
|
1262
|
+
const now = fileHashes(skillDir(cat, s));
|
|
1263
|
+
changed = [...new Set([...Object.keys(now), ...Object.keys(entry.files)])]
|
|
1264
|
+
.filter((f) => now[f] !== entry.files[f]).sort();
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
} catch (e) { /* unreachable source: current stays null */ }
|
|
1268
|
+
return { s, src, cat, installed: lver(entry), current, changed, pinned: isPinned(src) };
|
|
1269
|
+
});
|
|
1270
|
+
return { dest, ide, l, rows };
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
function update(args) {
|
|
1274
|
+
const { dest, ide, l, rows } = installedRows(args.flags);
|
|
1275
|
+
if (!rows.length) return console.log(`nothing installed at ${dest}`);
|
|
1276
|
+
const dry = !!args.flags["dry-run"];
|
|
1277
|
+
// A row is due when its version moved, or (with --changed) when its files moved under the
|
|
1278
|
+
// same version. Same-version changes are never taken silently: they are named and skipped.
|
|
1279
|
+
const isDue = (r) => r.current && (r.current !== r.installed || (args.flags.changed && r.changed.length));
|
|
1280
|
+
const due = rows.filter(isDue);
|
|
1281
|
+
if (args.flags.force && !dry) {
|
|
1282
|
+
const lost = due.flatMap((r) => editedFiles(dest, r.s, l.data[r.s]).map((f) => `${r.s}/${f}`));
|
|
1283
|
+
if (lost.length) {
|
|
1284
|
+
console.log(`--force overwrites ${lost.length} file(s) you edited:\n ${lost.join("\n ")}`);
|
|
1285
|
+
if (!confirm("Overwrite them?", args.flags)) return console.log("cancelled — nothing changed. Drop --force to keep your edits as <file>.upstream.");
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
if (dry) return planUpdate(dest, ide, l, rows, args.flags);
|
|
1289
|
+
let n = 0, conflicts = 0;
|
|
1290
|
+
const silent = rows.filter((r) => r.current === r.installed && r.changed.length && !args.flags.changed);
|
|
1291
|
+
for (const r of rows) {
|
|
1292
|
+
if (!r.current) { console.log(`skip ${r.s}: source '${r.src}' unreachable or skill gone from it`); continue; }
|
|
1293
|
+
if (!isDue(r)) continue;
|
|
1294
|
+
const problems = checkSkill(r.cat, r.s);
|
|
1295
|
+
if (problems.length) { console.log(`skip ${r.s}: fails structural check in '${r.src}' (${problems[0]})`); continue; }
|
|
1296
|
+
// No baseline (installed before RFC-0032) or --force: overwrite as before, and record one.
|
|
1297
|
+
const entry = l.data[r.s];
|
|
1298
|
+
if (args.flags.force || typeof entry !== "object" || !entry.files) {
|
|
1299
|
+
copyOne(r.cat, r.s, dest, ide, l);
|
|
1300
|
+
console.log(`updated ${r.s} ${r.installed === r.current ? `${r.current} (same version, changed upstream)` : `${r.installed} -> ${r.current}`} (${r.src})`);
|
|
1301
|
+
} else {
|
|
1302
|
+
const { kept, orphaned } = mergeOne(r.cat, r.s, dest, ide, l);
|
|
1303
|
+
console.log(`updated ${r.s} ${r.installed === r.current ? `${r.current} (same version, changed upstream)` : `${r.installed} -> ${r.current}`} (${r.src})`);
|
|
1304
|
+
for (const f of kept) console.log(` kept your edits in ${f} — new version at ${f}.upstream`);
|
|
1305
|
+
for (const f of orphaned) console.log(` kept ${f} — dropped upstream, but you edited it`);
|
|
1306
|
+
conflicts += kept.length;
|
|
1307
|
+
}
|
|
1308
|
+
n++;
|
|
1309
|
+
}
|
|
1310
|
+
saveLedger(l);
|
|
1311
|
+
console.log(n ? `\n${n} skill(s) updated.` : (silent.length ? "no version changes." : "everything up to date."));
|
|
1312
|
+
if (silent.length)
|
|
1313
|
+
console.log(`\nNot taken: ${silent.map((r) => r.s).join(", ")} changed upstream without a version bump.` +
|
|
1314
|
+
`\n Read the change (skilldrop diff <skill>), then take it with: skilldrop update --changed`);
|
|
1315
|
+
const pinned = rows.filter((r) => r.pinned);
|
|
1316
|
+
if (pinned.length)
|
|
1317
|
+
console.log(`\n${pinned.length} skill(s) come from a catalog pinned to a commit, so they stay as they are. To move them,` +
|
|
1318
|
+
`\n reinstall with the new commit: skilldrop install <skill> --from <url>#<new-commit>`);
|
|
1319
|
+
// An update is a fresh act of trust in a third-party catalog (OWASP AST07): scan what changed.
|
|
1320
|
+
const thirdParty = {};
|
|
1321
|
+
for (const r of rows) if (isDue(r) && r.src !== BUNDLED) (thirdParty[r.src] = thirdParty[r.src] || { cat: r.cat, names: [] }).names.push(r.s);
|
|
1322
|
+
for (const { cat, names } of Object.values(thirdParty)) {
|
|
1323
|
+
const { total } = printScan(cat, names, { compact: true });
|
|
1324
|
+
console.log(total ? `\n${total} pattern(s) in the updated skills from '${cat.source}' worth reading. Full detail: skilldrop scan --from ${cat.source}`
|
|
1325
|
+
: `\nSupply-chain scan of the updated skills from '${cat.source}': no flagged patterns.`);
|
|
1326
|
+
}
|
|
1327
|
+
if (conflicts)
|
|
1328
|
+
console.log(`${conflicts} file(s) kept your edits. Merge each <file>.upstream into its file, then delete it — or rerun with --force to take every new version.`);
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
/* update --dry-run: the same per-skill, per-file decisions update makes, reported, not applied. */
|
|
1332
|
+
function planUpdate(dest, ide, l, rows, flags) {
|
|
1333
|
+
console.log(`dry run — nothing will be written. Target: ${dest} (${ide})\n`);
|
|
1334
|
+
let n = 0;
|
|
1335
|
+
for (const r of rows) {
|
|
1336
|
+
if (!r.current) { console.log(`would skip ${r.s}: source '${r.src}' unreachable or skill gone from it`); continue; }
|
|
1337
|
+
if (r.current === r.installed && !r.changed.length) continue;
|
|
1338
|
+
if (r.current === r.installed && !flags.changed) {
|
|
1339
|
+
console.log(`would not take ${r.s}: ${r.changed.length} file(s) changed upstream without a version bump (add --changed to take them)`);
|
|
1340
|
+
continue;
|
|
1341
|
+
}
|
|
1342
|
+
const problems = checkSkill(r.cat, r.s);
|
|
1343
|
+
if (problems.length) { console.log(`would skip ${r.s}: fails structural check (${problems[0]})`); continue; }
|
|
1344
|
+
n++;
|
|
1345
|
+
const entry = l.data[r.s];
|
|
1346
|
+
console.log(`would update ${r.s} ${r.installed} -> ${r.current} (${r.src})`);
|
|
1347
|
+
if (flags.force || typeof entry !== "object" || !entry.files) {
|
|
1348
|
+
const lost = editedFiles(dest, r.s, entry);
|
|
1349
|
+
if (lost.length) console.log(` --force overwrites your edits in: ${lost.join(", ")}`);
|
|
1350
|
+
continue;
|
|
1351
|
+
}
|
|
1352
|
+
const { kept, orphaned } = mergeOne(r.cat, r.s, dest, ide, l, null, true);
|
|
1353
|
+
for (const f of kept) console.log(` would keep your edits in ${f} and write the new version to ${f}.upstream`);
|
|
1354
|
+
for (const f of orphaned) console.log(` would keep ${f} — dropped upstream, but you edited it`);
|
|
1355
|
+
}
|
|
1356
|
+
const held = rows.filter((r) => r.current && r.current === r.installed && r.changed.length && !flags.changed).length;
|
|
1357
|
+
console.log(n ? `\n${n} skill(s) would update. Rerun without --dry-run to apply.` : (held ? "no version changes." : "everything up to date."));
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
function outdated(args) {
|
|
1361
|
+
const { dest, rows } = installedRows(args.flags);
|
|
1362
|
+
const stale = rows.filter((r) => r.current && r.current !== r.installed);
|
|
1363
|
+
const drift = rows.filter((r) => r.changed && r.changed.length);
|
|
1364
|
+
if (args.flags.json)
|
|
1365
|
+
return emitJSON({
|
|
1366
|
+
dest,
|
|
1367
|
+
count: rows.length,
|
|
1368
|
+
outdatedCount: stale.length,
|
|
1369
|
+
changedWithoutVersionCount: drift.length,
|
|
1370
|
+
skills: rows.map((r) => ({
|
|
1371
|
+
name: r.s, installed: r.installed, current: r.current,
|
|
1372
|
+
source: r.src, outdated: !!(r.current && r.current !== r.installed),
|
|
1373
|
+
changedWithoutVersion: r.changed || [], pinned: r.pinned,
|
|
1374
|
+
})),
|
|
1375
|
+
});
|
|
1376
|
+
if (!rows.length) return console.log(`nothing installed at ${dest}`);
|
|
1377
|
+
for (const r of stale) console.log(`${r.s}: installed ${r.installed}, current ${r.current} (${r.src})`);
|
|
1378
|
+
for (const r of drift)
|
|
1379
|
+
console.log(`${r.s}: ${r.changed.length} file(s) changed in '${r.src}' without a version bump (${r.changed.slice(0, 3).join(", ")}${r.changed.length > 3 ? ", …" : ""})`);
|
|
1380
|
+
if (drift.length)
|
|
1381
|
+
console.log(`\n${drift.length} skill(s) changed upstream with the same version. Read the change first (skilldrop diff <skill>),` +
|
|
1382
|
+
`\nthen take it with: skilldrop update --changed`);
|
|
1383
|
+
console.log(stale.length ? `\n${stale.length} outdated — run: skilldrop update` : (drift.length ? "" : "everything up to date."));
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
function uninstall(args) {
|
|
1387
|
+
if (args.flags.agent) return uninstallAgents(args);
|
|
1388
|
+
if (args.flags.loop) return uninstallLoops(args);
|
|
1389
|
+
if (!args._.length) die("pass skill names to uninstall");
|
|
1390
|
+
const { dest, ide } = target(args.flags);
|
|
1391
|
+
const l = ledger(dest);
|
|
1392
|
+
const present = args._.filter((s) => l.data[s] || fs.existsSync(path.join(dest, s)));
|
|
1393
|
+
const missing = args._.filter((s) => !present.includes(s));
|
|
1394
|
+
for (const s of missing) console.log(`${s} is not installed at ${dest}`);
|
|
1395
|
+
if (!present.length) return;
|
|
1396
|
+
const edits = present.flatMap((s) => editedFiles(dest, s, l.data[s]).map((f) => `${s}/${f}`));
|
|
1397
|
+
if (args.flags["dry-run"]) {
|
|
1398
|
+
console.log(`dry run — nothing will be removed. Target: ${dest} (${ide})\n`);
|
|
1399
|
+
for (const s of present) {
|
|
1400
|
+
console.log(`would remove ${path.join(dest, s)}`);
|
|
1401
|
+
const w = wiringPath(ide, dest, s);
|
|
1402
|
+
if (w && fs.existsSync(w)) console.log(` and ${path.relative(process.cwd(), w)}`);
|
|
1403
|
+
}
|
|
1404
|
+
if (edits.length) console.log(`\nincluding ${edits.length} file(s) you edited: ${edits.join(", ")}`);
|
|
1405
|
+
return;
|
|
1406
|
+
}
|
|
1407
|
+
const what = `Remove ${present.length} skill(s) from ${dest}: ${present.join(", ")}` +
|
|
1408
|
+
(edits.length ? `\n (${edits.length} of the files have your edits: ${edits.join(", ")})` : "") + "?";
|
|
1409
|
+
if (!confirm(what, args.flags)) return console.log("cancelled — nothing removed.");
|
|
1410
|
+
for (const s of present) {
|
|
1411
|
+
fs.rmSync(path.join(dest, s), { recursive: true, force: true });
|
|
1412
|
+
const w = wiringPath(ide, dest, s);
|
|
1413
|
+
if (w) fs.rmSync(w, { force: true });
|
|
1414
|
+
removeHooksFor(s, dest, ide);
|
|
1415
|
+
delete l.data[s];
|
|
1416
|
+
console.log(`removed ${s} from ${dest}`);
|
|
1417
|
+
}
|
|
1418
|
+
saveLedger(l);
|
|
1419
|
+
const ex = removeLocal(dest, ide, present);
|
|
1420
|
+
if (ex) console.log(`dropped the --local entries from ${path.relative(process.cwd(), ex) || ex}`);
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
/* diff <skill>: what differs between the installed copy and the catalog's current one — your
|
|
1424
|
+
edits, an upstream change, or both. File-level always; line-level through git when it is on
|
|
1425
|
+
the PATH (git diff --no-index works outside any repo). */
|
|
1426
|
+
function diff(args) {
|
|
1427
|
+
const s = args._[0] || die("pass an installed skill name: skilldrop diff <skill>");
|
|
1428
|
+
const { dest } = target(args.flags);
|
|
1429
|
+
const l = ledger(dest);
|
|
1430
|
+
const entry = l.data[s];
|
|
1431
|
+
if (!entry && !fs.existsSync(path.join(dest, s))) die(`'${s}' is not installed at ${dest}`);
|
|
1432
|
+
if (entry && entry.loop) die(`'${s}' is a loop — diff compares skills`);
|
|
1433
|
+
const src = args.flags.from || (entry ? lsrc(entry) : BUNDLED);
|
|
1434
|
+
const cat = resolveCatalog(src === BUNDLED ? undefined : src);
|
|
1435
|
+
if (!skillExists(cat, s)) die(`'${s}' is no longer in catalog '${cat.source}'`);
|
|
1436
|
+
const theirs = fileHashes(skillDir(cat, s)), mine = fileHashes(path.join(dest, s));
|
|
1437
|
+
const base = (entry && entry.files) || {};
|
|
1438
|
+
const changed = Object.keys(theirs).filter((f) => f in mine && mine[f] !== theirs[f]).sort();
|
|
1439
|
+
const onlyUp = Object.keys(theirs).filter((f) => !(f in mine)).sort();
|
|
1440
|
+
const onlyMine = Object.keys(mine).filter((f) => !(f in theirs)).sort();
|
|
1441
|
+
const why = (f) => !base[f] ? "" : mine[f] !== base[f] && theirs[f] !== base[f] ? " (you edited it, and so did the catalog)"
|
|
1442
|
+
: mine[f] !== base[f] ? " (your edit)" : " (catalog changed it)";
|
|
1443
|
+
const iv = entry ? lver(entry) : "?", cv = manifestOf(cat, s).version;
|
|
1444
|
+
console.log(`${s}: installed ${iv} at ${path.join(dest, s)} vs catalog '${cat.source}' ${cv}`);
|
|
1445
|
+
if (!changed.length && !onlyUp.length && !onlyMine.length) return console.log("identical.");
|
|
1446
|
+
for (const f of changed) console.log(` M ${f}${why(f)}`);
|
|
1447
|
+
for (const f of onlyUp) console.log(` + ${f} (in the catalog, not installed)`);
|
|
1448
|
+
for (const f of onlyMine) console.log(` - ${f} (installed only)`);
|
|
1449
|
+
if (args.flags.stat) return;
|
|
1450
|
+
let git = true;
|
|
1451
|
+
for (const f of changed) {
|
|
1452
|
+
const a = path.join(skillDir(cat, s), ...f.split("/")), b = path.join(dest, s, ...f.split("/"));
|
|
1453
|
+
try {
|
|
1454
|
+
execFileSync("git", ["--no-pager", "diff", "--no-index", "--color=auto", "--src-prefix=catalog/", "--dst-prefix=installed/", a, b],
|
|
1455
|
+
{ stdio: ["ignore", "inherit", "inherit"] });
|
|
1456
|
+
} catch (e) {
|
|
1457
|
+
if (e.code === "ENOENT") { git = false; break; } // no git binary
|
|
1458
|
+
// exit status 1 is git diff's "files differ" — the expected case
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
if (!git) console.log("\n(git not found — line-level diff needs git on the PATH; file list above)");
|
|
1462
|
+
if (changed.length) console.log(`\nTake the catalog's version: skilldrop update --force, or install ${s} again.`);
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
/* doctor: check an install target against its ledger and report what's out of step — skills
|
|
1466
|
+
recorded but gone, skills on disk nobody recorded, leftover .upstream files, wiring and hooks
|
|
1467
|
+
for skills no longer installed. Report-only: it changes nothing, and each finding says the fix. */
|
|
1468
|
+
function doctor(args) {
|
|
1469
|
+
const { dest, ide } = target(args.flags);
|
|
1470
|
+
const findings = [];
|
|
1471
|
+
const add = (level, msg, fix) => findings.push({ level, msg, fix });
|
|
1472
|
+
const lp = path.join(dest, LEDGER);
|
|
1473
|
+
let data = {};
|
|
1474
|
+
const raw = readIfPresent(lp, null);
|
|
1475
|
+
if (raw === null) add("info", `no ledger at ${lp}`, "nothing installed here by skilldrop — or a different target; try --project or --ide");
|
|
1476
|
+
else try { data = JSON.parse(raw); } catch (e) { add("error", `${lp} is not valid JSON`, "fix or delete it; skilldrop can't track updates until then"); }
|
|
1477
|
+
const names = Object.keys(data);
|
|
1478
|
+
for (const n of names) {
|
|
1479
|
+
if (!fs.existsSync(path.join(dest, n))) add("error", `${n} is in the ledger but its folder is gone`, `skilldrop install ${n} (or uninstall ${n} to drop the record)`);
|
|
1480
|
+
else if (!data[n].loop && !data[n].file) {
|
|
1481
|
+
const e = editedFiles(dest, n, data[n]);
|
|
1482
|
+
if (e.length) add("info", `${n}: you edited ${e.join(", ")}`, `skilldrop diff ${n}`);
|
|
1483
|
+
if (typeof data[n] === "string" || !data[n].files) add("warn", `${n} has no file baseline (installed before 0.13)`, `skilldrop install ${n} once; update will keep your edits after that`);
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
if (fs.existsSync(dest)) {
|
|
1487
|
+
for (const d of fs.readdirSync(dest, { withFileTypes: true })) {
|
|
1488
|
+
if (!d.isDirectory()) continue;
|
|
1489
|
+
const dir = path.join(dest, d.name);
|
|
1490
|
+
if (fs.existsSync(path.join(dir, "SKILL.md")) && !data[d.name])
|
|
1491
|
+
add("info", `${d.name} is on disk but not in the ledger`, "installed by hand or another tool — skilldrop won't update it");
|
|
1492
|
+
for (const f of walkFiles(dir)) if (f.endsWith(".upstream"))
|
|
1493
|
+
add("warn", `${path.relative(dest, f)} is waiting to be merged`, `merge it into ${path.relative(dest, f.slice(0, -9))}, then delete it`);
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
if (ide === "cursor") {
|
|
1497
|
+
const rules = path.join(path.dirname(dest), "rules");
|
|
1498
|
+
for (const f of fs.existsSync(rules) ? fs.readdirSync(rules) : []) {
|
|
1499
|
+
const body = readIfPresent(path.join(rules, f));
|
|
1500
|
+
const m = body.match(/\.cursor\/skills\/([^/\s]+)\/SKILL\.md/);
|
|
1501
|
+
if (m && !fs.existsSync(path.join(dest, m[1]))) add("warn", `.cursor/rules/${f} points at ${m[1]}, which isn't installed`, `delete .cursor/rules/${f}`);
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
if (ide === "kiro") for (const n of names) {
|
|
1505
|
+
const w = wiringPath("kiro", dest, n);
|
|
1506
|
+
if (readIfPresent(w).startsWith(KIRO_SHIM_PREFIX)) add("warn", `stale steering shim ${path.relative(process.cwd(), w)}`, `skilldrop install ${n} --ide kiro removes it`);
|
|
1507
|
+
}
|
|
1508
|
+
const root = gitRoot(process.cwd());
|
|
1509
|
+
if (root) {
|
|
1510
|
+
const hook = readIfPresent(preCommitPath(root));
|
|
1511
|
+
for (const m of hook.matchAll(/# >>> skilldrop-hook:([^:\n]+):/g))
|
|
1512
|
+
if (!fs.existsSync(path.join(dest, m[1])) && !data[m[1]]) add("warn", `pre-commit reminder for ${m[1]}, which isn't installed here`, `skilldrop uninstall ${m[1]} removes it (or it belongs to another target)`);
|
|
1513
|
+
for (const e of readExclude(root).entries)
|
|
1514
|
+
if (!fs.existsSync(path.join(root, e.replace(/^\//, "")))) add("info", `--local entry ${e} matches nothing`, "harmless; skilldrop uninstall --local tidies it");
|
|
1515
|
+
}
|
|
1516
|
+
if (ide === "claude") for (const local of [false, true]) {
|
|
1517
|
+
const sp = claudeSettings(dest, local);
|
|
1518
|
+
const body = readIfPresent(sp, null);
|
|
1519
|
+
if (body === null) continue;
|
|
1520
|
+
for (const m of body.matchAll(/skilldrop-hook:([^:"\s]+):session-start/g))
|
|
1521
|
+
if (!fs.existsSync(path.join(dest, m[1]))) add("warn", `${path.basename(sp)} has a session-start hook for ${m[1]}, which isn't installed`, `skilldrop uninstall ${m[1]} removes it`);
|
|
1522
|
+
}
|
|
1523
|
+
if (args.flags.json) return emitJSON({ dest, ide, installed: names.length, findings });
|
|
1524
|
+
console.log(`skilldrop doctor — ${dest} (${ide}), ${names.length} recorded\n`);
|
|
1525
|
+
const icon = { error: "✗", warn: "!", info: "·" };
|
|
1526
|
+
for (const f of findings) console.log(`${icon[f.level]} ${f.msg}\n fix: ${f.fix}`);
|
|
1527
|
+
const bad = findings.filter((f) => f.level !== "info").length;
|
|
1528
|
+
console.log(findings.length ? `\n${bad} to fix, ${findings.length - bad} for information. Nothing was changed.` : "all good.");
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
/* new-skill <name> --pack <pack>: scaffold a skill in the catalog you're standing in, with every
|
|
1532
|
+
file validate.py checks for, so the first run fails only on the parts that need a human. */
|
|
1533
|
+
function newSkill(args) {
|
|
1534
|
+
const name = args._[0] || die("usage: skilldrop new-skill <name> --pack <pack> [--tier light|standard|heavy]");
|
|
1535
|
+
if (!/^[a-z][a-z0-9]*(-[a-z0-9]+)*$/.test(name)) die(`'${name}' isn't kebab-case — use lowercase words joined by hyphens`);
|
|
1536
|
+
const pack = args.flags.pack || die("pass --pack <pack> — the one pack it belongs in (core only if every role needs it)");
|
|
1537
|
+
const tier = args.flags.tier || "standard";
|
|
1538
|
+
if (!["light", "standard", "heavy"].includes(tier)) die("--tier is light, standard or heavy");
|
|
1539
|
+
const root = process.cwd();
|
|
1540
|
+
const pdir = path.join(root, "packs", pack);
|
|
1541
|
+
if (!fs.existsSync(path.join(pdir, "pack.json")))
|
|
1542
|
+
die(`no packs/${pack}/pack.json here — run new-skill from the root of a skilldrop catalog`);
|
|
1543
|
+
for (const p of fs.readdirSync(path.join(root, "packs")))
|
|
1544
|
+
if (fs.existsSync(path.join(root, "packs", p, "skills", name))) die(`packs/${p}/skills/${name} already exists`);
|
|
1545
|
+
const dir = path.join(pdir, "skills", name);
|
|
1546
|
+
const desc = `TODO: one sentence, use-case-first — what it produces. Use when the user asks for …`;
|
|
1547
|
+
const files = {
|
|
1548
|
+
"SKILL.md": `---\nname: ${name}\ndescription: ${desc}\n---\n\n# ${name}\n\nTODO: one paragraph — what you produce and for whom.\n\n` +
|
|
1549
|
+
`## How to respond\n\n1. **Ask for what's missing in one message.** TODO: the inputs, with defaults.\n2. TODO: the steps.\n3. TODO: the output, and how the user checks it.\n\n` +
|
|
1550
|
+
`**Non-interactive runs** (subagent, CI, headless): TODO — what to assume, and when to emit \`BLOCKED: <what is missing>\`.\n\n` +
|
|
1551
|
+
`## Quality bar\n\n- TODO: a checkable property of a good output\n\n## When to use this skill\n\n- ✅ TODO\n\n` +
|
|
1552
|
+
`## When NOT to use this skill\n\n- ❌ TODO; use \`<sibling-skill>\`\n\n## Anti-patterns to avoid\n\n- ❌ **TODO** — and why it fails\n`,
|
|
1553
|
+
"manifest.json": JSON.stringify({
|
|
1554
|
+
name, version: "0.1.0", description: desc, entrypoint: "SKILL.md",
|
|
1555
|
+
deps: { npm: [], pip: [] }, env: { required: [], optional: [] }, related: [], tags: ["todo"],
|
|
1556
|
+
model: { tier, rationale: "TODO: why this tier — how much reasoning the task needs" },
|
|
1557
|
+
}, null, 2) + "\n",
|
|
1558
|
+
"evals/evals.json": JSON.stringify({ skill_name: name, evals: [{ id: 1,
|
|
1559
|
+
prompt: "TODO: a realistic request with concrete details",
|
|
1560
|
+
assertions: ["TODO: a property the output must have, checkable by reading it"] }] }, null, 2) + "\n",
|
|
1561
|
+
"evals/eval_queries.json": "[\n" + [
|
|
1562
|
+
{ query: "TODO: a phrasing that should trigger this skill", should_trigger: true },
|
|
1563
|
+
{ query: "TODO: a near-miss that belongs to a sibling skill", should_trigger: false },
|
|
1564
|
+
].map((r) => " " + JSON.stringify(r)).join(",\n") + "\n]\n",
|
|
1565
|
+
};
|
|
1566
|
+
if (args.flags["dry-run"]) {
|
|
1567
|
+
for (const f of Object.keys(files)) console.log(`would write ${path.relative(root, path.join(dir, f))}`);
|
|
1568
|
+
return console.log(`would add ${name} (tier ${tier}) to model-routing.json`);
|
|
1569
|
+
}
|
|
1570
|
+
for (const [f, body] of Object.entries(files)) {
|
|
1571
|
+
fs.mkdirSync(path.dirname(path.join(dir, f)), { recursive: true });
|
|
1572
|
+
fs.writeFileSync(path.join(dir, f), body);
|
|
1573
|
+
console.log(`wrote ${path.relative(root, path.join(dir, f))}`);
|
|
1574
|
+
}
|
|
1575
|
+
const rp = path.join(root, "model-routing.json");
|
|
1576
|
+
const routingRaw = readIfPresent(rp, null); // read-and-handle, not check-then-read
|
|
1577
|
+
if (routingRaw !== null) {
|
|
1578
|
+
const routing = JSON.parse(routingRaw);
|
|
1579
|
+
routing.skills = routing.skills || {};
|
|
1580
|
+
routing.skills[name] = { tier, rationale: "TODO: why this tier" };
|
|
1581
|
+
routing.skills = Object.fromEntries(Object.entries(routing.skills).sort(([a], [b]) => a.localeCompare(b)));
|
|
1582
|
+
fs.writeFileSync(rp, JSON.stringify(routing, null, 2) + "\n");
|
|
1583
|
+
console.log(`added ${name} to model-routing.json (tier ${tier})`);
|
|
1584
|
+
}
|
|
1585
|
+
console.log(`\nNext:\n 1. Replace every TODO — the description in SKILL.md and manifest.json must match.` +
|
|
1586
|
+
`\n 2. Add ${name} to an outcome in catalogue.json, and a row in guides/reference/skill-catalogue.md.` +
|
|
1587
|
+
`\n 3. python3 build_marketplace.py && python3 build_llms.py, then python3 validate.py — it lists what's left.` +
|
|
1588
|
+
`\n 4. Try it from another repo: skilldrop install ${name} --from ${root} --local`);
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
/* loop-stats: summarise the opt-in run log loops append to when SKILLDROP_LOOP_LOG is set.
|
|
1592
|
+
Local only — the file is the user's, nothing is sent anywhere. Answers the questions a team
|
|
1593
|
+
asks of its loops: which gates pass first time, which keep looping back, which block. */
|
|
1594
|
+
function loopStats(args) {
|
|
1595
|
+
const file = args._[0] || process.env.SKILLDROP_LOOP_LOG || path.join(".skilldrop", "loop-log.jsonl");
|
|
1596
|
+
const raw = readIfPresent(file, null);
|
|
1597
|
+
if (raw === null)
|
|
1598
|
+
die(`no run log at ${file}.\n Turn it on: export SKILLDROP_LOOP_LOG="$PWD/.skilldrop/loop-log.jsonl" — each loop then appends one line per gate verdict.`);
|
|
1599
|
+
const days = Number(args.flags.days) || 0;
|
|
1600
|
+
const since = days ? Date.now() - days * 864e5 : 0;
|
|
1601
|
+
const rows = [], bad = [];
|
|
1602
|
+
raw.split(/\r?\n/).forEach((line, i) => {
|
|
1603
|
+
if (!line.trim()) return;
|
|
1604
|
+
try {
|
|
1605
|
+
const r = JSON.parse(line);
|
|
1606
|
+
if (!r.loop || !r.verdict) throw new Error("missing loop or verdict");
|
|
1607
|
+
if (since && Date.parse(r.ts) < since) return;
|
|
1608
|
+
rows.push(r);
|
|
1609
|
+
} catch (e) { bad.push(i + 1); }
|
|
1610
|
+
});
|
|
1611
|
+
const terms = (() => { try { return readJSON(path.join(ROOT, "contracts", "terminals.json")); } catch (e) { return {}; } })();
|
|
1612
|
+
// Verdict classes come from the shared vocabulary (RFC-0028), so a new verdict word is
|
|
1613
|
+
// classified the same way here as in the loops.
|
|
1614
|
+
const classOf = (v) => ((terms.verdicts || {})[v] || {}).class || (/BLOCK/.test(v) ? "blocked" : "unknown");
|
|
1615
|
+
const by = {};
|
|
1616
|
+
for (const r of rows) {
|
|
1617
|
+
const k = `${r.loop} ${r.gate || "-"} (${r.stage || "?"})`;
|
|
1618
|
+
const g = by[k] = by[k] || { n: 0, verdicts: {}, firstPass: 0, firsts: 0, blocked: 0, rounds: [] };
|
|
1619
|
+
g.n++; g.verdicts[r.verdict] = (g.verdicts[r.verdict] || 0) + 1;
|
|
1620
|
+
const cls = classOf(r.verdict);
|
|
1621
|
+
if (Number(r.round) === 1) { g.firsts++; if (cls === "pass" || cls === "conditional") g.firstPass++; }
|
|
1622
|
+
if (cls === "blocked") g.blocked++;
|
|
1623
|
+
if (r.round) g.rounds.push(Number(r.round));
|
|
1624
|
+
}
|
|
1625
|
+
if (args.flags.json) return emitJSON({ file, entries: rows.length, skipped: bad.length, gates: by });
|
|
1626
|
+
console.log(`${rows.length} gate verdict(s) in ${file}${days ? ` from the last ${days} days` : ""}\n`);
|
|
1627
|
+
for (const [k, g] of Object.entries(by).sort()) {
|
|
1628
|
+
const mix = Object.entries(g.verdicts).sort((a, b) => b[1] - a[1]).map(([v, n]) => `${v} ${n}`).join(", ");
|
|
1629
|
+
const fp = g.firsts ? `${Math.round(100 * g.firstPass / g.firsts)}% pass first time` : "no first-round entries";
|
|
1630
|
+
const maxRound = g.rounds.length ? Math.max(...g.rounds) : 0;
|
|
1631
|
+
console.log(`${k}\n ${g.n} verdict(s): ${mix}\n ${fp} · ${g.blocked} blocked · longest run ${maxRound} round(s)`);
|
|
1632
|
+
}
|
|
1633
|
+
if (bad.length) console.log(`\n${bad.length} line(s) skipped as unreadable: ${bad.slice(0, 10).join(", ")}${bad.length > 10 ? "…" : ""}`);
|
|
1634
|
+
}
|
|
1635
|
+
|
|
1636
|
+
/* init-catalogue <dir>: start a private catalogue (an internal mirror or a team's own skills)
|
|
1637
|
+
in the layout this CLI reads, with one example skill that passes `skilldrop validate`. */
|
|
1638
|
+
function initCatalogue(args) {
|
|
1639
|
+
const dir = path.resolve(args._[0] || die("usage: skilldrop init-catalogue <dir> [--pack <name>]"));
|
|
1640
|
+
const pack = args.flags.pack || "team";
|
|
1641
|
+
if (!/^[a-z][a-z0-9-]*$/.test(pack)) die(`--pack '${pack}' isn't kebab-case`);
|
|
1642
|
+
if (fs.existsSync(dir) && fs.readdirSync(dir).length) die(`${dir} isn't empty — pick a new folder`);
|
|
1643
|
+
const sk = "example-skill";
|
|
1644
|
+
const files = {
|
|
1645
|
+
"catalogue.json": JSON.stringify({ version: "0.1.0", packs: [pack] }, null, 2) + "\n",
|
|
1646
|
+
[`packs/${pack}/pack.json`]: JSON.stringify({ description: `Skills for the ${pack} team.`, display_name: pack }, null, 2) + "\n",
|
|
1647
|
+
[`packs/${pack}/skills/${sk}/SKILL.md`]: `---\nname: ${sk}\ndescription: Replace with one sentence on what this skill produces. Use when the user asks for …\n---\n\n# ${sk}\n\n` +
|
|
1648
|
+
`## How to respond\n\n1. Ask for what's missing in one message.\n2. Do the work.\n3. Say how to check it.\n\n## Quality bar\n\n- A checkable property of a good result\n\n## Anti-patterns to avoid\n\n- ❌ The most common way this goes wrong\n`,
|
|
1649
|
+
[`packs/${pack}/skills/${sk}/manifest.json`]: JSON.stringify({ name: sk, version: "0.1.0",
|
|
1650
|
+
description: "Replace with one sentence on what this skill produces. Use when the user asks for …",
|
|
1651
|
+
entrypoint: "SKILL.md", deps: { npm: [], pip: [] }, env: { required: [], optional: [] }, related: [], tags: ["example"],
|
|
1652
|
+
model: { tier: "standard", rationale: "Replace: how much reasoning the task needs." } }, null, 2) + "\n",
|
|
1653
|
+
"README.md": `# Skills catalogue\n\nInstall from it with the skilldrop CLI:\n\n\`\`\`bash\nnpx skilldrop-cli list --from <this repo's git URL>\nnpx skilldrop-cli install --pack ${pack} --from <this repo's git URL>#<tag>\n\`\`\`\n\n` +
|
|
1654
|
+
`Pin a tag: a branch can change under you. Add a skill with \`npx skilldrop-cli new-skill <name> --pack ${pack}\` from this folder.\n` +
|
|
1655
|
+
`Check the catalogue with \`npx skilldrop-cli validate --from .\` (CI runs it on every pull request).\n`,
|
|
1656
|
+
".github/workflows/validate.yml": `name: validate\non: [push, pull_request]\npermissions:\n contents: read\njobs:\n validate:\n runs-on: ubuntu-latest\n steps:\n` +
|
|
1657
|
+
` - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1\n with:\n persist-credentials: false\n` +
|
|
1658
|
+
` - run: npx --yes skilldrop-cli@${readJSON(path.join(ROOT, "package.json")).version} validate --from .\n - run: npx --yes skilldrop-cli@${readJSON(path.join(ROOT, "package.json")).version} scan --from .\n`,
|
|
1659
|
+
};
|
|
1660
|
+
for (const [rel, body] of Object.entries(files)) {
|
|
1661
|
+
fs.mkdirSync(path.dirname(path.join(dir, rel)), { recursive: true });
|
|
1662
|
+
fs.writeFileSync(path.join(dir, rel), body);
|
|
1663
|
+
console.log(`wrote ${path.relative(process.cwd(), path.join(dir, rel))}`);
|
|
1664
|
+
}
|
|
1665
|
+
console.log(`\nNext: push ${path.basename(dir)} to your git host, then: npx skilldrop-cli install --pack ${pack} --from <git-url>#<tag>`);
|
|
1666
|
+
console.log(`To mirror skills from this catalogue instead, use: skilldrop package <dir> --pack <name>`);
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
/* package <out-dir>: copy a vetted subset of a catalogue into a standalone catalogue an
|
|
1670
|
+
organisation can host internally (an air-gapped or reviewed mirror). Packs bring the packs
|
|
1671
|
+
they require; loops come only when every stage skill is included. MIRROR.json records where
|
|
1672
|
+
each file came from and its SHA-256, so a reviewer can tell exactly what was mirrored. */
|
|
1673
|
+
function packageCatalogue(args) {
|
|
1674
|
+
const out = path.resolve(args._[0] || die("usage: skilldrop package <out-dir> [--pack a,b] [--skills x,y] [--from <src>] [--agents]"));
|
|
1675
|
+
if (fs.existsSync(out) && fs.readdirSync(out).length) die(`${out} isn't empty — pick a new folder`);
|
|
1676
|
+
const cat = resolveCatalog(args.flags.from);
|
|
1677
|
+
if (cat.shape === "apm") die("package reads skilldrop catalogues; agentbundle ones aren't supported yet");
|
|
1678
|
+
const ps = packsOf(cat) || {};
|
|
1679
|
+
let packNames = args.flags.pack && args.flags.pack !== true ? String(args.flags.pack).split(",") : [];
|
|
1680
|
+
for (const p of packNames) if (!ps[p]) die(`unknown pack '${p}' in catalog '${cat.source}'`);
|
|
1681
|
+
for (const p of packNames.slice()) for (const r of ps[p].requires || []) if (!packNames.includes(r)) packNames.unshift(r);
|
|
1682
|
+
let skills = [];
|
|
1683
|
+
for (const p of packNames) for (const s of ps[p].skills || []) if (!skills.includes(s)) skills.push(s);
|
|
1684
|
+
if (args.flags.skills) for (const s of String(args.flags.skills).split(",")) {
|
|
1685
|
+
if (!skillExists(cat, s)) die(`unknown skill '${s}' in catalog '${cat.source}'`);
|
|
1686
|
+
if (!skills.includes(s)) skills.push(s);
|
|
1687
|
+
}
|
|
1688
|
+
if (!packNames.length && !args.flags.skills) { skills = skillsIn(cat); packNames = Object.keys(ps); }
|
|
1689
|
+
gate(cat, skills);
|
|
1690
|
+
const home = (s) => Object.keys(ps).find((p) => (ps[p].skills || []).includes(s)) || "mirror";
|
|
1691
|
+
const manifest = { source: cat.source, cli: readJSON(path.join(ROOT, "package.json")).version,
|
|
1692
|
+
created: new Date().toISOString(), files: {} };
|
|
1693
|
+
const copyTree = (src, dstRel) => {
|
|
1694
|
+
for (const f of walkFiles(src)) {
|
|
1695
|
+
const rel = path.join(dstRel, path.relative(src, f)).split(path.sep).join("/");
|
|
1696
|
+
fs.mkdirSync(path.dirname(path.join(out, rel)), { recursive: true });
|
|
1697
|
+
fs.copyFileSync(f, path.join(out, rel));
|
|
1698
|
+
manifest.files[rel] = sha256(fs.readFileSync(f));
|
|
1699
|
+
}
|
|
1700
|
+
};
|
|
1701
|
+
const usedPacks = [];
|
|
1702
|
+
for (const s of skills) {
|
|
1703
|
+
const p = home(s);
|
|
1704
|
+
if (!usedPacks.includes(p)) usedPacks.push(p);
|
|
1705
|
+
copyTree(skillDir(cat, s), path.join("packs", p, "skills", s));
|
|
1706
|
+
}
|
|
1707
|
+
const loops = [];
|
|
1708
|
+
for (const l of loopsIn(cat)) {
|
|
1709
|
+
if (loopSkills(cat, l).every((s) => skills.includes(s))) {
|
|
1710
|
+
const p = Object.keys(ps).find((x) => (ps[x].loops || []).includes(l)) || usedPacks[0];
|
|
1711
|
+
if (!usedPacks.includes(p)) continue;
|
|
1712
|
+
copyTree(loopDir(cat, l), path.join("packs", p, "loops", l));
|
|
1713
|
+
loops.push(l);
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
for (const p of usedPacks) {
|
|
1717
|
+
const meta = Object.assign({}, ps[p] || { description: "Mirrored skills." });
|
|
1718
|
+
delete meta.skills; delete meta.loops;
|
|
1719
|
+
if (meta.requires) meta.requires = meta.requires.filter((r) => usedPacks.includes(r));
|
|
1720
|
+
if (meta.journey) meta.journey.steps = (meta.journey.steps || []).filter((st) => skills.includes(st.skill));
|
|
1721
|
+
const rel = `packs/${p}/pack.json`;
|
|
1722
|
+
fs.mkdirSync(path.dirname(path.join(out, rel)), { recursive: true });
|
|
1723
|
+
fs.writeFileSync(path.join(out, rel), JSON.stringify(meta, null, 2) + "\n");
|
|
1724
|
+
}
|
|
1725
|
+
fs.writeFileSync(path.join(out, "catalogue.json"), JSON.stringify({ version: "0.1.0", packs: usedPacks }, null, 2) + "\n");
|
|
1726
|
+
let agents = [];
|
|
1727
|
+
if (args.flags.agents) for (const a of agentsIn(cat)) { copyTree(path.dirname(agentPath(cat, a)), "agents"); agents = agentsIn(cat); break; }
|
|
1728
|
+
fs.writeFileSync(path.join(out, "MIRROR.json"), JSON.stringify(manifest, null, 2) + "\n");
|
|
1729
|
+
console.log(`packaged ${skills.length} skill(s), ${loops.length} loop(s)${agents.length ? `, ${agents.length} agent(s)` : ""} in ${usedPacks.length} pack(s) from '${cat.source}' -> ${out}`);
|
|
1730
|
+
console.log(`MIRROR.json records the source and a SHA-256 for each of the ${Object.keys(manifest.files).length} files.`);
|
|
1731
|
+
console.log(`Next: review it, push it to your internal git host, then: npx skilldrop-cli install --pack <name> --from <internal-git-url>#<tag>`);
|
|
1732
|
+
}
|
|
1733
|
+
|
|
1734
|
+
function listAgents(args) {
|
|
1735
|
+
const cat = resolveCatalog(args.flags.from);
|
|
1736
|
+
const names = agentsIn(cat);
|
|
1737
|
+
if (args.flags.json)
|
|
1738
|
+
return emitJSON({
|
|
1739
|
+
catalog: cat.source,
|
|
1740
|
+
count: names.length,
|
|
1741
|
+
agents: names.map((a) => {
|
|
1742
|
+
const m = agentMeta(cat, a);
|
|
1743
|
+
return { name: a, description: m.description || null, tools: m.tools || null, model: m.model || null };
|
|
1744
|
+
}),
|
|
1745
|
+
});
|
|
1746
|
+
if (!names.length) return console.log(`catalog '${cat.source}' ships no agents.`);
|
|
1747
|
+
const w = Math.max(...names.map((n) => n.length));
|
|
1748
|
+
for (const a of names) {
|
|
1749
|
+
const m = agentMeta(cat, a);
|
|
1750
|
+
console.log(`${a.padEnd(w)} ${m.description}`);
|
|
1751
|
+
}
|
|
1752
|
+
console.log(`\n${names.length} agent(s). Install one: skilldrop install --agent <name>`);
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
/* Loops (RFC-0028): an ordered sequence of stages over existing skills, with a gate between
|
|
1756
|
+
them. LOOP.md's frontmatter is already SKILL.md's shape (name + description), so a loop
|
|
1757
|
+
projects into the target's skill directory as <name>/SKILL.md and becomes invokable exactly
|
|
1758
|
+
like a skill. loop.json travels beside it so stages, gates and cap stay machine-readable
|
|
1759
|
+
after install. A loop never contains a skill — installing one also installs the skills its
|
|
1760
|
+
stages name, unless --no-skills. Optional in a catalog. */
|
|
1761
|
+
function loopsIn(cat) {
|
|
1762
|
+
if (cat.shape === "apm") return [];
|
|
1763
|
+
if (cat.loopIndex) return Object.keys(cat.loopIndex).sort();
|
|
1764
|
+
const d = path.join(cat.dir, "loops");
|
|
1765
|
+
if (!fs.existsSync(d)) return [];
|
|
1766
|
+
return fs.readdirSync(d).filter((x) => fs.existsSync(path.join(d, x, "loop.json"))).sort();
|
|
1767
|
+
}
|
|
1768
|
+
function loopDir(cat, n) { return cat.loopIndex ? cat.loopIndex[n] : path.join(cat.dir, "loops", n); }
|
|
1769
|
+
function loopSpec(cat, n) { return readJSON(path.join(loopDir(cat, n), "loop.json")); }
|
|
1770
|
+
|
|
1771
|
+
function loopSkills(cat, n) {
|
|
1772
|
+
const seen = [];
|
|
1773
|
+
for (const st of loopSpec(cat, n).stages || [])
|
|
1774
|
+
for (const sk of st.skills || [])
|
|
1775
|
+
if (sk !== "*" && !seen.includes(sk)) seen.push(sk);
|
|
1776
|
+
return seen;
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
function checkLoop(cat, n) {
|
|
1780
|
+
const problems = [];
|
|
1781
|
+
if (!fs.existsSync(path.join(loopDir(cat, n), "LOOP.md"))) problems.push("no LOOP.md — it is the entrypoint");
|
|
1782
|
+
let spec;
|
|
1783
|
+
try { spec = loopSpec(cat, n); } catch (e) { return problems.concat("loop.json is missing or unparseable"); }
|
|
1784
|
+
if (spec.name !== n) problems.push(`loop.json name '${spec.name}' != folder '${n}'`);
|
|
1785
|
+
if (!Array.isArray(spec.stages) || !spec.stages.length) problems.push("no stages — a loop with none sequences nothing");
|
|
1786
|
+
for (const sk of loopSkills(cat, n))
|
|
1787
|
+
if (!skillExists(cat, sk)) problems.push(`stage names skill '${sk}', which is not in this catalog`);
|
|
1788
|
+
return problems;
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1791
|
+
function listLoops(args) {
|
|
1792
|
+
const cat = resolveCatalog(args.flags.from);
|
|
1793
|
+
const names = loopsIn(cat);
|
|
1794
|
+
if (args.flags.json)
|
|
1795
|
+
return emitJSON({
|
|
1796
|
+
catalog: cat.source, count: names.length,
|
|
1797
|
+
loops: names.map((n) => {
|
|
1798
|
+
const sp = loopSpec(cat, n);
|
|
1799
|
+
return { name: n, kind: sp.kind, cap: sp.cap ?? 3, description: sp.description,
|
|
1800
|
+
stages: (sp.stages || []).map((st) => ({ id: st.id, type: st.type, skills: st.skills,
|
|
1801
|
+
gate: st.gate ? { id: st.gate.id, kind: st.gate.kind } : null })) };
|
|
1802
|
+
}),
|
|
1803
|
+
});
|
|
1804
|
+
if (!names.length) return console.log(`catalog '${cat.source}' ships no loops.`);
|
|
1805
|
+
for (const n of names) {
|
|
1806
|
+
const sp = loopSpec(cat, n);
|
|
1807
|
+
const gates = (sp.stages || []).filter((st) => st.gate).map((st) => `${st.gate.id}:${st.gate.kind}`);
|
|
1808
|
+
console.log(`${n} [${sp.kind}, cap ${sp.cap ?? 3}]`);
|
|
1809
|
+
console.log(` ${(sp.stages || []).map((st) => st.id).join(" -> ")}`);
|
|
1810
|
+
console.log(` gates: ${gates.join(", ") || "none"}`);
|
|
1811
|
+
}
|
|
1812
|
+
console.log(`\n${names.length} loop(s). Install one: skilldrop install --loop <name>`);
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
function installLoops(args) {
|
|
1816
|
+
const cat = resolveCatalog(args.flags.from);
|
|
1817
|
+
const available = loopsIn(cat);
|
|
1818
|
+
if (!available.length) die(`catalog '${cat.source}' ships no loops`);
|
|
1819
|
+
let names = args._.slice();
|
|
1820
|
+
if (!names.length && args.flags.pack) {
|
|
1821
|
+
const ps = packsOf(cat);
|
|
1822
|
+
if (!ps) die(`catalog '${cat.source}' defines no packs`);
|
|
1823
|
+
packAlias(args.flags, ps);
|
|
1824
|
+
const pk = ps[args.flags.pack];
|
|
1825
|
+
if (!pk) die(`unknown pack '${args.flags.pack}' in catalog '${cat.source}'`);
|
|
1826
|
+
names = packMembers(ps, args.flags.pack, "loops");
|
|
1827
|
+
if (!names.length) die(`pack '${args.flags.pack}' declares no loops`);
|
|
1828
|
+
}
|
|
1829
|
+
if (!names.length && args.flags.all) names = available.slice();
|
|
1830
|
+
if (!names.length) die("nothing to install — pass loop names, --pack <name>, or --all");
|
|
1831
|
+
for (const n of names) if (!available.includes(n)) die(`unknown loop '${n}' in catalog '${cat.source}'`);
|
|
1832
|
+
|
|
1833
|
+
let bad = 0;
|
|
1834
|
+
for (const n of names) {
|
|
1835
|
+
const problems = checkLoop(cat, n);
|
|
1836
|
+
for (const pr of problems) console.error(`refused ${n}: ${pr}`);
|
|
1837
|
+
if (problems.length) bad++;
|
|
1838
|
+
}
|
|
1839
|
+
if (bad) die(`${bad} loop(s) failed the structural check — nothing was installed`);
|
|
1840
|
+
|
|
1841
|
+
const { dest, ide } = target(args.flags);
|
|
1842
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
1843
|
+
const l = ledger(dest);
|
|
1844
|
+
const notes = [];
|
|
1845
|
+
for (const n of names) {
|
|
1846
|
+
const sp = loopSpec(cat, n);
|
|
1847
|
+
const out = path.join(dest, n);
|
|
1848
|
+
fs.mkdirSync(out, { recursive: true });
|
|
1849
|
+
fs.copyFileSync(path.join(loopDir(cat, n), "LOOP.md"), path.join(out, "SKILL.md"));
|
|
1850
|
+
fs.copyFileSync(path.join(loopDir(cat, n), "loop.json"), path.join(out, "loop.json"));
|
|
1851
|
+
const note = writeWiring(ide, dest, n, sp.description);
|
|
1852
|
+
if (note) notes.push(note);
|
|
1853
|
+
l.data[n] = { version: sp.version || null, source: cat.source, loop: true };
|
|
1854
|
+
console.log(`installed loop ${n} -> ${out}`);
|
|
1855
|
+
}
|
|
1856
|
+
saveLedger(l);
|
|
1857
|
+
if (args.flags.local) addLocal(dest, ide, names);
|
|
1858
|
+
|
|
1859
|
+
const wanted = [];
|
|
1860
|
+
for (const n of names) for (const sk of loopSkills(cat, n)) if (!wanted.includes(sk)) wanted.push(sk);
|
|
1861
|
+
if (args.flags["no-skills"]) {
|
|
1862
|
+
console.log(`\n${names.length} loop(s) installed (${ide}). Skipped ${wanted.length} stage skill(s) (--no-skills).`);
|
|
1863
|
+
console.log(`A loop whose stage skills are absent still runs — each stage degrades via its declared fallback.`);
|
|
1864
|
+
} else if (wanted.length) {
|
|
1865
|
+
console.log(`\nInstalling ${wanted.length} stage skill(s) the loop sequences.\n`);
|
|
1866
|
+
const flags = Object.assign({}, args.flags);
|
|
1867
|
+
delete flags.loop; delete flags.pack; delete flags.all;
|
|
1868
|
+
install({ _: wanted, flags });
|
|
1869
|
+
}
|
|
1870
|
+
if (notes.length) console.log(`\n${notes.join("\n")}`);
|
|
1871
|
+
console.log(`\nRun a loop by name — e.g. "run the ${names[0]} loop on this". Its SKILL.md is the script; honour each gate and the cap.`);
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1874
|
+
function uninstallLoops(args) {
|
|
1875
|
+
if (!args._.length) die("pass loop names to uninstall");
|
|
1876
|
+
const { dest, ide } = target(args.flags);
|
|
1877
|
+
const l = ledger(dest);
|
|
1878
|
+
if (args.flags["dry-run"]) {
|
|
1879
|
+
for (const n of args._) console.log(`would remove loop ${path.join(dest, n)} (stage skills left in place)`);
|
|
1880
|
+
return;
|
|
1881
|
+
}
|
|
1882
|
+
if (!confirm(`Remove ${args._.length} loop(s) from ${dest}: ${args._.join(", ")}?`, args.flags)) return console.log("cancelled — nothing removed.");
|
|
1883
|
+
for (const n of args._) {
|
|
1884
|
+
fs.rmSync(path.join(dest, n), { recursive: true, force: true });
|
|
1885
|
+
const w = wiringPath(ide, dest, n);
|
|
1886
|
+
if (w) fs.rmSync(w, { force: true });
|
|
1887
|
+
delete l.data[n];
|
|
1888
|
+
console.log(`removed loop ${n} from ${dest} (stage skills left in place)`);
|
|
1889
|
+
}
|
|
1890
|
+
saveLedger(l);
|
|
1891
|
+
removeLocal(dest, ide, args._);
|
|
1892
|
+
}
|
|
1893
|
+
|
|
1894
|
+
function installAgents(args) {
|
|
1895
|
+
const cat = resolveCatalog(args.flags.from);
|
|
1896
|
+
const available = agentsIn(cat);
|
|
1897
|
+
if (!available.length) die(`catalog '${cat.source}' ships no agents`);
|
|
1898
|
+
const names = args._.length ? args._.slice() : (args.flags.all ? available : []);
|
|
1899
|
+
if (!names.length) die("nothing to install — pass agent names, or --all");
|
|
1900
|
+
for (const a of names) if (!available.includes(a)) die(`unknown agent '${a}' in catalog '${cat.source}'`);
|
|
1901
|
+
|
|
1902
|
+
let bad = 0;
|
|
1903
|
+
for (const a of names) {
|
|
1904
|
+
const problems = checkAgent(cat, a);
|
|
1905
|
+
for (const pr of problems) console.error(`refused ${a}: ${pr}`);
|
|
1906
|
+
if (problems.length) bad++;
|
|
1907
|
+
}
|
|
1908
|
+
if (bad) die(`${bad} agent(s) failed the structural check — nothing was installed`);
|
|
1909
|
+
|
|
1910
|
+
const { dest, ide } = agentTarget(args.flags);
|
|
1911
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
1912
|
+
const l = ledger(dest);
|
|
1913
|
+
const warns = [];
|
|
1914
|
+
for (const a of names) {
|
|
1915
|
+
const { file, warn } = writeAgent(cat, a, dest, ide);
|
|
1916
|
+
if (warn) warns.push(warn);
|
|
1917
|
+
l.data[a] = { version: null, source: cat.source, file }; // agents carry no version yet (RFC-0012)
|
|
1918
|
+
console.log(`installed ${a} -> ${path.join(dest, file)}`);
|
|
1919
|
+
}
|
|
1920
|
+
saveLedger(l);
|
|
1921
|
+
console.log(`\n${names.length} agent(s) installed (${ide}).`);
|
|
1922
|
+
if (warns.length) console.log(`\nTool mapping:\n${warns.join("\n")}`);
|
|
1923
|
+
if (ide === "claude") console.log("Delegate by name, e.g. \"use the devils-advocate agent on this diff\".");
|
|
1924
|
+
if (ide === "kiro") console.log("Kiro grants `tools` but not auto-approval — you are prompted per call by design.");
|
|
1925
|
+
if (ide === "copilot") console.log("Invoke with: copilot --agent <name>");
|
|
1926
|
+
if (ide === "codex") console.log("sandbox_mode is left unset, so each agent inherits the parent session's permissions.");
|
|
1927
|
+
if (ide === "antigravity") console.log("Emitted with `subagent: true` so invoke_subagent can reach them.");
|
|
1928
|
+
if (cat.source !== BUNDLED)
|
|
1929
|
+
console.log(`\nWARNING: third-party catalog '${cat.source}'. An agent is a system prompt your tool will adopt — read it before first use. Install copied files only; nothing was executed.`);
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1932
|
+
function uninstallAgents(args) {
|
|
1933
|
+
if (!args._.length) die("pass agent names to uninstall");
|
|
1934
|
+
const { dest } = agentTarget(args.flags);
|
|
1935
|
+
const l = ledger(dest);
|
|
1936
|
+
if (args.flags["dry-run"]) {
|
|
1937
|
+
for (const a of args._) console.log(`would remove agent ${a} from ${dest}`);
|
|
1938
|
+
return;
|
|
1939
|
+
}
|
|
1940
|
+
if (!confirm(`Remove ${args._.length} agent(s) from ${dest}: ${args._.join(", ")}?`, args.flags)) return console.log("cancelled — nothing removed.");
|
|
1941
|
+
for (const a of args._) {
|
|
1942
|
+
const rec = l.data[a];
|
|
1943
|
+
for (const f of new Set([rec && rec.file, `${a}.md`, `${a}.json`, `${a}.agent.md`, `${a}.toml`].filter(Boolean)))
|
|
1944
|
+
fs.rmSync(path.join(dest, f), { force: true });
|
|
1945
|
+
delete l.data[a];
|
|
1946
|
+
console.log(`removed ${a} from ${dest}`);
|
|
1947
|
+
}
|
|
1948
|
+
saveLedger(l);
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1951
|
+
function list(args) {
|
|
1952
|
+
const cat = resolveCatalog(args.flags.from);
|
|
1953
|
+
const rows = skillsIn(cat).map((s) => {
|
|
1954
|
+
try { const m = manifestOf(cat, s); return [s, m.version || "?", (m.model && m.model.tier) || "?"]; }
|
|
1955
|
+
catch (e) { return [s, "?", "?"]; }
|
|
1956
|
+
});
|
|
1957
|
+
if (args.flags.json)
|
|
1958
|
+
return emitJSON({
|
|
1959
|
+
catalog: cat.source,
|
|
1960
|
+
count: rows.length,
|
|
1961
|
+
skills: rows.map(([name, version, tier]) => ({ name, version, tier })),
|
|
1962
|
+
});
|
|
1963
|
+
const w = Math.max(...rows.map((r) => r[0].length));
|
|
1964
|
+
for (const [s, v, t] of rows) console.log(`${s.padEnd(w)} ${v} ${t}`);
|
|
1965
|
+
console.log(`\n${rows.length} skills in catalog '${cat.source}'. Details: skilldrop info <skill>`);
|
|
1966
|
+
}
|
|
1967
|
+
|
|
1968
|
+
/* RFC-0032: what to try first after installing a pack, how to tell it worked, what to do
|
|
1969
|
+
if it didn't — the same first-value block the pack's site page renders. */
|
|
1970
|
+
function firstValueLines(p) {
|
|
1971
|
+
const fv = p["first-value"];
|
|
1972
|
+
if (!fv) return [];
|
|
1973
|
+
const out = [`Start here: ${fv["starter-task"]}`, "", ` ${fv["starter-prompt"]}`, ""];
|
|
1974
|
+
for (const pre of fv.prerequisites || []) out.push(` before you start: ${pre}`);
|
|
1975
|
+
out.push(` worked if: ${fv.verification}`, ` if nothing happens: ${fv.recovery}`);
|
|
1976
|
+
return out;
|
|
1977
|
+
}
|
|
1978
|
+
|
|
1979
|
+
function infoPack(args) {
|
|
1980
|
+
const cat = resolveCatalog(args.flags.from);
|
|
1981
|
+
const ps = packsOf(cat) || die(`catalog '${cat.source}' has no packs`);
|
|
1982
|
+
packAlias(args.flags, ps);
|
|
1983
|
+
const name = args.flags.pack;
|
|
1984
|
+
const p = ps[name] || die(`unknown pack '${name}' — available: ${Object.keys(ps).join(", ")}`);
|
|
1985
|
+
if (args.flags.json)
|
|
1986
|
+
return emitJSON({ catalog: cat.source, name, ...p });
|
|
1987
|
+
console.log(`${p.display_name || name} (${name})\n\n${p.description}\n`);
|
|
1988
|
+
console.log(`skills: ${p.skills.join(", ")}`);
|
|
1989
|
+
if ((p.requires || []).length) console.log(`requires: ${p.requires.join(", ")} (installed with it)`);
|
|
1990
|
+
if ((p.loops || []).length) console.log(`loops: ${p.loops.join(", ")}`);
|
|
1991
|
+
if (p.links && p.links.documentation) console.log(`page: ${p.links.documentation}`);
|
|
1992
|
+
const fv = firstValueLines(p);
|
|
1993
|
+
if (fv.length) console.log("\n" + fv.join("\n"));
|
|
1994
|
+
}
|
|
1995
|
+
|
|
1996
|
+
function info(args) {
|
|
1997
|
+
if (args.flags.pack) return infoPack(args);
|
|
1998
|
+
const cat = resolveCatalog(args.flags.from);
|
|
1999
|
+
const s = args._[0] || die("pass a skill name, or --pack <name>");
|
|
2000
|
+
if (!skillExists(cat, s)) die(`unknown skill '${s}' in catalog '${cat.source}'`);
|
|
2001
|
+
const m = manifestOf(cat, s);
|
|
2002
|
+
const ps = packsOf(cat) || {};
|
|
2003
|
+
const inPacks = Object.entries(ps).filter(([, p]) => p.skills.includes(s)).map(([n]) => n);
|
|
2004
|
+
const needsPip = ((m.deps || {}).pip || []).length > 0 || fs.existsSync(path.join(skillDir(cat, s), "requirements.txt"));
|
|
2005
|
+
if (args.flags.json)
|
|
2006
|
+
return emitJSON({
|
|
2007
|
+
catalog: cat.source,
|
|
2008
|
+
name: m.name,
|
|
2009
|
+
version: m.version || null,
|
|
2010
|
+
description: m.description || null,
|
|
2011
|
+
tier: (m.model && m.model.tier) || null,
|
|
2012
|
+
packs: inPacks,
|
|
2013
|
+
related: m.related || [],
|
|
2014
|
+
tags: m.tags || [],
|
|
2015
|
+
deps: { pip: (m.deps || {}).pip || [], npm: (m.deps || {}).npm || [], requirementsTxt: needsPip },
|
|
2016
|
+
env: { required: (m.env || {}).required || [], optional: (m.env || {}).optional || [] },
|
|
2017
|
+
hooks: m.hooks || [],
|
|
2018
|
+
permissions: m.permissions || null,
|
|
2019
|
+
});
|
|
2020
|
+
console.log(`${m.name}@${m.version} (tier: ${(m.model && m.model.tier) || "n/a"})\n\n${m.description}\n`);
|
|
2021
|
+
console.log(`catalog: ${cat.source}`);
|
|
2022
|
+
console.log(`packs: ${inPacks.join(", ") || "-"}`);
|
|
2023
|
+
console.log(`related: ${(m.related || []).join(", ") || "-"}`);
|
|
2024
|
+
if (((m.deps || {}).pip || []).length || fs.existsSync(path.join(skillDir(cat, s), "requirements.txt")))
|
|
2025
|
+
console.log("deps: python (requirements.txt)");
|
|
2026
|
+
if (((m.env || {}).required || []).length) console.log(`env: ${m.env.required.join(", ")} (required)`);
|
|
2027
|
+
if (fs.existsSync(path.join(skillDir(cat, s), "scripts")))
|
|
2028
|
+
console.log(`scripts: ${permissionSummary(m.permissions)}${m.permissions && m.permissions.notes ? `\n ${m.permissions.notes}` : ""}`);
|
|
2029
|
+
}
|
|
2030
|
+
|
|
2031
|
+
function listPacks(args) {
|
|
2032
|
+
const cat = resolveCatalog(args.flags.from);
|
|
2033
|
+
const ps = packsOf(cat);
|
|
2034
|
+
if (args.flags.json)
|
|
2035
|
+
return emitJSON({
|
|
2036
|
+
catalog: cat.source,
|
|
2037
|
+
count: ps ? Object.keys(ps).length : 0,
|
|
2038
|
+
packs: Object.entries(ps || {}).map(([name, p]) => ({
|
|
2039
|
+
name, description: p.description || null, skills: p.skills || [], requires: p.requires || [],
|
|
2040
|
+
})),
|
|
2041
|
+
});
|
|
2042
|
+
if (!ps) return console.log(`catalog '${cat.source}' defines no packs.`);
|
|
2043
|
+
const w = Math.max(...Object.keys(ps).map((n) => n.length));
|
|
2044
|
+
for (const [n, p] of Object.entries(ps))
|
|
2045
|
+
console.log(`${n.padEnd(w)} (${p.skills.length} skills${(p.requires || []).length ? ` + ${p.requires.join(", ")}` : ""}) ${p.description}`);
|
|
2046
|
+
console.log("\nInstall one: skilldrop install --pack <name>");
|
|
2047
|
+
}
|
|
2048
|
+
|
|
2049
|
+
function validateCmd(args) {
|
|
2050
|
+
const cat = resolveCatalog(args.flags.from);
|
|
2051
|
+
const names = skillsIn(cat);
|
|
2052
|
+
let bad = 0;
|
|
2053
|
+
for (const s of names) {
|
|
2054
|
+
const problems = checkSkill(cat, s);
|
|
2055
|
+
for (const f of scanSkill(cat, s).filter((x) => x.id.startsWith("undeclared-")))
|
|
2056
|
+
problems.push(`${f.file}:${f.line} ${f.why} (RFC-0039)`);
|
|
2057
|
+
for (const p of problems) console.log(`FAIL ${s}: ${p}`);
|
|
2058
|
+
if (problems.length) bad++;
|
|
2059
|
+
}
|
|
2060
|
+
const ps = packsOf(cat);
|
|
2061
|
+
if (ps)
|
|
2062
|
+
for (const [n, p] of Object.entries(ps))
|
|
2063
|
+
for (const s of p.skills)
|
|
2064
|
+
if (!names.includes(s)) { console.log(`FAIL packs: pack '${n}' lists unknown skill '${s}'`); bad++; }
|
|
2065
|
+
if (bad) { console.log(`\n${bad} problem(s) in catalog '${cat.source}'.`); process.exit(1); }
|
|
2066
|
+
console.log(`OK: ${names.length} skills in catalog '${cat.source}' pass the structural check.`);
|
|
2067
|
+
}
|
|
2068
|
+
|
|
2069
|
+
/* ---------- bootstrap (enterprise distribution) ----------
|
|
2070
|
+
Writes the skilldrop marketplace into ~/.claude/settings.json so every Claude Code
|
|
2071
|
+
session on this machine discovers the catalogue without any per-session /plugin command.
|
|
2072
|
+
Idempotent: safe to run in a provisioning script or onboarding runbook. */
|
|
2073
|
+
const BOOTSTRAP_MARKETPLACE_KEY = "skilldrop";
|
|
2074
|
+
const BOOTSTRAP_GITHUB_OWNER = "sananthanarayan";
|
|
2075
|
+
const BOOTSTRAP_GITHUB_REPO = "skilldrop";
|
|
2076
|
+
|
|
2077
|
+
function bootstrap() {
|
|
2078
|
+
const settingsPath = path.join(os.homedir(), ".claude", "settings.json");
|
|
2079
|
+
let data = {};
|
|
2080
|
+
const raw = readIfPresent(settingsPath, null);
|
|
2081
|
+
if (raw !== null) {
|
|
2082
|
+
try { data = JSON.parse(raw); }
|
|
2083
|
+
catch (e) { die(`${settingsPath} is not valid JSON — fix it first, then re-run bootstrap`); }
|
|
2084
|
+
}
|
|
2085
|
+
data.extraKnownMarketplaces = data.extraKnownMarketplaces || {};
|
|
2086
|
+
const entry = { source: { source: "github", owner: BOOTSTRAP_GITHUB_OWNER, repo: BOOTSTRAP_GITHUB_REPO } };
|
|
2087
|
+
const existing = data.extraKnownMarketplaces[BOOTSTRAP_MARKETPLACE_KEY];
|
|
2088
|
+
if (existing && JSON.stringify(existing) === JSON.stringify(entry)) {
|
|
2089
|
+
console.log(`already configured: ${BOOTSTRAP_MARKETPLACE_KEY} -> github:${BOOTSTRAP_GITHUB_OWNER}/${BOOTSTRAP_GITHUB_REPO}`);
|
|
2090
|
+
console.log(`\nIn any Claude Code session:\n /plugin install <pack>@${BOOTSTRAP_MARKETPLACE_KEY}`);
|
|
2091
|
+
return;
|
|
2092
|
+
}
|
|
2093
|
+
data.extraKnownMarketplaces[BOOTSTRAP_MARKETPLACE_KEY] = entry;
|
|
2094
|
+
fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
|
|
2095
|
+
fs.writeFileSync(settingsPath, JSON.stringify(data, null, 2) + "\n");
|
|
2096
|
+
console.log(`configured ${settingsPath}:`);
|
|
2097
|
+
console.log(` extraKnownMarketplaces.${BOOTSTRAP_MARKETPLACE_KEY} -> github:${BOOTSTRAP_GITHUB_OWNER}/${BOOTSTRAP_GITHUB_REPO}`);
|
|
2098
|
+
console.log(`\nIn any Claude Code session, install a role pack:\n /plugin install solution-architect@${BOOTSTRAP_MARKETPLACE_KEY}`);
|
|
2099
|
+
console.log(` /plugin install dev-team@${BOOTSTRAP_MARKETPLACE_KEY}`);
|
|
2100
|
+
console.log(` /plugin install ai-engineering@${BOOTSTRAP_MARKETPLACE_KEY}`);
|
|
2101
|
+
console.log(`\nOr install the full catalogue:\n /plugin install ${BOOTSTRAP_MARKETPLACE_KEY}@${BOOTSTRAP_MARKETPLACE_KEY}`);
|
|
2102
|
+
}
|
|
2103
|
+
|
|
2104
|
+
const args = parseArgs(process.argv.slice(2));
|
|
2105
|
+
const cmd = args._.shift();
|
|
2106
|
+
const commands = { list, info, packs: listPacks, profiles: listProfiles, agents: listAgents, loops: listLoops, install, update, outdated, uninstall, diff, doctor, "new-skill": newSkill, "loop-stats": loopStats, "init-catalogue": initCatalogue, package: packageCatalogue, validate: validateCmd, scan, bootstrap };
|
|
2107
|
+
if (args.flags.version || cmd === "version") console.log(readJSON(path.join(ROOT, "package.json")).version);
|
|
2108
|
+
else if (!cmd || cmd === "help" || args.flags.help) console.log(HELP);
|
|
2109
|
+
else if (commands[cmd]) commands[cmd](args);
|
|
2110
|
+
else die(`unknown command '${cmd}' — run: skilldrop help`);
|