@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.
- package/dist/core/compact/auto-trigger.js +96 -0
- package/dist/core/compact/buffer-rewriter.js +115 -0
- package/dist/core/compact/summarizer.js +196 -0
- package/dist/core/compact/token-counter.js +108 -0
- package/dist/core/denial-tracking/index.js +8 -0
- package/dist/core/denial-tracking/state.js +264 -0
- package/dist/core/diagnostics/probe-runner.js +93 -0
- package/dist/core/diagnostics/probes/api.js +46 -0
- package/dist/core/diagnostics/probes/auth.js +86 -0
- package/dist/core/diagnostics/probes/cli-version.js +127 -0
- package/dist/core/diagnostics/probes/config.js +72 -0
- package/dist/core/diagnostics/probes/denial-tracking.js +57 -0
- package/dist/core/diagnostics/probes/disk.js +81 -0
- package/dist/core/diagnostics/probes/git.js +65 -0
- package/dist/core/diagnostics/probes/mcp.js +75 -0
- package/dist/core/diagnostics/probes/node.js +59 -0
- package/dist/core/diagnostics/probes/pnpm.js +36 -0
- package/dist/core/diagnostics/probes/session.js +74 -0
- package/dist/core/diagnostics/probes/status-snapshot.js +442 -0
- package/dist/core/diagnostics/probes/workspace.js +63 -0
- package/dist/core/diagnostics/types.js +70 -0
- package/dist/core/engine/native-pugi.js +20 -0
- package/dist/core/engine/strip-internal-fields.js +124 -0
- package/dist/core/engine/tool-bridge.js +251 -49
- package/dist/core/file-cache.js +113 -1
- package/dist/core/mcp/client.js +66 -6
- package/dist/core/mcp/registry.js +24 -2
- package/dist/core/permissions/gate.js +187 -0
- package/dist/core/permissions/index.js +18 -0
- package/dist/core/permissions/mode.js +102 -0
- package/dist/core/permissions/state.js +160 -0
- package/dist/core/permissions/tool-class.js +93 -0
- package/dist/core/repl/session.js +261 -9
- package/dist/core/repl/slash-commands.js +67 -4
- package/dist/runtime/cli.js +153 -58
- package/dist/runtime/commands/compact.js +296 -0
- package/dist/runtime/commands/doctor.js +369 -0
- package/dist/runtime/commands/mcp.js +290 -3
- package/dist/runtime/commands/permissions.js +87 -0
- package/dist/runtime/commands/status.js +178 -0
- package/dist/runtime/version.js +1 -1
- package/dist/tools/agent-tool.js +18 -4
- package/dist/tools/ask-user-question.js +213 -0
- package/dist/tools/file-tools.js +57 -14
- package/dist/tools/registry.js +7 -0
- package/dist/tui/ask-user-question-prompt.js +192 -0
- package/dist/tui/compact-banner.js +54 -0
- package/dist/tui/conversation-pane.js +68 -7
- package/dist/tui/doctor-table.js +31 -0
- package/dist/tui/status-table.js +7 -0
- 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
|
-
|
|
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: '
|
|
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: '
|
|
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 '
|
|
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':
|
package/dist/runtime/cli.js
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
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
|