@pugi/cli 0.1.0-beta.5 → 0.1.0-beta.50

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 (263) hide show
  1. package/THIRD_PARTY_NOTICES.md +40 -0
  2. package/assets/pugi-mascot.ansi +15 -25
  3. package/assets/pugi-prozr2-mascot.ansi +9 -0
  4. package/bin/run.js +33 -1
  5. package/dist/commands/jobs-watch.js +201 -0
  6. package/dist/commands/jobs.js +15 -0
  7. package/dist/commands/smoke.js +133 -0
  8. package/dist/core/agent-progress/cleanup.js +134 -0
  9. package/dist/core/agent-progress/schema.js +144 -0
  10. package/dist/core/agent-progress/writer.js +101 -0
  11. package/dist/core/artifact-chain/dispatcher.js +148 -0
  12. package/dist/core/artifact-chain/exporter.js +164 -0
  13. package/dist/core/artifact-chain/state.js +243 -0
  14. package/dist/core/artifact-chain/steps.js +169 -0
  15. package/dist/core/auth/ensure-authenticated.js +129 -0
  16. package/dist/core/auth/env-provider.js +238 -0
  17. package/dist/core/auto-update/channels.js +122 -0
  18. package/dist/core/auto-update/checker.js +241 -0
  19. package/dist/core/auto-update/state.js +235 -0
  20. package/dist/core/bare-mode/index.js +107 -0
  21. package/dist/core/bash-classifier.js +400 -4
  22. package/dist/core/checkpoint/resumer.js +149 -0
  23. package/dist/core/checkpoint/rewinder.js +291 -0
  24. package/dist/core/codegraph/decision-store.js +248 -0
  25. package/dist/core/codegraph/detect-repo.js +459 -0
  26. package/dist/core/codegraph/install.js +134 -0
  27. package/dist/core/codegraph/offer-hook.js +220 -0
  28. package/dist/core/compact/auto-trigger.js +96 -0
  29. package/dist/core/compact/buffer-rewriter.js +115 -0
  30. package/dist/core/compact/summarizer.js +208 -0
  31. package/dist/core/compact/token-counter.js +108 -0
  32. package/dist/core/consensus/diff-capture.js +112 -3
  33. package/dist/core/context/index.js +7 -0
  34. package/dist/core/context/markdown-traverse.js +255 -0
  35. package/dist/core/cost/rate-card.js +129 -0
  36. package/dist/core/cost/tracker.js +221 -0
  37. package/dist/core/denial-tracking/index.js +8 -0
  38. package/dist/core/denial-tracking/state.js +264 -0
  39. package/dist/core/diagnostics/probe-runner.js +93 -0
  40. package/dist/core/diagnostics/probes/api.js +46 -0
  41. package/dist/core/diagnostics/probes/auth.js +86 -0
  42. package/dist/core/diagnostics/probes/bare-mode.js +42 -0
  43. package/dist/core/diagnostics/probes/cli-version.js +127 -0
  44. package/dist/core/diagnostics/probes/config.js +72 -0
  45. package/dist/core/diagnostics/probes/denial-tracking.js +57 -0
  46. package/dist/core/diagnostics/probes/disk.js +81 -0
  47. package/dist/core/diagnostics/probes/git.js +65 -0
  48. package/dist/core/diagnostics/probes/hooks.js +118 -0
  49. package/dist/core/diagnostics/probes/mcp.js +75 -0
  50. package/dist/core/diagnostics/probes/node.js +59 -0
  51. package/dist/core/diagnostics/probes/pnpm.js +36 -0
  52. package/dist/core/diagnostics/probes/pugi-md.js +89 -0
  53. package/dist/core/diagnostics/probes/sandbox.js +40 -0
  54. package/dist/core/diagnostics/probes/session.js +74 -0
  55. package/dist/core/diagnostics/probes/status-snapshot.js +488 -0
  56. package/dist/core/diagnostics/probes/workspace.js +63 -0
  57. package/dist/core/diagnostics/types.js +70 -0
  58. package/dist/core/dispatch/cache-cleanup.js +197 -0
  59. package/dist/core/dispatch/cache-handoff.js +295 -0
  60. package/dist/core/edits/dispatch.js +218 -2
  61. package/dist/core/edits/journal.js +199 -0
  62. package/dist/core/edits/layer-d-ast.js +557 -14
  63. package/dist/core/edits/verify-hook.js +273 -0
  64. package/dist/core/edits/worktree.js +322 -0
  65. package/dist/core/engine/anvil-client.js +115 -5
  66. package/dist/core/engine/budgets.js +98 -0
  67. package/dist/core/engine/context-prefix.js +155 -0
  68. package/dist/core/engine/intent.js +260 -0
  69. package/dist/core/engine/native-pugi.js +860 -211
  70. package/dist/core/engine/prompts.js +88 -2
  71. package/dist/core/engine/strip-internal-fields.js +124 -0
  72. package/dist/core/engine/tool-bridge.js +1045 -36
  73. package/dist/core/feedback/queue.js +177 -0
  74. package/dist/core/feedback/submitter.js +145 -0
  75. package/dist/core/file-cache.js +113 -1
  76. package/dist/core/hooks/events.js +44 -0
  77. package/dist/core/hooks/index.js +15 -0
  78. package/dist/core/hooks/registry.js +213 -0
  79. package/dist/core/hooks/runner.js +236 -0
  80. package/dist/core/hooks/v2/event-emitter.js +115 -0
  81. package/dist/core/hooks/v2/executor.js +282 -0
  82. package/dist/core/hooks/v2/index.js +25 -0
  83. package/dist/core/hooks/v2/lifecycle.js +104 -0
  84. package/dist/core/hooks/v2/loader.js +216 -0
  85. package/dist/core/hooks/v2/matcher.js +125 -0
  86. package/dist/core/hooks/v2/trust.js +143 -0
  87. package/dist/core/hooks/v2/types.js +86 -0
  88. package/dist/core/lsp/cache.js +105 -0
  89. package/dist/core/lsp/client.js +776 -0
  90. package/dist/core/lsp/language-detect.js +66 -0
  91. package/dist/core/lsp/post-edit-diagnostics.js +171 -0
  92. package/dist/core/mcp/client.js +75 -6
  93. package/dist/core/mcp/http-server.js +553 -0
  94. package/dist/core/mcp/orchestrator-tools.js +662 -0
  95. package/dist/core/mcp/permission.js +190 -0
  96. package/dist/core/mcp/registry.js +24 -2
  97. package/dist/core/mcp/server-tools.js +219 -0
  98. package/dist/core/mcp/server.js +397 -0
  99. package/dist/core/memory/dual-write.js +416 -0
  100. package/dist/core/memory/phase1-kinds.js +20 -0
  101. package/dist/core/memory-sync/queue.js +158 -0
  102. package/dist/core/onboarding/ensure-initialized.js +133 -0
  103. package/dist/core/onboarding/marker.js +111 -0
  104. package/dist/core/onboarding/telemetry-state.js +108 -0
  105. package/dist/core/output-style/presets.js +176 -0
  106. package/dist/core/output-style/state.js +185 -0
  107. package/dist/core/path-security.js +284 -2
  108. package/dist/core/permissions/auto-classifier.js +124 -0
  109. package/dist/core/permissions/circuit-breaker.js +83 -0
  110. package/dist/core/permissions/gate.js +278 -0
  111. package/dist/core/permissions/index.js +20 -0
  112. package/dist/core/permissions/mode.js +174 -0
  113. package/dist/core/permissions/state.js +241 -0
  114. package/dist/core/permissions/tool-class.js +93 -0
  115. package/dist/core/prd-check/parser.js +215 -0
  116. package/dist/core/prd-check/reporter.js +127 -0
  117. package/dist/core/prd-check/session-review.js +557 -0
  118. package/dist/core/prd-check/verifiers.js +223 -0
  119. package/dist/core/pugi-md/context-injector.js +76 -0
  120. package/dist/core/pugi-md/walk-up.js +207 -0
  121. package/dist/core/release-notes/parser.js +241 -0
  122. package/dist/core/release-notes/state.js +116 -0
  123. package/dist/core/repl/history.js +11 -1
  124. package/dist/core/repl/model-pricing.js +135 -0
  125. package/dist/core/repl/session.js +1897 -37
  126. package/dist/core/repl/slash-commands.js +430 -15
  127. package/dist/core/repl/store/session-store.js +31 -2
  128. package/dist/core/repl/workspace-context.js +22 -0
  129. package/dist/core/repo-map/build.js +125 -0
  130. package/dist/core/repo-map/cache.js +185 -0
  131. package/dist/core/repo-map/extractor.js +254 -0
  132. package/dist/core/repo-map/formatter.js +145 -0
  133. package/dist/core/repo-map/scanner.js +211 -0
  134. package/dist/core/retry-budget/budget.js +284 -0
  135. package/dist/core/retry-budget/index.js +5 -0
  136. package/dist/core/session.js +92 -0
  137. package/dist/core/settings.js +80 -0
  138. package/dist/core/share/formatter.js +271 -0
  139. package/dist/core/share/redactor.js +221 -0
  140. package/dist/core/share/uploader.js +267 -0
  141. package/dist/core/skills/defaults.js +457 -0
  142. package/dist/core/smoke/headless-driver.js +174 -0
  143. package/dist/core/smoke/orchestrator.js +194 -0
  144. package/dist/core/smoke/runner.js +238 -0
  145. package/dist/core/smoke/scenario-parser.js +316 -0
  146. package/dist/core/subagents/dispatcher-real.js +600 -0
  147. package/dist/core/subagents/dispatcher.js +113 -24
  148. package/dist/core/subagents/index.js +18 -5
  149. package/dist/core/subagents/isolation-matrix.js +213 -0
  150. package/dist/core/subagents/spawn.js +19 -4
  151. package/dist/core/telemetry/emitter.js +229 -0
  152. package/dist/core/telemetry/queue.js +251 -0
  153. package/dist/core/theme/context.js +91 -0
  154. package/dist/core/theme/presets.js +228 -0
  155. package/dist/core/theme/state.js +181 -0
  156. package/dist/core/todos/invariant.js +10 -0
  157. package/dist/core/todos/state.js +177 -0
  158. package/dist/core/transport/version-interceptor.js +166 -0
  159. package/dist/core/vim/keymap.js +288 -0
  160. package/dist/core/vim/state.js +92 -0
  161. package/dist/core/worktree-manager/cleanup.js +123 -0
  162. package/dist/core/worktree-manager/manager.js +303 -0
  163. package/dist/index.js +28 -0
  164. package/dist/runtime/bootstrap.js +190 -0
  165. package/dist/runtime/cli.js +3241 -343
  166. package/dist/runtime/commands/cancel.js +231 -0
  167. package/dist/runtime/commands/chain.js +489 -0
  168. package/dist/runtime/commands/codegraph-status.js +227 -0
  169. package/dist/runtime/commands/compact.js +297 -0
  170. package/dist/runtime/commands/cost.js +199 -0
  171. package/dist/runtime/commands/delegate.js +242 -11
  172. package/dist/runtime/commands/dispatch.js +126 -0
  173. package/dist/runtime/commands/doctor.js +412 -0
  174. package/dist/runtime/commands/feedback.js +184 -0
  175. package/dist/runtime/commands/hooks.js +184 -0
  176. package/dist/runtime/commands/lsp.js +368 -0
  177. package/dist/runtime/commands/mcp.js +879 -0
  178. package/dist/runtime/commands/memory.js +508 -0
  179. package/dist/runtime/commands/model.js +237 -0
  180. package/dist/runtime/commands/onboarding.js +275 -0
  181. package/dist/runtime/commands/patch.js +128 -0
  182. package/dist/runtime/commands/permissions.js +112 -0
  183. package/dist/runtime/commands/plan.js +143 -0
  184. package/dist/runtime/commands/prd-check.js +285 -0
  185. package/dist/runtime/commands/redo-blob-store.js +92 -0
  186. package/dist/runtime/commands/redo.js +361 -0
  187. package/dist/runtime/commands/release-notes.js +229 -0
  188. package/dist/runtime/commands/repo-map.js +95 -0
  189. package/dist/runtime/commands/report.js +299 -0
  190. package/dist/runtime/commands/resume.js +118 -0
  191. package/dist/runtime/commands/review-consensus.js +17 -2
  192. package/dist/runtime/commands/rewind.js +333 -0
  193. package/dist/runtime/commands/sessions.js +163 -0
  194. package/dist/runtime/commands/share.js +316 -0
  195. package/dist/runtime/commands/status.js +186 -0
  196. package/dist/runtime/commands/stickers.js +82 -0
  197. package/dist/runtime/commands/style.js +194 -0
  198. package/dist/runtime/commands/theme.js +196 -0
  199. package/dist/runtime/commands/undo.js +32 -0
  200. package/dist/runtime/commands/update.js +289 -0
  201. package/dist/runtime/commands/vim.js +140 -0
  202. package/dist/runtime/commands/worktree.js +177 -0
  203. package/dist/runtime/commands/worktrees.js +155 -0
  204. package/dist/runtime/headless-repl.js +195 -0
  205. package/dist/runtime/headless.js +543 -0
  206. package/dist/runtime/load-hooks-or-exit.js +71 -0
  207. package/dist/runtime/plan-decompose.js +531 -0
  208. package/dist/runtime/version.js +65 -0
  209. package/dist/tools/agent-tool.js +229 -0
  210. package/dist/tools/apply-patch.js +556 -0
  211. package/dist/tools/ask-user-question.js +213 -0
  212. package/dist/tools/ask-user.js +115 -0
  213. package/dist/tools/bash.js +203 -4
  214. package/dist/tools/file-tools.js +85 -14
  215. package/dist/tools/lsp-tools.js +189 -0
  216. package/dist/tools/mcp-tool.js +260 -0
  217. package/dist/tools/multi-edit.js +361 -0
  218. package/dist/tools/powershell.js +268 -0
  219. package/dist/tools/registry.js +51 -0
  220. package/dist/tools/skill-tool.js +96 -0
  221. package/dist/tools/tasks.js +208 -0
  222. package/dist/tools/todo-write.js +184 -0
  223. package/dist/tools/web-fetch.js +147 -2
  224. package/dist/tools/web-search.js +458 -0
  225. package/dist/tui/agent-progress-card.js +111 -0
  226. package/dist/tui/agent-tree.js +10 -0
  227. package/dist/tui/ask-modal.js +2 -2
  228. package/dist/tui/ask-user-question-prompt.js +192 -0
  229. package/dist/tui/compact-banner.js +81 -0
  230. package/dist/tui/conversation-pane.js +82 -8
  231. package/dist/tui/cost-table.js +111 -0
  232. package/dist/tui/doctor-table.js +46 -0
  233. package/dist/tui/feedback-prompt.js +156 -0
  234. package/dist/tui/input-box.js +218 -3
  235. package/dist/tui/markdown-render.js +4 -4
  236. package/dist/tui/onboarding-wizard.js +240 -0
  237. package/dist/tui/permissions-picker.js +86 -0
  238. package/dist/tui/render.js +35 -0
  239. package/dist/tui/repl-render.js +313 -35
  240. package/dist/tui/repl-splash-art.js +1 -1
  241. package/dist/tui/repl-splash-mascot.js +32 -8
  242. package/dist/tui/repl-splash.js +2 -2
  243. package/dist/tui/repl.js +85 -5
  244. package/dist/tui/splash.js +1 -1
  245. package/dist/tui/status-bar.js +94 -16
  246. package/dist/tui/status-table.js +7 -0
  247. package/dist/tui/stickers-art.js +136 -0
  248. package/dist/tui/style-table.js +28 -0
  249. package/dist/tui/theme-table.js +29 -0
  250. package/dist/tui/thinking-spinner.js +123 -0
  251. package/dist/tui/tool-stream-pane.js +52 -3
  252. package/dist/tui/update-banner.js +27 -2
  253. package/dist/tui/vim-input.js +267 -0
  254. package/dist/tui/welcome-banner.js +107 -0
  255. package/dist/tui/welcome-data.js +293 -0
  256. package/docs/examples/codegraph.mcp.json +10 -0
  257. package/package.json +12 -6
  258. package/test/scenarios/codegen-create-file.scenario.txt +13 -0
  259. package/test/scenarios/compact-force.scenario.txt +11 -0
  260. package/test/scenarios/identity.scenario.txt +11 -0
  261. package/test/scenarios/persona-handoff.scenario.txt +11 -0
  262. package/test/scenarios/walkback.scenario.txt +12 -0
  263. package/dist/core/engine/compaction-hook.js +0 -154
@@ -0,0 +1,316 @@
1
+ /**
2
+ * `pugi share` / `/share` command handler — Leak L20 (2026-05-27).
3
+ *
4
+ * Exports the current session transcript as Markdown to either a GitHub
5
+ * Gist (default when `gh` is available + auth'd) or a pugi.io public URL
6
+ * (`--pugi`). Mirrors Claude Code's `/share` ergonomics — operator can
7
+ * pin a session for portfolio / debugging / team sharing without copy-
8
+ * pasting the REPL pane by hand.
9
+ *
10
+ * Subcommands / flags (see L20 spec):
11
+ *
12
+ * pugi share Default upload — picks gist when available,
13
+ * falls back to pugi.io.
14
+ * pugi share --gist Force gist target; refuses if `gh` is not
15
+ * installed / authenticated.
16
+ * pugi share --pugi Force pugi.io target.
17
+ * pugi share --redact Run PII scrubber first; shows finding
18
+ * counts in the privacy gate banner.
19
+ * pugi share --preview Print transcript to stdout WITHOUT
20
+ * upload. Always implies a redact step IF
21
+ * `--redact` is also set so the operator
22
+ * inspects the scrubbed shape, not the raw.
23
+ * pugi share --yes Skip the y/n confirmation prompt
24
+ * (CI / scripted callers). Default is
25
+ * ALWAYS to ask before uploading. Refuses
26
+ * if the heuristic detects an active
27
+ * `Bearer ` credential regardless of `--yes`.
28
+ * pugi share --json Emit the result envelope as JSON.
29
+ *
30
+ * Privacy gates (mandatory):
31
+ *
32
+ * 1. Active credential heuristic — refuses upload entirely when the
33
+ * transcript contains a live `Bearer <token>` span. Operators who
34
+ * want to share a debug session that captured an auth header MUST
35
+ * run `--redact` first; the redactor masks the credential before
36
+ * the upload path sees it.
37
+ * 2. Confirmation prompt — y/n question shown to the operator before
38
+ * every upload. Bypassed with `--yes`. Refuses upload on `n` /
39
+ * empty / Ctrl-C.
40
+ * 3. Redact preview — when `--redact` is set, the gate banner shows
41
+ * the per-category finding counts BEFORE the upload prompt so the
42
+ * operator sees what would leave the machine.
43
+ *
44
+ * Same handler powers both `pugi share` (top-level) and `/share`
45
+ * (in-REPL slash). The slash side wires `writeOutput` to the REPL's
46
+ * `appendSystemLine` and never hits stdout directly.
47
+ */
48
+ import { existsSync, readFileSync } from 'node:fs';
49
+ import { resolve } from 'node:path';
50
+ import { createInterface } from 'node:readline/promises';
51
+ import { formatTranscript } from '../../core/share/formatter.js';
52
+ import { containsActiveCredential, redactPii, summariseFindings, } from '../../core/share/redactor.js';
53
+ import { uploadShare, } from '../../core/share/uploader.js';
54
+ export function parseShareFlags(args) {
55
+ const flags = {
56
+ target: 'auto',
57
+ redact: false,
58
+ preview: false,
59
+ yes: false,
60
+ json: false,
61
+ };
62
+ for (const arg of args) {
63
+ if (arg === '--gist')
64
+ flags.target = 'gist';
65
+ else if (arg === '--pugi')
66
+ flags.target = 'pugi';
67
+ else if (arg === '--redact')
68
+ flags.redact = true;
69
+ else if (arg === '--preview')
70
+ flags.preview = true;
71
+ else if (arg === '--yes' || arg === '-y')
72
+ flags.yes = true;
73
+ else if (arg === '--json')
74
+ flags.json = true;
75
+ }
76
+ return flags;
77
+ }
78
+ export async function runShareCommand(args, ctx) {
79
+ const flags = parseShareFlags(args);
80
+ const eventsPath = resolve(ctx.workspaceRoot, '.pugi/events.jsonl');
81
+ if (!existsSync(eventsPath)) {
82
+ ctx.writeOutput({
83
+ command: 'share',
84
+ status: 'no_session',
85
+ message: '.pugi/events.jsonl not found',
86
+ }, 'pugi share: no session log found. Run any pugi command first to create .pugi/events.jsonl.');
87
+ process.exitCode = 2;
88
+ return;
89
+ }
90
+ // Read + format. The formatter walks the events stream once; size cap
91
+ // is implicit (we do not chunk multi-GB files because session logs are
92
+ // capped at a few MB by the cost-tracker / compaction subsystems).
93
+ const eventsJsonl = readFileSync(eventsPath, 'utf8');
94
+ const sessionId = ctx.sessionId ?? deriveSessionIdFromEvents(eventsJsonl) ?? 'no-session';
95
+ const formatted = formatTranscript({
96
+ sessionId,
97
+ workspaceRoot: ctx.workspaceRoot,
98
+ cliVersion: ctx.cliVersion,
99
+ eventsJsonl,
100
+ now: ctx.now,
101
+ });
102
+ // Active-credential gate. Refuses even with `--yes`; redact-first IS
103
+ // the only path to share a transcript with a Bearer token. We check
104
+ // BEFORE redact so the operator's intent is clear in the gate banner.
105
+ const hasLiveCredential = containsActiveCredential(formatted.markdown);
106
+ if (hasLiveCredential && !flags.redact) {
107
+ ctx.writeOutput({
108
+ command: 'share',
109
+ status: 'refused_active_credential',
110
+ message: 'Transcript contains an active Bearer credential. Re-run with --redact to scrub it before sharing.',
111
+ }, 'pugi share: refused — transcript contains an active `Bearer ` credential. ' +
112
+ 'Re-run with --redact to scrub it before sharing, or open .pugi/events.jsonl to inspect manually.');
113
+ process.exitCode = 4;
114
+ return;
115
+ }
116
+ // Optional redact pass. We run BEFORE the preview/upload decisions so
117
+ // the operator sees what would actually leave their machine.
118
+ let body = formatted.markdown;
119
+ let redactionSummary = null;
120
+ let redactionFindings = [];
121
+ if (flags.redact) {
122
+ const result = redactPii(body);
123
+ body = result.output;
124
+ redactionSummary = summariseFindings(result);
125
+ redactionFindings = result.findings;
126
+ }
127
+ // Preview path. Always non-destructive: prints the (possibly redacted)
128
+ // transcript + the redact summary, no upload, no prompt. Operators
129
+ // chain `--preview --redact` to see what would be shared before they
130
+ // commit, which is the L20 spec's "inspect first" affordance.
131
+ if (flags.preview) {
132
+ const payload = {
133
+ command: 'share',
134
+ status: 'preview',
135
+ sessionId,
136
+ turnCount: formatted.turnCount,
137
+ eventCount: formatted.eventCount,
138
+ redact: flags.redact,
139
+ redactionSummary,
140
+ redactionFindings,
141
+ markdown: body,
142
+ };
143
+ const text = [
144
+ redactionSummary ? `${redactionSummary}` : null,
145
+ '--- transcript preview (not uploaded) ---',
146
+ body,
147
+ ]
148
+ .filter((line) => line !== null)
149
+ .join('\n');
150
+ ctx.writeOutput(payload, text);
151
+ return;
152
+ }
153
+ // Resolve the target. `auto` picks gist if `gh` is available, else
154
+ // falls back to pugi.io. The probe is best-effort — a missing `gh`
155
+ // surfaces as `ghAvailable -> false` and we route to pugi without an
156
+ // error.
157
+ const resolvedTarget = await pickTarget(flags.target, ctx);
158
+ // Confirmation gate. Refused by default unless `--yes`.
159
+ if (!flags.yes) {
160
+ const promptText = redactionSummary
161
+ ? `${redactionSummary} Share session transcript to ${resolvedTarget}? [y/N] `
162
+ : `Share session transcript to ${resolvedTarget}? [y/N] `;
163
+ const answer = (await ((ctx.promptYesNo ?? defaultPromptYesNo)(promptText))).trim().toLowerCase();
164
+ if (answer !== 'y' && answer !== 'yes') {
165
+ ctx.writeOutput({
166
+ command: 'share',
167
+ status: 'cancelled',
168
+ target: resolvedTarget,
169
+ message: 'Operator declined the upload.',
170
+ }, 'pugi share: cancelled.');
171
+ return;
172
+ }
173
+ }
174
+ // Resolve pugi.io credentials lazily — gist target does not need them.
175
+ let credential = null;
176
+ if (resolvedTarget === 'pugi') {
177
+ credential = ctx.resolveCredential ? await safeResolveCredential(ctx.resolveCredential) : null;
178
+ }
179
+ const uploadReq = {
180
+ target: resolvedTarget,
181
+ sessionId,
182
+ markdown: body,
183
+ description: `Pugi session ${sessionId}`,
184
+ ...(credential?.apiUrl !== undefined ? { apiUrl: credential.apiUrl } : {}),
185
+ ...(credential?.apiToken !== undefined ? { apiToken: credential.apiToken } : {}),
186
+ ...(ctx.execaLike !== undefined ? { execaLike: ctx.execaLike } : {}),
187
+ ...(ctx.fetchLike !== undefined ? { fetchLike: ctx.fetchLike } : {}),
188
+ };
189
+ const result = await uploadShare(uploadReq);
190
+ emitUploadResult(ctx, sessionId, resolvedTarget, redactionSummary, redactionFindings, result);
191
+ }
192
+ /**
193
+ * Default-target selection. `--gist` / `--pugi` are honoured verbatim;
194
+ * `auto` consults the `gh` probe and picks gist when available. We
195
+ * deliberately do not cache the probe — it costs one syscall and the
196
+ * operator might `gh auth login` between two `pugi share` runs.
197
+ */
198
+ async function pickTarget(requested, ctx) {
199
+ if (requested === 'gist')
200
+ return 'gist';
201
+ if (requested === 'pugi')
202
+ return 'pugi';
203
+ // auto
204
+ const probe = ctx.ghAvailable ?? defaultGhAvailable(ctx.execaLike);
205
+ return (await probe()) ? 'gist' : 'pugi';
206
+ }
207
+ function defaultGhAvailable(execaLike) {
208
+ return async () => {
209
+ if (!execaLike) {
210
+ // Production: try to spawn `gh --version`. The default execa shim
211
+ // in uploader.ts is the right one but importing it here would
212
+ // create a small cycle; instead we replicate the minimal probe.
213
+ try {
214
+ const { defaultExecaLike } = await import('../../core/share/uploader.js');
215
+ const result = await defaultExecaLike('gh', ['--version']);
216
+ return result.exitCode === 0;
217
+ }
218
+ catch {
219
+ return false;
220
+ }
221
+ }
222
+ try {
223
+ const result = await execaLike('gh', ['--version']);
224
+ return result.exitCode === 0;
225
+ }
226
+ catch {
227
+ return false;
228
+ }
229
+ };
230
+ }
231
+ async function defaultPromptYesNo(question) {
232
+ if (!process.stdin.isTTY) {
233
+ // Non-interactive caller without --yes: refuse rather than block on
234
+ // an empty stdin. Mirrors the install-trust pattern in skills.ts.
235
+ process.stdout.write(`${question}\n(non-interactive stdin; declining)\n`);
236
+ return 'n';
237
+ }
238
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
239
+ try {
240
+ return await rl.question(question);
241
+ }
242
+ finally {
243
+ rl.close();
244
+ }
245
+ }
246
+ async function safeResolveCredential(resolver) {
247
+ try {
248
+ return await resolver();
249
+ }
250
+ catch {
251
+ return null;
252
+ }
253
+ }
254
+ /**
255
+ * Walk the JSONL log from the tail and pick the newest `session.created`
256
+ * id. Mirrors the helper in cost.ts so the two surfaces agree on what
257
+ * "current session" means.
258
+ */
259
+ function deriveSessionIdFromEvents(raw) {
260
+ const lines = raw.split('\n').filter((line) => line.trim().length > 0);
261
+ for (let i = lines.length - 1; i >= 0; i -= 1) {
262
+ try {
263
+ const parsed = JSON.parse(lines[i]);
264
+ if (parsed.type === 'session' && parsed.name === 'created' && typeof parsed.sessionId === 'string') {
265
+ return parsed.sessionId;
266
+ }
267
+ }
268
+ catch {
269
+ // skip
270
+ }
271
+ }
272
+ return null;
273
+ }
274
+ function emitUploadResult(ctx, sessionId, target, redactionSummary, redactionFindings, result) {
275
+ if (result.ok) {
276
+ const payload = {
277
+ command: 'share',
278
+ status: 'ok',
279
+ sessionId,
280
+ target,
281
+ url: result.url,
282
+ remoteId: result.remoteId ?? null,
283
+ redact: redactionSummary !== null,
284
+ redactionSummary,
285
+ redactionFindings,
286
+ };
287
+ const text = [
288
+ redactionSummary,
289
+ `pugi share: uploaded to ${target}.`,
290
+ `URL: ${result.url}`,
291
+ ]
292
+ .filter((line) => line !== null)
293
+ .join('\n');
294
+ ctx.writeOutput(payload, text);
295
+ return;
296
+ }
297
+ const payload = {
298
+ command: 'share',
299
+ status: 'upload_failed',
300
+ sessionId,
301
+ target,
302
+ reason: result.reason,
303
+ message: result.message,
304
+ redact: redactionSummary !== null,
305
+ redactionSummary,
306
+ };
307
+ const text = [
308
+ redactionSummary,
309
+ `pugi share: ${result.reason} — ${result.message}`,
310
+ ]
311
+ .filter((line) => line !== null)
312
+ .join('\n');
313
+ ctx.writeOutput(payload, text);
314
+ process.exitCode = 3;
315
+ }
316
+ //# sourceMappingURL=share.js.map
@@ -0,0 +1,186 @@
1
+ /**
2
+ * `pugi status` — concise session-state probe (Leak L34, 2026-05-27).
3
+ *
4
+ * Different from `pugi doctor` (environment health). The status
5
+ * command answers "what is this Pugi session doing right now?" —
6
+ * session id + age, cwd, permission mode, CLI version, token
7
+ * usage, dispatch counts, last command, compact boundaries, auth
8
+ * identity.
9
+ *
10
+ * # Module contract
11
+ *
12
+ * - This file owns the WIRING from CLI flags + ambient process
13
+ * state к the snapshot collector. The data collector itself
14
+ * lives in `core/diagnostics/probes/status-snapshot.ts` and
15
+ * has zero coupling к the CLI dispatch surface.
16
+ *
17
+ * - `runStatusCommand` is the single entry point. Both the
18
+ * top-level `pugi status` handler in `runtime/cli.ts` AND the
19
+ * in-REPL `/status` slash command call it. The function
20
+ * returns the `StatusSnapshot` so the REPL slash handler can
21
+ * mount the Ink renderer without re-collecting fields.
22
+ *
23
+ * - The credential resolver is captured behind a function so the
24
+ * spec can stub it without monkey-patching `core/credentials.ts`.
25
+ *
26
+ * - The permission state module is dynamic-imported with a
27
+ * try/catch so this command lands cleanly before the L6
28
+ * permission-mode work — when the module is absent the field
29
+ * degrades к "unknown" instead of crashing the snapshot.
30
+ *
31
+ * - Exit code is 0 unless the snapshot itself throws. The
32
+ * command is intentionally informational — even a fully
33
+ * "unavailable" snapshot (everything degraded к sentinels)
34
+ * exits 0 so monitoring scripts can rely on a stable contract.
35
+ */
36
+ import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
37
+ import { homedir } from 'node:os';
38
+ import { resolveActiveCredential } from '../../core/credentials.js';
39
+ import { PUGI_CLI_VERSION } from '../version.js';
40
+ import { collectStatusSnapshot, } from '../../core/diagnostics/probes/status-snapshot.js';
41
+ /**
42
+ * Default production filesystem stubs. Wraps node:fs so the
43
+ * snapshot collector can stay sync + structurally testable.
44
+ */
45
+ const DEFAULT_FS = {
46
+ existsSync,
47
+ statSync,
48
+ readdirSync: (p) => readdirSync(p),
49
+ readFileSync: (p, encoding) => readFileSync(p, encoding),
50
+ };
51
+ /**
52
+ * Default credential resolver. Wraps `resolveActiveCredential` so
53
+ * the snapshot only sees the trimmed `ResolvedCredentialSummary`
54
+ * shape and не the full store record.
55
+ */
56
+ function defaultResolveCredential(env, home) {
57
+ const cred = resolveActiveCredential(env, home);
58
+ if (!cred)
59
+ return null;
60
+ // Tier is не yet recorded в the credentials file — set к null
61
+ // until a future sprint surfaces `tier` on the stored record.
62
+ // The renderer hides the parenthetical when tier is null.
63
+ return {
64
+ apiUrl: cred.apiUrl,
65
+ label: cred.label ?? null,
66
+ tier: null,
67
+ identity: cred.label ?? null,
68
+ };
69
+ }
70
+ /**
71
+ * Default permission-mode loader. Dynamic-imports
72
+ * `core/permissions/state.ts` (L6 in the leak-parity roadmap);
73
+ * returns null when the module is absent OR malformed, which the
74
+ * snapshot field degrades к the "unknown" sentinel.
75
+ */
76
+ async function defaultResolvePermissionMode() {
77
+ try {
78
+ const mod = (await import('../../core/permissions/state.js').catch(() => null));
79
+ if (!mod || typeof mod.getCurrentPermissionMode !== 'function') {
80
+ return null;
81
+ }
82
+ const mode = mod.getCurrentPermissionMode();
83
+ return typeof mode === 'string' && mode.length > 0 ? mode : null;
84
+ }
85
+ catch {
86
+ return null;
87
+ }
88
+ }
89
+ /**
90
+ * Collect the snapshot + emit the output via the supplied
91
+ * writeOutput sink. Returns the snapshot so REPL callers can
92
+ * route it к the Ink renderer instead of the plain-text fallback.
93
+ */
94
+ export async function runStatusCommand(ctx) {
95
+ // Resolve the permission mode upfront — the snapshot is sync but
96
+ // the L6 loader is async (dynamic import). We materialise the
97
+ // value here and hand the snapshot a sync getter.
98
+ const permissionMode = ctx.resolvePermissionMode === undefined
99
+ ? await defaultResolvePermissionMode()
100
+ : null;
101
+ const deps = {
102
+ cwd: ctx.cwd,
103
+ home: ctx.home,
104
+ cliVersion: PUGI_CLI_VERSION,
105
+ now: ctx.now ?? Date.now,
106
+ liveSessionId: ctx.liveSessionId ?? null,
107
+ sessionStartedAtEpochMs: ctx.sessionStartedAtEpochMs ?? null,
108
+ liveTokensUsed: ctx.liveTokensUsed ?? null,
109
+ lastCommand: ctx.lastCommand ?? null,
110
+ lastCommandAtEpochMs: ctx.lastCommandAtEpochMs ?? null,
111
+ liveApiUrl: ctx.liveApiUrl ?? null,
112
+ workspaceLabel: ctx.workspaceLabel ?? null,
113
+ fs: ctx.fs ?? DEFAULT_FS,
114
+ resolveCredential: ctx.resolveCredential ?? (() => defaultResolveCredential(ctx.env, ctx.home)),
115
+ resolvePermissionMode: ctx.resolvePermissionMode ?? (() => permissionMode),
116
+ };
117
+ let snapshot;
118
+ try {
119
+ snapshot = collectStatusSnapshot(deps);
120
+ }
121
+ catch (error) {
122
+ // Defensive: the snapshot collector is fail-soft per field, but
123
+ // a structural crash (e.g. a hostile env that throws on
124
+ // `process.version` access) must не bring down `pugi status`.
125
+ // We synthesise a minimal envelope and surface the error в the
126
+ // text output so the operator can file a bug.
127
+ const message = error instanceof Error ? error.message : String(error);
128
+ snapshot = {
129
+ command: 'status',
130
+ fields: [
131
+ {
132
+ key: 'session',
133
+ label: 'Session',
134
+ value: 'unknown',
135
+ available: false,
136
+ note: `snapshot collector crashed: ${message}`,
137
+ },
138
+ ],
139
+ meta: {
140
+ cliVersion: PUGI_CLI_VERSION,
141
+ nodeVersion: process.version,
142
+ cwd: ctx.cwd,
143
+ capturedAt: new Date((ctx.now ?? Date.now)()).toISOString(),
144
+ },
145
+ };
146
+ }
147
+ const text = renderStatusTable(snapshot);
148
+ ctx.writeOutput(snapshot, text);
149
+ // Always exit 0 — `pugi status` is informational, never a gate.
150
+ process.exitCode = 0;
151
+ return snapshot;
152
+ }
153
+ /**
154
+ * Plain-text table renderer. Lays out the snapshot as a two-column
155
+ * (LABEL / VALUE) table with the brand-voice header `Pugi status`.
156
+ * Matches the column-light pattern used by `renderDoctorTable` so
157
+ * narrow terminals stay legible without a layout library.
158
+ */
159
+ export function renderStatusTable(snapshot) {
160
+ // Row syntax: `${Label}: ${value}` with exactly ONE space after the
161
+ // colon, then the value verbatim. The colon doubles as a visual
162
+ // anchor and the REPL spec assertions match on the single-space
163
+ // form (`Backend: https://api.pugi.io`) — multi-space column
164
+ // padding broke `.includes('Backend: https://...')` substring
165
+ // checks. Operators who want a column layout can run the Ink
166
+ // renderer (`<StatusTable>`); the plain-text fallback stays
167
+ // narrow-terminal friendly without padding columns.
168
+ const lines = [];
169
+ lines.push('Pugi status');
170
+ lines.push('═'.repeat(50));
171
+ for (const field of snapshot.fields) {
172
+ lines.push(`${field.label}: ${field.value}`);
173
+ }
174
+ lines.push('');
175
+ lines.push(`CLI ${snapshot.meta.cliVersion} Node ${snapshot.meta.nodeVersion} cwd ${snapshot.meta.cwd}`);
176
+ return lines.join('\n');
177
+ }
178
+ /**
179
+ * Default home dir resolver. Centralised so the CLI handler can
180
+ * call `runStatusCommand` without re-importing `os.homedir`
181
+ * everywhere.
182
+ */
183
+ export function defaultStatusHome() {
184
+ return homedir();
185
+ }
186
+ //# sourceMappingURL=status.js.map
@@ -0,0 +1,82 @@
1
+ /**
2
+ * `pugi stickers` — brand-personality gimmick command (Leak L33, 2026-05-27).
3
+ *
4
+ * Parity command with Claude Code's `/stickers` easter egg. Claude
5
+ * ships physical stickers OR shows ASCII brand art; Pugi's variant
6
+ * focuses на the ASCII branch — picks one of the curated pug-face
7
+ * variants at random and footers it with a rotating brand quote.
8
+ *
9
+ * # Module contract
10
+ *
11
+ * - This file owns the WIRING from CLI flags + ambient state to the
12
+ * art + quote pickers. The corpus + pure renderers live in
13
+ * `tui/stickers-art.tsx` and have zero coupling к the CLI dispatch
14
+ * surface.
15
+ *
16
+ * - `runStickersCommand` is the single entry point. Both the top-
17
+ * level `pugi stickers` handler в `runtime/cli.ts` AND the in-REPL
18
+ * `/stickers` slash command call it. The function returns the
19
+ * resolved `StickersResult` so the slash dispatcher can route the
20
+ * output к the REPL's system pane without re-picking the art.
21
+ *
22
+ * - Exit code is ALWAYS 0. The command is a brand surface — never a
23
+ * gate. Even when the corpus is somehow exhausted (impossible
24
+ * today, the lists are frozen), the handler synthesises a fallback
25
+ * line and exits 0 instead of crashing.
26
+ *
27
+ * - The random source is injected so the spec can pin the chosen
28
+ * art + quote without monkey-patching `Math.random`.
29
+ */
30
+ import { PUG_QUOTES, PUG_STICKERS, pickArtVariant, pickQuote, renderPugStickersText, } from '../../tui/stickers-art.js';
31
+ /**
32
+ * Pick + render the sticker, hand it к the output sink, exit 0.
33
+ * Returns the picked result so the REPL slash handler can mount the
34
+ * Ink renderer without re-picking.
35
+ */
36
+ export function runStickersCommand(ctx) {
37
+ const rng = ctx.rng ?? Math.random;
38
+ let art;
39
+ let quote;
40
+ try {
41
+ art = pickArtVariant(rng);
42
+ quote = pickQuote(rng);
43
+ }
44
+ catch {
45
+ // Defensive: the pickers are pure + the corpus is frozen, so this
46
+ // path is unreachable in production. If a future refactor swaps
47
+ // the corpus к an empty array we still want к exit 0 with a
48
+ // deterministic fallback so monitoring scripts that probe
49
+ // `pugi stickers` do not flap.
50
+ art = {
51
+ id: 'fallback',
52
+ caption: 'fallback',
53
+ art: '/\\___/\\\n( o o )\n( =^= )',
54
+ };
55
+ quote = 'Pugi: your engineering co-pilot.';
56
+ }
57
+ const text = renderPugStickersText(art, quote);
58
+ const result = {
59
+ command: 'stickers',
60
+ art,
61
+ quote,
62
+ text,
63
+ meta: {
64
+ artVariants: PUG_STICKERS.length,
65
+ quotes: PUG_QUOTES.length,
66
+ },
67
+ };
68
+ ctx.writeOutput(result, text);
69
+ // Brand surface — never a gate. The caller may have set a different
70
+ // exit code (e.g. dispatch loop reused this handler for an inline
71
+ // brand banner); we explicitly stamp 0 so `pugi stickers && echo ok`
72
+ // stays predictable.
73
+ process.exitCode = 0;
74
+ // Silence the unused-import warning in the rare case `ctx.asciiOnly`
75
+ // is added in a future surface without a switch. Today the flag is
76
+ // honoured by the CLI handler choosing the writeOutput sink — the
77
+ // result.text is the same regardless because the boxed renderer
78
+ // never lands в the plain stdout sink.
79
+ void ctx.asciiOnly;
80
+ return result;
81
+ }
82
+ //# sourceMappingURL=stickers.js.map