@pugi/cli 0.1.0-beta.45 → 0.1.0-beta.46
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/runtime/version.js +1 -1
- package/dist/tui/input-box.js +76 -26
- package/package.json +2 -2
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.46');
|
|
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/tui/input-box.js
CHANGED
|
@@ -286,42 +286,92 @@ export function InputBox(props) {
|
|
|
286
286
|
setSearch(initialSearchState(history));
|
|
287
287
|
return;
|
|
288
288
|
}
|
|
289
|
-
// P0 fix (CEO 2026-05-29 dogfood): bare LF (`\n`)
|
|
290
|
-
// brief, same as bare CR (`\r`). Ink's parseKeypress
|
|
291
|
-
// `key.return` and `\n` to `key.name === 'enter'`
|
|
292
|
-
// `key.return`. Most real terminals deliver CR for
|
|
293
|
-
// by default), so the `key.return` branch below
|
|
294
|
-
// when stdin is a PTY whose parent writes raw
|
|
295
|
-
// `pty.fork` + `os.write(fd, b"\n")`, automation
|
|
296
|
-
// certain SSH multiplexers), the LF arrives as a
|
|
297
|
-
//
|
|
298
|
-
// input box shows a multi-line composer, the brief never
|
|
299
|
-
// dispatches, status stays "idle". CEO PTY repro 2026-05-29.
|
|
289
|
+
// P0 fix (CEO 2026-05-29 dogfood, second iteration): bare LF (`\n`)
|
|
290
|
+
// MUST submit the brief, same as bare CR (`\r`). Ink's parseKeypress
|
|
291
|
+
// maps `\r` to `key.return` and `\n` to `key.name === 'enter'`
|
|
292
|
+
// WITHOUT setting `key.return`. Most real terminals deliver CR for
|
|
293
|
+
// Enter (ICRNL on by default), so the `key.return` branch below
|
|
294
|
+
// catches them. But when stdin is a PTY whose parent writes raw
|
|
295
|
+
// `\n` (Python's `pty.fork` + `os.write(fd, b"\n")`, automation
|
|
296
|
+
// harnesses, certain SSH multiplexers), the LF arrives as a
|
|
297
|
+
// printable char.
|
|
300
298
|
//
|
|
301
|
-
//
|
|
302
|
-
//
|
|
303
|
-
//
|
|
304
|
-
//
|
|
305
|
-
//
|
|
306
|
-
//
|
|
307
|
-
|
|
299
|
+
// PR #697 (beta.45) fixed the case where `input === '\n'` exactly.
|
|
300
|
+
// CEO PTY smoke 2026-05-29 surfaced the REAL shape: when the parent
|
|
301
|
+
// writes the brief AND the Enter as separate `os.write` calls (or
|
|
302
|
+
// even when it doesn't), Node's stdin buffer COALESCES them into
|
|
303
|
+
// ONE chunk before Ink delivers the `useInput` event. The repro
|
|
304
|
+
// confirmed via stderr instrumentation: typing `hi\n` arrives in
|
|
305
|
+
// input-box as `bytes=[68 69 0a] len=3 flags=-` — a SINGLE 3-char
|
|
306
|
+
// chunk "hi\n" with no key flags. The PR #697 branch (`input ===
|
|
307
|
+
// '\n'`) does not match, so `hi\n` falls through to the printable-
|
|
308
|
+
// char branch and the literal newline lands in the buffer as
|
|
309
|
+
// `› hi\n █` (multi-line composer, brief never dispatches, status
|
|
310
|
+
// stays `idle` forever).
|
|
311
|
+
//
|
|
312
|
+
// Fix: detect a TRAILING `\n` in a printable chunk with no
|
|
313
|
+
// modifiers — type the prefix into the buffer, then submit. The
|
|
314
|
+
// discriminator that keeps multi-line paste working: the chunk
|
|
315
|
+
// must contain EXACTLY ONE `\n` (the trailing one) and no other
|
|
316
|
+
// newlines. Multi-line pastes have ≥2 `\n` characters (or arrive
|
|
317
|
+
// wrapped in bracketed-paste markers handled below), so they
|
|
318
|
+
// still preserve interior newlines via the printable-char branch.
|
|
319
|
+
//
|
|
320
|
+
// Detection contract:
|
|
321
|
+
// - `input` ends with `\n`
|
|
322
|
+
// - no Ctrl / Meta / Shift modifiers
|
|
323
|
+
// - exactly ONE `\n` in the chunk (the trailing one)
|
|
324
|
+
// - chunk is not bracketed-paste wrapped (markers stripped below)
|
|
325
|
+
//
|
|
326
|
+
// Edge cases covered by `test/input-box-lf-submit.spec.tsx`:
|
|
327
|
+
// - bare `\n` → submit empty (no-op on empty buf)
|
|
328
|
+
// - `hi\n` → splice `hi` + submit
|
|
329
|
+
// - `hi\nthere\n` (multi-line) → printable branch, preserves \n
|
|
330
|
+
// - `\r` (CR) → key.return branch unchanged
|
|
331
|
+
// - `hi\r\n` (CRLF) → key.return branch (CR wins first)
|
|
332
|
+
const endsWithLf = input.length > 0 && input.charCodeAt(input.length - 1) === 0x0a;
|
|
333
|
+
const newlineCount = (input.match(/\n/g) || []).length;
|
|
334
|
+
if (endsWithLf
|
|
335
|
+
&& newlineCount === 1
|
|
336
|
+
&& !key.meta
|
|
337
|
+
&& !key.ctrl
|
|
338
|
+
&& !key.shift) {
|
|
339
|
+
// Splice the prefix (everything before the trailing `\n`) into
|
|
340
|
+
// the buffer at the cursor, then run the canonical submit path.
|
|
341
|
+
// Refs (cursorRef / lineRef) hold the latest committed values so
|
|
342
|
+
// the splice runs against the operator's most recent edits even
|
|
343
|
+
// if a previous async paste / setState is still mid-flight.
|
|
344
|
+
const prefix = input.slice(0, -1);
|
|
345
|
+
let mergedLine = lineRef.current;
|
|
346
|
+
let mergedCursor = cursorRef.current;
|
|
347
|
+
if (prefix.length > 0) {
|
|
348
|
+
// Same sanitisation as the printable-char branch below — strip
|
|
349
|
+
// bracketed-paste markers so a stray escape sequence never
|
|
350
|
+
// lands in the submitted brief.
|
|
351
|
+
const stripped = prefix
|
|
352
|
+
.replace(/\x1b\[200~/g, '')
|
|
353
|
+
.replace(/\x1b\[201~/g, '')
|
|
354
|
+
.replace(/\[200~/g, '')
|
|
355
|
+
.replace(/\[201~/g, '');
|
|
356
|
+
if (stripped.length > 0) {
|
|
357
|
+
mergedLine =
|
|
358
|
+
mergedLine.slice(0, mergedCursor) + stripped + mergedLine.slice(mergedCursor);
|
|
359
|
+
mergedCursor = mergedCursor + stripped.length;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
308
362
|
// Synthesise the same payload-shape the `key.return` branch
|
|
309
363
|
// below uses so palette completion + history dedup + onSubmit
|
|
310
|
-
// dispatch all run identically.
|
|
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.
|
|
364
|
+
// dispatch all run identically.
|
|
315
365
|
const paletteHere = !paletteSuppressed
|
|
316
|
-
? filterPalette(
|
|
366
|
+
? filterPalette(mergedLine)
|
|
317
367
|
: { rows: [], totalBeforeLimit: 0 };
|
|
318
368
|
const paletteOpenHere = paletteHere.rows.length > 0;
|
|
319
369
|
const paletteFocusedIndexHere = paletteHere.rows.length === 0
|
|
320
370
|
? 0
|
|
321
371
|
: Math.min(paletteIndex, paletteHere.rows.length - 1);
|
|
322
|
-
let payload =
|
|
372
|
+
let payload = mergedLine;
|
|
323
373
|
if (paletteOpenHere) {
|
|
324
|
-
const completed = completePalette(
|
|
374
|
+
const completed = completePalette(mergedLine, paletteHere.rows, paletteFocusedIndexHere);
|
|
325
375
|
if (completed !== null)
|
|
326
376
|
payload = completed;
|
|
327
377
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pugi/cli",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.46",
|
|
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.
|
|
58
|
+
"@pugi/sdk": "0.1.0-beta.46"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
61
|
"@types/node": "^22.0.0",
|