@pugi/cli 0.1.0-beta.3 → 0.1.0-beta.30
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/THIRD_PARTY_NOTICES.md +40 -0
- package/assets/pugi-mascot.ansi +15 -40
- package/bin/run.js +33 -1
- package/dist/commands/jobs-watch.js +201 -0
- package/dist/commands/jobs.js +15 -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/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/auth/env-provider.js +238 -0
- 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/checkpoint/resumer.js +149 -0
- package/dist/core/checkpoint/rewinder.js +291 -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/diff-capture.js +73 -0
- package/dist/core/context/index.js +7 -0
- package/dist/core/context/markdown-traverse.js +255 -0
- package/dist/core/cost/rate-card.js +129 -0
- package/dist/core/cost/tracker.js +221 -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 +86 -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/git.js +65 -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/session.js +74 -0
- package/dist/core/diagnostics/probes/status-snapshot.js +442 -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/dispatch.js +218 -2
- package/dist/core/edits/journal.js +199 -0
- package/dist/core/edits/layer-d-ast.js +557 -14
- package/dist/core/edits/verify-hook.js +273 -0
- package/dist/core/edits/worktree.js +111 -18
- package/dist/core/engine/anvil-client.js +115 -5
- package/dist/core/engine/budgets.js +89 -0
- package/dist/core/engine/context-prefix.js +155 -0
- package/dist/core/engine/intent.js +260 -0
- package/dist/core/engine/native-pugi.js +852 -210
- package/dist/core/engine/prompts.js +89 -6
- package/dist/core/engine/strip-internal-fields.js +124 -0
- package/dist/core/engine/tool-bridge.js +972 -33
- 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/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/init/scaffold.js +195 -0
- package/dist/core/lsp/cache.js +105 -0
- package/dist/core/lsp/client.js +174 -29
- package/dist/core/lsp/language-detect.js +66 -0
- package/dist/core/lsp/post-edit-diagnostics.js +171 -0
- package/dist/core/mcp/client.js +75 -6
- package/dist/core/mcp/http-server.js +553 -0
- package/dist/core/mcp/permission.js +190 -0
- package/dist/core/mcp/registry.js +24 -2
- package/dist/core/mcp/server-tools.js +219 -0
- package/dist/core/mcp/server.js +397 -0
- package/dist/core/memory/dual-write.js +416 -0
- package/dist/core/memory/dual-write.spec.js +297 -0
- package/dist/core/memory/phase1-kinds.js +20 -0
- package/dist/core/memory-sync/queue.js +158 -0
- package/dist/core/memory-sync/queue.spec.js +105 -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/permissions/gate.js +187 -0
- package/dist/core/permissions/index.js +18 -0
- package/dist/core/permissions/mode.js +102 -0
- package/dist/core/permissions/state.js +215 -0
- package/dist/core/permissions/tool-class.js +93 -0
- package/dist/core/prd-check/parser.js +215 -0
- package/dist/core/prd-check/reporter.js +127 -0
- package/dist/core/prd-check/verifiers.js +223 -0
- package/dist/core/pugi-md/context-injector.js +76 -0
- package/dist/core/pugi-md/walk-up.js +207 -0
- package/dist/core/release-notes/parser.js +241 -0
- package/dist/core/release-notes/state.js +116 -0
- package/dist/core/repl/codebase-survey.js +308 -0
- package/dist/core/repl/history.js +11 -1
- package/dist/core/repl/init-interview.js +457 -0
- package/dist/core/repl/model-pricing.js +135 -0
- package/dist/core/repl/onboarding-state.js +297 -0
- package/dist/core/repl/session.js +1486 -30
- package/dist/core/repl/slash-commands.js +345 -9
- package/dist/core/repl/store/session-store.js +31 -2
- package/dist/core/repl/workspace-context.js +22 -0
- 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/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/session.js +44 -0
- package/dist/core/settings.js +80 -0
- 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/subagents/dispatcher-real.js +600 -0
- package/dist/core/subagents/dispatcher.js +113 -24
- package/dist/core/subagents/index.js +18 -5
- 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/transport/version-interceptor.js +166 -0
- package/dist/core/vim/keymap.js +288 -0
- package/dist/core/vim/state.js +92 -0
- package/dist/index.js +28 -0
- package/dist/runtime/bootstrap.js +190 -0
- package/dist/runtime/cli.js +2595 -278
- package/dist/runtime/commands/chain.js +489 -0
- package/dist/runtime/commands/compact.js +297 -0
- package/dist/runtime/commands/cost.js +199 -0
- package/dist/runtime/commands/delegate.js +312 -0
- package/dist/runtime/commands/dispatch.js +126 -0
- package/dist/runtime/commands/doctor.js +390 -0
- package/dist/runtime/commands/feedback.js +184 -0
- package/dist/runtime/commands/hooks.js +184 -0
- package/dist/runtime/commands/lsp.js +212 -28
- package/dist/runtime/commands/mcp.js +824 -0
- package/dist/runtime/commands/memory.js +508 -0
- package/dist/runtime/commands/memory.spec.js +174 -0
- package/dist/runtime/commands/model.js +237 -0
- package/dist/runtime/commands/onboarding.js +275 -0
- package/dist/runtime/commands/patch.js +17 -0
- package/dist/runtime/commands/permissions.js +87 -0
- package/dist/runtime/commands/plan.js +143 -0
- package/dist/runtime/commands/prd-check.js +235 -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 +17 -2
- package/dist/runtime/commands/rewind.js +333 -0
- package/dist/runtime/commands/roster.js +117 -0
- package/dist/runtime/commands/sessions.js +163 -0
- package/dist/runtime/commands/share.js +316 -0
- package/dist/runtime/commands/status.js +178 -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/update.js +289 -0
- package/dist/runtime/commands/vim.js +140 -0
- package/dist/runtime/commands/worktree.js +50 -6
- 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/version.js +65 -0
- package/dist/tools/agent-tool.js +229 -0
- package/dist/tools/apply-patch.js +281 -39
- package/dist/tools/ask-user-question.js +213 -0
- package/dist/tools/ask-user.js +115 -0
- package/dist/tools/file-tools.js +85 -14
- package/dist/tools/mcp-tool.js +260 -0
- package/dist/tools/multi-edit.js +361 -0
- package/dist/tools/registry.js +30 -2
- package/dist/tools/skill-tool.js +96 -0
- package/dist/tools/tasks.js +208 -0
- package/dist/tools/todo-write.js +184 -0
- package/dist/tools/web-fetch.js +147 -2
- package/dist/tools/web-search.js +458 -0
- package/dist/tui/agent-progress-card.js +111 -0
- package/dist/tui/agent-tree.js +10 -0
- package/dist/tui/ask-modal.js +2 -2
- package/dist/tui/ask-user-question-prompt.js +192 -0
- package/dist/tui/compact-banner.js +81 -0
- package/dist/tui/conversation-pane.js +82 -8
- package/dist/tui/cost-table.js +111 -0
- package/dist/tui/doctor-table.js +46 -0
- package/dist/tui/feedback-prompt.js +156 -0
- package/dist/tui/input-box.js +46 -2
- package/dist/tui/markdown-render.js +4 -4
- package/dist/tui/onboarding-wizard.js +240 -0
- package/dist/tui/repl-render.js +293 -35
- package/dist/tui/repl-splash.js +2 -2
- package/dist/tui/repl.js +45 -13
- package/dist/tui/splash.js +1 -1
- package/dist/tui/status-bar.js +94 -16
- 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/tool-stream-pane.js +7 -0
- package/dist/tui/update-banner.js +20 -2
- package/dist/tui/vim-input.js +267 -0
- package/docs/examples/codegraph.mcp.json +10 -0
- package/package.json +9 -6
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/model` / `pugi model` — Wave 6 BT 8 (Claude Code 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
|
+
* 2026-05-26 list prices.
|
|
43
|
+
* - OpenAI (gpt-4o, gpt-4o-mini, o3, o3-mini): 2026-05-26 list prices.
|
|
44
|
+
* - Mistral (mistral-large, mistral-small): 2026-05-26 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
|
+
* Leak L25 (2026-05-27) — `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
|
|
@@ -36,12 +36,20 @@ const SECURITY_REASONS = new Set(['path_outside_workspace', 'protected_file', 's
|
|
|
36
36
|
export async function runPatchCommand(args, opts) {
|
|
37
37
|
const positional = [];
|
|
38
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;
|
|
39
46
|
for (let i = 0; i < args.length; i += 1) {
|
|
40
47
|
const arg = args[i] ?? '';
|
|
41
48
|
if (arg === '--dry-run')
|
|
42
49
|
applyOpts.dryRun = true;
|
|
43
50
|
else if (arg === '--3way') {
|
|
44
51
|
// honored only when --base is also supplied
|
|
52
|
+
threeWaySeen = true;
|
|
45
53
|
}
|
|
46
54
|
else if (arg === '--base') {
|
|
47
55
|
const next = args[i + 1];
|
|
@@ -59,6 +67,15 @@ export async function runPatchCommand(args, opts) {
|
|
|
59
67
|
positional.push(arg);
|
|
60
68
|
}
|
|
61
69
|
}
|
|
70
|
+
// R1 fix (2026-05-26, PR #413 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
|
+
}
|
|
62
79
|
let patch;
|
|
63
80
|
try {
|
|
64
81
|
patch = await readPatchSource(positional[0], opts);
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `pugi permissions` / `/permissions` — Leak L6 4-mode gate control.
|
|
3
|
+
*
|
|
4
|
+
* Two entry points share one runtime helper:
|
|
5
|
+
* 1. `/permissions` in the REPL — forwarded by `core/repl/session.ts`.
|
|
6
|
+
* 2. `pugi permissions ...` top-level CLI command (handler in
|
|
7
|
+
* `runtime/cli.ts`).
|
|
8
|
+
*
|
|
9
|
+
* Both pass a `PermissionsCommand` payload describing the operator
|
|
10
|
+
* intent (show / flip / persist) and a `writeOutput` callback that
|
|
11
|
+
* lets the caller route the rendered lines into the right surface
|
|
12
|
+
* (REPL transcript vs. stdout). The helper is intentionally I/O-free
|
|
13
|
+
* itself — it produces lines and lets the caller stream them.
|
|
14
|
+
*/
|
|
15
|
+
import { DEFAULT_PERMISSION_MODE, PERMISSION_MODES, PERMISSION_MODE_GLOSS, getCurrentMode, getGlobalDefaultMode, setCurrentMode, setGlobalDefaultMode, } from '../../core/permissions/index.js';
|
|
16
|
+
/**
|
|
17
|
+
* Run the `/permissions` or `pugi permissions` flow. Side effects:
|
|
18
|
+
* - When `command.mode` is undefined: prints the current mode + the
|
|
19
|
+
* 4-mode table (no writes).
|
|
20
|
+
* - When `command.mode === 'bypass'` without `confirmBypass`: prints
|
|
21
|
+
* a refusal + the safety copy, no writes.
|
|
22
|
+
* - When `command.mode` is set + valid: writes workspace session
|
|
23
|
+
* state; optionally writes global default when `persist` is true.
|
|
24
|
+
* - Always prints the new effective mode + a one-line confirmation.
|
|
25
|
+
*/
|
|
26
|
+
export async function runPermissionsCommand(command, ctx) {
|
|
27
|
+
if (!command.mode) {
|
|
28
|
+
renderCurrentMode(ctx);
|
|
29
|
+
renderModeTable(ctx);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (command.mode === 'bypass' && !command.confirmBypass) {
|
|
33
|
+
ctx.writeOutput('Bypass mode disables policy hooks (skill steering, denial tracking).');
|
|
34
|
+
ctx.writeOutput('Run `/permissions bypass --confirm` to acknowledge before flipping.');
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
setCurrentMode(ctx.workspaceRoot, command.mode);
|
|
38
|
+
if (command.persist) {
|
|
39
|
+
setGlobalDefaultMode(command.mode, ctx.homeDir);
|
|
40
|
+
}
|
|
41
|
+
const persistedHint = command.persist
|
|
42
|
+
? ' Persisted to ~/.pugi/config.json for future sessions.'
|
|
43
|
+
: '';
|
|
44
|
+
ctx.writeOutput(`Permission mode set to '${command.mode}'.${persistedHint} ${PERMISSION_MODE_GLOSS[command.mode]}`);
|
|
45
|
+
if (command.mode === 'bypass') {
|
|
46
|
+
ctx.writeOutput('BYPASS MODE — all tools execute without prompts AND policy hooks disabled. Switch back with /permissions allow.');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Print the resolved current mode + the layered source. The merge
|
|
51
|
+
* order mirrors `resolveMode()`: workspace > global > default.
|
|
52
|
+
*/
|
|
53
|
+
function renderCurrentMode(ctx) {
|
|
54
|
+
const workspace = getCurrentMode(ctx.workspaceRoot);
|
|
55
|
+
const global = getGlobalDefaultMode(ctx.homeDir);
|
|
56
|
+
const effective = workspace ?? global ?? DEFAULT_PERMISSION_MODE;
|
|
57
|
+
const source = workspace
|
|
58
|
+
? 'workspace session.json'
|
|
59
|
+
: global
|
|
60
|
+
? 'global ~/.pugi/config.json'
|
|
61
|
+
: 'default (no override)';
|
|
62
|
+
ctx.writeOutput(`Current permission mode: ${effective} (source: ${source})`);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Print the 4-mode reference table. Keeps the gloss + the side-effect
|
|
66
|
+
* matrix in one place so the operator can see the contract while they
|
|
67
|
+
* decide which mode to switch to.
|
|
68
|
+
*/
|
|
69
|
+
function renderModeTable(ctx) {
|
|
70
|
+
ctx.writeOutput('');
|
|
71
|
+
ctx.writeOutput('Permission modes:');
|
|
72
|
+
for (const mode of PERMISSION_MODES) {
|
|
73
|
+
ctx.writeOutput(` ${mode.padEnd(7)} ${PERMISSION_MODE_GLOSS[mode]}`);
|
|
74
|
+
}
|
|
75
|
+
ctx.writeOutput('');
|
|
76
|
+
ctx.writeOutput('Switch with `/permissions <mode> [--persist]`. Bypass requires `--confirm`.');
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Render the one-shot banner shown on session boot when the effective
|
|
80
|
+
* mode is `bypass`. The caller (engine adapter / REPL bootstrap) calls
|
|
81
|
+
* this once per session — repeated invocations are idempotent in copy
|
|
82
|
+
* but the caller is responsible for the once-only semantics.
|
|
83
|
+
*/
|
|
84
|
+
export function renderBypassBanner(writeOutput) {
|
|
85
|
+
writeOutput('BYPASS MODE — all tools execute without prompts AND policy hooks disabled. Switch back with /permissions allow.');
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=permissions.js.map
|