@pugi/cli 0.1.0-beta.4 → 0.1.0-beta.41

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.
Files changed (250) hide show
  1. package/THIRD_PARTY_NOTICES.md +40 -0
  2. package/assets/pugi-mascot.ansi +15 -25
  3. package/bin/run.js +33 -1
  4. package/dist/commands/jobs-watch.js +201 -0
  5. package/dist/commands/jobs.js +15 -0
  6. package/dist/commands/smoke.js +133 -0
  7. package/dist/core/agent-progress/cleanup.js +134 -0
  8. package/dist/core/agent-progress/schema.js +144 -0
  9. package/dist/core/agent-progress/writer.js +101 -0
  10. package/dist/core/artifact-chain/dispatcher.js +148 -0
  11. package/dist/core/artifact-chain/exporter.js +164 -0
  12. package/dist/core/artifact-chain/state.js +243 -0
  13. package/dist/core/artifact-chain/steps.js +169 -0
  14. package/dist/core/auth/ensure-authenticated.js +129 -0
  15. package/dist/core/auth/env-provider.js +238 -0
  16. package/dist/core/auto-update/channels.js +122 -0
  17. package/dist/core/auto-update/checker.js +241 -0
  18. package/dist/core/auto-update/state.js +235 -0
  19. package/dist/core/bare-mode/index.js +107 -0
  20. package/dist/core/bash-classifier.js +108 -1
  21. package/dist/core/checkpoint/resumer.js +149 -0
  22. package/dist/core/checkpoint/rewinder.js +291 -0
  23. package/dist/core/codegraph/decision-store.js +248 -0
  24. package/dist/core/codegraph/detect-repo.js +459 -0
  25. package/dist/core/codegraph/install.js +134 -0
  26. package/dist/core/codegraph/offer-hook.js +220 -0
  27. package/dist/core/compact/auto-trigger.js +96 -0
  28. package/dist/core/compact/buffer-rewriter.js +115 -0
  29. package/dist/core/compact/summarizer.js +208 -0
  30. package/dist/core/compact/token-counter.js +108 -0
  31. package/dist/core/consensus/diff-capture.js +73 -0
  32. package/dist/core/context/index.js +7 -0
  33. package/dist/core/context/markdown-traverse.js +255 -0
  34. package/dist/core/cost/rate-card.js +129 -0
  35. package/dist/core/cost/tracker.js +221 -0
  36. package/dist/core/denial-tracking/index.js +8 -0
  37. package/dist/core/denial-tracking/state.js +264 -0
  38. package/dist/core/diagnostics/probe-runner.js +93 -0
  39. package/dist/core/diagnostics/probes/api.js +46 -0
  40. package/dist/core/diagnostics/probes/auth.js +86 -0
  41. package/dist/core/diagnostics/probes/bare-mode.js +42 -0
  42. package/dist/core/diagnostics/probes/cli-version.js +127 -0
  43. package/dist/core/diagnostics/probes/config.js +72 -0
  44. package/dist/core/diagnostics/probes/denial-tracking.js +57 -0
  45. package/dist/core/diagnostics/probes/disk.js +81 -0
  46. package/dist/core/diagnostics/probes/git.js +65 -0
  47. package/dist/core/diagnostics/probes/mcp.js +75 -0
  48. package/dist/core/diagnostics/probes/node.js +59 -0
  49. package/dist/core/diagnostics/probes/pnpm.js +36 -0
  50. package/dist/core/diagnostics/probes/pugi-md.js +89 -0
  51. package/dist/core/diagnostics/probes/session.js +74 -0
  52. package/dist/core/diagnostics/probes/status-snapshot.js +488 -0
  53. package/dist/core/diagnostics/probes/workspace.js +63 -0
  54. package/dist/core/diagnostics/types.js +70 -0
  55. package/dist/core/dispatch/cache-cleanup.js +197 -0
  56. package/dist/core/dispatch/cache-handoff.js +295 -0
  57. package/dist/core/edits/dispatch.js +218 -2
  58. package/dist/core/edits/journal.js +199 -0
  59. package/dist/core/edits/layer-d-ast.js +557 -14
  60. package/dist/core/edits/verify-hook.js +273 -0
  61. package/dist/core/edits/worktree.js +322 -0
  62. package/dist/core/engine/anvil-client.js +115 -5
  63. package/dist/core/engine/budgets.js +98 -0
  64. package/dist/core/engine/context-prefix.js +155 -0
  65. package/dist/core/engine/intent.js +260 -0
  66. package/dist/core/engine/native-pugi.js +860 -211
  67. package/dist/core/engine/prompts.js +88 -2
  68. package/dist/core/engine/strip-internal-fields.js +124 -0
  69. package/dist/core/engine/tool-bridge.js +1045 -36
  70. package/dist/core/feedback/queue.js +177 -0
  71. package/dist/core/feedback/submitter.js +145 -0
  72. package/dist/core/file-cache.js +113 -1
  73. package/dist/core/hooks/events.js +44 -0
  74. package/dist/core/hooks/index.js +15 -0
  75. package/dist/core/hooks/registry.js +213 -0
  76. package/dist/core/hooks/runner.js +236 -0
  77. package/dist/core/hooks/v2/event-emitter.js +115 -0
  78. package/dist/core/hooks/v2/executor.js +282 -0
  79. package/dist/core/hooks/v2/index.js +25 -0
  80. package/dist/core/hooks/v2/lifecycle.js +104 -0
  81. package/dist/core/hooks/v2/loader.js +216 -0
  82. package/dist/core/hooks/v2/matcher.js +125 -0
  83. package/dist/core/hooks/v2/trust.js +143 -0
  84. package/dist/core/hooks/v2/types.js +86 -0
  85. package/dist/core/lsp/cache.js +105 -0
  86. package/dist/core/lsp/client.js +776 -0
  87. package/dist/core/lsp/language-detect.js +66 -0
  88. package/dist/core/lsp/post-edit-diagnostics.js +171 -0
  89. package/dist/core/mcp/client.js +75 -6
  90. package/dist/core/mcp/http-server.js +553 -0
  91. package/dist/core/mcp/orchestrator-tools.js +662 -0
  92. package/dist/core/mcp/permission.js +190 -0
  93. package/dist/core/mcp/registry.js +24 -2
  94. package/dist/core/mcp/server-tools.js +219 -0
  95. package/dist/core/mcp/server.js +397 -0
  96. package/dist/core/memory/dual-write.js +416 -0
  97. package/dist/core/memory/phase1-kinds.js +20 -0
  98. package/dist/core/memory-sync/queue.js +158 -0
  99. package/dist/core/onboarding/ensure-initialized.js +133 -0
  100. package/dist/core/onboarding/marker.js +111 -0
  101. package/dist/core/onboarding/telemetry-state.js +108 -0
  102. package/dist/core/output-style/presets.js +176 -0
  103. package/dist/core/output-style/state.js +185 -0
  104. package/dist/core/permissions/auto-classifier.js +124 -0
  105. package/dist/core/permissions/circuit-breaker.js +83 -0
  106. package/dist/core/permissions/gate.js +278 -0
  107. package/dist/core/permissions/index.js +20 -0
  108. package/dist/core/permissions/mode.js +174 -0
  109. package/dist/core/permissions/state.js +241 -0
  110. package/dist/core/permissions/tool-class.js +93 -0
  111. package/dist/core/prd-check/parser.js +215 -0
  112. package/dist/core/prd-check/reporter.js +127 -0
  113. package/dist/core/prd-check/session-review.js +557 -0
  114. package/dist/core/prd-check/verifiers.js +223 -0
  115. package/dist/core/pugi-md/context-injector.js +76 -0
  116. package/dist/core/pugi-md/walk-up.js +207 -0
  117. package/dist/core/release-notes/parser.js +241 -0
  118. package/dist/core/release-notes/state.js +116 -0
  119. package/dist/core/repl/history.js +11 -1
  120. package/dist/core/repl/model-pricing.js +135 -0
  121. package/dist/core/repl/session.js +1899 -38
  122. package/dist/core/repl/slash-commands.js +406 -21
  123. package/dist/core/repl/store/session-store.js +31 -2
  124. package/dist/core/repl/workspace-context.js +22 -0
  125. package/dist/core/repo-map/build.js +125 -0
  126. package/dist/core/repo-map/cache.js +185 -0
  127. package/dist/core/repo-map/extractor.js +254 -0
  128. package/dist/core/repo-map/formatter.js +145 -0
  129. package/dist/core/repo-map/scanner.js +211 -0
  130. package/dist/core/retry-budget/budget.js +284 -0
  131. package/dist/core/retry-budget/index.js +5 -0
  132. package/dist/core/session.js +92 -0
  133. package/dist/core/settings.js +80 -0
  134. package/dist/core/share/formatter.js +271 -0
  135. package/dist/core/share/redactor.js +221 -0
  136. package/dist/core/share/uploader.js +267 -0
  137. package/dist/core/skills/defaults.js +457 -0
  138. package/dist/core/smoke/headless-driver.js +174 -0
  139. package/dist/core/smoke/orchestrator.js +194 -0
  140. package/dist/core/smoke/runner.js +238 -0
  141. package/dist/core/smoke/scenario-parser.js +316 -0
  142. package/dist/core/subagents/dispatcher-real.js +600 -0
  143. package/dist/core/subagents/dispatcher.js +113 -24
  144. package/dist/core/subagents/index.js +18 -5
  145. package/dist/core/subagents/isolation-matrix.js +213 -0
  146. package/dist/core/subagents/spawn.js +19 -4
  147. package/dist/core/telemetry/emitter.js +229 -0
  148. package/dist/core/telemetry/queue.js +251 -0
  149. package/dist/core/theme/context.js +91 -0
  150. package/dist/core/theme/presets.js +228 -0
  151. package/dist/core/theme/state.js +181 -0
  152. package/dist/core/todos/invariant.js +10 -0
  153. package/dist/core/todos/state.js +177 -0
  154. package/dist/core/transport/version-interceptor.js +166 -0
  155. package/dist/core/vim/keymap.js +288 -0
  156. package/dist/core/vim/state.js +92 -0
  157. package/dist/index.js +28 -0
  158. package/dist/runtime/bootstrap.js +190 -0
  159. package/dist/runtime/cli.js +3073 -321
  160. package/dist/runtime/commands/cancel.js +231 -0
  161. package/dist/runtime/commands/chain.js +489 -0
  162. package/dist/runtime/commands/codegraph-status.js +227 -0
  163. package/dist/runtime/commands/compact.js +297 -0
  164. package/dist/runtime/commands/cost.js +199 -0
  165. package/dist/runtime/commands/delegate.js +242 -11
  166. package/dist/runtime/commands/dispatch.js +126 -0
  167. package/dist/runtime/commands/doctor.js +390 -0
  168. package/dist/runtime/commands/feedback.js +184 -0
  169. package/dist/runtime/commands/hooks.js +184 -0
  170. package/dist/runtime/commands/lsp.js +368 -0
  171. package/dist/runtime/commands/mcp.js +879 -0
  172. package/dist/runtime/commands/memory.js +508 -0
  173. package/dist/runtime/commands/model.js +237 -0
  174. package/dist/runtime/commands/onboarding.js +275 -0
  175. package/dist/runtime/commands/patch.js +128 -0
  176. package/dist/runtime/commands/permissions.js +112 -0
  177. package/dist/runtime/commands/plan.js +143 -0
  178. package/dist/runtime/commands/prd-check.js +285 -0
  179. package/dist/runtime/commands/redo-blob-store.js +92 -0
  180. package/dist/runtime/commands/redo.js +361 -0
  181. package/dist/runtime/commands/release-notes.js +229 -0
  182. package/dist/runtime/commands/repo-map.js +95 -0
  183. package/dist/runtime/commands/report.js +299 -0
  184. package/dist/runtime/commands/resume.js +118 -0
  185. package/dist/runtime/commands/review-consensus.js +17 -2
  186. package/dist/runtime/commands/rewind.js +333 -0
  187. package/dist/runtime/commands/sessions.js +163 -0
  188. package/dist/runtime/commands/share.js +316 -0
  189. package/dist/runtime/commands/status.js +186 -0
  190. package/dist/runtime/commands/stickers.js +82 -0
  191. package/dist/runtime/commands/style.js +194 -0
  192. package/dist/runtime/commands/theme.js +196 -0
  193. package/dist/runtime/commands/undo.js +32 -0
  194. package/dist/runtime/commands/update.js +289 -0
  195. package/dist/runtime/commands/vim.js +140 -0
  196. package/dist/runtime/commands/worktree.js +177 -0
  197. package/dist/runtime/headless-repl.js +195 -0
  198. package/dist/runtime/headless.js +543 -0
  199. package/dist/runtime/load-hooks-or-exit.js +71 -0
  200. package/dist/runtime/plan-decompose.js +531 -0
  201. package/dist/runtime/version.js +65 -0
  202. package/dist/tools/agent-tool.js +229 -0
  203. package/dist/tools/apply-patch.js +556 -0
  204. package/dist/tools/ask-user-question.js +213 -0
  205. package/dist/tools/ask-user.js +115 -0
  206. package/dist/tools/file-tools.js +85 -14
  207. package/dist/tools/lsp-tools.js +189 -0
  208. package/dist/tools/mcp-tool.js +260 -0
  209. package/dist/tools/multi-edit.js +361 -0
  210. package/dist/tools/powershell.js +156 -0
  211. package/dist/tools/registry.js +51 -0
  212. package/dist/tools/skill-tool.js +96 -0
  213. package/dist/tools/tasks.js +208 -0
  214. package/dist/tools/todo-write.js +184 -0
  215. package/dist/tools/web-fetch.js +147 -2
  216. package/dist/tools/web-search.js +458 -0
  217. package/dist/tui/agent-progress-card.js +111 -0
  218. package/dist/tui/agent-tree.js +10 -0
  219. package/dist/tui/ask-modal.js +2 -2
  220. package/dist/tui/ask-user-question-prompt.js +192 -0
  221. package/dist/tui/compact-banner.js +81 -0
  222. package/dist/tui/conversation-pane.js +82 -8
  223. package/dist/tui/cost-table.js +111 -0
  224. package/dist/tui/doctor-table.js +46 -0
  225. package/dist/tui/feedback-prompt.js +156 -0
  226. package/dist/tui/input-box.js +69 -2
  227. package/dist/tui/markdown-render.js +4 -4
  228. package/dist/tui/onboarding-wizard.js +240 -0
  229. package/dist/tui/permissions-picker.js +86 -0
  230. package/dist/tui/render.js +35 -0
  231. package/dist/tui/repl-render.js +303 -13
  232. package/dist/tui/repl-splash.js +2 -2
  233. package/dist/tui/repl.js +72 -14
  234. package/dist/tui/splash.js +1 -1
  235. package/dist/tui/status-bar.js +94 -16
  236. package/dist/tui/status-table.js +7 -0
  237. package/dist/tui/stickers-art.js +136 -0
  238. package/dist/tui/style-table.js +28 -0
  239. package/dist/tui/theme-table.js +29 -0
  240. package/dist/tui/tool-stream-pane.js +52 -3
  241. package/dist/tui/update-banner.js +20 -2
  242. package/dist/tui/vim-input.js +267 -0
  243. package/docs/examples/codegraph.mcp.json +10 -0
  244. package/package.json +12 -6
  245. package/test/scenarios/codegen-create-file.scenario.txt +13 -0
  246. package/test/scenarios/compact-force.scenario.txt +11 -0
  247. package/test/scenarios/identity.scenario.txt +11 -0
  248. package/test/scenarios/persona-handoff.scenario.txt +11 -0
  249. package/test/scenarios/walkback.scenario.txt +12 -0
  250. package/dist/core/engine/compaction-hook.js +0 -154
@@ -0,0 +1,194 @@
1
+ /**
2
+ * Leak L18 (2026-05-27) — `pugi style` top-level command + REPL slash
3
+ * companion.
4
+ *
5
+ * Operator surface:
6
+ *
7
+ * pugi style Show active preset + table.
8
+ * pugi style <name> Switch workspace preset (current cwd).
9
+ * pugi style <name> --persist Switch + also write user default.
10
+ * pugi style --reset Clear workspace override → back to default.
11
+ * pugi style --reset --user Also clear the user default.
12
+ * pugi style --list Print the catalogue (no flip).
13
+ * pugi style --json Structured envelope variant.
14
+ *
15
+ * The same runner powers `/style` from inside the REPL. The REPL
16
+ * dispatcher (see `core/repl/session.ts`) routes through here so the
17
+ * two surfaces stay single-sourced — operators trained on one read
18
+ * the same payload + table on the other.
19
+ *
20
+ * Exit codes:
21
+ * 0 — show / switch / reset all succeed
22
+ * 1 — unknown preset slug (returned BEFORE any write)
23
+ * 2 — conflicting flags (e.g. `--reset` with a positional slug)
24
+ *
25
+ * The exit codes are surfaced through `process.exitCode` by the
26
+ * dispatcher in `cli.ts` — this module returns a structured payload
27
+ * + writes via the injected `writeOutput`. Throwing is reserved for
28
+ * truly unexpected errors (fs permissions etc.); the spec hooks the
29
+ * happy + sad paths through `writeOutput` shape, not via try/catch
30
+ * on the throw.
31
+ */
32
+ import { DEFAULT_OUTPUT_STYLE, isOutputStyleSlug, OUTPUT_STYLE_SLUGS, renderStyleTable, } from '../../core/output-style/presets.js';
33
+ import { clearUserOutputStyle, clearWorkspaceOutputStyle, resolveOutputStyle, setUserOutputStyle, setWorkspaceOutputStyle, } from '../../core/output-style/state.js';
34
+ /**
35
+ * Entry point. Parses `args`, applies the operation, emits the
36
+ * payload + text via `ctx.writeOutput`, and returns the exit code the
37
+ * dispatcher should hand back to the shell.
38
+ */
39
+ export async function runStyleCommand(args, ctx) {
40
+ const flags = parseFlags(args);
41
+ // Reset path
42
+ if (flags.reset) {
43
+ if (flags.slug !== null) {
44
+ const payload = buildPayload({
45
+ status: 'invalid_flags',
46
+ ctx,
47
+ message: '/style --reset cannot be combined with a preset name. Use one or the other.',
48
+ });
49
+ ctx.writeOutput(payload, payload.message);
50
+ return 2;
51
+ }
52
+ if (flags.persist) {
53
+ const payload = buildPayload({
54
+ status: 'invalid_flags',
55
+ ctx,
56
+ message: '/style --reset cannot be combined with --persist. Use --reset --user to also clear the user default.',
57
+ });
58
+ ctx.writeOutput(payload, payload.message);
59
+ return 2;
60
+ }
61
+ clearWorkspaceOutputStyle({ workspaceRoot: ctx.workspaceRoot, env: ctx.env });
62
+ if (flags.user) {
63
+ clearUserOutputStyle({ workspaceRoot: ctx.workspaceRoot, env: ctx.env });
64
+ }
65
+ const payload = buildPayload({
66
+ status: 'reset',
67
+ ctx,
68
+ message: flags.user
69
+ ? `Cleared workspace + user output style. Active: ${DEFAULT_OUTPUT_STYLE} (default).`
70
+ : `Cleared workspace output style. Active: ${describeActive(ctx)}.`,
71
+ });
72
+ ctx.writeOutput(payload, payload.message);
73
+ return 0;
74
+ }
75
+ // List path
76
+ if (flags.list && flags.slug === null) {
77
+ const resolved = resolveOutputStyle({ workspaceRoot: ctx.workspaceRoot, env: ctx.env });
78
+ const payload = buildPayload({
79
+ status: 'listed',
80
+ ctx,
81
+ message: renderStyleTable(resolved.slug),
82
+ });
83
+ ctx.writeOutput(payload, payload.message);
84
+ return 0;
85
+ }
86
+ // Switch path
87
+ if (flags.slug !== null) {
88
+ if (!isOutputStyleSlug(flags.slug)) {
89
+ const payload = buildPayload({
90
+ status: 'invalid_slug',
91
+ ctx,
92
+ attemptedSlug: flags.slug,
93
+ message: `Unknown style "${flags.slug}". Try one of: ${OUTPUT_STYLE_SLUGS.join(', ')}.`,
94
+ });
95
+ ctx.writeOutput(payload, payload.message);
96
+ return 1;
97
+ }
98
+ const before = resolveOutputStyle({ workspaceRoot: ctx.workspaceRoot, env: ctx.env });
99
+ setWorkspaceOutputStyle(flags.slug, { workspaceRoot: ctx.workspaceRoot, env: ctx.env });
100
+ if (flags.persist) {
101
+ setUserOutputStyle(flags.slug, { workspaceRoot: ctx.workspaceRoot, env: ctx.env });
102
+ }
103
+ const tail = flags.persist ? ' (workspace + user default)' : ' (workspace)';
104
+ const payload = buildPayload({
105
+ status: 'switched',
106
+ ctx,
107
+ previous: before.slug,
108
+ persistedToUser: flags.persist,
109
+ message: `Output style → ${flags.slug}${tail}. Was: ${before.slug} (${before.source}).`,
110
+ });
111
+ ctx.writeOutput(payload, payload.message);
112
+ return 0;
113
+ }
114
+ // Show path (no args)
115
+ const resolved = resolveOutputStyle({ workspaceRoot: ctx.workspaceRoot, env: ctx.env });
116
+ const banner = `Active output style: ${resolved.slug} (${resolved.source})`;
117
+ const table = renderStyleTable(resolved.slug);
118
+ const payload = buildPayload({
119
+ status: 'show',
120
+ ctx,
121
+ message: `${banner}\n\n${table}`,
122
+ });
123
+ ctx.writeOutput(payload, payload.message);
124
+ return 0;
125
+ }
126
+ function describeActive(ctx) {
127
+ const resolved = resolveOutputStyle({ workspaceRoot: ctx.workspaceRoot, env: ctx.env });
128
+ return `${resolved.slug} (${resolved.source})`;
129
+ }
130
+ function buildPayload(args) {
131
+ const resolved = resolveOutputStyle({ workspaceRoot: args.ctx.workspaceRoot, env: args.ctx.env });
132
+ const payload = {
133
+ command: 'style',
134
+ status: args.status,
135
+ active: resolved.slug,
136
+ source: resolved.source,
137
+ presets: OUTPUT_STYLE_SLUGS,
138
+ message: args.message,
139
+ };
140
+ if (args.previous !== undefined)
141
+ payload.previous = args.previous;
142
+ if (args.persistedToUser !== undefined)
143
+ payload.persistedToUser = args.persistedToUser;
144
+ if (args.attemptedSlug !== undefined)
145
+ payload.attemptedSlug = args.attemptedSlug;
146
+ return payload;
147
+ }
148
+ function parseFlags(args) {
149
+ const flags = {
150
+ slug: null,
151
+ persist: false,
152
+ reset: false,
153
+ user: false,
154
+ list: false,
155
+ };
156
+ for (const arg of args) {
157
+ if (arg === '--persist')
158
+ flags.persist = true;
159
+ else if (arg === '--reset')
160
+ flags.reset = true;
161
+ else if (arg === '--user')
162
+ flags.user = true;
163
+ else if (arg === '--list')
164
+ flags.list = true;
165
+ else if (arg.startsWith('-')) {
166
+ // Unknown flag — keep simple parser. Treat as positional so the
167
+ // downstream isOutputStyleSlug check rejects it with a clear
168
+ // "unknown style" message rather than swallowing silently.
169
+ if (flags.slug === null)
170
+ flags.slug = arg;
171
+ }
172
+ else if (flags.slug === null) {
173
+ flags.slug = arg;
174
+ }
175
+ }
176
+ // Normalise the slug to lowercase so `pugi style TERSE` works the
177
+ // same as `pugi style terse`. The catalogue is lowercase-only by
178
+ // contract; this keeps operators from tripping on shift-key habits.
179
+ if (flags.slug !== null)
180
+ flags.slug = flags.slug.toLowerCase();
181
+ return flags;
182
+ }
183
+ /**
184
+ * Re-export for the slash-command dispatcher in `core/repl/session.ts`
185
+ * so it can render a compiled prompt block when the operator runs
186
+ * `/style --preview` (follow-up surface; current slash returns the
187
+ * runner's standard payload).
188
+ *
189
+ * Kept here so the runtime module is the single import point for
190
+ * style-related surfaces; consumers should NOT reach into
191
+ * `core/output-style/*` directly.
192
+ */
193
+ export { OUTPUT_STYLES as OUTPUT_STYLE_CATALOGUE, } from '../../core/output-style/presets.js';
194
+ //# sourceMappingURL=style.js.map
@@ -0,0 +1,196 @@
1
+ /**
2
+ * Leak L30 (2026-05-27) — `pugi theme` top-level command + REPL slash
3
+ * companion.
4
+ *
5
+ * Operator surface:
6
+ *
7
+ * pugi theme Show active theme + table.
8
+ * pugi theme <name> Switch workspace theme (current cwd).
9
+ * pugi theme <name> --persist Switch + also write user default.
10
+ * pugi theme --reset Clear workspace override → back to default.
11
+ * pugi theme --reset --user Also clear the user default.
12
+ * pugi theme --list Print the catalogue (no flip).
13
+ * pugi theme --json Structured envelope variant.
14
+ *
15
+ * The same runner powers `/theme` from inside the REPL. The REPL
16
+ * dispatcher (see `core/repl/session.ts`) routes through here so the
17
+ * two surfaces stay single-sourced — operators trained on one read
18
+ * the same payload + table on the other. Matches the leak L18
19
+ * `/style` runner exactly so the two settings surfaces are
20
+ * paste-comparable for the operator + grep-comparable for future
21
+ * maintenance.
22
+ *
23
+ * Exit codes:
24
+ * 0 — show / switch / reset all succeed
25
+ * 1 — unknown preset slug (returned BEFORE any write)
26
+ * 2 — conflicting flags (e.g. `--reset` with a positional slug)
27
+ *
28
+ * The exit codes are surfaced through `process.exitCode` by the
29
+ * dispatcher in `cli.ts` — this module returns a structured payload
30
+ * + writes via the injected `writeOutput`. Throwing is reserved for
31
+ * truly unexpected errors (fs permissions etc.); the spec hooks the
32
+ * happy + sad paths through `writeOutput` shape, not via try/catch
33
+ * on the throw.
34
+ */
35
+ import { DEFAULT_THEME, isThemeSlug, renderThemeTable, THEME_SLUGS, } from '../../core/theme/presets.js';
36
+ import { clearUserTheme, clearWorkspaceTheme, resolveTheme, setUserTheme, setWorkspaceTheme, } from '../../core/theme/state.js';
37
+ /**
38
+ * Entry point. Parses `args`, applies the operation, emits the
39
+ * payload + text via `ctx.writeOutput`, and returns the exit code the
40
+ * dispatcher should hand back to the shell.
41
+ */
42
+ export async function runThemeCommand(args, ctx) {
43
+ const flags = parseFlags(args);
44
+ // Reset path
45
+ if (flags.reset) {
46
+ if (flags.slug !== null) {
47
+ const payload = buildPayload({
48
+ status: 'invalid_flags',
49
+ ctx,
50
+ message: '/theme --reset cannot be combined with a preset name. Use one or the other.',
51
+ });
52
+ ctx.writeOutput(payload, payload.message);
53
+ return 2;
54
+ }
55
+ if (flags.persist) {
56
+ const payload = buildPayload({
57
+ status: 'invalid_flags',
58
+ ctx,
59
+ message: '/theme --reset cannot be combined with --persist. Use --reset --user to also clear the user default.',
60
+ });
61
+ ctx.writeOutput(payload, payload.message);
62
+ return 2;
63
+ }
64
+ clearWorkspaceTheme({ workspaceRoot: ctx.workspaceRoot, env: ctx.env });
65
+ if (flags.user) {
66
+ clearUserTheme({ workspaceRoot: ctx.workspaceRoot, env: ctx.env });
67
+ }
68
+ const payload = buildPayload({
69
+ status: 'reset',
70
+ ctx,
71
+ message: flags.user
72
+ ? `Cleared workspace + user theme. Active: ${DEFAULT_THEME} (default).`
73
+ : `Cleared workspace theme. Active: ${describeActive(ctx)}.`,
74
+ });
75
+ ctx.writeOutput(payload, payload.message);
76
+ return 0;
77
+ }
78
+ // List path
79
+ if (flags.list && flags.slug === null) {
80
+ const resolved = resolveTheme({ workspaceRoot: ctx.workspaceRoot, env: ctx.env });
81
+ const payload = buildPayload({
82
+ status: 'listed',
83
+ ctx,
84
+ message: renderThemeTable(resolved.slug),
85
+ });
86
+ ctx.writeOutput(payload, payload.message);
87
+ return 0;
88
+ }
89
+ // Switch path
90
+ if (flags.slug !== null) {
91
+ if (!isThemeSlug(flags.slug)) {
92
+ const payload = buildPayload({
93
+ status: 'invalid_slug',
94
+ ctx,
95
+ attemptedSlug: flags.slug,
96
+ message: `Unknown theme "${flags.slug}". Try one of: ${THEME_SLUGS.join(', ')}.`,
97
+ });
98
+ ctx.writeOutput(payload, payload.message);
99
+ return 1;
100
+ }
101
+ const before = resolveTheme({ workspaceRoot: ctx.workspaceRoot, env: ctx.env });
102
+ setWorkspaceTheme(flags.slug, { workspaceRoot: ctx.workspaceRoot, env: ctx.env });
103
+ if (flags.persist) {
104
+ setUserTheme(flags.slug, { workspaceRoot: ctx.workspaceRoot, env: ctx.env });
105
+ }
106
+ const tail = flags.persist ? ' (workspace + user default)' : ' (workspace)';
107
+ const payload = buildPayload({
108
+ status: 'switched',
109
+ ctx,
110
+ previous: before.slug,
111
+ persistedToUser: flags.persist,
112
+ message: `Theme → ${flags.slug}${tail}. Was: ${before.slug} (${before.source}).`,
113
+ });
114
+ ctx.writeOutput(payload, payload.message);
115
+ return 0;
116
+ }
117
+ // Show path (no args)
118
+ const resolved = resolveTheme({ workspaceRoot: ctx.workspaceRoot, env: ctx.env });
119
+ const banner = `Active theme: ${resolved.slug} (${resolved.source})`;
120
+ const table = renderThemeTable(resolved.slug);
121
+ const payload = buildPayload({
122
+ status: 'show',
123
+ ctx,
124
+ message: `${banner}\n\n${table}`,
125
+ });
126
+ ctx.writeOutput(payload, payload.message);
127
+ return 0;
128
+ }
129
+ function describeActive(ctx) {
130
+ const resolved = resolveTheme({ workspaceRoot: ctx.workspaceRoot, env: ctx.env });
131
+ return `${resolved.slug} (${resolved.source})`;
132
+ }
133
+ function buildPayload(args) {
134
+ const resolved = resolveTheme({ workspaceRoot: args.ctx.workspaceRoot, env: args.ctx.env });
135
+ const payload = {
136
+ command: 'theme',
137
+ status: args.status,
138
+ active: resolved.slug,
139
+ source: resolved.source,
140
+ presets: THEME_SLUGS,
141
+ message: args.message,
142
+ };
143
+ if (args.previous !== undefined)
144
+ payload.previous = args.previous;
145
+ if (args.persistedToUser !== undefined)
146
+ payload.persistedToUser = args.persistedToUser;
147
+ if (args.attemptedSlug !== undefined)
148
+ payload.attemptedSlug = args.attemptedSlug;
149
+ return payload;
150
+ }
151
+ function parseFlags(args) {
152
+ const flags = {
153
+ slug: null,
154
+ persist: false,
155
+ reset: false,
156
+ user: false,
157
+ list: false,
158
+ };
159
+ for (const arg of args) {
160
+ if (arg === '--persist')
161
+ flags.persist = true;
162
+ else if (arg === '--reset')
163
+ flags.reset = true;
164
+ else if (arg === '--user')
165
+ flags.user = true;
166
+ else if (arg === '--list')
167
+ flags.list = true;
168
+ else if (arg.startsWith('-')) {
169
+ // Unknown flag — keep simple parser. Treat as positional so the
170
+ // downstream isThemeSlug check rejects it with a clear "unknown
171
+ // theme" message rather than swallowing silently. Mirrors the
172
+ // L18 style runner's behaviour for grep-parity.
173
+ if (flags.slug === null)
174
+ flags.slug = arg;
175
+ }
176
+ else if (flags.slug === null) {
177
+ flags.slug = arg;
178
+ }
179
+ }
180
+ // Normalise the slug to lowercase so `pugi theme DARK` works the
181
+ // same as `pugi theme dark`. The catalogue is lowercase-only by
182
+ // contract; this keeps operators from tripping on shift-key habits.
183
+ if (flags.slug !== null)
184
+ flags.slug = flags.slug.toLowerCase();
185
+ return flags;
186
+ }
187
+ /**
188
+ * Re-export for the slash-command dispatcher in
189
+ * `core/repl/session.ts` so it can render a preview block if a
190
+ * future surface (`/theme --preview`) lands. Kept here so the
191
+ * runtime module is the single import point for theme-related
192
+ * surfaces; consumers should NOT reach into `core/theme/*` directly
193
+ * unless they are Ink components that need the React context.
194
+ */
195
+ export { THEMES as THEME_CATALOGUE, } from '../../core/theme/presets.js';
196
+ //# sourceMappingURL=theme.js.map
@@ -3,6 +3,7 @@ import { existsSync, readFileSync, renameSync, unlinkSync, writeFileSync } from
3
3
  import { resolve } from 'node:path';
4
4
  import { hashContent } from '../../core/file-cache.js';
5
5
  import { recordFileMutation, recordToolCall, recordToolResult, } from '../../core/session.js';
6
+ import { writeBlob } from './redo-blob-store.js';
6
7
  /**
7
8
  * `pugi undo` — revert the file mutations from the most recent successful
8
9
  * `write` / `edit` / `multi_edit` tool result.
@@ -81,6 +82,37 @@ export async function runUndoCommand(_args, ctx) {
81
82
  const restored = [];
82
83
  for (const step of plan.steps) {
83
84
  try {
85
+ // Wave 6 (2026-05-27): snapshot the AFTER state into the redo
86
+ // blob store BEFORE we revert the file on disk. /redo reads this
87
+ // blob keyed by `step.beforeHash` (= original afterHash) to
88
+ // reapply the change. We only snapshot for operations that have
89
+ // on-disk AFTER content: `create` (file exists, about to be
90
+ // deleted) and `update` (file exists, about to be overwritten).
91
+ // For `delete` reverts (file was deleted by Pugi, the "after" is
92
+ // nothing) redo replays the delete itself — no content needed.
93
+ // Best-effort: a blob-store failure must not abort the undo, so
94
+ // the writeBlob call is wrapped и any error swallowed.
95
+ if (step.operation === 'create' || step.operation === 'update') {
96
+ try {
97
+ const abs = resolve(ctx.workspaceRoot, step.path);
98
+ if (existsSync(abs) && step.beforeHash) {
99
+ const current = readFileSync(abs, 'utf8');
100
+ // Defensive: only snapshot if the current sha matches the
101
+ // pre-revert hash the planner verified. The planner already
102
+ // gated this, but a TOCTOU between plan + execute would
103
+ // produce a wrong blob — silently dropping it is safer than
104
+ // shipping content under the wrong sha.
105
+ if (hashContent(current) === step.beforeHash) {
106
+ writeBlob(ctx.workspaceRoot, current);
107
+ }
108
+ }
109
+ }
110
+ catch {
111
+ // Best-effort. Failure to snapshot means /redo will report
112
+ // "no captured content" — operator can re-run the mutation
113
+ // manually. Better than aborting the undo.
114
+ }
115
+ }
84
116
  executeRevert(ctx.workspaceRoot, step);
85
117
  restored.push({ path: step.path, operation: step.operation });
86
118
  }