@pugi/cli 0.1.0-beta.21 → 0.1.0-beta.23
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/auth/env-provider.js +238 -0
- package/dist/core/bare-mode/index.js +107 -0
- package/dist/core/diagnostics/probes/bare-mode.js +42 -0
- package/dist/core/diagnostics/probes/pugi-md.js +89 -0
- package/dist/core/engine/native-pugi.js +55 -11
- 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/pugi-md/context-injector.js +76 -0
- package/dist/core/pugi-md/walk-up.js +207 -0
- package/dist/core/release-notes/parser.js +241 -0
- package/dist/core/release-notes/state.js +116 -0
- package/dist/core/repl/session.js +482 -12
- package/dist/core/repl/slash-commands.js +134 -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/theme/context.js +91 -0
- package/dist/core/theme/presets.js +228 -0
- package/dist/core/theme/state.js +181 -0
- package/dist/core/todos/invariant.js +10 -0
- package/dist/core/todos/state.js +177 -0
- package/dist/core/vim/keymap.js +288 -0
- package/dist/core/vim/state.js +92 -0
- package/dist/runtime/cli.js +603 -15
- package/dist/runtime/commands/doctor.js +21 -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/release-notes.js +229 -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/commands/theme.js +196 -0
- package/dist/runtime/commands/vim.js +140 -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/doctor-table.js +32 -17
- package/dist/tui/feedback-prompt.js +156 -0
- package/dist/tui/onboarding-wizard.js +240 -0
- package/dist/tui/repl-render.js +26 -3
- package/dist/tui/repl.js +9 -1
- package/dist/tui/stickers-art.js +136 -0
- package/dist/tui/style-table.js +28 -0
- package/dist/tui/theme-table.js +29 -0
- package/dist/tui/vim-input.js +267 -0
- package/package.json +2 -2
- package/dist/core/engine/compaction-hook.js +0 -154
- package/dist/core/init/scaffold.js +0 -195
- package/dist/core/repl/codebase-survey.js +0 -308
- package/dist/core/repl/init-interview.js +0 -457
- package/dist/core/repl/onboarding-state.js +0 -297
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Leak L18 (2026-05-27) — `pugi style` top-level command + REPL slash
|
|
3
|
+
* companion.
|
|
4
|
+
*
|
|
5
|
+
* Operator surface:
|
|
6
|
+
*
|
|
7
|
+
* pugi style Show active preset + table.
|
|
8
|
+
* pugi style <name> Switch workspace preset (current cwd).
|
|
9
|
+
* pugi style <name> --persist Switch + also write user default.
|
|
10
|
+
* pugi style --reset Clear workspace override → back to default.
|
|
11
|
+
* pugi style --reset --user Also clear the user default.
|
|
12
|
+
* pugi style --list Print the catalogue (no flip).
|
|
13
|
+
* pugi style --json Structured envelope variant.
|
|
14
|
+
*
|
|
15
|
+
* The same runner powers `/style` from inside the REPL. The REPL
|
|
16
|
+
* dispatcher (see `core/repl/session.ts`) routes through here so the
|
|
17
|
+
* two surfaces stay single-sourced — operators trained on one read
|
|
18
|
+
* the same payload + table on the other.
|
|
19
|
+
*
|
|
20
|
+
* Exit codes:
|
|
21
|
+
* 0 — show / switch / reset all succeed
|
|
22
|
+
* 1 — unknown preset slug (returned BEFORE any write)
|
|
23
|
+
* 2 — conflicting flags (e.g. `--reset` with a positional slug)
|
|
24
|
+
*
|
|
25
|
+
* The exit codes are surfaced through `process.exitCode` by the
|
|
26
|
+
* dispatcher in `cli.ts` — this module returns a structured payload
|
|
27
|
+
* + writes via the injected `writeOutput`. Throwing is reserved for
|
|
28
|
+
* truly unexpected errors (fs permissions etc.); the spec hooks the
|
|
29
|
+
* happy + sad paths through `writeOutput` shape, not via try/catch
|
|
30
|
+
* on the throw.
|
|
31
|
+
*/
|
|
32
|
+
import { DEFAULT_OUTPUT_STYLE, isOutputStyleSlug, OUTPUT_STYLE_SLUGS, renderStyleTable, } from '../../core/output-style/presets.js';
|
|
33
|
+
import { clearUserOutputStyle, clearWorkspaceOutputStyle, resolveOutputStyle, setUserOutputStyle, setWorkspaceOutputStyle, } from '../../core/output-style/state.js';
|
|
34
|
+
/**
|
|
35
|
+
* Entry point. Parses `args`, applies the operation, emits the
|
|
36
|
+
* payload + text via `ctx.writeOutput`, and returns the exit code the
|
|
37
|
+
* dispatcher should hand back to the shell.
|
|
38
|
+
*/
|
|
39
|
+
export async function runStyleCommand(args, ctx) {
|
|
40
|
+
const flags = parseFlags(args);
|
|
41
|
+
// Reset path
|
|
42
|
+
if (flags.reset) {
|
|
43
|
+
if (flags.slug !== null) {
|
|
44
|
+
const payload = buildPayload({
|
|
45
|
+
status: 'invalid_flags',
|
|
46
|
+
ctx,
|
|
47
|
+
message: '/style --reset cannot be combined with a preset name. Use one or the other.',
|
|
48
|
+
});
|
|
49
|
+
ctx.writeOutput(payload, payload.message);
|
|
50
|
+
return 2;
|
|
51
|
+
}
|
|
52
|
+
if (flags.persist) {
|
|
53
|
+
const payload = buildPayload({
|
|
54
|
+
status: 'invalid_flags',
|
|
55
|
+
ctx,
|
|
56
|
+
message: '/style --reset cannot be combined with --persist. Use --reset --user to also clear the user default.',
|
|
57
|
+
});
|
|
58
|
+
ctx.writeOutput(payload, payload.message);
|
|
59
|
+
return 2;
|
|
60
|
+
}
|
|
61
|
+
clearWorkspaceOutputStyle({ workspaceRoot: ctx.workspaceRoot, env: ctx.env });
|
|
62
|
+
if (flags.user) {
|
|
63
|
+
clearUserOutputStyle({ workspaceRoot: ctx.workspaceRoot, env: ctx.env });
|
|
64
|
+
}
|
|
65
|
+
const payload = buildPayload({
|
|
66
|
+
status: 'reset',
|
|
67
|
+
ctx,
|
|
68
|
+
message: flags.user
|
|
69
|
+
? `Cleared workspace + user output style. Active: ${DEFAULT_OUTPUT_STYLE} (default).`
|
|
70
|
+
: `Cleared workspace output style. Active: ${describeActive(ctx)}.`,
|
|
71
|
+
});
|
|
72
|
+
ctx.writeOutput(payload, payload.message);
|
|
73
|
+
return 0;
|
|
74
|
+
}
|
|
75
|
+
// List path
|
|
76
|
+
if (flags.list && flags.slug === null) {
|
|
77
|
+
const resolved = resolveOutputStyle({ workspaceRoot: ctx.workspaceRoot, env: ctx.env });
|
|
78
|
+
const payload = buildPayload({
|
|
79
|
+
status: 'listed',
|
|
80
|
+
ctx,
|
|
81
|
+
message: renderStyleTable(resolved.slug),
|
|
82
|
+
});
|
|
83
|
+
ctx.writeOutput(payload, payload.message);
|
|
84
|
+
return 0;
|
|
85
|
+
}
|
|
86
|
+
// Switch path
|
|
87
|
+
if (flags.slug !== null) {
|
|
88
|
+
if (!isOutputStyleSlug(flags.slug)) {
|
|
89
|
+
const payload = buildPayload({
|
|
90
|
+
status: 'invalid_slug',
|
|
91
|
+
ctx,
|
|
92
|
+
attemptedSlug: flags.slug,
|
|
93
|
+
message: `Unknown style "${flags.slug}". Try one of: ${OUTPUT_STYLE_SLUGS.join(', ')}.`,
|
|
94
|
+
});
|
|
95
|
+
ctx.writeOutput(payload, payload.message);
|
|
96
|
+
return 1;
|
|
97
|
+
}
|
|
98
|
+
const before = resolveOutputStyle({ workspaceRoot: ctx.workspaceRoot, env: ctx.env });
|
|
99
|
+
setWorkspaceOutputStyle(flags.slug, { workspaceRoot: ctx.workspaceRoot, env: ctx.env });
|
|
100
|
+
if (flags.persist) {
|
|
101
|
+
setUserOutputStyle(flags.slug, { workspaceRoot: ctx.workspaceRoot, env: ctx.env });
|
|
102
|
+
}
|
|
103
|
+
const tail = flags.persist ? ' (workspace + user default)' : ' (workspace)';
|
|
104
|
+
const payload = buildPayload({
|
|
105
|
+
status: 'switched',
|
|
106
|
+
ctx,
|
|
107
|
+
previous: before.slug,
|
|
108
|
+
persistedToUser: flags.persist,
|
|
109
|
+
message: `Output style → ${flags.slug}${tail}. Was: ${before.slug} (${before.source}).`,
|
|
110
|
+
});
|
|
111
|
+
ctx.writeOutput(payload, payload.message);
|
|
112
|
+
return 0;
|
|
113
|
+
}
|
|
114
|
+
// Show path (no args)
|
|
115
|
+
const resolved = resolveOutputStyle({ workspaceRoot: ctx.workspaceRoot, env: ctx.env });
|
|
116
|
+
const banner = `Active output style: ${resolved.slug} (${resolved.source})`;
|
|
117
|
+
const table = renderStyleTable(resolved.slug);
|
|
118
|
+
const payload = buildPayload({
|
|
119
|
+
status: 'show',
|
|
120
|
+
ctx,
|
|
121
|
+
message: `${banner}\n\n${table}`,
|
|
122
|
+
});
|
|
123
|
+
ctx.writeOutput(payload, payload.message);
|
|
124
|
+
return 0;
|
|
125
|
+
}
|
|
126
|
+
function describeActive(ctx) {
|
|
127
|
+
const resolved = resolveOutputStyle({ workspaceRoot: ctx.workspaceRoot, env: ctx.env });
|
|
128
|
+
return `${resolved.slug} (${resolved.source})`;
|
|
129
|
+
}
|
|
130
|
+
function buildPayload(args) {
|
|
131
|
+
const resolved = resolveOutputStyle({ workspaceRoot: args.ctx.workspaceRoot, env: args.ctx.env });
|
|
132
|
+
const payload = {
|
|
133
|
+
command: 'style',
|
|
134
|
+
status: args.status,
|
|
135
|
+
active: resolved.slug,
|
|
136
|
+
source: resolved.source,
|
|
137
|
+
presets: OUTPUT_STYLE_SLUGS,
|
|
138
|
+
message: args.message,
|
|
139
|
+
};
|
|
140
|
+
if (args.previous !== undefined)
|
|
141
|
+
payload.previous = args.previous;
|
|
142
|
+
if (args.persistedToUser !== undefined)
|
|
143
|
+
payload.persistedToUser = args.persistedToUser;
|
|
144
|
+
if (args.attemptedSlug !== undefined)
|
|
145
|
+
payload.attemptedSlug = args.attemptedSlug;
|
|
146
|
+
return payload;
|
|
147
|
+
}
|
|
148
|
+
function parseFlags(args) {
|
|
149
|
+
const flags = {
|
|
150
|
+
slug: null,
|
|
151
|
+
persist: false,
|
|
152
|
+
reset: false,
|
|
153
|
+
user: false,
|
|
154
|
+
list: false,
|
|
155
|
+
};
|
|
156
|
+
for (const arg of args) {
|
|
157
|
+
if (arg === '--persist')
|
|
158
|
+
flags.persist = true;
|
|
159
|
+
else if (arg === '--reset')
|
|
160
|
+
flags.reset = true;
|
|
161
|
+
else if (arg === '--user')
|
|
162
|
+
flags.user = true;
|
|
163
|
+
else if (arg === '--list')
|
|
164
|
+
flags.list = true;
|
|
165
|
+
else if (arg.startsWith('-')) {
|
|
166
|
+
// Unknown flag — keep simple parser. Treat as positional so the
|
|
167
|
+
// downstream isOutputStyleSlug check rejects it with a clear
|
|
168
|
+
// "unknown style" message rather than swallowing silently.
|
|
169
|
+
if (flags.slug === null)
|
|
170
|
+
flags.slug = arg;
|
|
171
|
+
}
|
|
172
|
+
else if (flags.slug === null) {
|
|
173
|
+
flags.slug = arg;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
// Normalise the slug to lowercase so `pugi style TERSE` works the
|
|
177
|
+
// same as `pugi style terse`. The catalogue is lowercase-only by
|
|
178
|
+
// contract; this keeps operators from tripping on shift-key habits.
|
|
179
|
+
if (flags.slug !== null)
|
|
180
|
+
flags.slug = flags.slug.toLowerCase();
|
|
181
|
+
return flags;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Re-export for the slash-command dispatcher in `core/repl/session.ts`
|
|
185
|
+
* so it can render a compiled prompt block when the operator runs
|
|
186
|
+
* `/style --preview` (follow-up surface; current slash returns the
|
|
187
|
+
* runner's standard payload).
|
|
188
|
+
*
|
|
189
|
+
* Kept here so the runtime module is the single import point for
|
|
190
|
+
* style-related surfaces; consumers should NOT reach into
|
|
191
|
+
* `core/output-style/*` directly.
|
|
192
|
+
*/
|
|
193
|
+
export { OUTPUT_STYLES as OUTPUT_STYLE_CATALOGUE, } from '../../core/output-style/presets.js';
|
|
194
|
+
//# sourceMappingURL=style.js.map
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Leak L30 (2026-05-27) — `pugi theme` top-level command + REPL slash
|
|
3
|
+
* companion.
|
|
4
|
+
*
|
|
5
|
+
* Operator surface:
|
|
6
|
+
*
|
|
7
|
+
* pugi theme Show active theme + table.
|
|
8
|
+
* pugi theme <name> Switch workspace theme (current cwd).
|
|
9
|
+
* pugi theme <name> --persist Switch + also write user default.
|
|
10
|
+
* pugi theme --reset Clear workspace override → back to default.
|
|
11
|
+
* pugi theme --reset --user Also clear the user default.
|
|
12
|
+
* pugi theme --list Print the catalogue (no flip).
|
|
13
|
+
* pugi theme --json Structured envelope variant.
|
|
14
|
+
*
|
|
15
|
+
* The same runner powers `/theme` from inside the REPL. The REPL
|
|
16
|
+
* dispatcher (see `core/repl/session.ts`) routes through here so the
|
|
17
|
+
* two surfaces stay single-sourced — operators trained on one read
|
|
18
|
+
* the same payload + table on the other. Matches the leak L18
|
|
19
|
+
* `/style` runner exactly so the two settings surfaces are
|
|
20
|
+
* paste-comparable for the operator + grep-comparable for future
|
|
21
|
+
* maintenance.
|
|
22
|
+
*
|
|
23
|
+
* Exit codes:
|
|
24
|
+
* 0 — show / switch / reset all succeed
|
|
25
|
+
* 1 — unknown preset slug (returned BEFORE any write)
|
|
26
|
+
* 2 — conflicting flags (e.g. `--reset` with a positional slug)
|
|
27
|
+
*
|
|
28
|
+
* The exit codes are surfaced through `process.exitCode` by the
|
|
29
|
+
* dispatcher in `cli.ts` — this module returns a structured payload
|
|
30
|
+
* + writes via the injected `writeOutput`. Throwing is reserved for
|
|
31
|
+
* truly unexpected errors (fs permissions etc.); the spec hooks the
|
|
32
|
+
* happy + sad paths through `writeOutput` shape, not via try/catch
|
|
33
|
+
* on the throw.
|
|
34
|
+
*/
|
|
35
|
+
import { DEFAULT_THEME, isThemeSlug, renderThemeTable, THEME_SLUGS, } from '../../core/theme/presets.js';
|
|
36
|
+
import { clearUserTheme, clearWorkspaceTheme, resolveTheme, setUserTheme, setWorkspaceTheme, } from '../../core/theme/state.js';
|
|
37
|
+
/**
|
|
38
|
+
* Entry point. Parses `args`, applies the operation, emits the
|
|
39
|
+
* payload + text via `ctx.writeOutput`, and returns the exit code the
|
|
40
|
+
* dispatcher should hand back to the shell.
|
|
41
|
+
*/
|
|
42
|
+
export async function runThemeCommand(args, ctx) {
|
|
43
|
+
const flags = parseFlags(args);
|
|
44
|
+
// Reset path
|
|
45
|
+
if (flags.reset) {
|
|
46
|
+
if (flags.slug !== null) {
|
|
47
|
+
const payload = buildPayload({
|
|
48
|
+
status: 'invalid_flags',
|
|
49
|
+
ctx,
|
|
50
|
+
message: '/theme --reset cannot be combined with a preset name. Use one or the other.',
|
|
51
|
+
});
|
|
52
|
+
ctx.writeOutput(payload, payload.message);
|
|
53
|
+
return 2;
|
|
54
|
+
}
|
|
55
|
+
if (flags.persist) {
|
|
56
|
+
const payload = buildPayload({
|
|
57
|
+
status: 'invalid_flags',
|
|
58
|
+
ctx,
|
|
59
|
+
message: '/theme --reset cannot be combined with --persist. Use --reset --user to also clear the user default.',
|
|
60
|
+
});
|
|
61
|
+
ctx.writeOutput(payload, payload.message);
|
|
62
|
+
return 2;
|
|
63
|
+
}
|
|
64
|
+
clearWorkspaceTheme({ workspaceRoot: ctx.workspaceRoot, env: ctx.env });
|
|
65
|
+
if (flags.user) {
|
|
66
|
+
clearUserTheme({ workspaceRoot: ctx.workspaceRoot, env: ctx.env });
|
|
67
|
+
}
|
|
68
|
+
const payload = buildPayload({
|
|
69
|
+
status: 'reset',
|
|
70
|
+
ctx,
|
|
71
|
+
message: flags.user
|
|
72
|
+
? `Cleared workspace + user theme. Active: ${DEFAULT_THEME} (default).`
|
|
73
|
+
: `Cleared workspace theme. Active: ${describeActive(ctx)}.`,
|
|
74
|
+
});
|
|
75
|
+
ctx.writeOutput(payload, payload.message);
|
|
76
|
+
return 0;
|
|
77
|
+
}
|
|
78
|
+
// List path
|
|
79
|
+
if (flags.list && flags.slug === null) {
|
|
80
|
+
const resolved = resolveTheme({ workspaceRoot: ctx.workspaceRoot, env: ctx.env });
|
|
81
|
+
const payload = buildPayload({
|
|
82
|
+
status: 'listed',
|
|
83
|
+
ctx,
|
|
84
|
+
message: renderThemeTable(resolved.slug),
|
|
85
|
+
});
|
|
86
|
+
ctx.writeOutput(payload, payload.message);
|
|
87
|
+
return 0;
|
|
88
|
+
}
|
|
89
|
+
// Switch path
|
|
90
|
+
if (flags.slug !== null) {
|
|
91
|
+
if (!isThemeSlug(flags.slug)) {
|
|
92
|
+
const payload = buildPayload({
|
|
93
|
+
status: 'invalid_slug',
|
|
94
|
+
ctx,
|
|
95
|
+
attemptedSlug: flags.slug,
|
|
96
|
+
message: `Unknown theme "${flags.slug}". Try one of: ${THEME_SLUGS.join(', ')}.`,
|
|
97
|
+
});
|
|
98
|
+
ctx.writeOutput(payload, payload.message);
|
|
99
|
+
return 1;
|
|
100
|
+
}
|
|
101
|
+
const before = resolveTheme({ workspaceRoot: ctx.workspaceRoot, env: ctx.env });
|
|
102
|
+
setWorkspaceTheme(flags.slug, { workspaceRoot: ctx.workspaceRoot, env: ctx.env });
|
|
103
|
+
if (flags.persist) {
|
|
104
|
+
setUserTheme(flags.slug, { workspaceRoot: ctx.workspaceRoot, env: ctx.env });
|
|
105
|
+
}
|
|
106
|
+
const tail = flags.persist ? ' (workspace + user default)' : ' (workspace)';
|
|
107
|
+
const payload = buildPayload({
|
|
108
|
+
status: 'switched',
|
|
109
|
+
ctx,
|
|
110
|
+
previous: before.slug,
|
|
111
|
+
persistedToUser: flags.persist,
|
|
112
|
+
message: `Theme → ${flags.slug}${tail}. Was: ${before.slug} (${before.source}).`,
|
|
113
|
+
});
|
|
114
|
+
ctx.writeOutput(payload, payload.message);
|
|
115
|
+
return 0;
|
|
116
|
+
}
|
|
117
|
+
// Show path (no args)
|
|
118
|
+
const resolved = resolveTheme({ workspaceRoot: ctx.workspaceRoot, env: ctx.env });
|
|
119
|
+
const banner = `Active theme: ${resolved.slug} (${resolved.source})`;
|
|
120
|
+
const table = renderThemeTable(resolved.slug);
|
|
121
|
+
const payload = buildPayload({
|
|
122
|
+
status: 'show',
|
|
123
|
+
ctx,
|
|
124
|
+
message: `${banner}\n\n${table}`,
|
|
125
|
+
});
|
|
126
|
+
ctx.writeOutput(payload, payload.message);
|
|
127
|
+
return 0;
|
|
128
|
+
}
|
|
129
|
+
function describeActive(ctx) {
|
|
130
|
+
const resolved = resolveTheme({ workspaceRoot: ctx.workspaceRoot, env: ctx.env });
|
|
131
|
+
return `${resolved.slug} (${resolved.source})`;
|
|
132
|
+
}
|
|
133
|
+
function buildPayload(args) {
|
|
134
|
+
const resolved = resolveTheme({ workspaceRoot: args.ctx.workspaceRoot, env: args.ctx.env });
|
|
135
|
+
const payload = {
|
|
136
|
+
command: 'theme',
|
|
137
|
+
status: args.status,
|
|
138
|
+
active: resolved.slug,
|
|
139
|
+
source: resolved.source,
|
|
140
|
+
presets: THEME_SLUGS,
|
|
141
|
+
message: args.message,
|
|
142
|
+
};
|
|
143
|
+
if (args.previous !== undefined)
|
|
144
|
+
payload.previous = args.previous;
|
|
145
|
+
if (args.persistedToUser !== undefined)
|
|
146
|
+
payload.persistedToUser = args.persistedToUser;
|
|
147
|
+
if (args.attemptedSlug !== undefined)
|
|
148
|
+
payload.attemptedSlug = args.attemptedSlug;
|
|
149
|
+
return payload;
|
|
150
|
+
}
|
|
151
|
+
function parseFlags(args) {
|
|
152
|
+
const flags = {
|
|
153
|
+
slug: null,
|
|
154
|
+
persist: false,
|
|
155
|
+
reset: false,
|
|
156
|
+
user: false,
|
|
157
|
+
list: false,
|
|
158
|
+
};
|
|
159
|
+
for (const arg of args) {
|
|
160
|
+
if (arg === '--persist')
|
|
161
|
+
flags.persist = true;
|
|
162
|
+
else if (arg === '--reset')
|
|
163
|
+
flags.reset = true;
|
|
164
|
+
else if (arg === '--user')
|
|
165
|
+
flags.user = true;
|
|
166
|
+
else if (arg === '--list')
|
|
167
|
+
flags.list = true;
|
|
168
|
+
else if (arg.startsWith('-')) {
|
|
169
|
+
// Unknown flag — keep simple parser. Treat as positional so the
|
|
170
|
+
// downstream isThemeSlug check rejects it with a clear "unknown
|
|
171
|
+
// theme" message rather than swallowing silently. Mirrors the
|
|
172
|
+
// L18 style runner's behaviour for grep-parity.
|
|
173
|
+
if (flags.slug === null)
|
|
174
|
+
flags.slug = arg;
|
|
175
|
+
}
|
|
176
|
+
else if (flags.slug === null) {
|
|
177
|
+
flags.slug = arg;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
// Normalise the slug to lowercase so `pugi theme DARK` works the
|
|
181
|
+
// same as `pugi theme dark`. The catalogue is lowercase-only by
|
|
182
|
+
// contract; this keeps operators from tripping on shift-key habits.
|
|
183
|
+
if (flags.slug !== null)
|
|
184
|
+
flags.slug = flags.slug.toLowerCase();
|
|
185
|
+
return flags;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Re-export for the slash-command dispatcher in
|
|
189
|
+
* `core/repl/session.ts` so it can render a preview block if a
|
|
190
|
+
* future surface (`/theme --preview`) lands. Kept here so the
|
|
191
|
+
* runtime module is the single import point for theme-related
|
|
192
|
+
* surfaces; consumers should NOT reach into `core/theme/*` directly
|
|
193
|
+
* unless they are Ink components that need the React context.
|
|
194
|
+
*/
|
|
195
|
+
export { THEMES as THEME_CATALOGUE, } from '../../core/theme/presets.js';
|
|
196
|
+
//# sourceMappingURL=theme.js.map
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Leak L26 (2026-05-27) — `pugi vim` top-level command + REPL slash
|
|
3
|
+
* companion.
|
|
4
|
+
*
|
|
5
|
+
* Operator surface:
|
|
6
|
+
*
|
|
7
|
+
* pugi vim Show current vim-mode state.
|
|
8
|
+
* pugi vim on Enable + persist (~/.pugi/config.json).
|
|
9
|
+
* pugi vim off Disable + persist (default).
|
|
10
|
+
* /vim Toggle from inside the REPL.
|
|
11
|
+
* /vim on Enable from inside the REPL.
|
|
12
|
+
* /vim off Disable from inside the REPL.
|
|
13
|
+
*
|
|
14
|
+
* The same runner backs both surfaces so the slash + the shell verb
|
|
15
|
+
* stay single-sourced — operators trained on one read the same
|
|
16
|
+
* payload + banner on the other.
|
|
17
|
+
*
|
|
18
|
+
* Exit codes:
|
|
19
|
+
* 0 — show / enable / disable / toggle happy path
|
|
20
|
+
* 2 — unknown subcommand (e.g. `pugi vim chaos`)
|
|
21
|
+
*
|
|
22
|
+
* NOTE: there are NO `--persist` flag and no workspace tier. Vim mode
|
|
23
|
+
* is a single user-level preference, matching the leak research note
|
|
24
|
+
* that operators expect modal editing to be a body-memory trait, not
|
|
25
|
+
* a per-repo concern (see `core/vim/state.ts` header).
|
|
26
|
+
*/
|
|
27
|
+
import { resolveVimMode, setVimMode } from '../../core/vim/state.js';
|
|
28
|
+
/**
|
|
29
|
+
* Banner shown on enable so the operator can see the binding cheat
|
|
30
|
+
* sheet without reaching for `/help`. Echoed by both surfaces.
|
|
31
|
+
*/
|
|
32
|
+
export const VIM_ENABLED_BANNER = 'Vim mode on. Esc=normal, i=insert, :w=submit, :q=cancel.';
|
|
33
|
+
/**
|
|
34
|
+
* Entry point. Parses `args`, flips persistence as needed, emits the
|
|
35
|
+
* payload + text via `ctx.writeOutput`, and returns the exit code.
|
|
36
|
+
*/
|
|
37
|
+
export async function runVimCommand(args, ctx) {
|
|
38
|
+
// Validate args before reading state so a bad call never has a
|
|
39
|
+
// side-effect.
|
|
40
|
+
if (args.length > 1) {
|
|
41
|
+
const payload = {
|
|
42
|
+
command: 'vim',
|
|
43
|
+
status: 'invalid_args',
|
|
44
|
+
active: resolveVimMode({ env: ctx.env }),
|
|
45
|
+
message: `pugi vim takes at most one argument (on / off / toggle). Got: ${args.join(' ')}`,
|
|
46
|
+
attemptedArg: args.join(' '),
|
|
47
|
+
};
|
|
48
|
+
ctx.writeOutput(payload, payload.message);
|
|
49
|
+
return 2;
|
|
50
|
+
}
|
|
51
|
+
const current = resolveVimMode({ env: ctx.env });
|
|
52
|
+
const verb = args[0]?.toLowerCase() ?? '';
|
|
53
|
+
// No args → toggle (slash convention from the leak research). The
|
|
54
|
+
// CLI shell surface ALSO toggles on bare invocation so the two
|
|
55
|
+
// surfaces converge; if the operator wants the read-only show they
|
|
56
|
+
// can pipe through `pugi vim --json` or query the config directly.
|
|
57
|
+
// We surface the change as a `show` status when nothing flipped so
|
|
58
|
+
// scripts can distinguish read from write.
|
|
59
|
+
if (verb === '') {
|
|
60
|
+
const next = !current;
|
|
61
|
+
setVimMode(next, { env: ctx.env });
|
|
62
|
+
const status = next ? 'enabled' : 'disabled';
|
|
63
|
+
const message = next ? VIM_ENABLED_BANNER : 'Vim mode off.';
|
|
64
|
+
const payload = {
|
|
65
|
+
command: 'vim',
|
|
66
|
+
status,
|
|
67
|
+
active: next,
|
|
68
|
+
previous: current,
|
|
69
|
+
message,
|
|
70
|
+
};
|
|
71
|
+
ctx.writeOutput(payload, payload.message);
|
|
72
|
+
return 0;
|
|
73
|
+
}
|
|
74
|
+
if (verb === 'on' || verb === 'enable' || verb === 'true') {
|
|
75
|
+
if (current) {
|
|
76
|
+
const payload = {
|
|
77
|
+
command: 'vim',
|
|
78
|
+
status: 'unchanged',
|
|
79
|
+
active: true,
|
|
80
|
+
previous: true,
|
|
81
|
+
message: 'Vim mode already on.',
|
|
82
|
+
};
|
|
83
|
+
ctx.writeOutput(payload, payload.message);
|
|
84
|
+
return 0;
|
|
85
|
+
}
|
|
86
|
+
setVimMode(true, { env: ctx.env });
|
|
87
|
+
const payload = {
|
|
88
|
+
command: 'vim',
|
|
89
|
+
status: 'enabled',
|
|
90
|
+
active: true,
|
|
91
|
+
previous: current,
|
|
92
|
+
message: VIM_ENABLED_BANNER,
|
|
93
|
+
};
|
|
94
|
+
ctx.writeOutput(payload, payload.message);
|
|
95
|
+
return 0;
|
|
96
|
+
}
|
|
97
|
+
if (verb === 'off' || verb === 'disable' || verb === 'false') {
|
|
98
|
+
if (!current) {
|
|
99
|
+
const payload = {
|
|
100
|
+
command: 'vim',
|
|
101
|
+
status: 'unchanged',
|
|
102
|
+
active: false,
|
|
103
|
+
previous: false,
|
|
104
|
+
message: 'Vim mode already off.',
|
|
105
|
+
};
|
|
106
|
+
ctx.writeOutput(payload, payload.message);
|
|
107
|
+
return 0;
|
|
108
|
+
}
|
|
109
|
+
setVimMode(false, { env: ctx.env });
|
|
110
|
+
const payload = {
|
|
111
|
+
command: 'vim',
|
|
112
|
+
status: 'disabled',
|
|
113
|
+
active: false,
|
|
114
|
+
previous: current,
|
|
115
|
+
message: 'Vim mode off.',
|
|
116
|
+
};
|
|
117
|
+
ctx.writeOutput(payload, payload.message);
|
|
118
|
+
return 0;
|
|
119
|
+
}
|
|
120
|
+
if (verb === 'status' || verb === 'show') {
|
|
121
|
+
const payload = {
|
|
122
|
+
command: 'vim',
|
|
123
|
+
status: 'show',
|
|
124
|
+
active: current,
|
|
125
|
+
message: current ? 'Vim mode is on.' : 'Vim mode is off.',
|
|
126
|
+
};
|
|
127
|
+
ctx.writeOutput(payload, payload.message);
|
|
128
|
+
return 0;
|
|
129
|
+
}
|
|
130
|
+
const payload = {
|
|
131
|
+
command: 'vim',
|
|
132
|
+
status: 'invalid_args',
|
|
133
|
+
active: current,
|
|
134
|
+
message: `Unknown vim subcommand "${verb}". Try one of: on, off, status.`,
|
|
135
|
+
attemptedArg: verb,
|
|
136
|
+
};
|
|
137
|
+
ctx.writeOutput(payload, payload.message);
|
|
138
|
+
return 2;
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=vim.js.map
|
package/dist/runtime/version.js
CHANGED
|
@@ -44,7 +44,7 @@ export function sanitizeSemver(raw) {
|
|
|
44
44
|
* during import). When bumping the CLI version BOTH literals must be
|
|
45
45
|
* updated; the release smoke-test (`pack:smoke`) verifies they agree.
|
|
46
46
|
*/
|
|
47
|
-
export const PUGI_CLI_VERSION = sanitizeSemver('0.1.0-beta.
|
|
47
|
+
export const PUGI_CLI_VERSION = sanitizeSemver('0.1.0-beta.23');
|
|
48
48
|
/**
|
|
49
49
|
* Outbound: the CLI's installed semver. Read at request time by
|
|
50
50
|
* `version-interceptor.ts` and injected on every `fetch` call.
|
package/dist/tools/registry.js
CHANGED
|
@@ -33,6 +33,14 @@ const registry = [
|
|
|
33
33
|
{ name: 'task_get', permission: 'none', risk: 'low', concurrencySafe: true, m1: true },
|
|
34
34
|
{ name: 'task_list', permission: 'none', risk: 'low', concurrencySafe: true, m1: true },
|
|
35
35
|
{ name: 'task_update', permission: 'none', risk: 'low', concurrencySafe: false, m1: true },
|
|
36
|
+
// Leak L16 (2026-05-27): batch TodoWrite. Mirrors Claude Code's upstream
|
|
37
|
+
// surface — full board snapshot, single-in-progress invariant, atomic
|
|
38
|
+
// tmp+rename persistence to `.pugi/todos.json`. `concurrencySafe = false`
|
|
39
|
+
// because two concurrent writes could lose the loser's snapshot (the
|
|
40
|
+
// rename is atomic but the read-modify-write loop is not). Risk = low
|
|
41
|
+
// because the only filesystem mutation lands inside `.pugi/todos.json`,
|
|
42
|
+
// which is metadata, not source.
|
|
43
|
+
{ name: 'todo_write', permission: 'none', risk: 'low', concurrencySafe: false, m1: true },
|
|
36
44
|
{ name: 'web_fetch', permission: 'network', risk: 'medium', concurrencySafe: true, m1: true },
|
|
37
45
|
// α7.7: scratch worktree management. `worktree_create` writes nothing
|
|
38
46
|
// dangerous (a clone under `.pugi/worktrees/`); `worktree_promote`
|