@pugi/cli 0.1.0-beta.44 → 0.1.0-beta.45

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.
@@ -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.44');
47
+ export const PUGI_CLI_VERSION = sanitizeSemver('0.1.0-beta.45');
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.
@@ -238,7 +238,9 @@ export function InputBox(props) {
238
238
  setCursor(draftBeforeSearch.length);
239
239
  return;
240
240
  }
241
- if (key.return) {
241
+ // Bare LF accepts the focused match, same as CR (`key.return`).
242
+ // See the post-search block below for the rationale.
243
+ if (key.return || (input === '\n' && !key.meta && !key.ctrl && !key.shift)) {
242
244
  const picked = currentBrief(search);
243
245
  setSearch(undefined);
244
246
  if (picked !== null) {
@@ -257,6 +259,11 @@ export function InputBox(props) {
257
259
  return;
258
260
  }
259
261
  if (input && !key.meta && !key.ctrl) {
262
+ // Drop a bare LF from the search query — the Enter-accept
263
+ // branch above already handled it; falling through here would
264
+ // splice a newline into the search string.
265
+ if (input === '\n')
266
+ return;
260
267
  const nextQuery = search.query + input;
261
268
  setSearch(applyQuery(search, nextQuery, history));
262
269
  return;
@@ -279,6 +286,68 @@ export function InputBox(props) {
279
286
  setSearch(initialSearchState(history));
280
287
  return;
281
288
  }
289
+ // P0 fix (CEO 2026-05-29 dogfood): bare LF (`\n`) MUST submit the
290
+ // brief, same as bare CR (`\r`). Ink's parseKeypress maps `\r` to
291
+ // `key.return` and `\n` to `key.name === 'enter'` WITHOUT setting
292
+ // `key.return`. Most real terminals deliver CR for Enter (ICRNL on
293
+ // by default), so the `key.return` branch below catches them. But
294
+ // when stdin is a PTY whose parent writes raw `\n` (Python's
295
+ // `pty.fork` + `os.write(fd, b"\n")`, automation harnesses,
296
+ // certain SSH multiplexers), the LF arrives as a printable char
297
+ // and lands in the buffer as a literal newline. The result: the
298
+ // input box shows a multi-line composer, the brief never
299
+ // dispatches, status stays "idle". CEO PTY repro 2026-05-29.
300
+ //
301
+ // Detection contract: the chunk is a SINGLE bare `\n` with no
302
+ // modifiers and not inside a bracketed-paste burst. Multi-char
303
+ // chunks containing `\n` (multi-line pastes, terminal-pasted
304
+ // markdown blocks) preserve interior newlines so the operator
305
+ // sees the full text in the buffer — those are handled by the
306
+ // printable-char branch below.
307
+ if (input === '\n' && !key.meta && !key.ctrl && !key.shift) {
308
+ // Synthesise the same payload-shape the `key.return` branch
309
+ // below uses so palette completion + history dedup + onSubmit
310
+ // dispatch all run identically. We re-enter the handler with a
311
+ // synthetic key.return = true would require a refactor; the
312
+ // cheap fix is to inline the submit logic here in a way that
313
+ // mirrors the canonical branch exactly. Kept tight + obvious so
314
+ // future edits to one path get mirrored to the other.
315
+ const paletteHere = !paletteSuppressed
316
+ ? filterPalette(line)
317
+ : { rows: [], totalBeforeLimit: 0 };
318
+ const paletteOpenHere = paletteHere.rows.length > 0;
319
+ const paletteFocusedIndexHere = paletteHere.rows.length === 0
320
+ ? 0
321
+ : Math.min(paletteIndex, paletteHere.rows.length - 1);
322
+ let payload = line;
323
+ if (paletteOpenHere) {
324
+ const completed = completePalette(line, paletteHere.rows, paletteFocusedIndexHere);
325
+ if (completed !== null)
326
+ payload = completed;
327
+ }
328
+ const trimmed = payload.trim();
329
+ if (trimmed.length > 0) {
330
+ setHistory((prev) => {
331
+ if (prev[prev.length - 1] === trimmed)
332
+ return prev;
333
+ return [...prev, trimmed];
334
+ });
335
+ setHistoryIndex(-1);
336
+ if (props.workspaceSlug) {
337
+ appendHistory({
338
+ home: props.historyHome,
339
+ workspaceSlug: props.workspaceSlug,
340
+ brief: trimmed,
341
+ });
342
+ }
343
+ props.onSubmit(trimmed);
344
+ }
345
+ setLine('');
346
+ setCursor(0);
347
+ setPaletteSuppressed(false);
348
+ setPaletteIndex(0);
349
+ return;
350
+ }
282
351
  // Readline-style kill ring shortcuts. All four kills push the
283
352
  // removed slice onto the ring; Ctrl+Y yanks the most recent.
284
353
  if (key.ctrl && input === 'u') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pugi/cli",
3
- "version": "0.1.0-beta.44",
3
+ "version": "0.1.0-beta.45",
4
4
  "description": "Pugi CLI - terminal-native software execution system",
5
5
  "homepage": "https://pugi.io",
6
6
  "repository": {
@@ -55,7 +55,7 @@
55
55
  "undici": "^8.3.0",
56
56
  "zod": "^3.23.0",
57
57
  "@pugi/personas": "0.1.2",
58
- "@pugi/sdk": "0.1.0-beta.44"
58
+ "@pugi/sdk": "0.1.0-beta.45"
59
59
  },
60
60
  "devDependencies": {
61
61
  "@types/node": "^22.0.0",