@cydm/pie 2.6.8 → 2.6.10

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.
@@ -35,25 +35,38 @@ tui-use daemon stop
35
35
 
36
36
  - Use `wait --text <marker>` or `wait --debounce <ms>` rather than guessing with sleeps.
37
37
  - Always pass `--session <id>` on session operations. `use <id>` is a legacy
38
- single-session convenience. When multiple sessions exist, operations without
39
- an explicit ID fail instead of risking another task's session.
38
+ single-session convenience that only matters while exactly one session
39
+ exists: with multiple sessions its `[current]` mark changes nothing, and
40
+ operations without an explicit ID fail instead of risking another task's session.
40
41
  - `--text` and `find` use literal text by default. Add `--regex` explicitly for
41
42
  regular expressions; a bounded worker prevents regex from hanging the daemon.
43
+ `find` searches only the current viewport rows, never the scrollback buffer:
44
+ `scrollup`/`scrolldown` first, then `find` again.
42
45
  - `snapshot` reports the rendered screen, cursor, title, fullscreen state, and selected spans.
43
46
  - `list`, `use <id>`, and `rename <label>` support multiple sessions.
44
47
  - `start --cwd <dir> --cols <n> --rows <n>` controls the working directory and terminal size.
48
+ `start` prints the bare session ID on stdout (plain text for shell capture, no
49
+ `--format json` option).
45
50
  - `resize <cols> <rows>` updates a live session. `keys` lists the supported
46
51
  modifiers and function keys, including Shift-Tab and F11/F12.
47
52
  - Snapshot rows are preserved. Cursor, highlights and `find` use zero-based
48
- viewport rows and terminal-cell columns; `col_end` is inclusive. A cursor
49
- outside the viewport while scrolling is offscreen, not a screen text index.
53
+ viewport rows and terminal-cell columns; spans are measured in terminal cells,
54
+ so a wide character (CJK, emoji) occupies two cells and `col_end` is the last
55
+ occupied cell, not a character count. The cursor is offscreen exactly when its
56
+ `y` is outside `0 <= y < rows` (scrolling reports scrollback-absolute rows, so
57
+ `y >= rows` means offscreen); an offscreen cursor is not a screen text index.
50
58
  - Send an explicit `press enter` after typing when reliable submission matters.
59
+ `press` accepts only the special key names listed by `keys`; single printable
60
+ characters must be sent with `type` instead.
51
61
  - `wait --text` exits non-zero if the requested pattern is absent at the deadline.
52
62
  Read the returned screen and fix the input instead of assuming the wait succeeded.
53
63
  Do not rerun the identical failing wait; poll with `--poll` (below) or change the input.
54
64
  - Use `wait --exit` to confirm process termination. Seeing the last output or
55
65
  leaving fullscreen does not by itself mean the process has exited.
56
66
  - Kill only sessions you created. Do not restart or stop a daemon that owns another task's live sessions.
67
+ A killed session is retained like a naturally exited one: `info`, `snapshot`
68
+ and `wait --exit` (reporting the observed `exit_code`) keep working for it
69
+ until exited-session retention prunes it.
57
70
 
58
71
  ## Bounded Polling For Long Tasks
59
72
 
@@ -117,9 +130,11 @@ node <baseDir>/tui-use-cli.js --session <id> press enter
117
130
  node <baseDir>/tui-use-cli.js --session <id> wait 3000 --text 'Error: EXPECTED_ERROR'
118
131
  ```
119
132
 
120
- `--stdin` reads UTF-8 literally, without expanding `\n`, `\r` or `\t`, and
121
- removes one trailing pipeline line terminator. `paste --stdin` supports
122
- multiline input and submits each line.
133
+ Text passed as `type`/`paste` arguments expands the escape sequences `\n`, `\r`
134
+ and `\t` (`\n` acts as Enter for most programs). `--stdin` reads UTF-8
135
+ literally, without expanding `\n`, `\r` or `\t`, and removes one trailing
136
+ pipeline line terminator. `paste --stdin` supports multiline input and submits
137
+ each line.
123
138
 
124
139
  PowerShell pipelines containing Unicode must use UTF-8:
125
140
 
@@ -140,7 +155,8 @@ tui-use. `PIE_TUI_USE_STATE_DIR` can isolate a test or task.
140
155
  Windows uses a named pipe; Unix hosts use a local socket. Daemon logs are
141
156
  written to the state directory instead of inherited command output.
142
157
  Use a dedicated state directory per task when possible, even with explicit IDs.
143
- At most 32 sessions run concurrently; up to 16 exited sessions are retained.
158
+ At most 32 sessions run concurrently; up to 16 exited sessions (naturally exited
159
+ or killed) are retained, each for at most five minutes.
144
160
  An empty/exited daemon shuts down after five minutes without session activity.
145
161
 
146
162
  If startup fails, inspect `daemon status` and the state directory's
@@ -10320,6 +10320,48 @@ var SUPPORTED_KEYS = Object.keys(KEY_MAP);
10320
10320
  var import_node_fs2 = require("node:fs");
10321
10321
  var import_node_path3 = __toESM(require("node:path"));
10322
10322
  var program2 = new Command();
10323
+ function isWideCodePoint(cp) {
10324
+ return cp >= 4352 && (cp <= 4447 || // Hangul Jamo
10325
+ cp === 9001 || cp === 9002 || // angle brackets
10326
+ cp >= 11904 && cp <= 12350 || // CJK radicals … CJK symbols
10327
+ cp >= 12353 && cp <= 13311 || // Hiragana … CJK compatibility
10328
+ cp >= 13312 && cp <= 19903 || // CJK ext A
10329
+ cp >= 19968 && cp <= 40959 || // CJK Unified Ideographs
10330
+ cp >= 40960 && cp <= 42191 || // Yi
10331
+ cp >= 44032 && cp <= 55203 || // Hangul syllables
10332
+ cp >= 63744 && cp <= 64255 || // CJK compatibility ideographs
10333
+ cp >= 65072 && cp <= 65135 || // CJK compatibility forms
10334
+ cp >= 65280 && cp <= 65376 || // Fullwidth forms
10335
+ cp >= 65504 && cp <= 65510 || // Fullwidth signs
10336
+ cp >= 127744 && cp <= 128591 || // emoji
10337
+ cp >= 129280 && cp <= 129535 || // supplemental emoji
10338
+ cp >= 131072 && cp <= 262141);
10339
+ }
10340
+ function charCellWidth(char) {
10341
+ return isWideCodePoint(char.codePointAt(0) ?? 0) ? 2 : 1;
10342
+ }
10343
+ function stringCellWidth(value) {
10344
+ let width = 0;
10345
+ for (const char of value) width += charCellWidth(char);
10346
+ return width;
10347
+ }
10348
+ function truncateToCells(value, maxCells) {
10349
+ if (stringCellWidth(value) <= maxCells) return value;
10350
+ const budget = Math.max(1, maxCells - 1);
10351
+ let used = 0;
10352
+ let out = "";
10353
+ for (const char of value) {
10354
+ const w = charCellWidth(char);
10355
+ if (used + w > budget) break;
10356
+ out += char;
10357
+ used += w;
10358
+ }
10359
+ return out + "\u2026";
10360
+ }
10361
+ function padEndCells(value, width) {
10362
+ const missing = width - stringCellWidth(value);
10363
+ return missing > 0 ? value + " ".repeat(missing) : value;
10364
+ }
10323
10365
  function callerEnv() {
10324
10366
  const env = {};
10325
10367
  for (const [key, value] of Object.entries(process.env)) {
@@ -10356,12 +10398,12 @@ program2.command("start [args...]").description("Start an interactive program in
10356
10398
  }
10357
10399
  });
10358
10400
  });
10359
- program2.command("snapshot").description("Return the current rendered screen snapshot (requires: tui-use use <id> first)").option("--format <fmt>", "Output format: pretty, json (default: pretty)", "pretty").option("--color", "Preserve ANSI color/style escape sequences in output lines").action(async (opts) => {
10401
+ program2.command("snapshot").description("Return the rendered screen snapshot (pass --session <id>; in a single-session daemon, tui-use use <id> first also works)").option("--format <fmt>", "Output format: pretty, json (default: pretty)", "pretty").option("--color", "Preserve ANSI color/style escape sequences in output lines").action(async (opts) => {
10360
10402
  const res = await sendRequest({ type: "snapshot", color: opts.color ?? false, session_id: program2.opts().session });
10361
10403
  handleResponse(res, (r) => printScreen(r, opts.format));
10362
10404
  });
10363
10405
  program2.command("wait [duration]").description(
10364
- 'Wait for screen to change or timeout.\n wait \u2192 wait for screen to change (default 3000ms)\n wait 5000 \u2192 wait for 5000ms\n wait --text "pattern" \u2192 wait until screen contains text\n wait --debounce 300 \u2192 wait until screen is stable for 300ms (default: 100ms)\n wait --text "pattern" --poll \u2192 exit 0 with wait_state "pending" if the deadline passes while the session is still running'
10406
+ 'Wait for screen to change or timeout.\n wait \u2192 wait for screen to change and become stable (default 3000ms)\n wait 5000 \u2192 wait for 5000ms\n wait --text "pattern" \u2192 wait until screen contains text\n wait --debounce 300 \u2192 return once the screen has been stable for 300ms, even if it never changed (default: 100ms)\n wait --text "pattern" --poll \u2192 exit 0 with wait_state "pending" if the deadline passes while the session is still running'
10365
10407
  ).option("--text <text>", "Wait until screen contains literal text").option("--regex", "Interpret --text as a regex in a bounded worker").option("--exit", "Wait for process exit instead of just a screen change").option("--poll", 'Bounded polling: a deadline on a still-running session reports wait_state "pending" and exits 0; an exited session that never matched --text, and errors, exit 1. With --exit, "satisfied" means termination was observed, not success: check exit_code').option("--debounce <ms>", "Idle time after last change before resolving, in ms (default: 100)", "100").option("--format <fmt>", "Output format: pretty, json (default: pretty)", "pretty").option("--color", "Preserve ANSI color/style escape sequences in output lines").action(async (duration, opts) => {
10366
10408
  let timeoutMs = 3e3;
10367
10409
  if (duration) {
@@ -10396,7 +10438,7 @@ program2.command("wait [duration]").description(
10396
10438
  });
10397
10439
  });
10398
10440
  program2.command("type [input...]").description(
10399
- "Type text to the current session.\n Use \\n for Enter, \\t for Tab\n To specify a different session: tui-use use <id> first"
10441
+ "Type text to a session.\n Use \\n for Enter, \\t for Tab\n Pass --session <id> to target a session (single-session legacy: tui-use use <id> first)"
10400
10442
  ).option("--stdin", "Read literal UTF-8 input from stdin (safe for quotes and Unicode in PowerShell)").action(async (inputParts, opts) => {
10401
10443
  const input = inputText(inputParts, opts.stdin);
10402
10444
  const res = await sendRequest({ type: "type", input, literal: !!opts.stdin, session_id: program2.opts().session });
@@ -10416,7 +10458,7 @@ program2.command("press <key>").description(
10416
10458
  }
10417
10459
  });
10418
10460
  });
10419
- program2.command("kill").description("Terminate the current session (requires: tui-use use <id> first)").action(async () => {
10461
+ program2.command("kill").description("Terminate a session (pass --session <id>; single-session legacy: tui-use use <id> first). The killed session stays inspectable like an exited one").action(async () => {
10420
10462
  const res = await sendRequest({ type: "kill", session_id: program2.opts().session });
10421
10463
  handleResponse(res, (r) => {
10422
10464
  if (r.type === "kill") {
@@ -10531,17 +10573,21 @@ program2.command("list").description("List all active sessions").option("--forma
10531
10573
  console.log("No active sessions");
10532
10574
  return;
10533
10575
  }
10576
+ const LABEL_MAX = 24;
10577
+ const COMMAND_MAX = 40;
10578
+ const labels = r.sessions.map((s) => truncateToCells(s.label, LABEL_MAX));
10579
+ const commands = r.sessions.map((s) => truncateToCells(s.command, COMMAND_MAX));
10534
10580
  const idWidth = Math.max(10, ...r.sessions.map((s) => s.session_id.length));
10535
- const labelWidth = Math.max(5, ...r.sessions.map((s) => s.label.length));
10536
- const cmdWidth = Math.max(7, ...r.sessions.map((s) => s.command.length));
10537
- const header = `${"SESSION ID".padEnd(idWidth)} ${"LABEL".padEnd(labelWidth)} ${"COMMAND".padEnd(cmdWidth)} STATUS`;
10581
+ const labelWidth = Math.min(LABEL_MAX, Math.max(5, ...labels.map((label) => stringCellWidth(label))));
10582
+ const cmdWidth = Math.min(COMMAND_MAX, Math.max(7, ...commands.map((command) => stringCellWidth(command))));
10583
+ const header = `${"SESSION ID".padEnd(idWidth)} ${padEndCells("LABEL", labelWidth)} ${padEndCells("COMMAND", cmdWidth)} STATUS`;
10538
10584
  console.log(header);
10539
10585
  console.log("-".repeat(header.length));
10540
- for (const s of r.sessions) {
10586
+ for (const [index, s] of r.sessions.entries()) {
10541
10587
  const currentMark = s.session_id === r.current ? " [current]" : "";
10542
10588
  const status = s.status + currentMark;
10543
10589
  console.log(
10544
- `${s.session_id.padEnd(idWidth)} ${s.label.padEnd(labelWidth)} ${s.command.padEnd(cmdWidth)} ${status}`
10590
+ `${s.session_id.padEnd(idWidth)} ${padEndCells(labels[index], labelWidth)} ${padEndCells(commands[index], cmdWidth)} ${status}`
10545
10591
  );
10546
10592
  }
10547
10593
  }
@@ -7327,8 +7327,9 @@ var Session = class {
7327
7327
  }
7328
7328
  if (mapped === void 0) {
7329
7329
  const supported = Object.keys(KEY_MAP).join(", ");
7330
+ const hint = key.length === 1 && key >= " " && key !== "\x7F" ? " Printable characters should be sent with `type`, not `press`." : "";
7330
7331
  throw new Error(
7331
- `Unknown key: "${key}". Run \`tui-use keys\` to see all supported key names.
7332
+ `Unknown key: "${key}".${hint} Run \`tui-use keys\` to see all supported key names.
7332
7333
  Supported keys: ${supported}`
7333
7334
  );
7334
7335
  }
@@ -7384,7 +7385,6 @@ Supported keys: ${supported}`
7384
7385
  async wait(timeoutMs = 3e3, text, debounceMs = 100, options) {
7385
7386
  let satisfied = false;
7386
7387
  let timedOut = false;
7387
- const before = this.lastSnapshotState;
7388
7388
  const controller = new AbortController();
7389
7389
  const matches = async (screen) => text === void 0 || (await matchLines([screen], text, options?.regex, controller.signal))[0] !== null;
7390
7390
  if (this._status === "exited" && this.pendingWrites === 0) {
@@ -7435,7 +7435,7 @@ Supported keys: ${supported}`
7435
7435
  done();
7436
7436
  return;
7437
7437
  }
7438
- } else if (!options?.exit && this.screenIdentity() !== before) {
7438
+ } else if (!options?.exit) {
7439
7439
  if (idleTimer2) clearTimeout(idleTimer2);
7440
7440
  idleTimer2 = setTimeout(() => {
7441
7441
  satisfied = true;
@@ -7787,8 +7787,7 @@ async function handleRequest(req, signal) {
7787
7787
  return { type: "paste", ok: true };
7788
7788
  case "kill":
7789
7789
  await session.kill();
7790
- sessions.delete(id);
7791
- exitedAt.delete(id);
7790
+ exitedAt.set(id, Date.now());
7792
7791
  if (currentSession === id) currentSession = null;
7793
7792
  return { type: "kill", ok: true };
7794
7793
  case "find":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cydm/pie",
3
- "version": "2.6.8",
3
+ "version": "2.6.10",
4
4
  "description": "Pie AI Agent CLI",
5
5
  "type": "module",
6
6
  "bin": {