@pugi/cli 0.1.0-beta.20 → 0.1.0-beta.22
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/bare-mode/index.js +107 -0
- package/dist/core/diagnostics/probes/bare-mode.js +42 -0
- package/dist/core/engine/native-pugi.js +21 -10
- package/dist/core/engine/prompts.js +30 -2
- package/dist/core/engine/tool-bridge.js +32 -0
- package/dist/core/feedback/queue.js +177 -0
- package/dist/core/feedback/submitter.js +145 -0
- package/dist/core/onboarding/marker.js +111 -0
- package/dist/core/onboarding/telemetry-state.js +108 -0
- package/dist/core/output-style/presets.js +176 -0
- package/dist/core/output-style/state.js +185 -0
- package/dist/core/permissions/index.js +1 -1
- package/dist/core/permissions/state.js +55 -0
- package/dist/core/repl/session.js +375 -12
- package/dist/core/repl/slash-commands.js +99 -1
- package/dist/core/repl/workspace-context.js +22 -0
- package/dist/core/share/formatter.js +271 -0
- package/dist/core/share/redactor.js +221 -0
- package/dist/core/share/uploader.js +267 -0
- package/dist/core/todos/invariant.js +10 -0
- package/dist/core/todos/state.js +177 -0
- package/dist/runtime/cli.js +386 -1
- package/dist/runtime/commands/doctor.js +8 -0
- package/dist/runtime/commands/feedback.js +184 -0
- package/dist/runtime/commands/onboarding.js +275 -0
- package/dist/runtime/commands/plan.js +143 -0
- package/dist/runtime/commands/share.js +316 -0
- package/dist/runtime/commands/stickers.js +82 -0
- package/dist/runtime/commands/style.js +194 -0
- package/dist/runtime/version.js +1 -1
- package/dist/tools/registry.js +8 -0
- package/dist/tools/todo-write.js +184 -0
- package/dist/tui/compact-banner.js +28 -1
- package/dist/tui/conversation-pane.js +13 -0
- package/dist/tui/feedback-prompt.js +156 -0
- package/dist/tui/onboarding-wizard.js +240 -0
- package/dist/tui/repl-render.js +9 -1
- package/dist/tui/stickers-art.js +136 -0
- package/dist/tui/style-table.js +22 -0
- package/package.json +2 -2
package/dist/runtime/cli.js
CHANGED
|
@@ -25,14 +25,20 @@ import { clearApiKey, DEFAULT_API_URL, listStoredCredentials, maskApiKey, normal
|
|
|
25
25
|
import { runDeployCommand } from '../commands/deploy.js';
|
|
26
26
|
import { runJobsCommand } from '../commands/jobs.js';
|
|
27
27
|
import { runConfigCommand } from './commands/config.js';
|
|
28
|
+
import { runStyleCommand } from './commands/style.js';
|
|
29
|
+
import { runOnboardingCommand } from './commands/onboarding.js';
|
|
30
|
+
import { isOnboarded } from '../core/onboarding/marker.js';
|
|
28
31
|
import { runPrivacyCommand } from './commands/privacy.js';
|
|
29
32
|
import { runReport } from './commands/report.js';
|
|
30
33
|
import { runDoctorCommand, defaultHome as defaultDoctorHome } from './commands/doctor.js';
|
|
31
34
|
import { runStatusCommand, defaultStatusHome, } from './commands/status.js';
|
|
35
|
+
import { runStickersCommand } from './commands/stickers.js';
|
|
32
36
|
import { runUndoCommand } from './commands/undo.js';
|
|
33
37
|
import { runCompactCommand } from './commands/compact.js';
|
|
34
38
|
import { runBudgetCommand } from './commands/budget.js';
|
|
39
|
+
import { BARE_MODE_BANNER, isBareMode, setBareMode, } from '../core/bare-mode/index.js';
|
|
35
40
|
import { runCostCommand } from './commands/cost.js';
|
|
41
|
+
import { runShareCommand } from './commands/share.js';
|
|
36
42
|
import { runSkillsCommand } from './commands/skills.js';
|
|
37
43
|
import { installDefaultSkills } from '../core/skills/defaults.js';
|
|
38
44
|
import { runAgentsCommand } from './commands/agents.js';
|
|
@@ -43,6 +49,7 @@ import { resolveWorkspaceLabel } from '../core/repl/workspace-context.js';
|
|
|
43
49
|
import { runReviewConsensus } from './commands/review-consensus.js';
|
|
44
50
|
import { runMcpCommand } from './commands/mcp.js';
|
|
45
51
|
import { runPermissionsCommand } from './commands/permissions.js';
|
|
52
|
+
import { runPlanCommand } from './commands/plan.js';
|
|
46
53
|
import { parsePermissionMode } from '../core/permissions/index.js';
|
|
47
54
|
import { DECOMPOSE_PROMPT_SUFFIX, parseDecompositionFromText, writeDecomposition, } from './plan-decompose.js';
|
|
48
55
|
import { FtsSyntaxError, SqliteSessionStore, resolveProjectStoreDir } from '../core/repl/store/index.js';
|
|
@@ -94,7 +101,7 @@ const handlers = {
|
|
|
94
101
|
patch: dispatchPatch,
|
|
95
102
|
permissions: dispatchPermissions,
|
|
96
103
|
perms: dispatchPermissions,
|
|
97
|
-
plan:
|
|
104
|
+
plan: dispatchPlan,
|
|
98
105
|
'plan-review': dispatchPlanReview,
|
|
99
106
|
privacy: dispatchPrivacy,
|
|
100
107
|
// PAVF-7 (2026-05-27): `pugi report --from-error` captures the
|
|
@@ -105,9 +112,21 @@ const handlers = {
|
|
|
105
112
|
resume,
|
|
106
113
|
roster: dispatchRoster,
|
|
107
114
|
sessions,
|
|
115
|
+
share: dispatchShare,
|
|
108
116
|
skills: dispatchSkills,
|
|
109
117
|
status,
|
|
118
|
+
stickers,
|
|
119
|
+
// Leak L21 (2026-05-27): in-CLI feedback collector. Shares the
|
|
120
|
+
// same handler as the in-REPL `/feedback` slash; the wrapper just
|
|
121
|
+
// routes TTY vs non-TTY before mounting Ink.
|
|
122
|
+
feedback: dispatchFeedback,
|
|
110
123
|
sync,
|
|
124
|
+
style: dispatchStyle,
|
|
125
|
+
// Leak L25 (2026-05-27): `pugi onboarding` walks the new operator
|
|
126
|
+
// through auth / mode / style / MCP / telemetry. Idempotent;
|
|
127
|
+
// `--reset` clears the marker file so the bare-invocation hint
|
|
128
|
+
// re-arms without nuking persisted defaults.
|
|
129
|
+
onboarding: dispatchOnboarding,
|
|
111
130
|
undo: dispatchUndo,
|
|
112
131
|
compact: dispatchCompact,
|
|
113
132
|
// L19 (2026-05-27): `pugi usage` is an alias of `pugi cost` — same
|
|
@@ -288,6 +307,59 @@ async function dispatchPrivacy(args, flags, _session) {
|
|
|
288
307
|
writeOutput: (payload, text) => writeOutput(flags, payload, text),
|
|
289
308
|
});
|
|
290
309
|
}
|
|
310
|
+
/**
|
|
311
|
+
* Leak L18 (2026-05-27) — `pugi style` top-level dispatcher.
|
|
312
|
+
*
|
|
313
|
+
* Forwards to the shared `runStyleCommand` runner. The REPL `/style`
|
|
314
|
+
* slash uses the same runner via a dynamic import inside
|
|
315
|
+
* `core/repl/session.ts` so the two surfaces stay single-sourced.
|
|
316
|
+
*
|
|
317
|
+
* Exit-code policy:
|
|
318
|
+
* - 0 — show / switch / reset / list happy paths
|
|
319
|
+
* - 1 — unknown preset slug
|
|
320
|
+
* - 2 — conflicting flags (`--reset` + positional / `--reset --persist`)
|
|
321
|
+
*
|
|
322
|
+
* The runner returns the code; we attach it to `process.exitCode` so
|
|
323
|
+
* subsequent dispatch wrappers do not clobber it on success.
|
|
324
|
+
*/
|
|
325
|
+
async function dispatchStyle(args, flags, _session) {
|
|
326
|
+
const rc = await runStyleCommand(args, {
|
|
327
|
+
workspaceRoot: process.cwd(),
|
|
328
|
+
writeOutput: (payload, text) => writeOutput(flags, payload, text),
|
|
329
|
+
});
|
|
330
|
+
if (rc !== 0)
|
|
331
|
+
process.exitCode = rc;
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Leak L25 (2026-05-27) — `pugi onboarding` top-level dispatcher.
|
|
335
|
+
*
|
|
336
|
+
* Walks the new operator through auth / permission mode / output
|
|
337
|
+
* style / MCP / telemetry consent. The Ink wizard mounts only when
|
|
338
|
+
* stdin is a TTY and `--json` is not set; otherwise we dump the
|
|
339
|
+
* current snapshot + hints in the non-interactive envelope so
|
|
340
|
+
* scripted callers see the same structured payload.
|
|
341
|
+
*
|
|
342
|
+
* Auth status: we resolve credentials once up front and pass the
|
|
343
|
+
* boolean to the runner; the wizard surfaces a `pugi login` hint
|
|
344
|
+
* when auth is missing but DOES NOT block — local defaults are still
|
|
345
|
+
* configurable without an active credential.
|
|
346
|
+
*
|
|
347
|
+
* Exit-code policy:
|
|
348
|
+
* 0 — completed / cancelled / non-interactive / reset
|
|
349
|
+
* 2 — conflicting / unknown flags
|
|
350
|
+
*/
|
|
351
|
+
async function dispatchOnboarding(args, flags, _session) {
|
|
352
|
+
const credential = resolveActiveCredential();
|
|
353
|
+
const rc = await runOnboardingCommand(args, {
|
|
354
|
+
workspaceRoot: process.cwd(),
|
|
355
|
+
env: process.env,
|
|
356
|
+
authPresent: credential !== null,
|
|
357
|
+
interactive: isInteractive(flags) && !flags.json,
|
|
358
|
+
writeOutput: (payload, text) => writeOutput(flags, payload, text),
|
|
359
|
+
});
|
|
360
|
+
if (rc !== 0)
|
|
361
|
+
process.exitCode = rc;
|
|
362
|
+
}
|
|
291
363
|
/**
|
|
292
364
|
* PAVF-7 (2026-05-27): `pugi report --from-error` — bundle the most-
|
|
293
365
|
* recent failed session into a redacted local report so operators can
|
|
@@ -438,6 +510,124 @@ async function dispatchCost(args, flags, _session) {
|
|
|
438
510
|
writeOutput: (payload, text) => writeOutput(flags, payload, text),
|
|
439
511
|
});
|
|
440
512
|
}
|
|
513
|
+
/**
|
|
514
|
+
* Leak L20 (2026-05-27): `pugi share` top-level surface. Exports the
|
|
515
|
+
* current session transcript as Markdown to gist (default when `gh` is
|
|
516
|
+
* available) or pugi.io (--pugi). The handler delegates to
|
|
517
|
+
* `runShareCommand` so the slash surface (`/share`) and the shell
|
|
518
|
+
* surface share one code path. JSON output mode is honoured via the
|
|
519
|
+
* shared `writeOutput` wrapper.
|
|
520
|
+
*/
|
|
521
|
+
async function dispatchShare(args, flags, _session) {
|
|
522
|
+
await runShareCommand(args, {
|
|
523
|
+
workspaceRoot: process.cwd(),
|
|
524
|
+
cliVersion: PUGI_CLI_VERSION,
|
|
525
|
+
writeOutput: (payload, text) => writeOutput(flags, payload, text),
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* Leak L7 — `pugi plan [--back | --persist | <prompt...>]`.
|
|
530
|
+
*
|
|
531
|
+
* Quick mode-switch shortcut + optional one-shot engine dispatch. Slash
|
|
532
|
+
* surface `/plan` shares the same `runPlanCommand` helper so the
|
|
533
|
+
* workspace-state writes go through one code path. Argument grammar:
|
|
534
|
+
*
|
|
535
|
+
* pugi plan -> set workspace mode = plan + banner
|
|
536
|
+
* pugi plan --back -> restore the mode that was active
|
|
537
|
+
* before the most recent /plan entry
|
|
538
|
+
* pugi plan --persist -> set + also write ~/.pugi/config.json
|
|
539
|
+
* pugi plan <prompt...> -> set + run `runEngineTask('plan')`
|
|
540
|
+
* with the prompt (existing offline /
|
|
541
|
+
* engine path; the permission gate now
|
|
542
|
+
* sees plan as workspace state)
|
|
543
|
+
* pugi plan <prompt> --auto-back -> ALSO restore previous mode once
|
|
544
|
+
* the engine returns (defaults to
|
|
545
|
+
* leaving the operator in plan
|
|
546
|
+
* mode so they can iterate)
|
|
547
|
+
*
|
|
548
|
+
* The handler intentionally intercepts the mode-switch flags BEFORE
|
|
549
|
+
* delegating to `runEngineTask('plan')` for the prompt path. Without
|
|
550
|
+
* this wrapper, `pugi plan` (no args) would error out of the engine
|
|
551
|
+
* task ("requires a prompt") which is the legacy behaviour; the L7
|
|
552
|
+
* spec wants bare `pugi plan` to be the mode switch.
|
|
553
|
+
*/
|
|
554
|
+
async function dispatchPlan(args, flags, session) {
|
|
555
|
+
// Strip `--back` / `--auto-back` from the positional args — the global
|
|
556
|
+
// parseArgs does not consume them (they are command-local). Anything
|
|
557
|
+
// else stays in `prompt` so the engine sees the operator's text
|
|
558
|
+
// verbatim. The flag parser keeps both `--back` and the spelling
|
|
559
|
+
// variants the operator might type from muscle memory after using
|
|
560
|
+
// `git checkout --` style flows.
|
|
561
|
+
let back = false;
|
|
562
|
+
let autoBack = false;
|
|
563
|
+
const remaining = [];
|
|
564
|
+
for (const arg of args) {
|
|
565
|
+
if (arg === '--back') {
|
|
566
|
+
back = true;
|
|
567
|
+
}
|
|
568
|
+
else if (arg === '--auto-back') {
|
|
569
|
+
autoBack = true;
|
|
570
|
+
}
|
|
571
|
+
else {
|
|
572
|
+
remaining.push(arg);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
const hasPrompt = remaining.length > 0;
|
|
576
|
+
const persist = Boolean(flags.persist);
|
|
577
|
+
// --back and a prompt are mutually exclusive — back is a revert action,
|
|
578
|
+
// not a dispatch one. Refuse the combination with a clear hint instead
|
|
579
|
+
// of silently dropping one or the other.
|
|
580
|
+
if (back && hasPrompt) {
|
|
581
|
+
writeOutput(flags, { ok: false, error: 'pugi plan --back does not accept a prompt; revert first, then dispatch.' }, 'pugi plan --back does not accept a prompt; revert first, then dispatch.');
|
|
582
|
+
process.exitCode = 2;
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
// --back + --auto-back is incoherent (auto-back applies to the
|
|
586
|
+
// dispatch path) — refuse rather than degrade silently.
|
|
587
|
+
if (back && autoBack) {
|
|
588
|
+
writeOutput(flags, { ok: false, error: 'pugi plan --back and --auto-back cannot be combined.' }, 'pugi plan --back and --auto-back cannot be combined.');
|
|
589
|
+
process.exitCode = 2;
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
// When a prompt is going to be dispatched in --json mode, suppress
|
|
593
|
+
// the human-readable banner writes so the engine task remains the
|
|
594
|
+
// single JSON emitter on stdout. The mode write still happens. In
|
|
595
|
+
// human (non --json) mode the banner prints normally so the operator
|
|
596
|
+
// sees the gate-state change before the engine starts thinking.
|
|
597
|
+
const sinkSilent = hasPrompt && flags.json;
|
|
598
|
+
const writeLine = (line) => {
|
|
599
|
+
if (sinkSilent)
|
|
600
|
+
return;
|
|
601
|
+
writeOutput(flags, { text: line }, line);
|
|
602
|
+
};
|
|
603
|
+
const result = await runPlanCommand({ back, persist }, {
|
|
604
|
+
workspaceRoot: process.cwd(),
|
|
605
|
+
writeOutput: writeLine,
|
|
606
|
+
});
|
|
607
|
+
// No prompt → mode-switch only. Done.
|
|
608
|
+
if (!hasPrompt)
|
|
609
|
+
return;
|
|
610
|
+
// Prompt present → fall through to the existing engine task with the
|
|
611
|
+
// remaining args. The workspace mode is now `plan` (or stayed `plan`
|
|
612
|
+
// if already there); the engine sees the same plan-task semantics it
|
|
613
|
+
// always has — read-only schema + executor refusal sentinel — but the
|
|
614
|
+
// permission GATE now also enforces plan independently.
|
|
615
|
+
try {
|
|
616
|
+
await runEngineTask('plan')(remaining, flags, session);
|
|
617
|
+
}
|
|
618
|
+
finally {
|
|
619
|
+
// --auto-back restores the previous mode AFTER the engine returns
|
|
620
|
+
// (success OR failure) so the operator's gate state mirrors a normal
|
|
621
|
+
// `--back` invocation. Without --auto-back the operator stays in
|
|
622
|
+
// plan and can iterate / inspect before acting.
|
|
623
|
+
if (autoBack && (result.verdict === 'entered' || result.verdict === 'persisted')) {
|
|
624
|
+
await runPlanCommand({ back: true, persist: false }, {
|
|
625
|
+
workspaceRoot: process.cwd(),
|
|
626
|
+
writeOutput: writeLine,
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
441
631
|
async function dispatchSkills(args, flags, _session) {
|
|
442
632
|
await runSkillsCommand(args, {
|
|
443
633
|
workspaceRoot: process.cwd(),
|
|
@@ -567,6 +757,21 @@ async function dispatchWorktree(args, flags, _session) {
|
|
|
567
757
|
}
|
|
568
758
|
export async function runCli(argv) {
|
|
569
759
|
const { command, args, flags, isBareInvocation } = parseArgs(argv);
|
|
760
|
+
// Leak L22 — print the one-line bare banner once per invocation when
|
|
761
|
+
// the flag is active and stdout is NOT bound for JSON consumption. The
|
|
762
|
+
// banner goes to stderr so it never lands in a `--json` envelope or a
|
|
763
|
+
// pipe-captured stdout stream; operators see it on the terminal,
|
|
764
|
+
// scripted callers stay clean. Suppressed for `pugi version` / `pugi
|
|
765
|
+
// help` (short, scripted-friendly surfaces) and when the operator
|
|
766
|
+
// sets PUGI_BARE without the flag (avoids double-printing across
|
|
767
|
+
// scripted nested invocations).
|
|
768
|
+
if (flags.bare &&
|
|
769
|
+
!flags.json &&
|
|
770
|
+
command !== 'version' &&
|
|
771
|
+
command !== 'help' &&
|
|
772
|
+
argv.includes('--bare')) {
|
|
773
|
+
process.stderr.write(`${BARE_MODE_BANNER}\n`);
|
|
774
|
+
}
|
|
570
775
|
// β-headless dispatch (CEO directive 2026-05-27 "нужно тестирование по
|
|
571
776
|
// кругу"): when `--print <brief>` is set we route to the headless
|
|
572
777
|
// runner BEFORE the REPL / splash / command branches. The runner
|
|
@@ -598,6 +803,19 @@ export async function runCli(argv) {
|
|
|
598
803
|
process.exitCode = exitCode;
|
|
599
804
|
return;
|
|
600
805
|
}
|
|
806
|
+
// Leak L25 (2026-05-27): first-run hint. When the operator types a
|
|
807
|
+
// bare `pugi` on a real TTY AND the onboarding marker is absent, drop
|
|
808
|
+
// a one-line hint on stderr BEFORE the REPL splash mounts. Stderr so
|
|
809
|
+
// the line never lands in a `--json` envelope or a scripted stdout
|
|
810
|
+
// pipe; suppressed when --json is set or the operator already walked
|
|
811
|
+
// the wizard. The marker check is best-effort — a fs glitch returns
|
|
812
|
+
// false and we print the hint, which is harmless.
|
|
813
|
+
if (isBareInvocation
|
|
814
|
+
&& isInteractive(flags)
|
|
815
|
+
&& !flags.json
|
|
816
|
+
&& !isOnboarded(process.env)) {
|
|
817
|
+
process.stderr.write('Tip: run `pugi onboarding` to configure defaults.\n');
|
|
818
|
+
}
|
|
601
819
|
// Bare `pugi` on a TTY enters the REPL-by-default agentic session
|
|
602
820
|
// (Sprint α5.7, ADR-0056). The REPL is the customer-facing surface
|
|
603
821
|
// that brings Pugi to parity with Claude Code / Codex CLI. When the
|
|
@@ -697,8 +915,27 @@ function parseArgs(argv) {
|
|
|
697
915
|
// surface.
|
|
698
916
|
persist: false,
|
|
699
917
|
confirm: false,
|
|
918
|
+
// Leak L22 — `--bare` flag (skip project auto-discovery). Default
|
|
919
|
+
// honors the env var so a wrapper script that exports PUGI_BARE=1
|
|
920
|
+
// keeps the bit even when the operator forgets the flag, and the
|
|
921
|
+
// explicit flag overrides on the way through the loop below.
|
|
922
|
+
bare: isBareMode(),
|
|
923
|
+
// Leak L33 — `--ascii-only` for `pugi stickers`. Default off so the
|
|
924
|
+
// interactive surface keeps its boxed renderer; opt-in via flag
|
|
925
|
+
// for pipe / script use.
|
|
926
|
+
asciiOnly: false,
|
|
700
927
|
};
|
|
701
928
|
const args = [];
|
|
929
|
+
// Leak L22: scan for `--bare` BEFORE the early-return short-circuits
|
|
930
|
+
// below. Operators may pass `pugi --bare --version` or `pugi --bare
|
|
931
|
+
// --help` and the short-circuit return must still flip the bare bit
|
|
932
|
+
// so subprocesses + env-consulting modules see the activated state.
|
|
933
|
+
// The bit is idempotent — re-applied inside the main loop below for
|
|
934
|
+
// non-short-circuit paths.
|
|
935
|
+
if (argv.includes('--bare')) {
|
|
936
|
+
flags.bare = true;
|
|
937
|
+
setBareMode();
|
|
938
|
+
}
|
|
702
939
|
// Sprint 2E: `pugi --version` / `-v` are universal install-test conventions
|
|
703
940
|
// (npm uses --version on every published bin, Homebrew formula uses it in
|
|
704
941
|
// the test block). Normalize them to the `version` command so users can
|
|
@@ -767,6 +1004,12 @@ function parseArgs(argv) {
|
|
|
767
1004
|
// at the global level for consistency with --no-splash / --no-tool-stream.
|
|
768
1005
|
flags.noDefaults = true;
|
|
769
1006
|
}
|
|
1007
|
+
else if (arg === '--ascii-only') {
|
|
1008
|
+
// Leak L33 — `pugi stickers --ascii-only` skips the Ink boxed
|
|
1009
|
+
// renderer. Parsed globally so the dispatcher can pass the flag
|
|
1010
|
+
// through to runStickersCommand without per-command argv slicing.
|
|
1011
|
+
flags.asciiOnly = true;
|
|
1012
|
+
}
|
|
770
1013
|
else if (arg === '--decompose') {
|
|
771
1014
|
// α6.8 EXTEND PR1: plan-only flag. Other engine commands ignore
|
|
772
1015
|
// it. Parsed globally for symmetry with the rest of the flag
|
|
@@ -917,6 +1160,16 @@ function parseArgs(argv) {
|
|
|
917
1160
|
// acknowledgement).
|
|
918
1161
|
flags.confirm = true;
|
|
919
1162
|
}
|
|
1163
|
+
else if (arg === '--bare') {
|
|
1164
|
+
// Leak L22: disable project auto-discovery for this invocation.
|
|
1165
|
+
// Set BOTH the parsed flag and the process env so downstream
|
|
1166
|
+
// modules consulting `isBareMode()` (markdown-traverse callsite,
|
|
1167
|
+
// REPL auto-init gate, doctor probe, subprocess spawns) see a
|
|
1168
|
+
// coherent activated state without re-threading the bit through
|
|
1169
|
+
// every call signature.
|
|
1170
|
+
flags.bare = true;
|
|
1171
|
+
setBareMode();
|
|
1172
|
+
}
|
|
920
1173
|
else {
|
|
921
1174
|
args.push(arg);
|
|
922
1175
|
}
|
|
@@ -1044,6 +1297,28 @@ const COMMAND_HELP_BODIES = {
|
|
|
1044
1297
|
' pugi config get privacy',
|
|
1045
1298
|
' pugi config set privacy=<mode>',
|
|
1046
1299
|
],
|
|
1300
|
+
share: [
|
|
1301
|
+
'pugi share — export the current session transcript (leak L20).',
|
|
1302
|
+
'',
|
|
1303
|
+
'Reads .pugi/events.jsonl, formats it as Markdown, and uploads to',
|
|
1304
|
+
'either a GitHub Gist (`gh`-backed, default when `gh` is available)',
|
|
1305
|
+
'or pugi.io (--pugi). Always prompts before upload unless --yes is',
|
|
1306
|
+
'set. Refuses upload entirely if the transcript carries an active',
|
|
1307
|
+
'`Bearer ` credential — re-run with --redact to scrub it first.',
|
|
1308
|
+
'',
|
|
1309
|
+
'Flags:',
|
|
1310
|
+
' --gist Force gist target; refuses if gh CLI is absent.',
|
|
1311
|
+
' --pugi Force pugi.io target (requires `pugi login`).',
|
|
1312
|
+
' --redact Run PII scrubber before upload.',
|
|
1313
|
+
' --preview Print the transcript to stdout WITHOUT upload.',
|
|
1314
|
+
' --yes, -y Skip the y/n confirmation prompt.',
|
|
1315
|
+
' --json Emit a structured JSON envelope only.',
|
|
1316
|
+
'',
|
|
1317
|
+
'Examples:',
|
|
1318
|
+
' pugi share Auto-pick + confirm.',
|
|
1319
|
+
' pugi share --preview --redact See what would be shared.',
|
|
1320
|
+
' pugi share --gist --redact --yes Scripted secret-gist upload.',
|
|
1321
|
+
],
|
|
1047
1322
|
cost: [
|
|
1048
1323
|
'pugi cost — token + USD breakdown for the current Pugi session.',
|
|
1049
1324
|
'',
|
|
@@ -1155,6 +1430,32 @@ const COMMAND_HELP_BODIES = {
|
|
|
1155
1430
|
'Useful in shell scripts that need a human-confirm before a destructive',
|
|
1156
1431
|
'step. Exits 0 on yes, 1 on no, 2 on cancel.',
|
|
1157
1432
|
],
|
|
1433
|
+
stickers: [
|
|
1434
|
+
'pugi stickers — show a Pugi brand sticker (gimmick).',
|
|
1435
|
+
'',
|
|
1436
|
+
'Picks one of the curated pug-face ASCII variants at random and footers',
|
|
1437
|
+
'it with a rotating brand quote. Brand-personality surface — never a gate.',
|
|
1438
|
+
'',
|
|
1439
|
+
' --json Emit a structured envelope (id · caption · quote).',
|
|
1440
|
+
' --ascii-only Plain stdout (no box, no dim accents) for scripting.',
|
|
1441
|
+
'',
|
|
1442
|
+
'Also available as /stickers from inside the REPL.',
|
|
1443
|
+
],
|
|
1444
|
+
feedback: [
|
|
1445
|
+
'pugi feedback — file a bug / feature / general comment from the CLI.',
|
|
1446
|
+
'',
|
|
1447
|
+
'Interactive five-step wizard:',
|
|
1448
|
+
' 1. category (bug / feature / general / praise)',
|
|
1449
|
+
' 2. rating (1-5 stars)',
|
|
1450
|
+
' 3. comment (multi-line, Ctrl-D submits)',
|
|
1451
|
+
' 4. include redacted last 5 turns? (y/n, default n)',
|
|
1452
|
+
' 5. confirm submit (y/n, default y)',
|
|
1453
|
+
'',
|
|
1454
|
+
'On network failure the envelope is appended to',
|
|
1455
|
+
'.pugi/feedback-queue.jsonl and drained on the next online session.',
|
|
1456
|
+
'',
|
|
1457
|
+
'Also available as /feedback from inside the REPL.',
|
|
1458
|
+
],
|
|
1158
1459
|
deploy: [
|
|
1159
1460
|
'pugi deploy — trigger a vendor deployment from the bound Git source.',
|
|
1160
1461
|
'',
|
|
@@ -1248,6 +1549,10 @@ async function help(args, flags, _session) {
|
|
|
1248
1549
|
' Pairs with PUGI_HIDE_TOOL_STREAM=1.',
|
|
1249
1550
|
' --no-defaults Skip bundled default-skills install on',
|
|
1250
1551
|
' `pugi init`. Pairs with PUGI_INIT_NO_DEFAULTS=1.',
|
|
1552
|
+
' --bare Disable project auto-discovery — no PUGI.md /',
|
|
1553
|
+
' AGENTS.md / CLAUDE.md / GEMINI.md walk-up, no',
|
|
1554
|
+
' auto-init of .pugi/, no persona auto-load.',
|
|
1555
|
+
' Pairs with PUGI_BARE=1.',
|
|
1251
1556
|
'',
|
|
1252
1557
|
PUGI_TAGLINE,
|
|
1253
1558
|
'Execution defaults to local. Use --remote or --web to create a handoff bundle.',
|
|
@@ -1301,6 +1606,86 @@ async function status(_args, flags, _session) {
|
|
|
1301
1606
|
writeOutput: (payload, text) => writeOutput(flags, payload, text),
|
|
1302
1607
|
});
|
|
1303
1608
|
}
|
|
1609
|
+
/**
|
|
1610
|
+
* `pugi stickers` — Leak L33 (2026-05-27). Brand-personality gimmick
|
|
1611
|
+
* mirroring Claude Code's `/stickers` easter egg. Picks one curated
|
|
1612
|
+
* pug-face ASCII variant at random + footers it with a rotating quote
|
|
1613
|
+
* from the Pugi brand corpus. Always exits 0 — never a gate.
|
|
1614
|
+
*
|
|
1615
|
+
* The handler stays thin: corpus + picker + pure renderers live in
|
|
1616
|
+
* `tui/stickers-art.tsx`; this wrapper just hands the resolved result
|
|
1617
|
+
* к the shared `writeOutput` helper so `--json` keeps producing a
|
|
1618
|
+
* structured envelope (id + caption + quote + meta) for scripted
|
|
1619
|
+
* callers. The `--ascii-only` flag drops the box decoration in the
|
|
1620
|
+
* non-JSON path so pipes (`pugi stickers --ascii-only | lolcat`) get
|
|
1621
|
+
* clean plain-text frames.
|
|
1622
|
+
*
|
|
1623
|
+
* The same handler powers the in-REPL `/stickers` slash, which routes
|
|
1624
|
+
* the text through the conversation system pane line-buffer.
|
|
1625
|
+
*/
|
|
1626
|
+
async function stickers(_args, flags, _session) {
|
|
1627
|
+
runStickersCommand({
|
|
1628
|
+
json: flags.json,
|
|
1629
|
+
asciiOnly: flags.asciiOnly,
|
|
1630
|
+
writeOutput: (payload, text) => writeOutput(flags, payload, text),
|
|
1631
|
+
});
|
|
1632
|
+
}
|
|
1633
|
+
/**
|
|
1634
|
+
* `pugi feedback` — Leak L21 (2026-05-27). In-CLI feedback collector.
|
|
1635
|
+
*
|
|
1636
|
+
* Five-step wizard:
|
|
1637
|
+
* 1. category (bug / feature / general / praise)
|
|
1638
|
+
* 2. rating (1-5)
|
|
1639
|
+
* 3. comment (multi-line, Ctrl-D submits)
|
|
1640
|
+
* 4. include redacted session context? (y/n, default n)
|
|
1641
|
+
* 5. confirm submit (y/n, default y)
|
|
1642
|
+
*
|
|
1643
|
+
* POSTs to `<apiUrl>/api/pugi/feedback`. On transient failure (404,
|
|
1644
|
+
* 5xx, network error) the envelope is appended to
|
|
1645
|
+
* `<cwd>/.pugi/feedback-queue.jsonl`. On next online session the
|
|
1646
|
+
* background flusher drains the queue silently.
|
|
1647
|
+
*
|
|
1648
|
+
* Non-TTY callers (CI, pipes) get a one-line "non-interactive — re-run
|
|
1649
|
+
* in a real terminal" stub. The feedback wizard is intentionally
|
|
1650
|
+
* TTY-only — scripting a star-rating + multi-line comment from a
|
|
1651
|
+
* shell pipe would just produce low-signal noise.
|
|
1652
|
+
*/
|
|
1653
|
+
async function dispatchFeedback(_args, flags, _session) {
|
|
1654
|
+
if (!isInteractive(flags)) {
|
|
1655
|
+
writeOutput(flags, {
|
|
1656
|
+
ok: false,
|
|
1657
|
+
error: 'pugi feedback requires an interactive terminal. Re-run from a real TTY.',
|
|
1658
|
+
}, 'pugi feedback: non-interactive shell — re-run from a real terminal.');
|
|
1659
|
+
process.exitCode = 2;
|
|
1660
|
+
return;
|
|
1661
|
+
}
|
|
1662
|
+
const { renderFeedbackPrompt } = await import('../tui/feedback-prompt.js');
|
|
1663
|
+
const { runFeedbackCommand, renderFeedbackToast } = await import('./commands/feedback.js');
|
|
1664
|
+
const { submitFeedback } = await import('../core/feedback/submitter.js');
|
|
1665
|
+
const verdict = await renderFeedbackPrompt();
|
|
1666
|
+
if (verdict.cancelled || !verdict.draft) {
|
|
1667
|
+
writeOutput(flags, { ok: true, kind: 'cancelled' }, 'Feedback cancelled. Nothing was sent.');
|
|
1668
|
+
return;
|
|
1669
|
+
}
|
|
1670
|
+
// Best-effort credential resolution. Anonymous submission is allowed
|
|
1671
|
+
// (the server may still accept it for ungated `/api/pugi/feedback`
|
|
1672
|
+
// routes); on no-credential we route the POST through an empty
|
|
1673
|
+
// bearer + the operator gets the 4xx → "rejected" toast if the
|
|
1674
|
+
// server requires auth.
|
|
1675
|
+
const credential = resolveActiveCredential(process.env);
|
|
1676
|
+
const apiUrl = credential?.apiUrl ?? (process.env.PUGI_API_URL || 'https://api.pugi.io');
|
|
1677
|
+
const apiKey = credential?.apiKey ?? '';
|
|
1678
|
+
const result = await runFeedbackCommand({
|
|
1679
|
+
cwd: process.cwd(),
|
|
1680
|
+
cliVersion: PUGI_CLI_VERSION,
|
|
1681
|
+
submit: async (env) => submitFeedback(env, { apiUrl, apiKey }),
|
|
1682
|
+
draft: verdict.draft,
|
|
1683
|
+
// `pugi feedback` from a fresh shell has no live transcript — the
|
|
1684
|
+
// session-context provider is omitted. The REPL slash variant
|
|
1685
|
+
// wires this in via `runFeedbackSlash` (session.ts).
|
|
1686
|
+
});
|
|
1687
|
+
writeOutput(flags, { ok: true, result }, renderFeedbackToast(result));
|
|
1688
|
+
}
|
|
1304
1689
|
/**
|
|
1305
1690
|
* Programmatic init scaffolder. Idempotent — every helper call is a
|
|
1306
1691
|
* `*_IfMissing` write, so re-running over an existing .pugi/ workspace
|
|
@@ -49,6 +49,7 @@ import { probeMcp } from '../../core/diagnostics/probes/mcp.js';
|
|
|
49
49
|
import { probeConfig } from '../../core/diagnostics/probes/config.js';
|
|
50
50
|
import { probeSession } from '../../core/diagnostics/probes/session.js';
|
|
51
51
|
import { probeDenialTracking } from '../../core/diagnostics/probes/denial-tracking.js';
|
|
52
|
+
import { probeBareMode } from '../../core/diagnostics/probes/bare-mode.js';
|
|
52
53
|
/**
|
|
53
54
|
* Default API URL when no PUGI_API_URL env override is set. Mirrors
|
|
54
55
|
* the constant in `core/credentials.ts` (kept local to avoid an
|
|
@@ -206,6 +207,13 @@ export function buildDefaultProbes(ctx, options = {}) {
|
|
|
206
207
|
...(options.denialTracking ? { tracker: options.denialTracking } : {}),
|
|
207
208
|
}),
|
|
208
209
|
},
|
|
210
|
+
// Leak L22 (2026-05-27): BARE MODE row. Always present so the JSON
|
|
211
|
+
// schema stays stable; status flips to `ok` when `--bare` or
|
|
212
|
+
// `PUGI_BARE=1` is active, otherwise `skipped`.
|
|
213
|
+
{
|
|
214
|
+
name: 'BARE MODE',
|
|
215
|
+
run: async () => probeBareMode({ env: ctx.env }),
|
|
216
|
+
},
|
|
209
217
|
];
|
|
210
218
|
return probes;
|
|
211
219
|
}
|