@pugi/cli 0.1.0-beta.9 → 0.1.0-beta.91
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +132 -0
- package/LICENSE +1 -1
- package/assets/pugi-prozr2-mascot.ansi +9 -0
- package/bin/run.js +33 -1
- package/dist/commands/deploy.js +40 -40
- package/dist/commands/flatten.js +191 -0
- package/dist/commands/jobs-watch.js +201 -0
- package/dist/commands/jobs.js +42 -27
- package/dist/commands/smoke.js +133 -0
- package/dist/core/agent-progress/cleanup.js +134 -0
- package/dist/core/agent-progress/schema.js +144 -0
- package/dist/core/agent-progress/writer.js +101 -0
- package/dist/core/agents/adaptive-router.js +330 -0
- package/dist/core/agents/query-decomposer.js +297 -0
- package/dist/core/agents/registry.js +3 -3
- package/dist/core/approvals/shortcut-resolver.js +98 -0
- package/dist/core/artifact-chain/dispatcher.js +148 -0
- package/dist/core/artifact-chain/exporter.js +164 -0
- package/dist/core/artifact-chain/state.js +243 -0
- package/dist/core/artifact-chain/steps.js +169 -0
- package/dist/core/ask-user/question.js +92 -0
- package/dist/core/audit/audit-trail.js +275 -0
- package/dist/core/auth/ensure-authenticated.js +129 -0
- package/dist/core/auth/env-provider.js +238 -0
- package/dist/core/auto-open-browser.js +4 -4
- package/dist/core/auto-update/channels.js +122 -0
- package/dist/core/auto-update/checker.js +241 -0
- package/dist/core/auto-update/state.js +235 -0
- package/dist/core/bare-mode/index.js +107 -0
- package/dist/core/bash/redirect.js +281 -0
- package/dist/core/bash-classifier.js +436 -40
- package/dist/core/checkpoint/resumer.js +149 -0
- package/dist/core/checkpoint/rewinder.js +291 -0
- package/dist/core/checkpoints/shadow-git.js +670 -0
- package/dist/core/citations/parser.js +109 -0
- package/dist/core/classifier/yolo-classifier.js +88 -0
- package/dist/core/codegraph/decision-store.js +248 -0
- package/dist/core/codegraph/detect-repo.js +459 -0
- package/dist/core/codegraph/install.js +134 -0
- package/dist/core/codegraph/offer-hook.js +220 -0
- package/dist/core/compact/auto-trigger.js +96 -0
- package/dist/core/compact/buffer-rewriter.js +115 -0
- package/dist/core/compact/summarizer.js +208 -0
- package/dist/core/compact/token-counter.js +108 -0
- package/dist/core/consensus/anvil-fanout.js +25 -25
- package/dist/core/consensus/diff-capture.js +121 -12
- package/dist/core/consensus/rubric.js +21 -21
- package/dist/core/context/builder.js +6 -6
- package/dist/core/context/compaction-events.js +8 -8
- package/dist/core/context/compaction.js +31 -31
- package/dist/core/context/index.js +15 -8
- package/dist/core/context/invariants.js +51 -51
- package/dist/core/context/markdown-loader.js +28 -10
- package/dist/core/context/markdown-traverse.js +255 -0
- package/dist/core/context/pugiignore.js +41 -41
- package/dist/core/context/repo-skeleton.js +37 -37
- package/dist/core/context/tool-eviction.js +55 -0
- package/dist/core/context/watcher.js +32 -32
- package/dist/core/context/working-set.js +23 -23
- package/dist/core/coordinator/agent-tools.js +77 -0
- package/dist/core/coordinator/agent-toolset.js +65 -0
- package/dist/core/coordinator/fsm.js +73 -0
- package/dist/core/coordinator/mode-fsm.js +70 -0
- package/dist/core/cost/rate-card.js +129 -0
- package/dist/core/cost/tracker.js +221 -0
- package/dist/core/credentials.js +13 -13
- package/dist/core/cron/scheduler.js +138 -0
- package/dist/core/denial-tracking/index.js +8 -0
- package/dist/core/denial-tracking/state.js +264 -0
- package/dist/core/diagnostics/probe-runner.js +93 -0
- package/dist/core/diagnostics/probes/api.js +46 -0
- package/dist/core/diagnostics/probes/auth.js +93 -0
- package/dist/core/diagnostics/probes/bare-mode.js +42 -0
- package/dist/core/diagnostics/probes/cli-version.js +127 -0
- package/dist/core/diagnostics/probes/config.js +72 -0
- package/dist/core/diagnostics/probes/denial-tracking.js +57 -0
- package/dist/core/diagnostics/probes/disk.js +81 -0
- package/dist/core/diagnostics/probes/engine-live.js +46 -0
- package/dist/core/diagnostics/probes/git.js +65 -0
- package/dist/core/diagnostics/probes/hooks.js +118 -0
- package/dist/core/diagnostics/probes/mcp.js +75 -0
- package/dist/core/diagnostics/probes/node.js +59 -0
- package/dist/core/diagnostics/probes/pnpm.js +36 -0
- package/dist/core/diagnostics/probes/pugi-md.js +89 -0
- package/dist/core/diagnostics/probes/sandbox.js +40 -0
- package/dist/core/diagnostics/probes/session.js +74 -0
- package/dist/core/diagnostics/probes/status-snapshot.js +488 -0
- package/dist/core/diagnostics/probes/workspace.js +63 -0
- package/dist/core/diagnostics/types.js +70 -0
- package/dist/core/dispatch/cache-cleanup.js +197 -0
- package/dist/core/dispatch/cache-handoff.js +295 -0
- package/dist/core/edits/apply-patch-layer-e.js +189 -0
- package/dist/core/edits/dispatch.js +333 -7
- package/dist/core/edits/format-detector.js +260 -0
- package/dist/core/edits/format-matrix.js +26 -0
- package/dist/core/edits/fuzzy-ladder.js +650 -0
- package/dist/core/edits/index.js +5 -1
- package/dist/core/edits/journal.js +199 -0
- package/dist/core/edits/layer-a-apply.js +15 -15
- package/dist/core/edits/layer-a-fuzzy-apply.js +198 -0
- package/dist/core/edits/layer-b-apply.js +9 -9
- package/dist/core/edits/layer-c-apply.js +6 -6
- package/dist/core/edits/layer-d-ast.js +557 -14
- package/dist/core/edits/marker-parser.js +12 -12
- package/dist/core/edits/security-gate.js +27 -27
- package/dist/core/edits/verify-hook.js +273 -0
- package/dist/core/edits/worktree.js +29 -29
- package/dist/core/engine/anvil-client.js +214 -26
- package/dist/core/engine/auto-compact.js +179 -0
- package/dist/core/engine/budgets.js +186 -0
- package/dist/core/engine/context-prefix.js +155 -0
- package/dist/core/engine/index.js +1 -1
- package/dist/core/engine/intensity.js +158 -0
- package/dist/core/engine/intent.js +260 -0
- package/dist/core/engine/native-pugi.js +1295 -227
- package/dist/core/engine/prompts.js +129 -19
- package/dist/core/engine/strip-internal-fields.js +124 -0
- package/dist/core/engine/tool-bridge.js +1792 -59
- package/dist/core/evaluation/golden-dataset.js +293 -0
- package/dist/core/feedback/queue.js +177 -0
- package/dist/core/feedback/submitter.js +145 -0
- package/dist/core/file-cache.js +113 -1
- package/dist/core/flatten/flatten-repo.js +439 -0
- package/dist/core/format/osc8-link.js +28 -0
- package/dist/core/hook-chains.js +392 -0
- package/dist/core/hooks/citation-verify-hook.js +138 -0
- package/dist/core/hooks/citation-verify.js +112 -0
- package/dist/core/hooks/events.js +46 -0
- package/dist/core/hooks/index.js +15 -0
- package/dist/core/hooks/registry.js +216 -0
- package/dist/core/hooks/runner.js +236 -0
- package/dist/core/hooks/v2/event-emitter.js +115 -0
- package/dist/core/hooks/v2/executor.js +282 -0
- package/dist/core/hooks/v2/index.js +25 -0
- package/dist/core/hooks/v2/lifecycle.js +104 -0
- package/dist/core/hooks/v2/loader.js +216 -0
- package/dist/core/hooks/v2/matcher.js +125 -0
- package/dist/core/hooks/v2/trust.js +143 -0
- package/dist/core/hooks/v2/types.js +86 -0
- package/dist/core/hooks/worktree-events.js +158 -0
- package/dist/core/image/renderer.js +71 -0
- package/dist/core/init/detector.js +582 -0
- package/dist/core/init/template-renderer.js +242 -0
- package/dist/core/jobs/registry.js +18 -18
- package/dist/core/ledger/results-tsv.js +142 -0
- package/dist/core/log-discipline/stdout-redirect.js +51 -0
- package/dist/core/lsp/cache.js +105 -0
- package/dist/core/lsp/client.js +551 -41
- package/dist/core/lsp/language-detect.js +66 -0
- package/dist/core/lsp/post-edit-diagnostics.js +171 -0
- package/dist/core/lsp/server-detect.js +173 -0
- package/dist/core/lsp/symbol-cache.js +162 -0
- package/dist/core/lsp/symbol-tools.js +664 -0
- package/dist/core/mcp/client.js +97 -28
- package/dist/core/mcp/http-server.js +553 -0
- package/dist/core/mcp/orchestrator-tools.js +662 -0
- package/dist/core/mcp/permission.js +190 -0
- package/dist/core/mcp/registry.js +39 -17
- package/dist/core/mcp/server-tools.js +219 -0
- package/dist/core/mcp/server.js +397 -0
- package/dist/core/mcp/trust.js +10 -10
- package/dist/core/memory/dual-write.js +416 -0
- package/dist/core/memory/passive-extract.js +130 -0
- package/dist/core/memory/phase1-kinds.js +20 -0
- package/dist/core/memory/secret-scanner.js +304 -0
- package/dist/core/memory-sync/queue.js +170 -0
- package/dist/core/metrics/extract.js +113 -0
- package/dist/core/modes/roo-modes.js +68 -0
- package/dist/core/onboarding/ensure-initialized.js +133 -0
- package/dist/core/onboarding/marker.js +111 -0
- package/dist/core/onboarding/telemetry-state.js +108 -0
- package/dist/core/output-style/presets.js +176 -0
- package/dist/core/output-style/state.js +185 -0
- package/dist/core/path-security.js +287 -5
- package/dist/core/permission.js +82 -22
- package/dist/core/permissions/auto-classifier.js +124 -0
- package/dist/core/permissions/bash-parser.js +371 -0
- package/dist/core/permissions/circuit-breaker.js +83 -0
- package/dist/core/permissions/constrained-edit.js +91 -0
- package/dist/core/permissions/gate.js +278 -0
- package/dist/core/permissions/index.js +20 -0
- package/dist/core/permissions/mode.js +174 -0
- package/dist/core/permissions/network-egress.js +137 -0
- package/dist/core/permissions/state.js +241 -0
- package/dist/core/permissions/tool-class.js +93 -0
- package/dist/core/plan-mode/ui-state.js +51 -0
- package/dist/core/plans/plan-artifact.js +721 -0
- package/dist/core/policy-limits/etag-store.js +122 -0
- package/dist/core/prd-check/parser.js +215 -0
- package/dist/core/prd-check/reporter.js +127 -0
- package/dist/core/prd-check/session-review.js +557 -0
- package/dist/core/prd-check/verifiers.js +223 -0
- package/dist/core/prompt-cache/client-cache.js +99 -0
- package/dist/core/prompts/assembly.js +29 -0
- package/dist/core/prompts/registry.js +364 -0
- package/dist/core/pugi-md/cc-compat-rules.js +735 -0
- package/dist/core/pugi-md/context-injector.js +76 -0
- package/dist/core/pugi-md/walk-up.js +207 -0
- package/dist/core/python/uv-installer.js +270 -0
- package/dist/core/python/uv-resolver.js +83 -0
- package/dist/core/rate-limit/narrator.js +146 -0
- package/dist/core/recipes/cli-types.js +20 -0
- package/dist/core/recipes/loader.js +103 -0
- package/dist/core/recipes/runner.js +345 -0
- package/dist/core/recipes/schema.js +587 -0
- package/dist/core/release-notes/parser.js +241 -0
- package/dist/core/release-notes/state.js +116 -0
- package/dist/core/repl/ask.js +37 -37
- package/dist/core/repl/cancellation.js +26 -26
- package/dist/core/repl/cap-warning.js +4 -4
- package/dist/core/repl/clipboard-read.js +11 -11
- package/dist/core/repl/dispatch-fsm.js +12 -12
- package/dist/core/repl/history-search.js +15 -15
- package/dist/core/repl/history.js +28 -18
- package/dist/core/repl/kill-ring.js +5 -5
- package/dist/core/repl/model-pricing.js +135 -0
- package/dist/core/repl/privacy-banner.js +22 -22
- package/dist/core/repl/session.js +2148 -217
- package/dist/core/repl/slash-commands.js +501 -41
- package/dist/core/repl/store/index.js +1 -1
- package/dist/core/repl/store/jsonl-log.js +22 -22
- package/dist/core/repl/store/lockfile.js +10 -10
- package/dist/core/repl/store/session-store.js +136 -107
- package/dist/core/repl/store/types.js +15 -15
- package/dist/core/repl/store/uuid-v7.js +12 -12
- package/dist/core/repl/workspace-context.js +43 -21
- package/dist/core/repo-map/build.js +125 -0
- package/dist/core/repo-map/cache.js +185 -0
- package/dist/core/repo-map/extractor.js +254 -0
- package/dist/core/repo-map/formatter.js +145 -0
- package/dist/core/repo-map/page-rank.js +105 -0
- package/dist/core/repo-map/scanner.js +211 -0
- package/dist/core/retry-budget/budget.js +284 -0
- package/dist/core/retry-budget/index.js +5 -0
- package/dist/core/retry-budget/retry-cap.js +74 -0
- package/dist/core/routing/lead-worker.js +43 -0
- package/dist/core/routing/pre-flight-estimator.js +108 -0
- package/dist/core/runs/run-tree.js +103 -0
- package/dist/core/security/injection-scanner.js +367 -0
- package/dist/core/security/output-filter.js +418 -0
- package/dist/core/session/env-file.js +105 -0
- package/dist/core/session/section-budgets.js +140 -0
- package/dist/core/session.js +92 -0
- package/dist/core/settings.js +324 -5
- package/dist/core/share/formatter.js +271 -0
- package/dist/core/share/redactor.js +221 -0
- package/dist/core/share/uploader.js +267 -0
- package/dist/core/skills/defaults.js +30 -30
- package/dist/core/skills/loader.js +22 -22
- package/dist/core/skills/sources.js +27 -27
- package/dist/core/smoke/headless-driver.js +174 -0
- package/dist/core/smoke/orchestrator.js +194 -0
- package/dist/core/smoke/runner.js +238 -0
- package/dist/core/smoke/scenario-parser.js +316 -0
- package/dist/core/statusline.js +99 -0
- package/dist/core/subagents/dispatcher-real.js +600 -0
- package/dist/core/subagents/dispatcher.js +132 -43
- package/dist/core/subagents/index.js +19 -6
- package/dist/core/subagents/isolation-matrix.js +213 -0
- package/dist/core/subagents/spawn.js +19 -4
- package/dist/core/telemetry/emitter.js +229 -0
- package/dist/core/telemetry/queue.js +251 -0
- package/dist/core/theme/context.js +91 -0
- package/dist/core/theme/presets.js +228 -0
- package/dist/core/theme/state.js +181 -0
- package/dist/core/todos/invariant.js +10 -0
- package/dist/core/todos/state.js +177 -0
- package/dist/core/tool-schema/compressor.js +89 -0
- package/dist/core/transport/version-interceptor.js +166 -0
- package/dist/core/trust.js +2 -2
- package/dist/core/tui/thinking-block.js +64 -0
- package/dist/core/vim/keymap.js +288 -0
- package/dist/core/vim/state.js +92 -0
- package/dist/core/watch-markers/marker-watcher.js +133 -0
- package/dist/core/worktree/include-parser.js +249 -0
- package/dist/core/worktree-manager/cleanup.js +123 -0
- package/dist/core/worktree-manager/manager.js +303 -0
- package/dist/index.js +36 -0
- package/dist/runtime/bootstrap.js +190 -0
- package/dist/runtime/cli.js +4185 -549
- package/dist/runtime/commands/agents.js +31 -31
- package/dist/runtime/commands/budget.js +5 -5
- package/dist/runtime/commands/cancel.js +231 -0
- package/dist/runtime/commands/chain.js +489 -0
- package/dist/runtime/commands/codegraph-status.js +227 -0
- package/dist/runtime/commands/compact.js +297 -0
- package/dist/runtime/commands/config.js +73 -39
- package/dist/runtime/commands/cost.js +199 -0
- package/dist/runtime/commands/delegate.js +27 -4
- package/dist/runtime/commands/dispatch.js +126 -0
- package/dist/runtime/commands/doctor.js +579 -0
- package/dist/runtime/commands/feedback.js +184 -0
- package/dist/runtime/commands/hooks.js +187 -0
- package/dist/runtime/commands/init.js +254 -0
- package/dist/runtime/commands/lsp.js +200 -38
- package/dist/runtime/commands/mcp.js +879 -0
- package/dist/runtime/commands/memory.js +582 -0
- package/dist/runtime/commands/model.js +237 -0
- package/dist/runtime/commands/onboarding.js +275 -0
- package/dist/runtime/commands/patch.js +12 -12
- package/dist/runtime/commands/permissions.js +112 -0
- package/dist/runtime/commands/plan.js +143 -0
- package/dist/runtime/commands/prd-check.js +285 -0
- package/dist/runtime/commands/privacy.js +17 -17
- package/dist/runtime/commands/recipe.js +325 -0
- package/dist/runtime/commands/redo-blob-store.js +92 -0
- package/dist/runtime/commands/redo.js +361 -0
- package/dist/runtime/commands/release-notes.js +229 -0
- package/dist/runtime/commands/repo-map.js +95 -0
- package/dist/runtime/commands/report.js +299 -0
- package/dist/runtime/commands/resume.js +118 -0
- package/dist/runtime/commands/review-consensus.js +68 -53
- package/dist/runtime/commands/rewind.js +333 -0
- package/dist/runtime/commands/roster.js +14 -14
- package/dist/runtime/commands/sessions.js +163 -0
- package/dist/runtime/commands/share.js +316 -0
- package/dist/runtime/commands/skills.js +31 -31
- package/dist/runtime/commands/status.js +186 -0
- package/dist/runtime/commands/stickers.js +82 -0
- package/dist/runtime/commands/style.js +194 -0
- package/dist/runtime/commands/theme.js +196 -0
- package/dist/runtime/commands/undo.js +54 -22
- package/dist/runtime/commands/update.js +289 -0
- package/dist/runtime/commands/vim.js +140 -0
- package/dist/runtime/commands/worktree.js +8 -8
- package/dist/runtime/commands/worktrees.js +155 -0
- package/dist/runtime/headless-repl.js +195 -0
- package/dist/runtime/headless.js +543 -0
- package/dist/runtime/load-hooks-or-exit.js +71 -0
- package/dist/runtime/plan-decompose.js +22 -22
- package/dist/runtime/sigint-guard.js +272 -0
- package/dist/runtime/update-check.js +28 -28
- package/dist/runtime/version.js +65 -0
- package/dist/runtime/worktree-bootstrap.js +579 -0
- package/dist/skills/bundled/batch.js +617 -0
- package/dist/skills/bundled/index.js +45 -0
- package/dist/skills/bundled/loop.js +358 -0
- package/dist/skills/bundled/remember.js +383 -0
- package/dist/skills/bundled/simplify.js +289 -0
- package/dist/skills/bundled/skillify.js +373 -0
- package/dist/skills/bundled/stuck.js +558 -0
- package/dist/skills/bundled/verify.js +439 -0
- package/dist/testing/vcr.js +486 -0
- package/dist/tools/agent-tool.js +229 -0
- package/dist/tools/apply-patch.js +89 -28
- package/dist/tools/ask-user-question.js +337 -0
- package/dist/tools/ask-user.js +115 -0
- package/dist/tools/bash.js +624 -46
- package/dist/tools/brief.js +224 -0
- package/dist/tools/cron.js +433 -0
- package/dist/tools/enter-worktree.js +250 -0
- package/dist/tools/exit-worktree.js +147 -0
- package/dist/tools/file-tools.js +161 -44
- package/dist/tools/lsp-tools.js +377 -1
- package/dist/tools/mcp-tool.js +260 -0
- package/dist/tools/multi-edit.js +361 -0
- package/dist/tools/powershell.js +268 -0
- package/dist/tools/registry.js +99 -4
- package/dist/tools/skill-tool.js +96 -0
- package/dist/tools/sleep.js +99 -0
- package/dist/tools/synthetic-output.js +133 -0
- package/dist/tools/tasks.js +208 -0
- package/dist/tools/todo-write.js +184 -0
- package/dist/tools/verify-plan-execution.js +295 -0
- package/dist/tools/web-fetch-injection-scanner.js +207 -0
- package/dist/tools/web-fetch.js +195 -10
- package/dist/tools/web-search.js +458 -0
- package/dist/tui/agent-progress-card.js +111 -0
- package/dist/tui/agent-tree.js +11 -1
- package/dist/tui/ask-modal.js +14 -14
- package/dist/tui/ask-user-question-chips.js +315 -0
- package/dist/tui/ask-user-question-prompt.js +203 -0
- package/dist/tui/compact-banner.js +81 -0
- package/dist/tui/conversation-pane.js +85 -11
- package/dist/tui/cost-table.js +111 -0
- package/dist/tui/device-flow.js +2 -2
- package/dist/tui/doctor-table.js +46 -0
- package/dist/tui/feedback-prompt.js +156 -0
- package/dist/tui/input-box.js +247 -32
- package/dist/tui/login-picker.js +3 -3
- package/dist/tui/markdown-render.js +6 -6
- package/dist/tui/multi-file-diff-approval.js +375 -0
- package/dist/tui/onboarding-wizard.js +240 -0
- package/dist/tui/permissions-picker.js +86 -0
- package/dist/tui/render.js +36 -1
- package/dist/tui/repl-render.js +176 -25
- package/dist/tui/repl-splash-art.js +16 -16
- package/dist/tui/repl-splash-mascot.js +48 -24
- package/dist/tui/repl-splash.js +22 -22
- package/dist/tui/repl.js +125 -45
- package/dist/tui/slash-palette.js +6 -6
- package/dist/tui/splash.js +2 -2
- package/dist/tui/status-bar.js +109 -31
- package/dist/tui/status-table.js +7 -0
- package/dist/tui/stickers-art.js +136 -0
- package/dist/tui/style-table.js +28 -0
- package/dist/tui/theme-table.js +29 -0
- package/dist/tui/thinking-spinner.js +123 -0
- package/dist/tui/tool-stream-pane.js +53 -4
- package/dist/tui/update-banner.js +27 -2
- package/dist/tui/vim-input.js +267 -0
- package/dist/tui/welcome-banner.js +107 -0
- package/dist/tui/welcome-data.js +293 -0
- package/dist/tui/workspace-context.js +2 -2
- package/package.json +31 -16
- package/test/scenarios/codegen-create-file.scenario.txt +13 -0
- package/test/scenarios/compact-force.scenario.txt +12 -0
- package/test/scenarios/identity.scenario.txt +12 -0
- package/test/scenarios/persona-handoff.scenario.txt +12 -0
- package/test/scenarios/walkback.scenario.txt +12 -0
- package/dist/core/engine/compaction-hook.js +0 -154
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Upload paths for `pugi share` ().
|
|
3
|
+
*
|
|
4
|
+
* Two targets:
|
|
5
|
+
*
|
|
6
|
+
* - `gist` shells out to `gh gist create` (requires the `gh` CLI in
|
|
7
|
+
* PATH AND `gh auth status` ok, OR `GITHUB_TOKEN` env). The
|
|
8
|
+
* gist is created with a fixed filename so the URL paths
|
|
9
|
+
* stay stable across re-shares.
|
|
10
|
+
* - `pugi` POSTs to admin-api `/api/pugi/share`. The endpoint is NOT
|
|
11
|
+
* present in admin-api today (2026-05-27 audit) — the
|
|
12
|
+
* handler degrades gracefully: it surfaces a clear "endpoint
|
|
13
|
+
* not yet wired" message and tells the operator to use
|
|
14
|
+
* `--gist` for now. The structured payload is otherwise
|
|
15
|
+
* ready for the server-side handler to consume; landing the
|
|
16
|
+
* endpoint is a separate sprint.
|
|
17
|
+
*
|
|
18
|
+
* The two paths share one decision shape (`UploadResult`) so the
|
|
19
|
+
* command handler renders identical telemetry regardless of which target
|
|
20
|
+
* was chosen.
|
|
21
|
+
*
|
|
22
|
+
* Why we shell out for gist instead of using octokit: octokit would add
|
|
23
|
+
* a transitive HTTP client + ~200 KB to the npm package surface for a
|
|
24
|
+
* single feature. `gh gist create` is the operator-friendly form
|
|
25
|
+
* (already auth'd, public URL on stdout, attribution in the gist
|
|
26
|
+
* metadata) and degrades cleanly when `gh` is absent.
|
|
27
|
+
*/
|
|
28
|
+
import { spawn } from 'node:child_process';
|
|
29
|
+
/**
|
|
30
|
+
* Default execa shim. Spawns the binary with `args`, pipes `input` into
|
|
31
|
+
* stdin if provided, captures stdout + stderr in memory. The CLI ships
|
|
32
|
+
* with `execa` already pulled for other paths; we use the lighter
|
|
33
|
+
* `child_process.spawn` here so the share module stays import-clean.
|
|
34
|
+
*/
|
|
35
|
+
export const defaultExecaLike = (file, args, options) => {
|
|
36
|
+
return new Promise((resolveProm, rejectProm) => {
|
|
37
|
+
const child = spawn(file, [...args], { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
38
|
+
let stdout = '';
|
|
39
|
+
let stderr = '';
|
|
40
|
+
child.stdout.on('data', (chunk) => {
|
|
41
|
+
stdout += chunk.toString('utf8');
|
|
42
|
+
});
|
|
43
|
+
child.stderr.on('data', (chunk) => {
|
|
44
|
+
stderr += chunk.toString('utf8');
|
|
45
|
+
});
|
|
46
|
+
child.on('error', (err) => {
|
|
47
|
+
// ENOENT (binary missing) lands here; the caller maps it.
|
|
48
|
+
rejectProm(err);
|
|
49
|
+
});
|
|
50
|
+
child.on('close', (code) => {
|
|
51
|
+
resolveProm({ exitCode: code ?? 0, stdout, stderr });
|
|
52
|
+
});
|
|
53
|
+
if (options?.input) {
|
|
54
|
+
child.stdin.write(options.input);
|
|
55
|
+
}
|
|
56
|
+
child.stdin.end();
|
|
57
|
+
});
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Top-level upload dispatch. The handler picks the right path and
|
|
61
|
+
* surfaces a uniform result envelope.
|
|
62
|
+
*/
|
|
63
|
+
export async function uploadShare(req) {
|
|
64
|
+
if (req.target === 'gist') {
|
|
65
|
+
return uploadGist(req);
|
|
66
|
+
}
|
|
67
|
+
return uploadPugi(req);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Gist upload. Two-step: probe `gh --version` (fast, costs nothing) to
|
|
71
|
+
* detect a missing binary cleanly, then run `gh gist create`. We pipe
|
|
72
|
+
* the markdown into stdin to avoid temp files + the OS-level argv
|
|
73
|
+
* length cap.
|
|
74
|
+
*/
|
|
75
|
+
async function uploadGist(req) {
|
|
76
|
+
const exec = req.execaLike ?? defaultExecaLike;
|
|
77
|
+
const description = req.description ?? `Pugi session ${req.sessionId}`;
|
|
78
|
+
try {
|
|
79
|
+
// Probe step. `gh --version` returns 0 quickly and surfaces a
|
|
80
|
+
// distinctive "command not found" via ENOENT on the reject path.
|
|
81
|
+
const probe = await exec('gh', ['--version']);
|
|
82
|
+
if (probe.exitCode !== 0) {
|
|
83
|
+
return {
|
|
84
|
+
ok: false,
|
|
85
|
+
target: 'gist',
|
|
86
|
+
reason: 'gh_not_installed',
|
|
87
|
+
message: 'gh CLI not available. Install from https://cli.github.com or use --pugi instead.',
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return {
|
|
93
|
+
ok: false,
|
|
94
|
+
target: 'gist',
|
|
95
|
+
reason: 'gh_not_installed',
|
|
96
|
+
message: 'gh CLI not available. Install from https://cli.github.com or use --pugi instead.',
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
// Create the gist. `gh` reads stdin when `-` is the filename arg, which
|
|
100
|
+
// works with our `--filename` override. The `--public` flag is
|
|
101
|
+
// intentionally omitted — gists default to secret (unlisted URL), which
|
|
102
|
+
// is the right default for a session transcript. Operators who want a
|
|
103
|
+
// public gist can run `gh gist edit --add-public <id>` after the fact.
|
|
104
|
+
const createArgs = [
|
|
105
|
+
'gist',
|
|
106
|
+
'create',
|
|
107
|
+
'--filename',
|
|
108
|
+
'pugi-session.md',
|
|
109
|
+
'--desc',
|
|
110
|
+
description,
|
|
111
|
+
'-',
|
|
112
|
+
];
|
|
113
|
+
try {
|
|
114
|
+
const result = await exec('gh', createArgs, { input: req.markdown });
|
|
115
|
+
if (result.exitCode !== 0) {
|
|
116
|
+
// Auth failure is the common case. `gh` prints "gh auth login" to
|
|
117
|
+
// stderr; we tag it specifically so the gate banner can hint.
|
|
118
|
+
const looksLikeAuth = /auth/i.test(result.stderr) || /authenticated/i.test(result.stderr);
|
|
119
|
+
return {
|
|
120
|
+
ok: false,
|
|
121
|
+
target: 'gist',
|
|
122
|
+
reason: looksLikeAuth ? 'gh_unauthenticated' : 'gh_failed',
|
|
123
|
+
message: looksLikeAuth
|
|
124
|
+
? 'gh is installed but not authenticated. Run `gh auth login` first.'
|
|
125
|
+
: `gh gist create exited ${result.exitCode}: ${result.stderr.trim().slice(0, 200)}`,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
// gh prints the URL on stdout. Trim newline + any leading whitespace.
|
|
129
|
+
const url = result.stdout.trim().split('\n').pop() ?? '';
|
|
130
|
+
if (!/^https?:\/\//.test(url)) {
|
|
131
|
+
return {
|
|
132
|
+
ok: false,
|
|
133
|
+
target: 'gist',
|
|
134
|
+
reason: 'gh_failed',
|
|
135
|
+
message: `gh did not return a URL (stdout: "${result.stdout.trim().slice(0, 200)}")`,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
const remoteId = url.split('/').pop() ?? undefined;
|
|
139
|
+
return remoteId !== undefined
|
|
140
|
+
? { ok: true, target: 'gist', url, remoteId }
|
|
141
|
+
: { ok: true, target: 'gist', url };
|
|
142
|
+
}
|
|
143
|
+
catch (err) {
|
|
144
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
145
|
+
return {
|
|
146
|
+
ok: false,
|
|
147
|
+
target: 'gist',
|
|
148
|
+
reason: 'gh_failed',
|
|
149
|
+
message: `gh gist create threw: ${message}`,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Pugi.io upload. POSTs the transcript to admin-api `/api/pugi/share`.
|
|
155
|
+
* The endpoint is NOT yet wired (audit); when it returns 404
|
|
156
|
+
* we surface a friendly hint instead of a stack trace. When the operator
|
|
157
|
+
* is signed-out we surface `pugi_auth_missing` so the gate banner can
|
|
158
|
+
* point at `pugi login`.
|
|
159
|
+
*
|
|
160
|
+
* The wire payload is intentionally minimal so a future server-side
|
|
161
|
+
* implementation has a stable contract to build against:
|
|
162
|
+
*
|
|
163
|
+
* { sessionId, markdown, description?, cliVersion? }
|
|
164
|
+
*
|
|
165
|
+
* Response (when wired):
|
|
166
|
+
*
|
|
167
|
+
* 200 { ok: true, url, id } URL is the pugi.io/share/<id> public link.
|
|
168
|
+
* 404 / 501 endpoint not yet implemented — graceful skip.
|
|
169
|
+
* 401 auth missing/expired — operator runs `pugi login`.
|
|
170
|
+
*/
|
|
171
|
+
async function uploadPugi(req) {
|
|
172
|
+
const fetchFn = req.fetchLike ?? globalThis.fetch;
|
|
173
|
+
if (typeof fetchFn !== 'function') {
|
|
174
|
+
return {
|
|
175
|
+
ok: false,
|
|
176
|
+
target: 'pugi',
|
|
177
|
+
reason: 'pugi_network_error',
|
|
178
|
+
message: 'No fetch implementation available (Node >=18 expected).',
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
if (!req.apiUrl) {
|
|
182
|
+
return {
|
|
183
|
+
ok: false,
|
|
184
|
+
target: 'pugi',
|
|
185
|
+
reason: 'pugi_auth_missing',
|
|
186
|
+
message: 'pugi.io share requires a signed-in session. Run `pugi login` and retry.',
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
const url = `${req.apiUrl.replace(/\/+$/u, '')}/api/pugi/share`;
|
|
190
|
+
const headers = {
|
|
191
|
+
'content-type': 'application/json',
|
|
192
|
+
accept: 'application/json',
|
|
193
|
+
};
|
|
194
|
+
if (req.apiToken) {
|
|
195
|
+
headers.authorization = `Bearer ${req.apiToken}`;
|
|
196
|
+
}
|
|
197
|
+
const body = JSON.stringify({
|
|
198
|
+
sessionId: req.sessionId,
|
|
199
|
+
markdown: req.markdown,
|
|
200
|
+
description: req.description ?? `Pugi session ${req.sessionId}`,
|
|
201
|
+
});
|
|
202
|
+
let res;
|
|
203
|
+
try {
|
|
204
|
+
res = await fetchFn(url, { method: 'POST', headers, body });
|
|
205
|
+
}
|
|
206
|
+
catch (err) {
|
|
207
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
208
|
+
return {
|
|
209
|
+
ok: false,
|
|
210
|
+
target: 'pugi',
|
|
211
|
+
reason: 'pugi_network_error',
|
|
212
|
+
message: `pugi.io upload failed: ${message}`,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
// 404 / 501 → endpoint not yet wired. Surface a friendly hint instead
|
|
216
|
+
// of dumping the response body.
|
|
217
|
+
if (res.status === 404 || res.status === 501) {
|
|
218
|
+
return {
|
|
219
|
+
ok: false,
|
|
220
|
+
target: 'pugi',
|
|
221
|
+
reason: 'pugi_endpoint_unimplemented',
|
|
222
|
+
message: 'pugi.io /api/pugi/share is not yet wired in admin-api. ' +
|
|
223
|
+
'Use `--gist` for now; the pugi.io upload lands in a follow-up sprint.',
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
if (res.status === 401 || res.status === 403) {
|
|
227
|
+
return {
|
|
228
|
+
ok: false,
|
|
229
|
+
target: 'pugi',
|
|
230
|
+
reason: 'pugi_auth_missing',
|
|
231
|
+
message: 'pugi.io rejected the credentials. Run `pugi login` and retry.',
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
if (!res.ok) {
|
|
235
|
+
return {
|
|
236
|
+
ok: false,
|
|
237
|
+
target: 'pugi',
|
|
238
|
+
reason: 'pugi_network_error',
|
|
239
|
+
message: `pugi.io upload returned ${res.status} ${res.statusText}.`,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
let payload;
|
|
243
|
+
try {
|
|
244
|
+
payload = (await res.json());
|
|
245
|
+
}
|
|
246
|
+
catch (err) {
|
|
247
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
248
|
+
return {
|
|
249
|
+
ok: false,
|
|
250
|
+
target: 'pugi',
|
|
251
|
+
reason: 'pugi_network_error',
|
|
252
|
+
message: `pugi.io upload returned non-JSON: ${message}`,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
if (!payload.ok || !payload.url) {
|
|
256
|
+
return {
|
|
257
|
+
ok: false,
|
|
258
|
+
target: 'pugi',
|
|
259
|
+
reason: 'pugi_network_error',
|
|
260
|
+
message: 'pugi.io upload succeeded but the response was missing { ok, url }.',
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
return payload.id !== undefined
|
|
264
|
+
? { ok: true, target: 'pugi', url: payload.url, remoteId: payload.id }
|
|
265
|
+
: { ok: true, target: 'pugi', url: payload.url };
|
|
266
|
+
}
|
|
267
|
+
//# sourceMappingURL=uploader.js.map
|
|
@@ -25,30 +25,30 @@ export const DEFAULT_SKILLS = [
|
|
|
25
25
|
'## Language split',
|
|
26
26
|
'',
|
|
27
27
|
'- Code, comments, commit messages, PR titles + bodies, technical docs,',
|
|
28
|
-
"
|
|
28
|
+
" generated tests, log strings: **English**. Do not switch mid-line.",
|
|
29
29
|
'- Operator-facing chat replies follow the workspace `settings.json`',
|
|
30
|
-
'
|
|
30
|
+
' `workflow.chatLang` (defaults to English when unset).',
|
|
31
31
|
'',
|
|
32
32
|
'## No AI attribution',
|
|
33
33
|
'',
|
|
34
34
|
'- Never add `Co-Authored-By: Claude` (or Codex, Gemini, Cursor) trailers',
|
|
35
|
-
'
|
|
36
|
-
'- Never write "Generated with
|
|
37
|
-
'
|
|
35
|
+
' to commits or PR bodies.',
|
|
36
|
+
'- Never write "Generated with the upstream tool", "Made with AI", or any',
|
|
37
|
+
' variant in PR descriptions, README content, or release notes.',
|
|
38
38
|
'- Pugi itself is the byline; the underlying model is implementation',
|
|
39
|
-
'
|
|
39
|
+
' detail and stays out of customer-visible text.',
|
|
40
40
|
'',
|
|
41
41
|
'## No emoji decoration',
|
|
42
42
|
'',
|
|
43
43
|
'- Skip decorative emoji in commit messages, PR bodies, code comments,',
|
|
44
|
-
'
|
|
44
|
+
' log lines, and chat replies.',
|
|
45
45
|
'- Functional unicode that carries meaning (math symbols, currency,',
|
|
46
|
-
'
|
|
46
|
+
' language scripts) is fine.',
|
|
47
47
|
'',
|
|
48
48
|
'## No em dashes',
|
|
49
49
|
'',
|
|
50
50
|
'- Prefer regular hyphens or restructure the sentence. The em dash',
|
|
51
|
-
'
|
|
51
|
+
' character reads as ChatGPT-flavoured and we keep our voice distinct.',
|
|
52
52
|
'',
|
|
53
53
|
'## Verb grammar',
|
|
54
54
|
'',
|
|
@@ -76,15 +76,15 @@ export const DEFAULT_SKILLS = [
|
|
|
76
76
|
'## Rule',
|
|
77
77
|
'',
|
|
78
78
|
'1. Before any tool call that writes a URL (`write`, `edit`, `patch`),',
|
|
79
|
-
'
|
|
79
|
+
' enumerate the URLs the diff introduces.',
|
|
80
80
|
'2. For each URL whose host is reachable from the workspace, issue a',
|
|
81
|
-
'
|
|
81
|
+
' `HEAD` request (or `GET` when the host blocks `HEAD`).',
|
|
82
82
|
'3. Treat the URL as **valid** only when the response is 2xx, 3xx, or',
|
|
83
|
-
'
|
|
83
|
+
' a documented 401/403 (auth-gated endpoint we expect).',
|
|
84
84
|
'4. Treat the URL as **invalid** on `ENOTFOUND`, `ECONNREFUSED`,',
|
|
85
|
-
'
|
|
86
|
-
'
|
|
87
|
-
'
|
|
85
|
+
' timeout, or 4xx/5xx other than the documented auth codes. In that',
|
|
86
|
+
' case do not write the URL; ask the operator for the correct one or',
|
|
87
|
+
' pull it from `.pugi/settings.json` / `.env`.',
|
|
88
88
|
'',
|
|
89
89
|
'## Why this matters',
|
|
90
90
|
'',
|
|
@@ -97,11 +97,11 @@ export const DEFAULT_SKILLS = [
|
|
|
97
97
|
'## Skip cases',
|
|
98
98
|
'',
|
|
99
99
|
'- Localhost URLs (`http://127.0.0.1:*`, `http://localhost:*`): skip',
|
|
100
|
-
'
|
|
101
|
-
'
|
|
100
|
+
' the probe; verify visually that the port matches the workspace dev',
|
|
101
|
+
' server config instead.',
|
|
102
102
|
'- Example URLs in comments explicitly marked `// example:` or',
|
|
103
|
-
'
|
|
104
|
-
'
|
|
103
|
+
' `# example:`: skip the probe; the operator has flagged the URL as',
|
|
104
|
+
' illustrative.',
|
|
105
105
|
'',
|
|
106
106
|
].join('\n'),
|
|
107
107
|
},
|
|
@@ -118,17 +118,17 @@ export const DEFAULT_SKILLS = [
|
|
|
118
118
|
'Whenever a `build` task renames a package or its binary, the README',
|
|
119
119
|
'must move with it. Stale install snippets are the most common Pugi',
|
|
120
120
|
'regression: the binary ships as `@pugi/cli` but the README still',
|
|
121
|
-
'tells the operator to `npm i -g @
|
|
121
|
+
'tells the operator to `npm i -g @pugi/cli`, and the install',
|
|
122
122
|
"fails before they ever see `pugi --version`.",
|
|
123
123
|
'',
|
|
124
124
|
'## Rule',
|
|
125
125
|
'',
|
|
126
126
|
'Whenever a diff touches `package.json` and changes any of:',
|
|
127
127
|
'',
|
|
128
|
-
'
|
|
129
|
-
'
|
|
130
|
-
'
|
|
131
|
-
'
|
|
128
|
+
' - `name`',
|
|
129
|
+
' - `bin`',
|
|
130
|
+
' - `scripts.start` / `scripts.dev`',
|
|
131
|
+
' - top-level `homepage` / `repository.url`',
|
|
132
132
|
'',
|
|
133
133
|
'then the same diff MUST also update the corresponding sections of the',
|
|
134
134
|
"package's `README.md` (and, when the package has a docs landing page,",
|
|
@@ -152,7 +152,7 @@ export const DEFAULT_SKILLS = [
|
|
|
152
152
|
'## Idempotence',
|
|
153
153
|
'',
|
|
154
154
|
'This rule applies to every package in the workspace, not just the',
|
|
155
|
-
"first one the operator mentions. A monorepo-wide rename (`@
|
|
155
|
+
"first one the operator mentions. A monorepo-wide rename (`@pugi/*`",
|
|
156
156
|
"to `@pugi/*`) has to sync every README, not just the customer-facing",
|
|
157
157
|
'CLI README.',
|
|
158
158
|
'',
|
|
@@ -168,7 +168,7 @@ export const DEFAULT_SKILLS = [
|
|
|
168
168
|
body: [
|
|
169
169
|
'# Zoom out',
|
|
170
170
|
'',
|
|
171
|
-
'**Source:** [mattpocock/skills](https://github.com/mattpocock/skills/blob/main/skills/engineering/zoom-out/SKILL.md) - MIT licensed, authored by Matt Pocock. Ported verbatim into the Pugi bundle on
|
|
171
|
+
'**Source:** [mattpocock/skills](https://github.com/mattpocock/skills/blob/main/skills/engineering/zoom-out/SKILL.md) - MIT licensed, authored by Matt Pocock. Ported verbatim into the Pugi bundle on.',
|
|
172
172
|
'',
|
|
173
173
|
'I do not know this area of code well. Go up a layer of abstraction.',
|
|
174
174
|
'Give me a map of all the relevant modules and callers, using the',
|
|
@@ -186,7 +186,7 @@ export const DEFAULT_SKILLS = [
|
|
|
186
186
|
body: [
|
|
187
187
|
'# Diagnose',
|
|
188
188
|
'',
|
|
189
|
-
'**Source:** [mattpocock/skills](https://github.com/mattpocock/skills/blob/main/skills/engineering/diagnose/SKILL.md) - MIT licensed, authored by Matt Pocock. Ported into the Pugi bundle on
|
|
189
|
+
'**Source:** [mattpocock/skills](https://github.com/mattpocock/skills/blob/main/skills/engineering/diagnose/SKILL.md) - MIT licensed, authored by Matt Pocock. Ported into the Pugi bundle on with minor de-namespacing (cross-skill links rewritten, project-glossary references generalised).',
|
|
190
190
|
'',
|
|
191
191
|
'A discipline for hard bugs. Skip phases only when explicitly justified.',
|
|
192
192
|
'',
|
|
@@ -349,7 +349,7 @@ export const DEFAULT_SKILLS = [
|
|
|
349
349
|
body: [
|
|
350
350
|
'# Grill me',
|
|
351
351
|
'',
|
|
352
|
-
'**Source:** [mattpocock/skills](https://github.com/mattpocock/skills/blob/main/skills/productivity/grill-me/SKILL.md) - MIT licensed, authored by Matt Pocock. Ported verbatim into the Pugi bundle on
|
|
352
|
+
'**Source:** [mattpocock/skills](https://github.com/mattpocock/skills/blob/main/skills/productivity/grill-me/SKILL.md) - MIT licensed, authored by Matt Pocock. Ported verbatim into the Pugi bundle on (terminology swap: "user" -> "operator" to match Pugi vocabulary).',
|
|
353
353
|
'',
|
|
354
354
|
'Interview the operator relentlessly about every aspect of this plan',
|
|
355
355
|
'until we reach a shared understanding. Walk down each branch of the',
|
|
@@ -377,8 +377,8 @@ function renderSkillMarkdown(spec) {
|
|
|
377
377
|
`name: ${spec.name}`,
|
|
378
378
|
`description: ${spec.description}`,
|
|
379
379
|
'metadata:',
|
|
380
|
-
'
|
|
381
|
-
`
|
|
380
|
+
' type: skill',
|
|
381
|
+
` version: ${spec.version}`,
|
|
382
382
|
'---',
|
|
383
383
|
'',
|
|
384
384
|
].join('\n');
|
|
@@ -6,12 +6,12 @@ const FRONTMATTER_DELIM = '---';
|
|
|
6
6
|
* Parse a markdown file with YAML frontmatter into a structured form.
|
|
7
7
|
*
|
|
8
8
|
* Throws when:
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* - no frontmatter delimiter pair is present
|
|
10
|
+
* - frontmatter does not parse as the minimal YAML grammar
|
|
11
|
+
* - required keys `name`, `description`, `metadata.type` are missing
|
|
12
12
|
*
|
|
13
13
|
* Frontmatter `tools` accepts either flow array (`[a, b, c]`) or block
|
|
14
|
-
* array (newline + `
|
|
14
|
+
* array (newline + ` - a` lines). Strings are unquoted, single, or
|
|
15
15
|
* double quoted. Multi-line scalar values are NOT supported (no `>` or
|
|
16
16
|
* `|` folding) — Skills do not need them.
|
|
17
17
|
*/
|
|
@@ -146,7 +146,7 @@ function parseBlockObject(lines, from) {
|
|
|
146
146
|
consumed += arr.consumed + 1;
|
|
147
147
|
continue;
|
|
148
148
|
}
|
|
149
|
-
// Deeper objects are out of scope for
|
|
149
|
+
// Deeper objects are out of scope for — keep frontmatter
|
|
150
150
|
// shape predictable. Throw with a clear pointer.
|
|
151
151
|
throw new Error(`SKILL_PARSE: nested objects deeper than 1 level are not supported (key "${key}")`);
|
|
152
152
|
}
|
|
@@ -212,12 +212,12 @@ function validateFrontmatter(raw) {
|
|
|
212
212
|
// Two frontmatter dialects coexist in the wild:
|
|
213
213
|
//
|
|
214
214
|
// 1. Pugi-native — explicit `metadata: { type: skill|agent, ... }`.
|
|
215
|
-
//
|
|
216
|
-
//
|
|
215
|
+
// Future Pugi-published skills + agents use this so the kind is
|
|
216
|
+
// self-declared at parse time.
|
|
217
217
|
//
|
|
218
218
|
// 2. Anthropic Skills — flat top-level keys, no `metadata` block, no
|
|
219
|
-
//
|
|
220
|
-
//
|
|
219
|
+
// `type` field. Every file inside `github.com/anthropics/skills`
|
|
220
|
+
// follows this shape (e.g. `algorithmic-art`, `pdf`, `pptx`).
|
|
221
221
|
//
|
|
222
222
|
// We accept both. When `metadata` is absent we synthesize one with
|
|
223
223
|
// `type` defaulted to `skill` — the on-disk layout (`SKILL.md` in a
|
|
@@ -267,7 +267,7 @@ function validateFrontmatter(raw) {
|
|
|
267
267
|
if (node.kind === 'scalar') {
|
|
268
268
|
fm[key] = node.value;
|
|
269
269
|
// Anthropic Skills flat dialect — also surface select top-level
|
|
270
|
-
// keys inside metadata so downstream consumers (
|
|
270
|
+
// keys inside metadata so downstream consumers (Pugi system
|
|
271
271
|
// prompt builder, `skills list` table) have one consistent path.
|
|
272
272
|
if (key === 'license' || key === 'version' || key === 'model' || key === 'allowed-tools') {
|
|
273
273
|
const metaKey = key === 'allowed-tools' ? 'tools' : key;
|
|
@@ -317,20 +317,20 @@ function expectScalar(obj, key) {
|
|
|
317
317
|
/* ----------------------------- Slug validation ----------------------------- */
|
|
318
318
|
/**
|
|
319
319
|
* Slug grammar for skill + agent names. Allowed characters:
|
|
320
|
-
*
|
|
321
|
-
*
|
|
320
|
+
* - lowercase ASCII letters, digits, hyphen, underscore, dot
|
|
321
|
+
* - 1–64 characters total, must START with [a-z0-9]
|
|
322
322
|
*
|
|
323
323
|
* Anything outside this grammar is rejected BEFORE the name reaches a
|
|
324
324
|
* `path.join` call. Attacker payloads we close here:
|
|
325
|
-
*
|
|
326
|
-
*
|
|
327
|
-
*
|
|
328
|
-
*
|
|
329
|
-
*
|
|
330
|
-
*
|
|
331
|
-
*
|
|
332
|
-
*
|
|
333
|
-
*
|
|
325
|
+
* - `../../foo` (path traversal segment)
|
|
326
|
+
* - `/etc/passwd` (absolute path)
|
|
327
|
+
* - `..\\..\\foo` (Windows traversal)
|
|
328
|
+
* - `\0name` (null-byte truncation)
|
|
329
|
+
* - `name\\with\\backslash` (Windows separator)
|
|
330
|
+
* - `..` or `.` (dot-only segments)
|
|
331
|
+
* - empty string (matches existing skill on globbed list)
|
|
332
|
+
* - 65+ char strings (DoS via huge directory names)
|
|
333
|
+
* - uppercase / unicode (collision on case-insensitive filesystems)
|
|
334
334
|
*
|
|
335
335
|
* The CLI commands also accept `--as <slug>` so this guard runs on the
|
|
336
336
|
* operator-supplied override AND on the parsed frontmatter `name`. Both
|
|
@@ -415,7 +415,7 @@ function loadSkill(skillDir, scope) {
|
|
|
415
415
|
* responsible for trust-prompting + hashing before this is invoked.
|
|
416
416
|
*
|
|
417
417
|
* Refuses to install when the payload does not contain a `SKILL.md` at
|
|
418
|
-
* its root: that file is the contract every consumer (
|
|
418
|
+
* its root: that file is the contract every consumer (Pugi system
|
|
419
419
|
* prompt, `skills list`, `skills info`) reads from.
|
|
420
420
|
*/
|
|
421
421
|
export function installSkill(input) {
|
|
@@ -12,29 +12,29 @@ import { validateHostnameForFetch } from '../../tools/web-fetch.js';
|
|
|
12
12
|
*
|
|
13
13
|
* Supported source schemes:
|
|
14
14
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
15
|
+
* 1. `gh:owner/repo[/subdir][@ref]`
|
|
16
|
+
* → `gh:anthropics/skills/python-coding-standards@main`
|
|
17
|
+
* Fetches the GitHub tarball via the public codeload endpoint,
|
|
18
|
+
* extracts the requested subtree.
|
|
19
19
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
20
|
+
* 2. `https://github.com/<owner>/<repo>/tree/<ref>/<subdir>` (or `/blob/`)
|
|
21
|
+
* Normalised to the gh: form above.
|
|
22
22
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
23
|
+
* 3. `anthropic:<slug>` — convenience alias for
|
|
24
|
+
* `gh:anthropics/skills/<slug>@main`. Hard-coded base; the only
|
|
25
|
+
* reason this exists is so operators can copy a slug from the
|
|
26
|
+
* Anthropic docs without remembering the org name.
|
|
27
27
|
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
28
|
+
* 4. `npm:<package>` — fetches a tarball from the npm registry,
|
|
29
|
+
* extracts, looks for `SKILL.md` at the package root.
|
|
30
30
|
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
31
|
+
* 5. Local path — `./relative` or `/abs/path`. Copied to tmp so the
|
|
32
|
+
* caller can mutate the original without affecting install.
|
|
33
33
|
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
34
|
+
* 6. Catalog name — bare slug, queried against
|
|
35
|
+
* `https://catalog.pugi.dev/api/skills/<name>`. Returns a 404 →
|
|
36
|
+
* we surface a hint pointing at the `gh:anthropics/skills/<name>`
|
|
37
|
+
* form rather than crashing.
|
|
38
38
|
*
|
|
39
39
|
* Every resolver writes the payload into a fresh temp dir under
|
|
40
40
|
* `/tmp/pugi-skill-XXXXXX/` (caller cleans up after install completes).
|
|
@@ -58,7 +58,7 @@ export async function fetchSource(source) {
|
|
|
58
58
|
throw new Error(`SOURCE_PARSE: anthropic: source needs a bare slug (got "${source}"). Example: anthropic:algorithmic-art`);
|
|
59
59
|
}
|
|
60
60
|
// Real layout is `anthropics/skills` repo → `skills/<slug>/SKILL.md`.
|
|
61
|
-
// Verified
|
|
61
|
+
// Verified against the live repo tarball.
|
|
62
62
|
return fetchGitHub(`${ANTHROPIC_REPO}/skills/${slug}@main`.slice(3));
|
|
63
63
|
}
|
|
64
64
|
if (source.startsWith('npm:')) {
|
|
@@ -113,7 +113,7 @@ async function fetchGitHub(raw) {
|
|
|
113
113
|
const spec = parseGithubSpec(raw);
|
|
114
114
|
// Use codeload.github.com — the public tarball endpoint requires no
|
|
115
115
|
// auth for public repos and returns a single .tar.gz of the requested
|
|
116
|
-
// ref's tree. Private repos are out of scope for
|
|
116
|
+
// ref's tree. Private repos are out of scope for .
|
|
117
117
|
const tarUrl = `https://codeload.github.com/${spec.owner}/${spec.repo}/tar.gz/${spec.ref}`;
|
|
118
118
|
const tmpRoot = mkdtempSync(join(tmpdir(), 'pugi-skill-gh-'));
|
|
119
119
|
const tarPath = join(tmpRoot, 'payload.tar.gz');
|
|
@@ -268,9 +268,9 @@ async function requestFollow(url) {
|
|
|
268
268
|
// future: webhook delivery). Drift between two copies of that block
|
|
269
269
|
// list would be a real footgun — the SSRF cheat-sheet covers ~10
|
|
270
270
|
// ranges and missing one (e.g. SIIT/NAT64) is exactly the class of
|
|
271
|
-
// bug Codex caught in PR
|
|
271
|
+
// bug Codex caught in PR.
|
|
272
272
|
// Initial scheme — locked for entire redirect chain. Codex P2 review
|
|
273
|
-
// (PR
|
|
273
|
+
// (PR v2): an HTTPS source that 302s к public http:// URL would
|
|
274
274
|
// otherwise be fetched cleartext, MITM tampers payload. Stay TLS.
|
|
275
275
|
const initialScheme = new URL(currentUrl).protocol;
|
|
276
276
|
await guardOutboundUrl(currentUrl, 'initial request', initialScheme);
|
|
@@ -321,7 +321,7 @@ async function guardOutboundUrl(rawUrl, label, initialScheme) {
|
|
|
321
321
|
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
322
322
|
throw new Error(`SOURCE_SSRF: ${label} uses unsupported scheme ${parsed.protocol} (only http/https).`);
|
|
323
323
|
}
|
|
324
|
-
// Codex P2 PR
|
|
324
|
+
// Codex P2 PR v2: HTTPS source MUST NOT downgrade к HTTP across
|
|
325
325
|
// redirect chain — would let MITM tamper с payload after the initial
|
|
326
326
|
// TLS hop. Once we started TLS, stay TLS.
|
|
327
327
|
if (initialScheme === 'https:' && parsed.protocol === 'http:') {
|
|
@@ -405,20 +405,20 @@ async function extractTarball(tarPath, destDir) {
|
|
|
405
405
|
// violations and throw AFTER extraction completes (see below).
|
|
406
406
|
filter: (path, entry) => {
|
|
407
407
|
// 1. Block any symlink or hardlink — these are the tar-slip
|
|
408
|
-
//
|
|
409
|
-
//
|
|
408
|
+
// vectors. A symlink to ../../home/user/.ssh + a follow-up
|
|
409
|
+
// write to that symlink would exfil secrets.
|
|
410
410
|
if (entry.type === 'SymbolicLink' || entry.type === 'Link') {
|
|
411
411
|
violations.push(`SOURCE_TAR_SYMLINK: tarball contains ${entry.type} entry (${path} → ${entry.linkpath ?? '?'}). Refusing extraction.`);
|
|
412
412
|
return false;
|
|
413
413
|
}
|
|
414
414
|
// 2. Block absolute paths — `tar` strips the leading "/" in
|
|
415
|
-
//
|
|
415
|
+
// permissive mode and writes anyway. We refuse such entries.
|
|
416
416
|
if (path.startsWith('/')) {
|
|
417
417
|
violations.push(`SOURCE_TAR_ABSOLUTE: tarball entry has absolute path: ${path}`);
|
|
418
418
|
return false;
|
|
419
419
|
}
|
|
420
420
|
// 3. Block parent-traversal segments. `..` as a path segment
|
|
421
|
-
//
|
|
421
|
+
// cannot be present in any legitimate skill/agent payload.
|
|
422
422
|
const segments = path.split(/[\\/]+/);
|
|
423
423
|
if (segments.includes('..')) {
|
|
424
424
|
violations.push(`SOURCE_TAR_TRAVERSAL: tarball entry has parent-traversal segment: ${path}`);
|