@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.
Files changed (3) hide show
  1. package/README.md +74 -74
  2. package/dist/cli.js +40 -40
  3. package/package.json +14 -6
package/README.md CHANGED
@@ -1,67 +1,67 @@
1
- # nextup (nombre provisional)
1
+ # nextup (placeholder name)
2
2
 
3
- Menú de "siguientes pasos" para agentes de código. MVP: **Claude Code** y **Codex** (mismo protocolo de hooks).
3
+ Next-step menu for coding agents. MVP: **Claude Code** and **Codex** (same hook protocol).
4
4
 
5
- > El nombre es un placeholder. Ojo: la captura de referencia es de un producto llamado "Then", así que conviene no usar ese nombre.
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
- Al terminar cada turno sustancial, muestra hasta 4 opciones contextuales (con indicador de consumo). El usuario responde escribiendo el número en la caja normal de Claude Code.
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
- ◆ ¿Cómo seguimos? Escribe un número · +1 2 = varias
11
- consumo ▾
12
- 1 Hacer commit ★ sugerida ■□□ bajo
13
- 2 Revisar mis cambios ■■□ medio
14
- 3 Escribir pruebas ■■■ alto
15
- 4 Dejarlo así □□□ nada
16
- 5 Otra cosa (libre)
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
- ## Probarlo (5 minutos)
19
+ ## Try it (5 minutes)
20
20
 
21
21
  ```bash
22
22
  npm install
23
- npm test # 76 pruebas
24
- npm run preview # ver el menú sin Claude (itera el diseño aquí)
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
- # en un repo de prueba, desde el paquete publicado:
28
+ # in a test repo, from the published package:
29
29
  npm install -g @ketchao/nextup
30
30
  nextup init --agent codex
31
- claude # pide un cambio pequeño y mira qué pasa al terminar
31
+ claude # ask for a small change and watch what happens when it finishes
32
32
  ```
33
33
 
34
- `init --user` instala los hooks globalmente; `init --remove` los quita. Hace un backup `settings.json.nextup.bak` la primera vez.
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
- Probar un hook sin Claude:
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
- ## Probar con Codex (experimental)
43
+ ## Try it with Codex (experimental)
44
44
 
45
- Los comandos `hook stop` y `hook prompt-submit` leen y escriben el mismo formato JSON que usa Codex, así que no hay adaptador nuevo: solo cambia dónde se registran.
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 # escribe .codex/hooks.json (o --user para ~/.codex/hooks.json)
49
- codex # luego escribe /hooks y aprueba (trust) los 2 hooks de nextup
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
- Notas:
53
- - Codex ignora los hooks nuevos o modificados hasta que los revisas con `/hooks`.
54
- - En Windows nativo (Codex CLI 0.166.0, cmd) los hooks **sí se ejecutan**. El `systemMessage` aparece como "Hook · ..." con los colores ANSI y la alineación intactos.
55
- - El lector de transcript (`transcript.ts`) es *best effort* y entiende los formatos de Claude Code y de Codex. Solo lee lo ocurrido en el turno actual (el hook `UserPromptSubmit` anota dónde empieza). Si algo no cuadra, activa `debug` y revisa `debug.jsonl`: la entrada `stop-signals` muestra los comandos y códigos de salida detectados, y un resumen de las líneas leídas.
56
- - Codex ejecuta los hooks desde un servidor en segundo plano que **no hereda tus variables de entorno**: usa `~/.nextup/config.json` (ver abajo) o abre `codex --no-daemon`.
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
- ## Configuración
58
+ ## Configuration
59
59
 
60
- Variables de entorno **o** el archivo `~/.nextup/config.json` (las variables mandan sobre el archivo):
60
+ Environment variables **or** the file `~/.nextup/config.json` (environment variables win):
61
61
 
62
62
  ```json
63
63
  {
64
- "language": "es",
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` es el tope de filas, contando "Dejarlo así" (1–4). `showCustomOption` es la fila "Otra cosa". `showTokenEstimation` es la columna de consumo. `autoSuggest: false` deja de mostrar menús nuevos; un menú que ya está en pantalla sigue aceptando el número. `backgroundCheck` corre `npm test` (y `npm run lint` si existe) antes de armar el menú, con un tope de unos segundos: si 3 pruebas fallan, la fila dice «3 pruebas fallan, arreglarlas». `false` lo apaga. `consumption` es `measured` (el tiempo real: `1.5s`, o `—` si esa acción todavía no se midió) o `estimated` (bajo, medio, alto, nada). `providers` apaga un agente: Codex se reconoce por la ruta `.codex` del transcript. OpenCode todavía no tiene adaptador, así que su flag se guarda y no corta nada. `language` es el idioma cuando el prompt no alcanza para saberlo (un número, un «ok» o una mezcla). Si el prompt es una frase en inglés, el menú sale en inglés; si es en español, en español. Siguen valiendo `lang`, `mode`, `color`, `width`, `always` y `disabled`. El entorno manda sobre el archivo.
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
- Para verlo o cambiarlo sin editar el JSON:
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 es
88
+ nextup config set language en
89
89
  nextup config set consumption estimated
90
90
  ```
91
91
 
92
- Los interruptores aceptan `true`/`false` (también `on`/`off` y `si`/`no`). En la caja del agente, sin salir de Codex y sin tener `nextup` en el PATH:
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
- El cambio se guarda y el agente no lo ejecuta. Se ve en el menú siguiente. `color` y `default` pintan cada fila según el consumo. `blue`, `green`, `amber`, `pink`, `teal` y `violet` pintan todo el menú de un color. `mono` lo deja sin color. También valen `azul`, `ambar`, `rosa` y `cian`. `nextup init` crea el archivo con todo activo si todavía no existe, y no pisa el que ya tengas.
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
- Con `"debug": true`, cada hook guarda lo que recibió y devolvió en `~/.nextup/state/debug.jsonl` (contiene rutas y texto de tu sesión; es solo local). Aparte, cada menú deja una línea en `~/.nextup/state/choices.jsonl`: si elegiste un número, si lo dejaste así, o si escribiste otra cosa. No guarda la frase. Si una fila sale tres veces en turnos parecidos y no la eliges, baja.
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
- ## Cómo funciona
103
+ ## How it works
104
104
 
105
- 1. **Hook `Stop`**: lee el payload, junta señales baratas (cambios en `git status`, comandos `Bash` del turno en el transcript, mensaje final), genera el menú y lo muestra con `systemMessage`. Guarda el menú pendiente en `~/.nextup/state/<session_id>.json`.
106
- 2. El usuario escribe el número de la fila (`2`) o varios a la vez (`+2 4`, `1 3`, `1 y 3`). El `+` es opcional.
107
- 3. **Hook `UserPromptSubmit`**: si hay menú pendiente y la respuesta es una selección, inyecta las instrucciones como `additionalContext` (el hook no puede reescribir el prompt; Claude recibe "3" + el contexto que lo explica). "Dejarlo así" y "Otra cosa" bloquean el prompt y no gastan tokens. Cualquier otro texto descarta el menú y pasa intacto.
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
- Decisiones de diseño (vienen de la crítica a la maqueta):
110
- - **No sugiere lo que ya pasó**: si las pruebas ya pasaron, no ofrece correrlas otra vez.
111
- - **No repite lo que acabas de elegir** (solo para el menú siguiente) **ni ofrece correr pruebas si el repo no tiene ninguna configurada**: en su lugar sugiere el commit.
112
- - **0 a 4 opciones, y a veces ninguna**: no hay menú si no hubo cambios, o si el agente terminó con una pregunta.
113
- - **La sugerida nunca es saliente**: el esquema (`SuggestionSchema`) rechaza `recommended` + `outward` (push, deploy...).
114
- - **El menú por defecto pinta la fila**: verde, ámbar, rojo o gris según el consumo, y "Otra cosa" en azul. El número va en una pastilla más clara. `blue`, `green`, `amber`, `pink`, `teal` y `violet` pintan todo el menú de un solo color. `mono` y `color: false` lo dejan en texto plano. El `☐` no se marca: se elige con el número. En la caja del agente, sin barra: `nextup theme blue` (también `verde`, `azul`, `ambar`, `rosa`, `cian`). La barra es de Codex y rechaza `/nextup`.
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: lo que hay que verificar primero
116
+ ## Spike: what to verify first
117
117
 
118
- Estas cuatro cosas **no están confirmadas**; todo el diseño depende de ellas:
118
+ These four things are **not confirmed**; the design depends on them:
119
119
 
120
- | # | Pregunta | Cómo probarla | Si falla |
121
- |---|----------|---------------|----------|
122
- | 1 | ¿Se ve el `systemMessage` de un Stop sin bloqueo? | Correr `claude`, pedir un cambio, mirar al terminar | Usar `NEXTUP_MODE=block` |
123
- | 2 | ¿Respeta ANSI y saltos de línea? | Comparar con `NEXTUP_COLOR=0` | Dejar color apagado por defecto |
124
- | 3 | ¿Claude actúa bien con `"3"` + contexto inyectado? | Elegir opciones varias veces | Reforzar el texto de `additionalContext` |
125
- | 4 | ¿El ancho detectado es correcto? | Redimensionar la terminal; probar `NEXTUP_WIDTH` | Ancho fijo o más compacto |
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
- Además: mide la latencia real del turno (el hook arranca en ~60 ms; las reglas son locales, así que hoy no hay llamada a un modelo) y prueba también en la extensión de VS Code/Cursor, cuya interfaz puede mostrar los hooks distinto a la terminal.
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
- ## Límites conocidos
129
+ ## Known limits
130
130
 
131
- - El menú mira solo los archivos cuyo diff cambió en este turno (baseline guardado en `UserPromptSubmit`). Si esa marca no está, cae al `git status` completo.
132
- - El transcript JSONL no es un contrato público: `transcript.ts` es *best effort* y degrada a "sin señales".
133
- - Detección de pruebas: usa los comandos y códigos de salida del transcript (formato real de Codex verificado con `fixtures/codex-rollout-sample.jsonl`). Si el fallo parece del entorno (EPERM, "Access is denied"...), se trata como *bloqueado*, no como "pruebas rotas": la sugerida pasa a ser revisar el diff. Sin código de salida, cae a una heurística sobre el mensaje final.
134
- - Sin detección de ejecuciones headless (`claude -p`); si más adelante llamas a `claude -p` desde el hook, exporta `NEXTUP_INNER=1` en esa llamada para evitar recursión.
135
- - Claude y Codex comparten el protocolo de hooks (`systemMessage` / `additionalContext`). OpenCode necesita un presentador propio (añadir al prompt).
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
- ## Siguientes pasos
137
+ ## Next steps
138
138
 
139
- 1. Pasar el spike (tabla de arriba) y usarlo una semana propia; registrar qué opciones se eligen.
140
- 2. `llm.ts`: 1 o 2 sugerencias específicas con un modelo pequeño (API key o `claude -p`), manteniendo las reglas deterministas como base.
141
- 3. Consumo medido (duración real de la última corrida de pruebas) en vez de fijo por tipo de acción.
142
- 4. Presentador de OpenCode (añadir al prompt). Claude y Codex ya comparten `src/adapters/shared/`.
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
- ## Estructura
144
+ ## Layout
145
145
 
146
146
  ```
147
- src/core/ independiente del agente: tipos (zod), reglas, render, parser, estado
148
- src/adapters/shared/ protocolo de hooks: payloads, transcript, contexto, Stop y UserPromptSubmit
149
- src/adapters/claude/ instalación en .claude/settings.json
150
- src/adapters/codex/ instalación en .codex/hooks.json (timeoutSec)
151
- src/cli/ preview, init, config, entrada de los hooks
152
- test/ parse, suggest, render, hooks (con repo git temporal), adaptadores
153
- fixtures/ payloads de ejemplo
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: "es",
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 entend\xED "${raw}". Usa true o false.`;
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 entend\xED "${raw}". Usa true o false.`;
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 entend\xED "${raw}". language es es o en.`;
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 entend\xED "${raw}". suggestions es un entero de 1 a 4.`;
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 entend\xED "${raw}". consumption es estimated o measured.`;
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 entend\xED "${raw}". theme es ${THEMES.join(", ")}.`;
258
+ if (!theme) return `No such value "${raw}". theme is ${THEMES.join(", ")}.`;
259
259
  cfg.theme = theme;
260
260
  } else {
261
- return `No conozco "${key}". Interruptores: ${SWITCHES.join(", ")}, providers.claude, providers.codex, providers.opencode. Tambi\xE9n language, suggestions, consumption (estimated|measured) y theme (${THEMES.join(", ")}).`;
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} (a\xFAn no existe)`;
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 <clave> true|false
287
- Interruptores: ${SWITCHES.join(", ")}, providers.claude, providers.codex, providers.opencode
288
- Tambi\xE9n: language es|en, suggestions 1-4, consumption estimated|measured, theme ${THEMES.join("|")}
289
- Si defines variables NEXTUP_*, mandan sobre este archivo.`;
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: "Una acci\xF3n 'outward' (push, deploy...) no puede ser la sugerida por defecto."
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 === "en" ? "en" : "es";
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: `Temas: ${names}
5406
- Ejemplo: nextup theme blue`
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 conozco "${raw}". Temas: ${names}` });
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: `Tema ${theme}. Se ve en el pr\xF3ximo men\xFA.` });
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: "Uso: nextup config set <clave> <valor>\nEjemplo: nextup config set consumption estimated"
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
- Se ve en el pr\xF3ximo men\xFA.`;
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: "En Codex: abre `codex`, escribe /hooks y aprueba (trust) los dos hooks de nextup; los hooks nuevos o modificados se ignoran hasta revisarlos."
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 "Compila primero (`npm run build`) y ejecuta `node dist/cli.js init`: los hooks necesitan el archivo .js.";
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 `Agente desconocido "${agentId ?? ""}". Opciones: claude, codex.`;
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 `No pude leer ${target} (JSON inv\xE1lido). No toqu\xE9 nada.`;
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
- Configuraci\xF3n: ${cfg.path}${cfg.created ? " (creada, todo activo)" : ""}` : "";
6389
- return (remove ? `Hooks de nextup quitados de ${target}` : `Hooks de nextup instalados en ${target}`) + cfgLine + extra;
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 "Uso: nextup config set <clave> <valor>\nEjemplo: nextup config set autoSuggest false";
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 `Escenario desconocido "${scenarioName}". Opciones: ${Object.keys(SCENARIOS).join(", ")}`;
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 `(escenario "${scenarioName}": no se muestra men\xFA)`;
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 men\xFA de siguientes pasos para agentes de c\xF3digo (Claude Code y Codex)
6464
+ var HELP = `nextup \u2014 next-step menu for coding agents (Claude Code and Codex)
6465
6465
 
6466
- Uso:
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
- registra los hooks (proyecto actual, o --user para global)
6470
- nextup config muestra ~/.nextup/config.json
6471
- nextup config set <clave> <valor> activa o apaga una opci\xF3n (true|false)
6472
- tambi\xE9n: nextup theme blue (en la caja del agente, sin /)
6473
- nextup hook stop (lo llama el agente; lee JSON por stdin)
6474
- nextup hook prompt-submit (lo llama el agente; lee JSON por stdin)
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 (guarda payloads en <state>/debug.jsonl) \xB7 NEXTUP_STATE_DIR=ruta`;
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("Este comando espera JSON por stdin. Ejemplo:\n cat fixtures/stop.json | node dist/cli.js hook stop");
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.0",
4
- "publishConfig": { "access": "public" },
5
- "description": "Menú de siguientes pasos para agentes de código (Claude Code y Codex)",
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": { "nextup": "dist/cli.js" },
8
- "files": ["dist"],
9
- "engines": { "node": ">=18" },
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",