@pugi/cli 0.1.0-beta.8 → 0.1.0-beta.87
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 +96 -0
- package/THIRD_PARTY_NOTICES.md +40 -0
- 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 +2 -2
- 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 +12 -12
- 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 +293 -7
- package/dist/core/edits/format-matrix.js +26 -0
- package/dist/core/edits/fuzzy-ladder.js +650 -0
- package/dist/core/edits/index.js +3 -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 +322 -0
- package/dist/core/engine/anvil-client.js +140 -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 +134 -16
- package/dist/core/engine/strip-internal-fields.js +124 -0
- package/dist/core/engine/tool-bridge.js +1295 -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 +44 -0
- package/dist/core/hooks/index.js +15 -0
- package/dist/core/hooks/registry.js +213 -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/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 +776 -0
- package/dist/core/lsp/language-detect.js +66 -0
- package/dist/core/lsp/post-edit-diagnostics.js +171 -0
- package/dist/core/lsp/symbol-tools.js +372 -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 +2157 -214
- package/dist/core/repl/slash-commands.js +533 -40
- 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 +286 -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 +457 -0
- 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-manager/cleanup.js +123 -0
- package/dist/core/worktree-manager/manager.js +303 -0
- package/dist/index.js +28 -0
- package/dist/runtime/bootstrap.js +190 -0
- package/dist/runtime/cli.js +4151 -489
- package/dist/runtime/commands/agents.js +30 -30
- 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 +32 -32
- package/dist/runtime/commands/cost.js +199 -0
- package/dist/runtime/commands/delegate.js +244 -13
- 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 +184 -0
- package/dist/runtime/commands/init.js +254 -0
- package/dist/runtime/commands/lsp.js +368 -0
- 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 +128 -0
- 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 +177 -0
- 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 +531 -0
- package/dist/runtime/update-check.js +28 -28
- package/dist/runtime/version.js +65 -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 +556 -0
- package/dist/tools/ask-user-question.js +222 -0
- package/dist/tools/ask-user.js +115 -0
- package/dist/tools/bash.js +623 -45
- package/dist/tools/brief.js +224 -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 +189 -0
- 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 +85 -0
- 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-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/onboarding-wizard.js +240 -0
- package/dist/tui/permissions-picker.js +86 -0
- package/dist/tui/render.js +35 -0
- package/dist/tui/repl-render.js +332 -54
- 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 +124 -44
- 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/docs/examples/codegraph.mcp.json +10 -0
- package/package.json +23 -6
- package/test/scenarios/codegen-create-file.scenario.txt +13 -0
- package/test/scenarios/compact-force.scenario.txt +11 -0
- package/test/scenarios/identity.scenario.txt +11 -0
- package/test/scenarios/persona-handoff.scenario.txt +11 -0
- package/test/scenarios/walkback.scenario.txt +12 -0
- package/dist/core/engine/compaction-hook.js +0 -154
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/model` / `pugi model` — BT 8 (the upstream tool parity).
|
|
3
|
+
*
|
|
4
|
+
* Lists OR selects the active model. The MVP uses a hardcoded
|
|
5
|
+
* tier-aware registry mirrored from the per-model price ladder in
|
|
6
|
+
* `core/repl/model-pricing.ts` so the operator sees the same models
|
|
7
|
+
* the cost meter understands. A future iteration can swap the static
|
|
8
|
+
* registry for an admin-api `GET /api/pugi/models` round-trip — the
|
|
9
|
+
* runner contract (`MODEL_REGISTRY` + `runModelCommand`) is the right
|
|
10
|
+
* shape for that swap because the renderer + writer paths are
|
|
11
|
+
* decoupled from the source.
|
|
12
|
+
*
|
|
13
|
+
* Persistence lands at `<workspaceRoot>/.pugi/settings.json` under a
|
|
14
|
+
* top-level `model.slug` key. We extend the file directly with a
|
|
15
|
+
* round-trip read-merge-write so we do not invalidate the existing
|
|
16
|
+
* Zod schema in `core/settings.ts` — the schema there reads only the
|
|
17
|
+
* keys it knows about, leaving `model.*` as a forward-compatible
|
|
18
|
+
* passthrough.
|
|
19
|
+
*
|
|
20
|
+
* Tier gating mirrors the four-tier pricing (Free / Founder $20 /
|
|
21
|
+
* Builder $99 / Team $199). The slugs here are the same ones the
|
|
22
|
+
* cost meter renders, so the operator can always swap to a cheaper
|
|
23
|
+
* fallback if they hit a quota wall.
|
|
24
|
+
*/
|
|
25
|
+
import { mkdirSync, readFileSync, writeFileSync, existsSync } from 'node:fs';
|
|
26
|
+
import { dirname, resolve } from 'node:path';
|
|
27
|
+
/**
|
|
28
|
+
* Tier rank for comparing "user tier >= model.minTier". The order is
|
|
29
|
+
* Free < Founder < Builder < Team. The cabinet's pricing module owns
|
|
30
|
+
* the canonical pricing copy; the CLI just needs the rank to filter
|
|
31
|
+
* the menu.
|
|
32
|
+
*/
|
|
33
|
+
export const TIER_RANK = Object.freeze({
|
|
34
|
+
free: 0,
|
|
35
|
+
founder: 1,
|
|
36
|
+
builder: 2,
|
|
37
|
+
team: 3,
|
|
38
|
+
});
|
|
39
|
+
/**
|
|
40
|
+
* Static model registry. Sources:
|
|
41
|
+
* - Anthropic Claude family (claude-opus, claude-sonnet, claude-haiku):
|
|
42
|
+
* list prices.
|
|
43
|
+
* - OpenAI (gpt-4o, gpt-4o-mini, o3, o3-mini): list prices.
|
|
44
|
+
* - Mistral (mistral-large, mistral-small): list prices.
|
|
45
|
+
*
|
|
46
|
+
* Family fallbacks (`claude-opus-4-7` falling back onto the
|
|
47
|
+
* `claude-opus-` family entry in `model-pricing.ts`) live in the price
|
|
48
|
+
* ladder, not here — the registry is the canonical selectable surface.
|
|
49
|
+
*/
|
|
50
|
+
export const MODEL_REGISTRY = Object.freeze([
|
|
51
|
+
// Free tier — cheap defaults so the meter never blanks on a fresh signup.
|
|
52
|
+
{
|
|
53
|
+
slug: 'claude-haiku-4-5',
|
|
54
|
+
label: 'Claude Haiku 4.5',
|
|
55
|
+
minTier: 'free',
|
|
56
|
+
inputUsdPerM: 0.8,
|
|
57
|
+
outputUsdPerM: 4.0,
|
|
58
|
+
summary: 'Fast + cheap. Default for quick dispatches and the free tier.',
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
slug: 'gpt-4o-mini',
|
|
62
|
+
label: 'GPT-4o mini',
|
|
63
|
+
minTier: 'free',
|
|
64
|
+
inputUsdPerM: 0.15,
|
|
65
|
+
outputUsdPerM: 0.6,
|
|
66
|
+
summary: 'Cheapest mainstream model. Good for short turns.',
|
|
67
|
+
},
|
|
68
|
+
// Founder ($20) — mid-tier mainstream models.
|
|
69
|
+
{
|
|
70
|
+
slug: 'claude-sonnet-4-6',
|
|
71
|
+
label: 'Claude Sonnet 4.6',
|
|
72
|
+
minTier: 'founder',
|
|
73
|
+
inputUsdPerM: 3.0,
|
|
74
|
+
outputUsdPerM: 15.0,
|
|
75
|
+
summary: 'Balanced for code dispatch. Strong tool use.',
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
slug: 'gpt-4o',
|
|
79
|
+
label: 'GPT-4o',
|
|
80
|
+
minTier: 'founder',
|
|
81
|
+
inputUsdPerM: 2.5,
|
|
82
|
+
outputUsdPerM: 10.0,
|
|
83
|
+
summary: 'Multimodal-capable. Good general-purpose flagship.',
|
|
84
|
+
},
|
|
85
|
+
// Builder ($99) — mid-strong reasoning + Mistral options.
|
|
86
|
+
{
|
|
87
|
+
slug: 'o3-mini',
|
|
88
|
+
label: 'OpenAI o3-mini',
|
|
89
|
+
minTier: 'builder',
|
|
90
|
+
inputUsdPerM: 1.1,
|
|
91
|
+
outputUsdPerM: 4.4,
|
|
92
|
+
summary: 'Cheaper reasoning. Good for plan/review turns.',
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
slug: 'mistral-large',
|
|
96
|
+
label: 'Mistral Large',
|
|
97
|
+
minTier: 'builder',
|
|
98
|
+
inputUsdPerM: 2.0,
|
|
99
|
+
outputUsdPerM: 6.0,
|
|
100
|
+
summary: 'Strong open-weight reasoning. EU-hosted option.',
|
|
101
|
+
},
|
|
102
|
+
// Team ($199) — top-tier reasoning.
|
|
103
|
+
{
|
|
104
|
+
slug: 'claude-opus-4-7',
|
|
105
|
+
label: 'Claude Opus 4.7',
|
|
106
|
+
minTier: 'team',
|
|
107
|
+
inputUsdPerM: 15.0,
|
|
108
|
+
outputUsdPerM: 75.0,
|
|
109
|
+
summary: 'Flagship reasoning. Best for hard refactors + multi-file edits.',
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
slug: 'o3',
|
|
113
|
+
label: 'OpenAI o3',
|
|
114
|
+
minTier: 'team',
|
|
115
|
+
inputUsdPerM: 10.0,
|
|
116
|
+
outputUsdPerM: 40.0,
|
|
117
|
+
summary: 'Strong reasoning + deliberation. Good for plan/critique loops.',
|
|
118
|
+
},
|
|
119
|
+
]);
|
|
120
|
+
/** Returns the registry filtered to models the operator's tier can use. */
|
|
121
|
+
export function modelsForTier(tier) {
|
|
122
|
+
const rank = TIER_RANK[tier];
|
|
123
|
+
return MODEL_REGISTRY.filter((m) => TIER_RANK[m.minTier] <= rank);
|
|
124
|
+
}
|
|
125
|
+
/** Looks up a descriptor by slug. */
|
|
126
|
+
export function lookupModel(slug) {
|
|
127
|
+
return MODEL_REGISTRY.find((m) => m.slug === slug);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Entry point for the slash + the top-level CLI. Side effects:
|
|
131
|
+
* - command.slug undefined: prints the tier-gated menu + current row.
|
|
132
|
+
* - command.slug set + unknown: prints a one-line error.
|
|
133
|
+
* - command.slug set + above tier: prints a tier-lock message.
|
|
134
|
+
* - command.slug set + valid + reachable: merges `model.slug` into
|
|
135
|
+
* <workspaceRoot>/.pugi/settings.json and prints a confirmation.
|
|
136
|
+
*
|
|
137
|
+
* The function never throws on a missing settings file — it creates
|
|
138
|
+
* `.pugi/` + writes a fresh object. Read failures (malformed JSON) are
|
|
139
|
+
* surfaced as a system line + the runner refuses to write so the
|
|
140
|
+
* operator can recover by hand.
|
|
141
|
+
*/
|
|
142
|
+
export async function runModelCommand(command, ctx) {
|
|
143
|
+
const tier = ctx.tier ?? 'team';
|
|
144
|
+
const fs = ctx.fs ?? defaultFs(ctx.workspaceRoot);
|
|
145
|
+
if (command.slug === undefined) {
|
|
146
|
+
return renderMenu(ctx, fs, tier);
|
|
147
|
+
}
|
|
148
|
+
const descriptor = lookupModel(command.slug);
|
|
149
|
+
if (!descriptor) {
|
|
150
|
+
ctx.writeOutput(`/model: unknown slug '${command.slug}'. Run /model to see the menu.`);
|
|
151
|
+
return { command: 'model', status: 'unknown_slug', reason: command.slug };
|
|
152
|
+
}
|
|
153
|
+
if (TIER_RANK[descriptor.minTier] > TIER_RANK[tier]) {
|
|
154
|
+
ctx.writeOutput(`/model: '${descriptor.slug}' requires the ${descriptor.minTier} tier. `
|
|
155
|
+
+ `You are on '${tier}'. Run /model to see available options.`);
|
|
156
|
+
return { command: 'model', status: 'tier_locked', slug: descriptor.slug };
|
|
157
|
+
}
|
|
158
|
+
// Merge + persist. Any IO error becomes a one-line warning; the
|
|
159
|
+
// session continues without crashing.
|
|
160
|
+
try {
|
|
161
|
+
const current = fs.readSettings() ?? {};
|
|
162
|
+
const nextSettings = {
|
|
163
|
+
...current,
|
|
164
|
+
model: {
|
|
165
|
+
...(typeof current.model === 'object' && current.model !== null
|
|
166
|
+
? current.model
|
|
167
|
+
: {}),
|
|
168
|
+
slug: descriptor.slug,
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
fs.writeSettings(nextSettings);
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
175
|
+
ctx.writeOutput(`/model: persist failed — ${message}. Selection is session-only.`);
|
|
176
|
+
}
|
|
177
|
+
ctx.writeOutput(`Model set to '${descriptor.label}' (${descriptor.slug}). ` +
|
|
178
|
+
`Cost: $${descriptor.inputUsdPerM.toFixed(2)}/M in, $${descriptor.outputUsdPerM.toFixed(2)}/M out.`);
|
|
179
|
+
return { command: 'model', status: 'selected', slug: descriptor.slug };
|
|
180
|
+
}
|
|
181
|
+
function renderMenu(ctx, fs, tier) {
|
|
182
|
+
const current = readCurrentSlug(fs);
|
|
183
|
+
const visible = modelsForTier(tier);
|
|
184
|
+
ctx.writeOutput(`Current model: ${current ?? '(unset, default)'}. Your tier: ${tier}.`);
|
|
185
|
+
ctx.writeOutput('');
|
|
186
|
+
ctx.writeOutput('Available models:');
|
|
187
|
+
for (const m of visible) {
|
|
188
|
+
const mark = current === m.slug ? '*' : ' ';
|
|
189
|
+
const price = `$${m.inputUsdPerM.toFixed(2)}/M in, $${m.outputUsdPerM.toFixed(2)}/M out`;
|
|
190
|
+
ctx.writeOutput(` ${mark} ${m.slug.padEnd(22)} ${m.label.padEnd(22)} ${price}`);
|
|
191
|
+
ctx.writeOutput(` ${m.summary}`);
|
|
192
|
+
}
|
|
193
|
+
const locked = MODEL_REGISTRY.filter((m) => TIER_RANK[m.minTier] > TIER_RANK[tier]);
|
|
194
|
+
if (locked.length > 0) {
|
|
195
|
+
ctx.writeOutput('');
|
|
196
|
+
ctx.writeOutput(`Higher-tier models (upgrade required):`);
|
|
197
|
+
for (const m of locked) {
|
|
198
|
+
ctx.writeOutput(` - ${m.slug} (${m.minTier})`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
ctx.writeOutput('');
|
|
202
|
+
ctx.writeOutput('Switch with `/model <slug>`. The choice persists to .pugi/settings.json.');
|
|
203
|
+
return { command: 'model', status: 'listed', slug: current ?? undefined };
|
|
204
|
+
}
|
|
205
|
+
function readCurrentSlug(fs) {
|
|
206
|
+
try {
|
|
207
|
+
const data = fs.readSettings();
|
|
208
|
+
if (!data || typeof data !== 'object')
|
|
209
|
+
return null;
|
|
210
|
+
const model = data.model;
|
|
211
|
+
if (!model || typeof model !== 'object')
|
|
212
|
+
return null;
|
|
213
|
+
const slug = model.slug;
|
|
214
|
+
return typeof slug === 'string' && slug.length > 0 ? slug : null;
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
function defaultFs(workspaceRoot) {
|
|
221
|
+
const path = resolve(workspaceRoot, '.pugi/settings.json');
|
|
222
|
+
return {
|
|
223
|
+
readSettings: () => {
|
|
224
|
+
if (!existsSync(path))
|
|
225
|
+
return null;
|
|
226
|
+
const raw = readFileSync(path, 'utf8');
|
|
227
|
+
if (raw.trim().length === 0)
|
|
228
|
+
return null;
|
|
229
|
+
return JSON.parse(raw);
|
|
230
|
+
},
|
|
231
|
+
writeSettings: (next) => {
|
|
232
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
233
|
+
writeFileSync(path, JSON.stringify(next, null, 2) + '\n', 'utf8');
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
//# sourceMappingURL=model.js.map
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* — `pugi onboarding` first-run wizard runner.
|
|
3
|
+
*
|
|
4
|
+
* Six-step interactive walk that lands a new operator on a configured
|
|
5
|
+
* Pugi:
|
|
6
|
+
*
|
|
7
|
+
* 1. Welcome + auth status — suggests `pugi login` when no creds
|
|
8
|
+
* 2. Default permission mode — plan / ask / allow / bypass (L6)
|
|
9
|
+
* 3. Output style — default / terse / explanatory /
|
|
10
|
+
* russian-formal / casual (L18)
|
|
11
|
+
* 4. MCP server pointer — link to `pugi mcp add` (L13)
|
|
12
|
+
* 5. Telemetry consent — off / anonymous / community
|
|
13
|
+
* 6. Recap card + marker touch — `pugi doctor` next-step hint
|
|
14
|
+
*
|
|
15
|
+
* Two execution modes:
|
|
16
|
+
*
|
|
17
|
+
* - **Interactive (TTY + no `--non-interactive` flag)** — mount the
|
|
18
|
+
* Ink wizard (`tui/onboarding-wizard.tsx`) and await the
|
|
19
|
+
* operator's verdict step by step. The wizard returns a structured
|
|
20
|
+
* `OnboardingVerdict` describing each chosen value (or "skip"
|
|
21
|
+
* when the operator pressed Enter on the current-value row).
|
|
22
|
+
*
|
|
23
|
+
* - **Non-interactive (CI, pipes, `--non-interactive`, `--json`)** —
|
|
24
|
+
* skip the Ink mount. Print the current values + the wizard tip
|
|
25
|
+
* so a scripted caller sees the structured envelope without an
|
|
26
|
+
* unresponsive raw-mode prompt.
|
|
27
|
+
*
|
|
28
|
+
* Idempotency:
|
|
29
|
+
*
|
|
30
|
+
* - Re-running the wizard reads the CURRENT persisted values (L6
|
|
31
|
+
* `getGlobalDefaultMode`, L18 `resolveOutputStyle`, this module's
|
|
32
|
+
* telemetry-state) and surfaces them as the highlighted row, so
|
|
33
|
+
* pressing Enter on each step is a no-op.
|
|
34
|
+
*
|
|
35
|
+
* - The marker file (`~/.pugi/.onboarded`) is touched on every
|
|
36
|
+
* successful completion. The marker existence is what suppresses
|
|
37
|
+
* the first-run hint on bare `pugi`; resetting it via
|
|
38
|
+
* `pugi onboarding --reset` re-arms the hint without nuking
|
|
39
|
+
* persisted values.
|
|
40
|
+
*
|
|
41
|
+
* Exit codes:
|
|
42
|
+
* 0 — wizard completed (interactive OR non-interactive path)
|
|
43
|
+
* 0 — `--reset` cleared the marker
|
|
44
|
+
* 2 — conflicting flags (e.g. `--reset` + a verdict flag)
|
|
45
|
+
*
|
|
46
|
+
* Exit code 0 for non-interactive is intentional: a CI caller running
|
|
47
|
+
* `pugi onboarding --non-interactive` to dump the current state should
|
|
48
|
+
* not see a non-zero rc; failures (fs EIO, unknown flag) raise to the
|
|
49
|
+
* caller via thrown errors which `runtime/cli.ts` catches.
|
|
50
|
+
*/
|
|
51
|
+
import { DEFAULT_PERMISSION_MODE, PERMISSION_MODES, PERMISSION_MODE_GLOSS, getGlobalDefaultMode, setGlobalDefaultMode, } from '../../core/permissions/index.js';
|
|
52
|
+
import { OUTPUT_STYLES, OUTPUT_STYLE_SLUGS, } from '../../core/output-style/presets.js';
|
|
53
|
+
import { resolveOutputStyle, setUserOutputStyle, } from '../../core/output-style/state.js';
|
|
54
|
+
import { clearOnboarded, isOnboarded, markOnboarded, } from '../../core/onboarding/marker.js';
|
|
55
|
+
import { TELEMETRY_CHOICES, readTelemetryChoice, writeTelemetryChoice, } from '../../core/onboarding/telemetry-state.js';
|
|
56
|
+
/**
|
|
57
|
+
* Entry point. Parses argv, resolves the snapshot, optionally drives
|
|
58
|
+
* the wizard, writes verdicts to the L6 / L18 / telemetry tiers,
|
|
59
|
+
* touches the marker, and emits a single structured payload via
|
|
60
|
+
* `writeOutput`.
|
|
61
|
+
*/
|
|
62
|
+
export async function runOnboardingCommand(args, ctx) {
|
|
63
|
+
const flags = parseFlags(args);
|
|
64
|
+
if (flags === null) {
|
|
65
|
+
const snapshot = readSnapshot(ctx);
|
|
66
|
+
ctx.writeOutput(buildPayload({
|
|
67
|
+
status: 'invalid_flags',
|
|
68
|
+
before: snapshot,
|
|
69
|
+
after: snapshot,
|
|
70
|
+
hints: [],
|
|
71
|
+
message: invalidFlagsMessage(args),
|
|
72
|
+
}), invalidFlagsMessage(args));
|
|
73
|
+
return 2;
|
|
74
|
+
}
|
|
75
|
+
if (flags.reset) {
|
|
76
|
+
const before = readSnapshot(ctx);
|
|
77
|
+
clearOnboarded(ctx.env ?? process.env);
|
|
78
|
+
const after = readSnapshot(ctx);
|
|
79
|
+
const message = 'Onboarding marker cleared. Run `pugi onboarding` to walk the wizard again.';
|
|
80
|
+
ctx.writeOutput(buildPayload({
|
|
81
|
+
status: 'reset',
|
|
82
|
+
before,
|
|
83
|
+
after,
|
|
84
|
+
hints: [],
|
|
85
|
+
message,
|
|
86
|
+
}), `${message}\n`);
|
|
87
|
+
return 0;
|
|
88
|
+
}
|
|
89
|
+
const before = readSnapshot(ctx);
|
|
90
|
+
if (!ctx.interactive || flags.nonInteractive) {
|
|
91
|
+
const text = renderSnapshotCard(before, {
|
|
92
|
+
heading: 'Pugi onboarding — current configuration',
|
|
93
|
+
footer: 'Re-run `pugi onboarding` from a real terminal to walk the wizard interactively.',
|
|
94
|
+
});
|
|
95
|
+
ctx.writeOutput(buildPayload({
|
|
96
|
+
status: 'non_interactive',
|
|
97
|
+
before,
|
|
98
|
+
after: before,
|
|
99
|
+
hints: buildHints(before),
|
|
100
|
+
message: text,
|
|
101
|
+
}), `${text}\n`);
|
|
102
|
+
return 0;
|
|
103
|
+
}
|
|
104
|
+
const verdict = await invokeWizard(ctx, before);
|
|
105
|
+
if (verdict.cancelled) {
|
|
106
|
+
const text = 'Onboarding cancelled. No changes written.';
|
|
107
|
+
ctx.writeOutput(buildPayload({
|
|
108
|
+
status: 'cancelled',
|
|
109
|
+
before,
|
|
110
|
+
after: before,
|
|
111
|
+
hints: buildHints(before),
|
|
112
|
+
message: text,
|
|
113
|
+
}), `${text}\n`);
|
|
114
|
+
return 0;
|
|
115
|
+
}
|
|
116
|
+
applyVerdict(verdict, ctx);
|
|
117
|
+
markOnboarded(ctx.env ?? process.env);
|
|
118
|
+
const after = readSnapshot(ctx);
|
|
119
|
+
const text = renderSnapshotCard(after, {
|
|
120
|
+
heading: 'Setup complete.',
|
|
121
|
+
footer: 'Run `pugi doctor` to verify your environment.',
|
|
122
|
+
});
|
|
123
|
+
ctx.writeOutput(buildPayload({
|
|
124
|
+
status: 'completed',
|
|
125
|
+
before,
|
|
126
|
+
after,
|
|
127
|
+
hints: buildHints(after),
|
|
128
|
+
message: text,
|
|
129
|
+
}), `${text}\n`);
|
|
130
|
+
return 0;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Pure snapshot reader. Called twice per invocation (before + after)
|
|
134
|
+
* so the payload can surface the diff a scripted caller would see.
|
|
135
|
+
*/
|
|
136
|
+
export function readSnapshot(ctx) {
|
|
137
|
+
const permissionMode = getGlobalDefaultMode(ctx.homeDir) ?? DEFAULT_PERMISSION_MODE;
|
|
138
|
+
const styleResolution = resolveOutputStyle({
|
|
139
|
+
workspaceRoot: ctx.workspaceRoot,
|
|
140
|
+
env: ctx.env ?? process.env,
|
|
141
|
+
});
|
|
142
|
+
const telemetry = readTelemetryChoice({ env: ctx.env ?? process.env });
|
|
143
|
+
const previouslyOnboarded = isOnboarded(ctx.env ?? process.env);
|
|
144
|
+
return {
|
|
145
|
+
authPresent: ctx.authPresent,
|
|
146
|
+
permissionMode,
|
|
147
|
+
outputStyle: styleResolution.slug,
|
|
148
|
+
outputStyleSource: styleResolution.source,
|
|
149
|
+
telemetry,
|
|
150
|
+
previouslyOnboarded,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Apply the wizard's verdict. Each step is independent — operator can
|
|
155
|
+
* skip individual steps (verdict.X === null) and only the non-null
|
|
156
|
+
* values write through to disk.
|
|
157
|
+
*/
|
|
158
|
+
function applyVerdict(verdict, ctx) {
|
|
159
|
+
if (verdict.permissionMode !== null) {
|
|
160
|
+
setGlobalDefaultMode(verdict.permissionMode, ctx.homeDir);
|
|
161
|
+
}
|
|
162
|
+
if (verdict.outputStyle !== null) {
|
|
163
|
+
setUserOutputStyle(verdict.outputStyle, {
|
|
164
|
+
workspaceRoot: ctx.workspaceRoot,
|
|
165
|
+
env: ctx.env ?? process.env,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
if (verdict.telemetry !== null) {
|
|
169
|
+
writeTelemetryChoice(verdict.telemetry, { env: ctx.env ?? process.env });
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Render the recap card surfaced both as the Step 6 exit screen and
|
|
174
|
+
* as the non-interactive snapshot dump.
|
|
175
|
+
*/
|
|
176
|
+
export function renderSnapshotCard(snapshot, opts) {
|
|
177
|
+
const styleGloss = OUTPUT_STYLES[snapshot.outputStyle].gloss;
|
|
178
|
+
const modeGloss = PERMISSION_MODE_GLOSS[snapshot.permissionMode];
|
|
179
|
+
const telemetryGloss = TELEMETRY_GLOSS[snapshot.telemetry];
|
|
180
|
+
const authLine = snapshot.authPresent
|
|
181
|
+
? 'Signed in (run `pugi whoami` for details).'
|
|
182
|
+
: 'Not signed in. Run `pugi login` to authenticate.';
|
|
183
|
+
const lines = [
|
|
184
|
+
opts.heading,
|
|
185
|
+
'',
|
|
186
|
+
` Auth: ${authLine}`,
|
|
187
|
+
` Permission mode: ${snapshot.permissionMode} — ${modeGloss}`,
|
|
188
|
+
` Output style: ${snapshot.outputStyle} (${snapshot.outputStyleSource}) — ${styleGloss}`,
|
|
189
|
+
` Telemetry: ${snapshot.telemetry} — ${telemetryGloss}`,
|
|
190
|
+
` MCP servers: add via \`pugi mcp add <name> <command>\``,
|
|
191
|
+
'',
|
|
192
|
+
opts.footer,
|
|
193
|
+
];
|
|
194
|
+
return lines.join('\n');
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Build the hint list — auth pointer + re-onboarding tip. Hints are
|
|
198
|
+
* structured (in the payload) so a JSON caller can dispatch on them.
|
|
199
|
+
*/
|
|
200
|
+
function buildHints(snapshot) {
|
|
201
|
+
const hints = [];
|
|
202
|
+
if (!snapshot.authPresent) {
|
|
203
|
+
hints.push('Run `pugi login` to sign in. Pugi works offline-first but auth unlocks the engine + sync.');
|
|
204
|
+
}
|
|
205
|
+
if (snapshot.previouslyOnboarded) {
|
|
206
|
+
hints.push('You have onboarded before — re-running the wizard is safe; Enter on any step keeps the current value.');
|
|
207
|
+
}
|
|
208
|
+
hints.push('Run `pugi doctor` to verify your environment.');
|
|
209
|
+
hints.push('Add MCP servers with `pugi mcp add` or list them via `pugi mcp list`.');
|
|
210
|
+
return Object.freeze(hints);
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Drive the wizard. Production callers leave `ctx.promptWizard`
|
|
214
|
+
* undefined, in which case we dynamic-import the Ink wizard so the
|
|
215
|
+
* command module stays free of the React/Ink module graph on the
|
|
216
|
+
* non-interactive path. Specs inject a stub.
|
|
217
|
+
*/
|
|
218
|
+
async function invokeWizard(ctx, snapshot) {
|
|
219
|
+
if (ctx.promptWizard) {
|
|
220
|
+
return ctx.promptWizard(snapshot);
|
|
221
|
+
}
|
|
222
|
+
const { renderOnboardingWizard } = await import('../../tui/onboarding-wizard.js');
|
|
223
|
+
return renderOnboardingWizard({ snapshot });
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Flag parser. Returns `null` on conflicting flags so the runner can
|
|
227
|
+
* emit `invalid_flags` + rc=2 without a thrown error.
|
|
228
|
+
*/
|
|
229
|
+
function parseFlags(args) {
|
|
230
|
+
let reset = false;
|
|
231
|
+
let nonInteractive = false;
|
|
232
|
+
for (const arg of args) {
|
|
233
|
+
if (arg === '--reset') {
|
|
234
|
+
reset = true;
|
|
235
|
+
}
|
|
236
|
+
else if (arg === '--non-interactive' || arg === '--no-tty') {
|
|
237
|
+
nonInteractive = true;
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
// `--reset` + `--non-interactive` is redundant but not conflicting — reset wins.
|
|
244
|
+
// No other combinations are illegal at the moment.
|
|
245
|
+
return { reset, nonInteractive };
|
|
246
|
+
}
|
|
247
|
+
function invalidFlagsMessage(args) {
|
|
248
|
+
return [
|
|
249
|
+
`pugi onboarding: unknown flag in \`${args.join(' ')}\`.`,
|
|
250
|
+
'Usage: pugi onboarding [--reset] [--non-interactive]',
|
|
251
|
+
].join('\n');
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* One-line gloss per telemetry choice — surfaced in the recap card.
|
|
255
|
+
* Mirrors the brand voice (no hedging, operator-grade).
|
|
256
|
+
*/
|
|
257
|
+
const TELEMETRY_GLOSS = Object.freeze({
|
|
258
|
+
off: 'No telemetry of any kind.',
|
|
259
|
+
anonymous: 'Counts + error categories only; no payloads.',
|
|
260
|
+
community: 'Anonymous + opt-in usage panels.',
|
|
261
|
+
});
|
|
262
|
+
function buildPayload(input) {
|
|
263
|
+
return {
|
|
264
|
+
command: 'onboarding',
|
|
265
|
+
status: input.status,
|
|
266
|
+
snapshotBefore: input.before,
|
|
267
|
+
snapshotAfter: input.after,
|
|
268
|
+
permissionModes: PERMISSION_MODES,
|
|
269
|
+
outputStyles: OUTPUT_STYLE_SLUGS,
|
|
270
|
+
telemetryChoices: TELEMETRY_CHOICES,
|
|
271
|
+
hints: input.hints,
|
|
272
|
+
message: input.message,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
//# sourceMappingURL=onboarding.js.map
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `pugi patch` — Phase 1.
|
|
3
|
+
*
|
|
4
|
+
* Apply a unified-diff patch from stdin or a file. The dominant use is
|
|
5
|
+
* the `pugi patch < patch.diff` shell pattern that lets external tools
|
|
6
|
+
* (Codex, manual `git diff`) hand off changes through pugi's same
|
|
7
|
+
* security gate the layers use.
|
|
8
|
+
*
|
|
9
|
+
* Surface:
|
|
10
|
+
*
|
|
11
|
+
* pugi patch # read patch from stdin
|
|
12
|
+
* pugi patch <file.diff> # read patch from file
|
|
13
|
+
* pugi patch --dry-run # run --check only, report
|
|
14
|
+
* pugi patch --3way --base=<sha> # enable git apply --3way fuzz
|
|
15
|
+
*
|
|
16
|
+
* Exit codes:
|
|
17
|
+
*
|
|
18
|
+
* 0 patch applied (or dry-run check passed)
|
|
19
|
+
* 1 patch rejected (any non-security reason)
|
|
20
|
+
* 2 usage error
|
|
21
|
+
* 3 security gate refused the patch (path traversal / protected file / symlink escape)
|
|
22
|
+
*
|
|
23
|
+
* Distinct exit codes let CI loops differentiate "operator typo" from
|
|
24
|
+
* "model produced a hostile patch" — the latter is a security event
|
|
25
|
+
* worth alerting on.
|
|
26
|
+
*
|
|
27
|
+
* Brand voice: ASCII only, no emoji, no banned words.
|
|
28
|
+
*/
|
|
29
|
+
import { readFileSync } from 'node:fs';
|
|
30
|
+
import { resolve } from 'node:path';
|
|
31
|
+
import { applyPatch } from '../../tools/apply-patch.js';
|
|
32
|
+
import { FileReadCache } from '../../core/file-cache.js';
|
|
33
|
+
import { openSession } from '../../core/session.js';
|
|
34
|
+
import { loadSettings } from '../../core/settings.js';
|
|
35
|
+
const SECURITY_REASONS = new Set(['path_outside_workspace', 'protected_file', 'symlink_escape']);
|
|
36
|
+
export async function runPatchCommand(args, opts) {
|
|
37
|
+
const positional = [];
|
|
38
|
+
const applyOpts = {};
|
|
39
|
+
// Seed from caller-supplied options first; arg-flag parsing below
|
|
40
|
+
// overrides when present.
|
|
41
|
+
if (opts.dryRun)
|
|
42
|
+
applyOpts.dryRun = true;
|
|
43
|
+
if (opts.baseSha)
|
|
44
|
+
applyOpts.baseSha = opts.baseSha;
|
|
45
|
+
let threeWaySeen = false;
|
|
46
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
47
|
+
const arg = args[i] ?? '';
|
|
48
|
+
if (arg === '--dry-run')
|
|
49
|
+
applyOpts.dryRun = true;
|
|
50
|
+
else if (arg === '--3way') {
|
|
51
|
+
// honored only when --base is also supplied
|
|
52
|
+
threeWaySeen = true;
|
|
53
|
+
}
|
|
54
|
+
else if (arg === '--base') {
|
|
55
|
+
const next = args[i + 1];
|
|
56
|
+
if (next)
|
|
57
|
+
applyOpts.baseSha = next;
|
|
58
|
+
i += 1;
|
|
59
|
+
}
|
|
60
|
+
else if (arg.startsWith('--base=')) {
|
|
61
|
+
applyOpts.baseSha = arg.slice('--base='.length);
|
|
62
|
+
}
|
|
63
|
+
else if (arg === '--json') {
|
|
64
|
+
// already parsed by the outer CLI
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
positional.push(arg);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// R1 fix (2026-05-26, PR r1, P2 #14): `--3way` without `--base`
|
|
71
|
+
// is meaningless because `git apply --3way` falls back to the index,
|
|
72
|
+
// which a CLI-side `pugi patch` invocation does not have populated
|
|
73
|
+
// with the patch's pre-image. Warn the operator instead of dropping
|
|
74
|
+
// the flag silently.
|
|
75
|
+
if (threeWaySeen && !applyOpts.baseSha) {
|
|
76
|
+
const warn = opts.warn ?? ((m) => console.warn(m));
|
|
77
|
+
warn('warning: --3way ignored without --base=<sha>; pass --base or drop --3way');
|
|
78
|
+
}
|
|
79
|
+
let patch;
|
|
80
|
+
try {
|
|
81
|
+
patch = await readPatchSource(positional[0], opts);
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
85
|
+
return failure({ ok: false, filesChanged: [], reason: 'invalid_patch', detail: message }, opts.json, 2);
|
|
86
|
+
}
|
|
87
|
+
const ctx = {
|
|
88
|
+
root: opts.cwd,
|
|
89
|
+
settings: loadSettings(opts.cwd),
|
|
90
|
+
session: openSession(opts.cwd),
|
|
91
|
+
readCache: new FileReadCache(),
|
|
92
|
+
};
|
|
93
|
+
const result = applyPatch(ctx, patch, applyOpts);
|
|
94
|
+
if (!result.ok) {
|
|
95
|
+
const exitCode = result.reason && SECURITY_REASONS.has(result.reason) ? 3 : 1;
|
|
96
|
+
return failure(result, opts.json, exitCode);
|
|
97
|
+
}
|
|
98
|
+
const text = opts.json
|
|
99
|
+
? JSON.stringify(result, null, 2)
|
|
100
|
+
: `applied ${result.filesChanged.length} files:\n ${result.filesChanged.join('\n ')}`;
|
|
101
|
+
return { ok: true, text, exitCode: 0, result };
|
|
102
|
+
}
|
|
103
|
+
function failure(result, json, exitCode) {
|
|
104
|
+
const text = json
|
|
105
|
+
? JSON.stringify(result, null, 2)
|
|
106
|
+
: `patch refused: ${result.reason ?? 'unknown'}${result.detail ? `\n ${result.detail}` : ''}`;
|
|
107
|
+
return { ok: false, text, exitCode, result };
|
|
108
|
+
}
|
|
109
|
+
async function readPatchSource(filePath, opts) {
|
|
110
|
+
if (filePath) {
|
|
111
|
+
const resolved = resolve(opts.cwd, filePath);
|
|
112
|
+
return readFileSync(resolved, 'utf8');
|
|
113
|
+
}
|
|
114
|
+
if (opts.stdinOverride !== undefined)
|
|
115
|
+
return opts.stdinOverride;
|
|
116
|
+
// Read all of stdin. The process pipe is the canonical CLI handoff
|
|
117
|
+
// for inbound diffs (e.g. `git diff origin/main | pugi patch`).
|
|
118
|
+
return new Promise((resolveFn, rejectFn) => {
|
|
119
|
+
let body = '';
|
|
120
|
+
process.stdin.setEncoding('utf8');
|
|
121
|
+
process.stdin.on('data', (chunk) => {
|
|
122
|
+
body += chunk;
|
|
123
|
+
});
|
|
124
|
+
process.stdin.on('end', () => resolveFn(body));
|
|
125
|
+
process.stdin.on('error', (error) => rejectFn(error));
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=patch.js.map
|