@ketchao/nextup 0.1.0 → 0.1.1
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/README.md +74 -74
- package/dist/cli.js +40 -40
- package/package.json +14 -6
package/README.md
CHANGED
|
@@ -1,67 +1,67 @@
|
|
|
1
|
-
# nextup (
|
|
1
|
+
# nextup (placeholder name)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Next-step menu for coding agents. MVP: **Claude Code** and **Codex** (same hook protocol).
|
|
4
4
|
|
|
5
|
-
>
|
|
5
|
+
> The name is a placeholder. The reference screenshot is from a product called "Then", so that name should not be used.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
After each substantial turn, it shows up to 4 contextual options (with a cost indicator). The user answers by typing the number in the normal Claude Code box.
|
|
8
8
|
|
|
9
9
|
```
|
|
10
|
-
◆
|
|
11
|
-
|
|
12
|
-
1
|
|
13
|
-
2
|
|
14
|
-
3
|
|
15
|
-
4
|
|
16
|
-
5
|
|
10
|
+
◆ What's next? Type a number · +1 2 = several
|
|
11
|
+
cost ▾
|
|
12
|
+
1 Commit changes ★ suggested ■□□ low
|
|
13
|
+
2 Review my changes ■■□ medium
|
|
14
|
+
3 Write tests ■■■ high
|
|
15
|
+
4 Leave it as is □□□ none
|
|
16
|
+
5 Something else (free text)
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
##
|
|
19
|
+
## Try it (5 minutes)
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
22
|
npm install
|
|
23
|
-
npm test #
|
|
24
|
-
npm run preview #
|
|
23
|
+
npm test # 103 tests
|
|
24
|
+
npm run preview # see the menu without Claude (iterate the design here)
|
|
25
25
|
npm run preview -- --scenario failed --width 60
|
|
26
26
|
npm run build
|
|
27
27
|
|
|
28
|
-
#
|
|
28
|
+
# in a test repo, from the published package:
|
|
29
29
|
npm install -g @ketchao/nextup
|
|
30
30
|
nextup init --agent codex
|
|
31
|
-
claude #
|
|
31
|
+
claude # ask for a small change and watch what happens when it finishes
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
`init --user`
|
|
34
|
+
`init --user` installs the hooks globally; `init --remove` removes them. The first time, it writes a `settings.json.nextup.bak` backup.
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
Try a hook without Claude:
|
|
37
37
|
|
|
38
38
|
```bash
|
|
39
39
|
cat fixtures/stop.json | NEXTUP_ALWAYS=1 node dist/cli.js hook stop
|
|
40
40
|
cat fixtures/prompt-submit.json | node dist/cli.js hook prompt-submit
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
-
##
|
|
43
|
+
## Try it with Codex (experimental)
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
`hook stop` and `hook prompt-submit` read and write the same JSON format Codex uses, so there is no new adapter: only the registration path changes.
|
|
46
46
|
|
|
47
47
|
```bash
|
|
48
|
-
node dist/cli.js init --agent codex #
|
|
49
|
-
codex #
|
|
48
|
+
node dist/cli.js init --agent codex # writes .codex/hooks.json (or --user for ~/.codex/hooks.json)
|
|
49
|
+
codex # then type /hooks and approve (trust) the 2 nextup hooks
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
- Codex
|
|
54
|
-
-
|
|
55
|
-
-
|
|
56
|
-
- Codex
|
|
52
|
+
Notes:
|
|
53
|
+
- Codex ignores new or changed hooks until you review them with `/hooks`.
|
|
54
|
+
- On native Windows (Codex CLI 0.166.0, cmd) the hooks **do run**. The `systemMessage` shows up as "Hook · ..." with ANSI colors and alignment intact.
|
|
55
|
+
- The transcript reader (`transcript.ts`) is best effort and understands Claude Code and Codex formats. It only reads the current turn (the `UserPromptSubmit` hook marks where it starts). If something looks wrong, turn on `debug` and check `debug.jsonl`: the `stop-signals` entry shows the detected commands and exit codes, plus a summary of the lines read.
|
|
56
|
+
- Codex runs hooks from a background server that **does not inherit your environment variables**: use `~/.nextup/config.json` (below) or start `codex --no-daemon`.
|
|
57
57
|
|
|
58
|
-
##
|
|
58
|
+
## Configuration
|
|
59
59
|
|
|
60
|
-
|
|
60
|
+
Environment variables **or** the file `~/.nextup/config.json` (environment variables win):
|
|
61
61
|
|
|
62
62
|
```json
|
|
63
63
|
{
|
|
64
|
-
"language": "
|
|
64
|
+
"language": "en",
|
|
65
65
|
"suggestions": 4,
|
|
66
66
|
"showCustomOption": true,
|
|
67
67
|
"showTokenEstimation": true,
|
|
@@ -74,9 +74,9 @@ Variables de entorno **o** el archivo `~/.nextup/config.json` (las variables man
|
|
|
74
74
|
}
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
-
`suggestions`
|
|
77
|
+
`suggestions` is the row cap, including "Leave it as is" (1–4). `showCustomOption` is the "Something else" row. `showTokenEstimation` is the cost column. `autoSuggest: false` stops new menus; a menu already on screen still accepts a number. `backgroundCheck` runs `npm test` (and `npm run lint` if it exists) before building the menu, with a short time cap: if 3 tests fail, the row says "3 tests failed, fix them". `false` turns that off. `consumption` is `measured` (real time: `1.5s`, or `—` if that action has not been measured yet) or `estimated` (low, medium, high, none). `providers` turns an agent off: Codex is recognized by a `.codex` path in the transcript. OpenCode has no adapter yet, so its flag is stored and does not cut anything off. `language` is the menu language when the prompt is not enough to tell (a number, an "ok", or a mix). The default is `en`. An existing `config.json` is not rewritten: switch it with `nextup config set language en`. A prompt that is a sentence in English shows the menu in English; a sentence in Spanish shows it in Spanish. `lang`, `mode`, `color`, `width`, `always`, and `disabled` still work. The environment overrides the file.
|
|
78
78
|
|
|
79
|
-
|
|
79
|
+
To view or change it without editing the JSON:
|
|
80
80
|
|
|
81
81
|
```
|
|
82
82
|
nextup config
|
|
@@ -85,70 +85,70 @@ nextup config set providers.codex false
|
|
|
85
85
|
nextup config set showCustomOption true
|
|
86
86
|
nextup config set suggestions 3
|
|
87
87
|
nextup config set theme blue
|
|
88
|
-
nextup config set language
|
|
88
|
+
nextup config set language en
|
|
89
89
|
nextup config set consumption estimated
|
|
90
90
|
```
|
|
91
91
|
|
|
92
|
-
|
|
92
|
+
Switches accept `true`/`false` (also `on`/`off` and `si`/`no`). In the agent box, without leaving Codex and without `nextup` on `PATH`:
|
|
93
93
|
|
|
94
94
|
```
|
|
95
95
|
nextup theme blue
|
|
96
96
|
nextup config set consumption estimated
|
|
97
97
|
```
|
|
98
98
|
|
|
99
|
-
|
|
99
|
+
The change is saved and the agent does not run it. It shows up on the next menu. `color` and `default` paint each row by cost. `blue`, `green`, `amber`, `pink`, `teal`, and `violet` paint the whole menu one color. `mono` leaves it uncolored. `azul`, `ambar`, `rosa`, and `cian` are accepted aliases. `nextup init` creates the file with everything on if it does not exist yet, and does not overwrite one you already have.
|
|
100
100
|
|
|
101
|
-
|
|
101
|
+
With `"debug": true`, each hook stores what it received and returned in `~/.nextup/state/debug.jsonl` (paths and text from your session; local only). Each menu also leaves a line in `~/.nextup/state/choices.jsonl`: whether you picked a number, left it as is, or typed something else. It does not store the sentence. If a row is offered three times on similar turns and you never pick it, it drops.
|
|
102
102
|
|
|
103
|
-
##
|
|
103
|
+
## How it works
|
|
104
104
|
|
|
105
|
-
1.
|
|
106
|
-
2.
|
|
107
|
-
3.
|
|
105
|
+
1. **`Stop` hook**: reads the payload, gathers cheap signals (`git status` changes, `Bash` commands from the turn in the transcript, the final message), builds the menu, and shows it with `systemMessage`. Saves the pending menu in `~/.nextup/state/<session_id>.json`.
|
|
106
|
+
2. The user types the row number (`2`) or several at once (`+2 4`, `1 3`, `1 y 3`). The `+` is optional.
|
|
107
|
+
3. **`UserPromptSubmit` hook**: if a menu is pending and the reply is a selection, it injects the instructions as `additionalContext` (the hook cannot rewrite the prompt; Claude receives "3" plus the context that explains it). "Leave it as is" and "Something else" block the prompt and spend no tokens. Any other text discards the menu and passes through unchanged.
|
|
108
108
|
|
|
109
|
-
|
|
110
|
-
- **
|
|
111
|
-
- **
|
|
112
|
-
- **0
|
|
113
|
-
- **
|
|
114
|
-
- **
|
|
109
|
+
Design decisions (from the critique of the mock):
|
|
110
|
+
- **It does not suggest what already happened**: if the tests already passed, it does not offer to run them again.
|
|
111
|
+
- **It does not repeat what you just picked** (only for the next menu) **and it does not offer to run tests if the repo has none configured**: it suggests the commit instead.
|
|
112
|
+
- **0 to 4 options, and sometimes none**: no menu if nothing changed, or if the agent ended with a question.
|
|
113
|
+
- **The suggested row is never outward**: the schema (`SuggestionSchema`) rejects `recommended` + `outward` (push, deploy, ...).
|
|
114
|
+
- **The default menu paints the row**: green, amber, red, or gray by cost, and "Something else" in blue. The number sits on a lighter chip. `blue`, `green`, `amber`, `pink`, `teal`, and `violet` paint the whole menu one color. `mono` and `color: false` leave it plain text. The `☐` is not a checkbox: you pick with the number. In the agent box, with no slash: `nextup theme blue` (also `verde`, `azul`, `ambar`, `rosa`, `cian`). The slash command belongs to Codex and rejects `/nextup`.
|
|
115
115
|
|
|
116
|
-
## Spike:
|
|
116
|
+
## Spike: what to verify first
|
|
117
117
|
|
|
118
|
-
|
|
118
|
+
These four things are **not confirmed**; the design depends on them:
|
|
119
119
|
|
|
120
|
-
| # |
|
|
121
|
-
|
|
122
|
-
| 1 |
|
|
123
|
-
| 2 |
|
|
124
|
-
| 3 |
|
|
125
|
-
| 4 |
|
|
120
|
+
| # | Question | How to test | If it fails |
|
|
121
|
+
|---|----------|-------------|-------------|
|
|
122
|
+
| 1 | Does a Stop `systemMessage` show without blocking? | Run `claude`, ask for a change, look when it finishes | Use `NEXTUP_MODE=block` |
|
|
123
|
+
| 2 | Does it keep ANSI and line breaks? | Compare with `NEXTUP_COLOR=0` | Leave color off by default |
|
|
124
|
+
| 3 | Does Claude act well on `"3"` plus injected context? | Pick options several times | Strengthen the `additionalContext` text |
|
|
125
|
+
| 4 | Is the detected width correct? | Resize the terminal; try `NEXTUP_WIDTH` | Fixed or more compact width |
|
|
126
126
|
|
|
127
|
-
|
|
127
|
+
Also measure real turn latency (the hook starts in ~60 ms; the rules are local, so there is no model call today) and try the VS Code/Cursor extension, whose UI may show hooks differently from the terminal.
|
|
128
128
|
|
|
129
|
-
##
|
|
129
|
+
## Known limits
|
|
130
130
|
|
|
131
|
-
-
|
|
132
|
-
-
|
|
133
|
-
-
|
|
134
|
-
-
|
|
135
|
-
- Claude
|
|
131
|
+
- The menu only looks at files whose diff changed this turn (baseline saved in `UserPromptSubmit`). If that mark is missing, it falls back to the full `git status`.
|
|
132
|
+
- The JSONL transcript is not a public contract: `transcript.ts` is best effort and degrades to "no signals".
|
|
133
|
+
- Test detection uses commands and exit codes from the transcript (real Codex format checked with `fixtures/codex-rollout-sample.jsonl`). If the failure looks environmental (EPERM, "Access is denied", ...), it is treated as *blocked*, not as "tests broken": the suggestion becomes review the diff. With no exit code, it falls back to a heuristic on the final message.
|
|
134
|
+
- No detection of headless runs (`claude -p`); if you later call `claude -p` from the hook, export `NEXTUP_INNER=1` on that call to avoid recursion.
|
|
135
|
+
- Claude and Codex share the hook protocol (`systemMessage` / `additionalContext`). OpenCode needs its own presenter (append to the prompt).
|
|
136
136
|
|
|
137
|
-
##
|
|
137
|
+
## Next steps
|
|
138
138
|
|
|
139
|
-
1.
|
|
140
|
-
2. `llm.ts`: 1
|
|
141
|
-
3.
|
|
142
|
-
4.
|
|
139
|
+
1. Pass the spike (table above) and use it for a week; record which options get picked.
|
|
140
|
+
2. `llm.ts`: 1 or 2 specific suggestions from a small model (API key or `claude -p`), keeping the deterministic rules as the base.
|
|
141
|
+
3. Measured cost (real duration of the last test run) instead of a fixed value per action type.
|
|
142
|
+
4. OpenCode presenter (append to the prompt). Claude and Codex already share `src/adapters/shared/`.
|
|
143
143
|
|
|
144
|
-
##
|
|
144
|
+
## Layout
|
|
145
145
|
|
|
146
146
|
```
|
|
147
|
-
src/core/
|
|
148
|
-
src/adapters/shared/
|
|
149
|
-
src/adapters/claude/
|
|
150
|
-
src/adapters/codex/
|
|
151
|
-
src/cli/ preview, init, config,
|
|
152
|
-
test/ parse, suggest, render, hooks (
|
|
153
|
-
fixtures/ payloads
|
|
147
|
+
src/core/ agent-independent: types (zod), rules, render, parser, state
|
|
148
|
+
src/adapters/shared/ hook protocol: payloads, transcript, context, Stop and UserPromptSubmit
|
|
149
|
+
src/adapters/claude/ install into .claude/settings.json
|
|
150
|
+
src/adapters/codex/ install into .codex/hooks.json (timeoutSec)
|
|
151
|
+
src/cli/ preview, init, config, hook entry
|
|
152
|
+
test/ parse, suggest, render, hooks (temp git repo), adapters
|
|
153
|
+
fixtures/ sample payloads
|
|
154
154
|
```
|
package/dist/cli.js
CHANGED
|
@@ -184,7 +184,7 @@ var SWITCHES = [
|
|
|
184
184
|
];
|
|
185
185
|
function defaultConfig() {
|
|
186
186
|
return {
|
|
187
|
-
language: "
|
|
187
|
+
language: "en",
|
|
188
188
|
suggestions: 4,
|
|
189
189
|
showCustomOption: true,
|
|
190
190
|
showTokenEstimation: true,
|
|
@@ -234,31 +234,31 @@ function setConfigValue(key, raw, env = process.env) {
|
|
|
234
234
|
const provider = /^providers\.(claude|codex|opencode)$/.exec(key);
|
|
235
235
|
if (provider) {
|
|
236
236
|
const on = parseBool(value);
|
|
237
|
-
if (on === void 0) return `No
|
|
237
|
+
if (on === void 0) return `No such value "${raw}". Use true or false.`;
|
|
238
238
|
const id = provider[1];
|
|
239
239
|
const providers = { ...asObject(cfg.providers), [id]: on };
|
|
240
240
|
cfg.providers = providers;
|
|
241
241
|
} else if (SWITCHES.includes(key)) {
|
|
242
242
|
const on = parseBool(value);
|
|
243
|
-
if (on === void 0) return `No
|
|
243
|
+
if (on === void 0) return `No such value "${raw}". Use true or false.`;
|
|
244
244
|
cfg[key] = on;
|
|
245
245
|
} else if (key === "language") {
|
|
246
|
-
if (value !== "es" && value !== "en") return `No
|
|
246
|
+
if (value !== "es" && value !== "en") return `No such value "${raw}". language is es or en.`;
|
|
247
247
|
cfg.language = value;
|
|
248
248
|
} else if (key === "suggestions") {
|
|
249
249
|
const n = Number(value);
|
|
250
|
-
if (!Number.isInteger(n) || n < 1 || n > 4) return `No
|
|
250
|
+
if (!Number.isInteger(n) || n < 1 || n > 4) return `No such value "${raw}". suggestions is an integer from 1 to 4.`;
|
|
251
251
|
cfg.suggestions = n;
|
|
252
252
|
} else if (key === "consumption") {
|
|
253
253
|
const mode = value === "estimated" || value === "estimado" ? "estimated" : value === "measured" || value === "medido" ? "measured" : void 0;
|
|
254
|
-
if (!mode) return `No
|
|
254
|
+
if (!mode) return `No such value "${raw}". consumption is estimated or measured.`;
|
|
255
255
|
cfg.consumption = mode;
|
|
256
256
|
} else if (key === "theme") {
|
|
257
257
|
const theme = parseTheme(value);
|
|
258
|
-
if (!theme) return `No
|
|
258
|
+
if (!theme) return `No such value "${raw}". theme is ${THEMES.join(", ")}.`;
|
|
259
259
|
cfg.theme = theme;
|
|
260
260
|
} else {
|
|
261
|
-
return `No
|
|
261
|
+
return `No such key "${key}". Switches: ${SWITCHES.join(", ")}, providers.claude, providers.codex, providers.opencode. Also language, suggestions, consumption (estimated|measured), and theme (${THEMES.join(", ")}).`;
|
|
262
262
|
}
|
|
263
263
|
writeConfigFile(cfg, env);
|
|
264
264
|
if (provider) {
|
|
@@ -278,15 +278,15 @@ function formatConfig(env = process.env) {
|
|
|
278
278
|
...file2,
|
|
279
279
|
providers: { ...asObject(defaults.providers), ...asObject(file2.providers) }
|
|
280
280
|
};
|
|
281
|
-
const where = existsSync(path) ? path : `${path} (
|
|
281
|
+
const where = existsSync(path) ? path : `${path} (not created yet)`;
|
|
282
282
|
return `${where}
|
|
283
283
|
|
|
284
284
|
${JSON.stringify(merged, null, 2)}
|
|
285
285
|
|
|
286
|
-
nextup config set <
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
286
|
+
nextup config set <key> true|false
|
|
287
|
+
Switches: ${SWITCHES.join(", ")}, providers.claude, providers.codex, providers.opencode
|
|
288
|
+
Also: language es|en, suggestions 1-4, consumption estimated|measured, theme ${THEMES.join("|")}
|
|
289
|
+
NEXTUP_* environment variables override this file.`;
|
|
290
290
|
}
|
|
291
291
|
function loadConfigEnv(base = process.env) {
|
|
292
292
|
try {
|
|
@@ -4389,7 +4389,7 @@ var SuggestionSchema = external_exports.object({
|
|
|
4389
4389
|
outward: external_exports.boolean().default(false),
|
|
4390
4390
|
kind: external_exports.enum(["action", "finish"]).default("action")
|
|
4391
4391
|
}).refine((s) => !(s.recommended && s.outward), {
|
|
4392
|
-
message: "
|
|
4392
|
+
message: "An outward action (push, deploy, ...) cannot be the default suggestion."
|
|
4393
4393
|
});
|
|
4394
4394
|
var MenuSchema = external_exports.object({
|
|
4395
4395
|
sessionId: external_exports.string().min(1),
|
|
@@ -4735,7 +4735,7 @@ function lintLabel(lang, count) {
|
|
|
4735
4735
|
return count === 1 ? "1 problema de lint, arreglarlo" : `${count} problemas de lint, arreglarlos`;
|
|
4736
4736
|
}
|
|
4737
4737
|
function resolveLang(env = process.env) {
|
|
4738
|
-
return env.NEXTUP_LANG === "
|
|
4738
|
+
return env.NEXTUP_LANG === "es" ? "es" : "en";
|
|
4739
4739
|
}
|
|
4740
4740
|
var ES_WORDS = new Set(
|
|
4741
4741
|
"el la los las un una unos unas que de del al en lo es por para con sin su sus mi mis tu tus se este esta esto ese esa como pero porque cuando donde mas funcion devuelve devolver devuelva agrega agregue anade anadir prueba pruebas probar haz hacer hagas numeros numero digitos digito lista vacia comentario archivo cambios ahora puedes quiero escribe mayor anos tambien entonces vale siguiente nuevo nueva".split(
|
|
@@ -5402,16 +5402,16 @@ function themeSwitch(prompt, env) {
|
|
|
5402
5402
|
if (!raw) {
|
|
5403
5403
|
return JSON.stringify({
|
|
5404
5404
|
decision: "block",
|
|
5405
|
-
reason: `
|
|
5406
|
-
|
|
5405
|
+
reason: `Themes: ${names}
|
|
5406
|
+
Example: nextup theme blue`
|
|
5407
5407
|
});
|
|
5408
5408
|
}
|
|
5409
5409
|
const theme = parseTheme(raw);
|
|
5410
5410
|
if (!theme) {
|
|
5411
|
-
return JSON.stringify({ decision: "block", reason: `No
|
|
5411
|
+
return JSON.stringify({ decision: "block", reason: `No such theme "${raw}". Themes: ${names}` });
|
|
5412
5412
|
}
|
|
5413
5413
|
setConfigValue("theme", theme, env);
|
|
5414
|
-
return JSON.stringify({ decision: "block", reason: `
|
|
5414
|
+
return JSON.stringify({ decision: "block", reason: `Theme ${theme}. It shows up on the next menu.` });
|
|
5415
5415
|
}
|
|
5416
5416
|
var CONFIG_CMD = /^(?:\/)?nextup\s+config(?:\s+([\s\S]*))?\s*$/i;
|
|
5417
5417
|
function configSwitch(prompt, env) {
|
|
@@ -5423,12 +5423,12 @@ function configSwitch(prompt, env) {
|
|
|
5423
5423
|
if (!set?.[1] || !set[2]?.trim()) {
|
|
5424
5424
|
return JSON.stringify({
|
|
5425
5425
|
decision: "block",
|
|
5426
|
-
reason: "
|
|
5426
|
+
reason: "Usage: nextup config set <key> <value>\nExample: nextup config set consumption estimated"
|
|
5427
5427
|
});
|
|
5428
5428
|
}
|
|
5429
5429
|
const msg = setConfigValue(set[1], set[2].trim(), env);
|
|
5430
5430
|
const reason = msg.startsWith("No ") ? msg : `${msg}
|
|
5431
|
-
|
|
5431
|
+
It shows up on the next menu.`;
|
|
5432
5432
|
return JSON.stringify({ decision: "block", reason });
|
|
5433
5433
|
}
|
|
5434
5434
|
|
|
@@ -6322,7 +6322,7 @@ var codex = {
|
|
|
6322
6322
|
file: "hooks.json",
|
|
6323
6323
|
timeoutKey: "timeoutSec",
|
|
6324
6324
|
timeoutVal: 10,
|
|
6325
|
-
afterInstall: "
|
|
6325
|
+
afterInstall: "In Codex: open `codex`, type /hooks, and approve (trust) the two nextup hooks. New or changed hooks are ignored until you review them."
|
|
6326
6326
|
};
|
|
6327
6327
|
|
|
6328
6328
|
// src/adapters/index.ts
|
|
@@ -6345,13 +6345,13 @@ function runInit(args) {
|
|
|
6345
6345
|
const cliPath = fileURLToPath(import.meta.url).replace(/\\/g, "/");
|
|
6346
6346
|
const remove = args.includes("--remove");
|
|
6347
6347
|
if (!remove && !cliPath.endsWith(".js")) {
|
|
6348
|
-
return "
|
|
6348
|
+
return "Build first (`npm run build`) and run `node dist/cli.js init`: hooks need the .js file.";
|
|
6349
6349
|
}
|
|
6350
6350
|
const agentIdx = args.indexOf("--agent");
|
|
6351
6351
|
const agentId = agentIdx >= 0 ? args[agentIdx + 1] : "claude";
|
|
6352
6352
|
const agent = resolveAgent(agentId);
|
|
6353
6353
|
if (!agent) {
|
|
6354
|
-
return `
|
|
6354
|
+
return `Unknown agent "${agentId ?? ""}". Options: claude, codex.`;
|
|
6355
6355
|
}
|
|
6356
6356
|
const target = args.includes("--user") ? join9(homedir3(), agent.dir, agent.file) : join9(process.cwd(), agent.dir, agent.file);
|
|
6357
6357
|
let settings = {};
|
|
@@ -6359,7 +6359,7 @@ function runInit(args) {
|
|
|
6359
6359
|
try {
|
|
6360
6360
|
settings = JSON.parse(readFileSync8(target, "utf8"));
|
|
6361
6361
|
} catch {
|
|
6362
|
-
return `
|
|
6362
|
+
return `Could not read ${target} (invalid JSON). Nothing was changed.`;
|
|
6363
6363
|
}
|
|
6364
6364
|
const bak = `${target}.nextup.bak`;
|
|
6365
6365
|
if (!existsSync4(bak)) copyFileSync(target, bak);
|
|
@@ -6385,8 +6385,8 @@ function runInit(args) {
|
|
|
6385
6385
|
const extra = !remove && agent.afterInstall ? `
|
|
6386
6386
|
${agent.afterInstall}` : "";
|
|
6387
6387
|
const cfgLine = cfg ? `
|
|
6388
|
-
|
|
6389
|
-
return (remove ? `
|
|
6388
|
+
Config: ${cfg.path}${cfg.created ? " (created, everything on)" : ""}` : "";
|
|
6389
|
+
return (remove ? `nextup hooks removed from ${target}` : `nextup hooks installed in ${target}`) + cfgLine + extra;
|
|
6390
6390
|
}
|
|
6391
6391
|
|
|
6392
6392
|
// src/cli/config.ts
|
|
@@ -6395,7 +6395,7 @@ function runConfig(args, env = process.env) {
|
|
|
6395
6395
|
const key = args[1];
|
|
6396
6396
|
const value = args.slice(2).join(" ");
|
|
6397
6397
|
if (!key || !value.trim()) {
|
|
6398
|
-
return "
|
|
6398
|
+
return "Usage: nextup config set <key> <value>\nExample: nextup config set autoSuggest false";
|
|
6399
6399
|
}
|
|
6400
6400
|
return setConfigValue(key, value, env);
|
|
6401
6401
|
}
|
|
@@ -6440,7 +6440,7 @@ function runPreview(args) {
|
|
|
6440
6440
|
const scenarioName = get("--scenario") ?? "passed";
|
|
6441
6441
|
const scenario = SCENARIOS[scenarioName];
|
|
6442
6442
|
if (!scenario) {
|
|
6443
|
-
return `
|
|
6443
|
+
return `Unknown scenario "${scenarioName}". Options: ${Object.keys(SCENARIOS).join(", ")}`;
|
|
6444
6444
|
}
|
|
6445
6445
|
const lang = get("--lang") ?? resolveLang();
|
|
6446
6446
|
const width = get("--width") ? Number(get("--width")) : detectWidth();
|
|
@@ -6456,25 +6456,25 @@ function runPreview(args) {
|
|
|
6456
6456
|
...scenario
|
|
6457
6457
|
};
|
|
6458
6458
|
const menu = suggest(ctx);
|
|
6459
|
-
if (!menu) return `(
|
|
6459
|
+
if (!menu) return `(scenario "${scenarioName}": no menu)`;
|
|
6460
6460
|
return renderMenu(menu, { width, color, theme });
|
|
6461
6461
|
}
|
|
6462
6462
|
|
|
6463
6463
|
// src/cli/index.ts
|
|
6464
|
-
var HELP = `nextup \u2014
|
|
6464
|
+
var HELP = `nextup \u2014 next-step menu for coding agents (Claude Code and Codex)
|
|
6465
6465
|
|
|
6466
|
-
|
|
6466
|
+
Usage:
|
|
6467
6467
|
nextup preview [--scenario passed|no-tests|no-test-setup|failed|question|comment|symbol] [--width N] [--lang es|en] [--theme default|color|violet|mono] [--no-color]
|
|
6468
6468
|
nextup init [--agent claude|codex] [--user] [--remove]
|
|
6469
|
-
|
|
6470
|
-
nextup config
|
|
6471
|
-
nextup config set <
|
|
6472
|
-
|
|
6473
|
-
nextup hook stop (
|
|
6474
|
-
nextup hook prompt-submit (
|
|
6469
|
+
register hooks (current project, or --user for global)
|
|
6470
|
+
nextup config show ~/.nextup/config.json
|
|
6471
|
+
nextup config set <key> <value> turn an option on or off (true|false)
|
|
6472
|
+
also: nextup theme blue (in the agent box, no slash)
|
|
6473
|
+
nextup hook stop (called by the agent; reads JSON from stdin)
|
|
6474
|
+
nextup hook prompt-submit (called by the agent; reads JSON from stdin)
|
|
6475
6475
|
|
|
6476
6476
|
Variables: NEXTUP_MODE=system|block \xB7 NEXTUP_LANG=es|en \xB7 NEXTUP_COLOR=0 \xB7 NEXTUP_ALWAYS=1
|
|
6477
|
-
NEXTUP_DISABLED=1 \xB7 NEXTUP_WIDTH=N \xB7 NEXTUP_DEBUG=1 (
|
|
6477
|
+
NEXTUP_DISABLED=1 \xB7 NEXTUP_WIDTH=N \xB7 NEXTUP_DEBUG=1 (writes payloads to <state>/debug.jsonl) \xB7 NEXTUP_STATE_DIR=path`;
|
|
6478
6478
|
function main(argv) {
|
|
6479
6479
|
const [cmd, ...rest] = argv;
|
|
6480
6480
|
if (cmd === "preview") return void console.log(runPreview(rest));
|
|
@@ -6483,7 +6483,7 @@ function main(argv) {
|
|
|
6483
6483
|
if (cmd === "hook") {
|
|
6484
6484
|
const which = rest[0];
|
|
6485
6485
|
if (process.stdin.isTTY) {
|
|
6486
|
-
console.error("
|
|
6486
|
+
console.error("This command expects JSON on stdin. Example:\n cat fixtures/stop.json | node dist/cli.js hook stop");
|
|
6487
6487
|
return;
|
|
6488
6488
|
}
|
|
6489
6489
|
try {
|
package/package.json
CHANGED
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ketchao/nextup",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"publishConfig": {
|
|
5
|
-
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"publishConfig": {
|
|
5
|
+
"access": "public"
|
|
6
|
+
},
|
|
7
|
+
"description": "Next-step menu for coding agents (Claude Code and Codex)",
|
|
6
8
|
"type": "module",
|
|
7
|
-
"bin": {
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
"bin": {
|
|
10
|
+
"nextup": "dist/cli.js"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist"
|
|
14
|
+
],
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=18"
|
|
17
|
+
},
|
|
10
18
|
"scripts": {
|
|
11
19
|
"build": "tsup",
|
|
12
20
|
"dev": "tsx src/cli/index.ts",
|