@pugi/cli 0.1.0-beta.17 → 0.1.0-beta.19

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 (51) hide show
  1. package/dist/core/compact/auto-trigger.js +96 -0
  2. package/dist/core/compact/buffer-rewriter.js +115 -0
  3. package/dist/core/compact/summarizer.js +196 -0
  4. package/dist/core/compact/token-counter.js +108 -0
  5. package/dist/core/denial-tracking/index.js +8 -0
  6. package/dist/core/denial-tracking/state.js +264 -0
  7. package/dist/core/diagnostics/probe-runner.js +93 -0
  8. package/dist/core/diagnostics/probes/api.js +46 -0
  9. package/dist/core/diagnostics/probes/auth.js +86 -0
  10. package/dist/core/diagnostics/probes/cli-version.js +127 -0
  11. package/dist/core/diagnostics/probes/config.js +72 -0
  12. package/dist/core/diagnostics/probes/denial-tracking.js +57 -0
  13. package/dist/core/diagnostics/probes/disk.js +81 -0
  14. package/dist/core/diagnostics/probes/git.js +65 -0
  15. package/dist/core/diagnostics/probes/mcp.js +75 -0
  16. package/dist/core/diagnostics/probes/node.js +59 -0
  17. package/dist/core/diagnostics/probes/pnpm.js +36 -0
  18. package/dist/core/diagnostics/probes/session.js +74 -0
  19. package/dist/core/diagnostics/probes/status-snapshot.js +442 -0
  20. package/dist/core/diagnostics/probes/workspace.js +63 -0
  21. package/dist/core/diagnostics/types.js +70 -0
  22. package/dist/core/engine/native-pugi.js +20 -0
  23. package/dist/core/engine/strip-internal-fields.js +124 -0
  24. package/dist/core/engine/tool-bridge.js +251 -49
  25. package/dist/core/file-cache.js +113 -1
  26. package/dist/core/mcp/client.js +66 -6
  27. package/dist/core/mcp/registry.js +24 -2
  28. package/dist/core/permissions/gate.js +187 -0
  29. package/dist/core/permissions/index.js +18 -0
  30. package/dist/core/permissions/mode.js +102 -0
  31. package/dist/core/permissions/state.js +160 -0
  32. package/dist/core/permissions/tool-class.js +93 -0
  33. package/dist/core/repl/session.js +261 -9
  34. package/dist/core/repl/slash-commands.js +67 -4
  35. package/dist/runtime/cli.js +153 -58
  36. package/dist/runtime/commands/compact.js +296 -0
  37. package/dist/runtime/commands/doctor.js +369 -0
  38. package/dist/runtime/commands/mcp.js +290 -3
  39. package/dist/runtime/commands/permissions.js +87 -0
  40. package/dist/runtime/commands/status.js +178 -0
  41. package/dist/runtime/version.js +1 -1
  42. package/dist/tools/agent-tool.js +18 -4
  43. package/dist/tools/ask-user-question.js +213 -0
  44. package/dist/tools/file-tools.js +57 -14
  45. package/dist/tools/registry.js +7 -0
  46. package/dist/tui/ask-user-question-prompt.js +192 -0
  47. package/dist/tui/compact-banner.js +54 -0
  48. package/dist/tui/conversation-pane.js +68 -7
  49. package/dist/tui/doctor-table.js +31 -0
  50. package/dist/tui/status-table.js +7 -0
  51. package/package.json +2 -2
@@ -38,7 +38,10 @@ import { listRoles } from '../agents/registry.js';
38
38
  * silently appear here with empty placeholders.
39
39
  */
40
40
  export const SLASH_STUB_MESSAGES = Object.freeze({
41
- compact: 'Manual context compaction lands in α6.5b.',
41
+ // Leak L8 (2026-05-27): /compact graduated from stub. The session
42
+ // module now owns the summariser round-trip + boundary marker append
43
+ // via `dispatchCompact`. Keep the type record exhaustive so a future
44
+ // stub addition cannot silently overlap the wired set.
42
45
  memory: 'Session memory editor lands in α6.5b.',
43
46
  config: 'Run `pugi config list` from a fresh shell for the full surface; in-REPL editor lands in α6.5.',
44
47
  // alpha 6.13: /privacy graduated from stub; nothing reads this at
@@ -62,7 +65,7 @@ export const SLASH_COMMAND_HELP = Object.freeze([
62
65
  { name: 'clear', args: '', gloss: 'Clear conversation pane', group: 'Session' },
63
66
  { name: 'resume', args: '', gloss: 'Pick a stored session to restore', group: 'Session' },
64
67
  { name: 'context', args: '', gloss: 'Show three-tier context summary (Tier 0 skeleton + Tier 1 working set)', group: 'Session' },
65
- { name: 'compact', args: '', gloss: 'Manual context compaction (α6.5b)', group: 'Session', stub: true },
68
+ { name: 'compact', args: '', gloss: 'Summarise older turns into a boundary marker (leak L8)', group: 'Session' },
66
69
  { name: 'memory', args: '', gloss: 'Session memory editor (α6.5b)', group: 'Session', stub: true },
67
70
  { name: 'init', args: '', gloss: 'Scaffold .pugi/ in the current workspace (β1 Sl11)', group: 'Session' },
68
71
  // Pugi tools
@@ -70,17 +73,19 @@ export const SLASH_COMMAND_HELP = Object.freeze([
70
73
  { name: 'diff', args: '', gloss: 'Show pending diff', group: 'Pugi tools' },
71
74
  { name: 'cost', args: '', gloss: 'Session token + USD totals + last 5 turn breakdown', group: 'Pugi tools' },
72
75
  { name: 'quota', args: '', gloss: 'Plan tier + monthly usage caps (sync / review / engine)', group: 'Pugi tools' },
73
- { name: 'status', args: '', gloss: 'Backend + tenant status', group: 'Pugi tools' },
76
+ { name: 'status', args: '', gloss: 'Session snapshot id · cwd · mode · tokens · dispatches · auth', group: 'Pugi tools' },
74
77
  { name: 'consensus', args: '[ref]', gloss: '3-model consensus review (codex · claude · deepseek)', group: 'Pugi tools' },
75
78
  // Settings
76
79
  { name: 'config', args: '', gloss: 'Show config', group: 'Settings', stub: true },
77
80
  { name: 'privacy', args: '', gloss: 'Show privacy mode + contract', group: 'Settings' },
81
+ { name: 'permissions', args: '[mode] [--persist]', gloss: 'Show or flip permission mode (plan / ask / allow / bypass)', group: 'Settings' },
78
82
  { name: 'budget', args: '', gloss: 'Show usage budget', group: 'Settings', stub: true },
79
83
  { name: 'mcp', args: '[sub]', gloss: 'MCP servers — list / trust / deny / install / serve / perms', group: 'Settings' },
80
84
  { name: 'undo', args: '', gloss: 'Undo last write', group: 'Settings', stub: true },
81
85
  // Meta
82
86
  { name: 'help', args: '', gloss: 'Show this help overlay', group: 'Meta' },
83
87
  { name: 'version', args: '', gloss: 'Show CLI version', group: 'Meta' },
88
+ { name: 'doctor', args: '', gloss: 'Environment health report (auth · API · Node · disk · MCP · …)', group: 'Meta' },
84
89
  { name: 'quit', args: '', gloss: 'Exit the REPL', group: 'Meta' },
85
90
  ]);
86
91
  /**
@@ -256,6 +261,49 @@ export function parseSlashCommand(input) {
256
261
  // device flow + audit identity are wired correctly).
257
262
  return { kind: 'privacy' };
258
263
  }
264
+ case 'permissions':
265
+ case 'perms': {
266
+ // Leak L6: `/permissions [mode] [--persist] [--confirm]`.
267
+ //
268
+ // Argument grammar (single line, no quoting):
269
+ // /permissions -> show current mode + table
270
+ // /permissions plan|ask|allow -> flip mode
271
+ // /permissions bypass --confirm -> flip to bypass (refused
272
+ // without --confirm — safety)
273
+ // /permissions <mode> --persist -> also write to ~/.pugi/config.json
274
+ //
275
+ // Anything else returns an `error` result so the runtime can
276
+ // render the usage hint inline.
277
+ const tokens = tail.length === 0 ? [] : tail.split(/\s+/).filter((s) => s.length > 0);
278
+ if (tokens.length === 0) {
279
+ return { kind: 'permissions', persist: false, confirmBypass: false };
280
+ }
281
+ const head0 = tokens[0]?.toLowerCase();
282
+ if (head0 !== 'plan' && head0 !== 'ask' && head0 !== 'allow' && head0 !== 'bypass') {
283
+ return {
284
+ kind: 'error',
285
+ message: `Usage: /permissions [plan|ask|allow|bypass] [--persist] [--confirm]; unknown mode '${tokens[0] ?? ''}'`,
286
+ };
287
+ }
288
+ const flags = tokens.slice(1);
289
+ let persist = false;
290
+ let confirmBypass = false;
291
+ for (const flag of flags) {
292
+ if (flag === '--persist') {
293
+ persist = true;
294
+ }
295
+ else if (flag === '--confirm') {
296
+ confirmBypass = true;
297
+ }
298
+ else {
299
+ return {
300
+ kind: 'error',
301
+ message: `/permissions: unknown flag '${flag}' (allowed: --persist, --confirm)`,
302
+ };
303
+ }
304
+ }
305
+ return { kind: 'permissions', mode: head0, persist, confirmBypass };
306
+ }
259
307
  case 'init': {
260
308
  // β1 Sl11: surface the init flow inside the REPL. Tail args
261
309
  // are ignored — the init handler is parameterless today; `pugi
@@ -271,7 +319,22 @@ export function parseSlashCommand(input) {
271
319
  const tokens = tail.length === 0 ? [] : tail.split(/\s+/).filter((s) => s.length > 0);
272
320
  return { kind: 'mcp', args: tokens };
273
321
  }
274
- case 'compact':
322
+ case 'doctor':
323
+ case 'health': {
324
+ // L17 (2026-05-27): run the probe sweep inline. Tail is ignored —
325
+ // the doctor command has no operator-facing arguments (every
326
+ // probe runs unconditionally; per-probe disable lives on the CLI
327
+ // shell surface, not the slash one).
328
+ return { kind: 'doctor' };
329
+ }
330
+ case 'compact': {
331
+ // Leak L8 (2026-05-27): graduated from stub. The session module
332
+ // owns the summariser round-trip; tail args are ignored today
333
+ // because the surface is parameterless. Operators wanting a
334
+ // per-session compact run `pugi compact --session <id>` from a
335
+ // fresh shell.
336
+ return { kind: 'compact' };
337
+ }
275
338
  case 'memory':
276
339
  case 'config':
277
340
  case 'budget':
@@ -1,13 +1,11 @@
1
- import { createHash, randomUUID } from 'node:crypto';
1
+ import { randomUUID } from 'node:crypto';
2
2
  import { execFileSync } from 'node:child_process';
3
3
  import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
4
4
  import { statSync } from 'node:fs';
5
5
  import { dirname, relative, resolve } from 'node:path';
6
6
  import { fileURLToPath } from 'node:url';
7
7
  import { AnvilEngineLoopClient } from '../core/engine/anvil-client.js';
8
- import { NoopEngineAdapter } from '../core/engine/noop.js';
9
8
  import { NativePugiEngineAdapter } from '../core/engine/native-pugi.js';
10
- import { decidePermission } from '../core/permission.js';
11
9
  import { loadMcpRegistry } from '../core/mcp/registry.js';
12
10
  import { loadHookRegistryOrExit } from './load-hooks-or-exit.js';
13
11
  import { defaultNonInteractiveMcpPrompt } from '../tools/mcp-tool.js';
@@ -16,7 +14,6 @@ import { loadSettings } from '../core/settings.js';
16
14
  import { FileReadCache } from '../core/file-cache.js';
17
15
  import { resolveWorkspacePath } from '../core/path-security.js';
18
16
  import { globTool, grepTool, readTool } from '../tools/file-tools.js';
19
- import { toolRegistry, toolSchemaBundleHashInput } from '../tools/registry.js';
20
17
  import { webFetchTool } from '../tools/web-fetch.js';
21
18
  import { emptyIndex, rebuildIndex, readIndex, upsertArtifact, writeIndex, } from '../core/index-store.js';
22
19
  import { signatureForPlanReview } from '../core/repl/ask.js';
@@ -30,7 +27,10 @@ import { runJobsCommand } from '../commands/jobs.js';
30
27
  import { runConfigCommand } from './commands/config.js';
31
28
  import { runPrivacyCommand } from './commands/privacy.js';
32
29
  import { runReport } from './commands/report.js';
30
+ import { runDoctorCommand, defaultHome as defaultDoctorHome } from './commands/doctor.js';
31
+ import { runStatusCommand, defaultStatusHome, } from './commands/status.js';
33
32
  import { runUndoCommand } from './commands/undo.js';
33
+ import { runCompactCommand } from './commands/compact.js';
34
34
  import { runBudgetCommand } from './commands/budget.js';
35
35
  import { runSkillsCommand } from './commands/skills.js';
36
36
  import { installDefaultSkills } from '../core/skills/defaults.js';
@@ -41,6 +41,8 @@ import { runWorktreeCommand } from './commands/worktree.js';
41
41
  import { resolveWorkspaceLabel } from '../core/repl/workspace-context.js';
42
42
  import { runReviewConsensus } from './commands/review-consensus.js';
43
43
  import { runMcpCommand } from './commands/mcp.js';
44
+ import { runPermissionsCommand } from './commands/permissions.js';
45
+ import { parsePermissionMode } from '../core/permissions/index.js';
44
46
  import { DECOMPOSE_PROMPT_SUFFIX, parseDecompositionFromText, writeDecomposition, } from './plan-decompose.js';
45
47
  import { FtsSyntaxError, SqliteSessionStore, resolveProjectStoreDir } from '../core/repl/store/index.js';
46
48
  import { slugForCwd } from '../core/repl/history.js';
@@ -88,6 +90,8 @@ const handlers = {
88
90
  lsp: dispatchLsp,
89
91
  mcp: dispatchMcp,
90
92
  patch: dispatchPatch,
93
+ permissions: dispatchPermissions,
94
+ perms: dispatchPermissions,
91
95
  plan: runEngineTask('plan'),
92
96
  'plan-review': dispatchPlanReview,
93
97
  privacy: dispatchPrivacy,
@@ -100,8 +104,10 @@ const handlers = {
100
104
  roster: dispatchRoster,
101
105
  sessions,
102
106
  skills: dispatchSkills,
107
+ status,
103
108
  sync,
104
109
  undo: dispatchUndo,
110
+ compact: dispatchCompact,
105
111
  version,
106
112
  web: dispatchWeb,
107
113
  whoami,
@@ -355,12 +361,64 @@ async function dispatchUndo(args, flags, session) {
355
361
  writeOutput: (payload, text) => writeOutput(flags, payload, text),
356
362
  });
357
363
  }
364
+ /**
365
+ * Leak L8 (2026-05-27) — `pugi compact` summarises older REPL turns
366
+ * into a single boundary marker, freeing context for the next `pugi
367
+ * resume <id>`. The slash `/compact` inside a live REPL forwards
368
+ * through the same runner via session.ts so the surface stays single-
369
+ * sourced.
370
+ */
371
+ async function dispatchCompact(args, flags, _session) {
372
+ const result = await runCompactCommand(args, {
373
+ workspaceRoot: process.cwd(),
374
+ writeOutput: (payload, text) => writeOutput(flags, payload, text),
375
+ });
376
+ if (result.status === 'failed_no_session'
377
+ || result.status === 'failed_transport'
378
+ || result.status === 'failed_store') {
379
+ process.exitCode = 1;
380
+ return;
381
+ }
382
+ if (result.status === 'noop_empty' || result.status === 'noop_recent_marker') {
383
+ process.exitCode = 2;
384
+ }
385
+ }
358
386
  async function dispatchBudget(args, flags, _session) {
359
387
  await runBudgetCommand(args, {
360
388
  workspaceRoot: process.cwd(),
361
389
  writeOutput: (payload, text) => writeOutput(flags, payload, text),
362
390
  });
363
391
  }
392
+ /**
393
+ * Leak L6 — `pugi permissions [mode] [--persist] [--confirm]`.
394
+ *
395
+ * Surface the same intent as the in-REPL `/permissions` slash. Mode
396
+ * arg is positional; `--persist` and `--confirm` are zero-arg flags
397
+ * already consumed by `parseArgs` into `flags.persist` / `flags.confirm`.
398
+ *
399
+ * Examples:
400
+ * pugi permissions -> show current mode + table
401
+ * pugi permissions plan -> flip workspace state to plan
402
+ * pugi permissions allow --persist -> flip + write ~/.pugi/config.json
403
+ * pugi permissions bypass --confirm -> flip to bypass (acknowledge banner)
404
+ */
405
+ async function dispatchPermissions(args, flags, _session) {
406
+ const head = args[0];
407
+ if (head && parsePermissionMode(head) === null) {
408
+ writeOutput(flags, { error: 'unknown_mode', mode: head }, `Unknown mode '${head}'. Allowed: plan, ask, allow, bypass.`);
409
+ process.exitCode = 1;
410
+ return;
411
+ }
412
+ const mode = head ? parsePermissionMode(head) : undefined;
413
+ await runPermissionsCommand({
414
+ ...(mode ? { mode } : {}),
415
+ persist: Boolean(flags.persist),
416
+ confirmBypass: Boolean(flags.confirm),
417
+ }, {
418
+ workspaceRoot: process.cwd(),
419
+ writeOutput: (text) => writeOutput(flags, { text }, text),
420
+ });
421
+ }
364
422
  async function dispatchSkills(args, flags, _session) {
365
423
  await runSkillsCommand(args, {
366
424
  workspaceRoot: process.cwd(),
@@ -615,6 +673,11 @@ function parseArgs(argv) {
615
673
  // β-headless: --no-tools default OFF so existing flag-free invocations
616
674
  // keep tool advertisement. Flipped only by explicit operator opt-in.
617
675
  noTools: false,
676
+ // Leak L6 — `pugi permissions <mode> --persist/--confirm`. Default
677
+ // false so existing invocations stay no-op on the new permission
678
+ // surface.
679
+ persist: false,
680
+ confirm: false,
618
681
  };
619
682
  const args = [];
620
683
  // Sprint 2E: `pugi --version` / `-v` are universal install-test conventions
@@ -809,6 +872,32 @@ function parseArgs(argv) {
809
872
  flags.base = next;
810
873
  index += 1;
811
874
  }
875
+ else if (arg.startsWith('--mode=')) {
876
+ // Leak L6: top-level `--mode plan|ask|allow|bypass`. Validation
877
+ // happens at the consumer side (parsePermissionMode) so the
878
+ // parser stays string-typed; an invalid value surfaces a clean
879
+ // error in the dispatcher rather than blowing up here.
880
+ flags.mode = arg.slice('--mode='.length);
881
+ }
882
+ else if (arg === '--mode') {
883
+ const next = argv[index + 1];
884
+ if (!next || next.startsWith('--')) {
885
+ throw new Error('--mode requires plan|ask|allow|bypass');
886
+ }
887
+ flags.mode = next;
888
+ index += 1;
889
+ }
890
+ else if (arg === '--persist') {
891
+ // Leak L6: paired with `pugi permissions <mode>` to also write
892
+ // the mode to ~/.pugi/config.json::defaultPermissionMode.
893
+ flags.persist = true;
894
+ }
895
+ else if (arg === '--confirm') {
896
+ // Leak L6: required for `pugi permissions bypass` (bypass
897
+ // disables policy hooks; the gate refuses the flip without
898
+ // acknowledgement).
899
+ flags.confirm = true;
900
+ }
812
901
  else {
813
902
  args.push(arg);
814
903
  }
@@ -997,6 +1086,20 @@ const COMMAND_HELP_BODIES = {
997
1086
  'event log, settings), permission mode, and the capability matrix per',
998
1087
  'engine adapter. Safe to run anywhere; no network calls.',
999
1088
  ],
1089
+ status: [
1090
+ 'pugi status — concise session snapshot.',
1091
+ '',
1092
+ 'Different from `pugi doctor` (environment health). Status answers',
1093
+ '"what is this Pugi session doing right now?" — session id + age,',
1094
+ 'cwd, permission mode, CLI version, token usage, active + completed',
1095
+ 'dispatches, last command, compact boundary count, auth identity.',
1096
+ '',
1097
+ ' --json Emit a structured envelope to stdout.',
1098
+ '',
1099
+ 'Live REPL state (tokens, last command) is only available via the',
1100
+ 'in-REPL `/status` slash; the shell path degrades those fields к',
1101
+ '"n/a" and exits 0.',
1102
+ ],
1000
1103
  report: [
1001
1104
  'pugi report — capture a bug report from the most-recent session.',
1002
1105
  '',
@@ -1111,61 +1214,53 @@ async function help(args, flags, _session) {
1111
1214
  'Execution defaults to local. Use --remote or --web to create a handoff bundle.',
1112
1215
  ].join('\n'));
1113
1216
  }
1217
+ /**
1218
+ * `pugi doctor` — Leak L17 (2026-05-27). Delegates to the diagnostics
1219
+ * probe runner in `runtime/commands/doctor.ts`. The handler stays
1220
+ * thin so the probe surface stays single-sourced between the CLI
1221
+ * shell command, the `pnpm run doctor --json` package script, and
1222
+ * the in-REPL `/doctor` slash command.
1223
+ *
1224
+ * Exit codes are set by `runDoctorCommand` (0 = healthy/warnings,
1225
+ * 2 = at least one error probe). The pre-L17 minimal doctor surface
1226
+ * (adapter capabilities + schema bundle hash) is preserved under
1227
+ * `payload.meta.legacy` so any operator scripts that grep the JSON
1228
+ * keep working through the transition; the field is marked for
1229
+ * removal in a follow-up sprint once the new shape is the
1230
+ * documented contract.
1231
+ */
1114
1232
  async function doctor(_args, flags, _session) {
1115
- const cwd = process.cwd();
1116
- const settings = loadSettings(cwd);
1117
- // `doctor` reports adapter capabilities only; we pass a no-op client
1118
- // so we do not require an Anvil endpoint to run `pugi doctor`. The
1119
- // adapter never invokes `client.send()` from inside `capabilities()`.
1120
- const inertClient = {
1121
- async send() {
1122
- return {
1123
- stop: 'error',
1124
- code: 'failed',
1125
- message: 'doctor: inert client',
1126
- };
1127
- },
1128
- };
1129
- const adapters = [
1130
- new NoopEngineAdapter(),
1131
- new NativePugiEngineAdapter({ client: inertClient }),
1132
- ];
1133
- const capabilities = await Promise.all(adapters.map(async (adapter) => ({
1134
- name: adapter.name,
1135
- capabilities: await adapter.capabilities(),
1136
- })));
1137
- const payload = {
1138
- cliVersion: PUGI_CLI_VERSION,
1139
- nodeVersion: process.version,
1140
- workspaceRoot: cwd,
1141
- pugiMode: existsSync(resolve(cwd, 'CLAUDE.md')),
1142
- pugiDir: existsSync(resolve(cwd, '.pugi')),
1143
- eventLog: existsSync(resolve(cwd, '.pugi/events.jsonl')),
1144
- permissionMode: settings.permissions.mode,
1145
- approvals: settings.workflow.approvals,
1146
- notAutomatic: [...settings.workflow.notAutomatic, ...settings.permissions.notAutomatic],
1147
- protectedFileCheck: decidePermission({ tool: 'doctor', kind: 'edit', target: '.env' }, settings, cwd),
1148
- protectedFileSafety: 'configured-in-m1',
1149
- mcpTrust: 'not-configured',
1150
- releaseGuard: 'scaffolded',
1151
- tools: toolRegistry,
1152
- engineAdapters: capabilities,
1153
- schemaBundleHash: createHash('sha256')
1154
- .update(toolSchemaBundleHashInput())
1155
- .digest('hex'),
1156
- };
1157
- writeOutput(flags, payload, [
1158
- 'Pugi doctor',
1159
- `CLI: ${payload.cliVersion}`,
1160
- `Node: ${payload.nodeVersion}`,
1161
- `Workspace: ${payload.workspaceRoot}`,
1162
- `Pugi mode: ${payload.pugiMode ? 'detected' : 'not detected'}`,
1163
- `Pugi dir: ${payload.pugiDir ? 'present' : 'missing'}`,
1164
- `Event log: ${payload.eventLog ? 'present' : 'missing'}`,
1165
- `Permission mode: ${payload.permissionMode}`,
1166
- `Approvals: ${payload.approvals}`,
1167
- `Release guard: ${payload.releaseGuard}`,
1168
- ].join('\n'));
1233
+ await runDoctorCommand({
1234
+ cwd: process.cwd(),
1235
+ home: defaultDoctorHome(),
1236
+ env: process.env,
1237
+ json: flags.json,
1238
+ writeOutput: (payload, text) => writeOutput(flags, payload, text),
1239
+ });
1240
+ }
1241
+ /**
1242
+ * `pugi status` — Leak L34 (2026-05-27). Concise session-state probe
1243
+ * mirroring Claude Code's `/status`. Distinct from `pugi doctor`
1244
+ * (environment health) — `status` answers "what is THIS Pugi
1245
+ * session doing right now?" with session id + age, cwd, permission
1246
+ * mode, CLI version, token usage, dispatch count, last command,
1247
+ * compact boundaries, and auth identity.
1248
+ *
1249
+ * The top-level shell invocation has no live REPL state — fields
1250
+ * that need a live session (`tokens`, `lastCommand`) degrade к the
1251
+ * `n/a` sentinel. The same handler powers the in-REPL `/status`
1252
+ * slash, which passes live state through `StatusCommandContext`.
1253
+ *
1254
+ * Always exits 0 — the command is informational, never a gate.
1255
+ */
1256
+ async function status(_args, flags, _session) {
1257
+ await runStatusCommand({
1258
+ cwd: process.cwd(),
1259
+ home: defaultStatusHome(),
1260
+ env: process.env,
1261
+ json: flags.json,
1262
+ writeOutput: (payload, text) => writeOutput(flags, payload, text),
1263
+ });
1169
1264
  }
1170
1265
  /**
1171
1266
  * Programmatic init scaffolder. Idempotent — every helper call is a