terminal-pilot 0.0.28 → 0.0.30

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 (57) hide show
  1. package/dist/cli.js +430 -68
  2. package/dist/cli.js.map +3 -3
  3. package/dist/commands/close-session.js +73 -6
  4. package/dist/commands/close-session.js.map +2 -2
  5. package/dist/commands/create-session.js +80 -9
  6. package/dist/commands/create-session.js.map +2 -2
  7. package/dist/commands/fill.js +73 -6
  8. package/dist/commands/fill.js.map +2 -2
  9. package/dist/commands/get-session.js +73 -6
  10. package/dist/commands/get-session.js.map +2 -2
  11. package/dist/commands/index.js +371 -62
  12. package/dist/commands/index.js.map +3 -3
  13. package/dist/commands/install.js +10 -5
  14. package/dist/commands/install.js.map +2 -2
  15. package/dist/commands/installer.js +10 -5
  16. package/dist/commands/installer.js.map +2 -2
  17. package/dist/commands/list-sessions.js +66 -4
  18. package/dist/commands/list-sessions.js.map +2 -2
  19. package/dist/commands/press-key.js +146 -71
  20. package/dist/commands/press-key.js.map +3 -3
  21. package/dist/commands/read-history.js +81 -7
  22. package/dist/commands/read-history.js.map +2 -2
  23. package/dist/commands/read-screen.js +73 -6
  24. package/dist/commands/read-screen.js.map +2 -2
  25. package/dist/commands/resize.js +75 -8
  26. package/dist/commands/resize.js.map +2 -2
  27. package/dist/commands/runtime.js +66 -4
  28. package/dist/commands/runtime.js.map +2 -2
  29. package/dist/commands/screenshot.js +228 -23
  30. package/dist/commands/screenshot.js.map +3 -3
  31. package/dist/commands/send-signal.js +73 -6
  32. package/dist/commands/send-signal.js.map +2 -2
  33. package/dist/commands/type.js +73 -6
  34. package/dist/commands/type.js.map +2 -2
  35. package/dist/commands/uninstall.js +10 -5
  36. package/dist/commands/uninstall.js.map +2 -2
  37. package/dist/commands/wait-for-exit.js +79 -7
  38. package/dist/commands/wait-for-exit.js.map +2 -2
  39. package/dist/commands/wait-for.js +90 -8
  40. package/dist/commands/wait-for.js.map +3 -3
  41. package/dist/index.js +52 -1
  42. package/dist/index.js.map +2 -2
  43. package/dist/keys.d.ts +1 -0
  44. package/dist/keys.js +15 -0
  45. package/dist/keys.js.map +2 -2
  46. package/dist/terminal-buffer.d.ts +2 -0
  47. package/dist/terminal-buffer.js +38 -1
  48. package/dist/terminal-buffer.js.map +2 -2
  49. package/dist/terminal-pilot.js +52 -1
  50. package/dist/terminal-pilot.js.map +2 -2
  51. package/dist/terminal-session.js +52 -1
  52. package/dist/terminal-session.js.map +2 -2
  53. package/dist/testing/cli-repl.js +430 -68
  54. package/dist/testing/cli-repl.js.map +3 -3
  55. package/dist/testing/qa-cli.js +430 -68
  56. package/dist/testing/qa-cli.js.map +3 -3
  57. package/package.json +1 -1
@@ -425,6 +425,84 @@ function defineCommand(config) {
425
425
  );
426
426
  }
427
427
 
428
+ // src/keys.ts
429
+ var NAMED_KEY_SEQUENCES = {
430
+ Enter: "\r",
431
+ Tab: " ",
432
+ Escape: "\x1B",
433
+ Backspace: "\x7F",
434
+ Delete: "\x1B[3~",
435
+ ArrowUp: "\x1B[A",
436
+ ArrowDown: "\x1B[B",
437
+ ArrowRight: "\x1B[C",
438
+ ArrowLeft: "\x1B[D",
439
+ Home: "\x1B[H",
440
+ End: "\x1B[F",
441
+ PageUp: "\x1B[5~",
442
+ PageDown: "\x1B[6~",
443
+ Space: " "
444
+ };
445
+ var NAMED_KEY_LOWER = new Map(
446
+ Object.entries(NAMED_KEY_SEQUENCES).map(([k, v]) => [k.toLowerCase(), v])
447
+ );
448
+ var VALID_KEYS_HINT = `Valid keys: ${Object.keys(NAMED_KEY_SEQUENCES).join(", ")}, Control+<letter>, Alt+<key>`;
449
+ var NAMED_KEY_PATTERN = Object.keys(NAMED_KEY_SEQUENCES).map(caseInsensitivePattern).join("|");
450
+ var TERMINAL_KEY_PATTERN = `^(?:${NAMED_KEY_PATTERN}|.|[Cc][Oo][Nn][Tt][Rr][Oo][Ll]\\+[A-Za-z]|[Aa][Ll][Tt]\\+.+)$`;
451
+ function unknownKeyError(key) {
452
+ return new Error(`Unknown terminal key: ${key}. ${VALID_KEYS_HINT}`);
453
+ }
454
+ function keyToSequence(key) {
455
+ const lowerKey = key.toLowerCase();
456
+ const namedSequence = NAMED_KEY_LOWER.get(lowerKey);
457
+ if (namedSequence !== void 0) {
458
+ return namedSequence;
459
+ }
460
+ if (lowerKey.startsWith("control+")) {
461
+ return controlKeyToSequence(key.slice("control+".length));
462
+ }
463
+ if (lowerKey.startsWith("alt+")) {
464
+ const nestedKey = key.slice("alt+".length);
465
+ if (nestedKey.length === 0) {
466
+ throw unknownKeyError(key);
467
+ }
468
+ if (nestedKey.length === 1) {
469
+ return "\x1B" + nestedKey;
470
+ }
471
+ try {
472
+ return "\x1B" + keyToSequence(nestedKey);
473
+ } catch {
474
+ throw unknownKeyError(key);
475
+ }
476
+ }
477
+ if (key.length === 1) {
478
+ return key;
479
+ }
480
+ throw unknownKeyError(key);
481
+ }
482
+ function controlKeyToSequence(controlKey) {
483
+ if (controlKey.length !== 1) {
484
+ throw unknownKeyError(`Control+${controlKey}`);
485
+ }
486
+ const uppercaseLetter = controlKey.toUpperCase();
487
+ const charCode = uppercaseLetter.charCodeAt(0);
488
+ if (charCode < 65 || charCode > 90) {
489
+ throw unknownKeyError(`Control+${controlKey}`);
490
+ }
491
+ return String.fromCharCode(charCode - 64);
492
+ }
493
+ function caseInsensitivePattern(value) {
494
+ let pattern = "";
495
+ for (const character of value) {
496
+ const lower = character.toLowerCase();
497
+ const upper = character.toUpperCase();
498
+ pattern += lower === upper ? escapePatternCharacter(character) : `[${upper}${lower}]`;
499
+ }
500
+ return pattern;
501
+ }
502
+ function escapePatternCharacter(character) {
503
+ return character.replace(/[\\^$.*+?()[\]{}|]/g, "\\$&");
504
+ }
505
+
428
506
  // src/terminal-pilot.ts
429
507
  import { randomUUID } from "node:crypto";
430
508
 
@@ -678,6 +756,10 @@ var TerminalBuffer = class {
678
756
  }
679
757
  _writePrintable(ch) {
680
758
  const width = this._cellWidth(ch);
759
+ if (width === 0) {
760
+ this._appendCombiningMark(ch);
761
+ return;
762
+ }
681
763
  if (this._autoWrap && this._pendingWrap) {
682
764
  this._cursorX = 0;
683
765
  this._newline();
@@ -712,11 +794,40 @@ var TerminalBuffer = class {
712
794
  _cellWidth(ch) {
713
795
  const codePoint = ch.codePointAt(0);
714
796
  if (codePoint === void 0) return 0;
797
+ if (isCombiningCodePoint(codePoint)) {
798
+ return 0;
799
+ }
715
800
  if (codePoint >= 4352 && codePoint <= 4447 || codePoint === 9001 || codePoint === 9002 || codePoint >= 11904 && codePoint <= 12350 || codePoint >= 12353 && codePoint <= 13247 || codePoint >= 13312 && codePoint <= 19903 || codePoint >= 19968 && codePoint <= 42191 || codePoint >= 44032 && codePoint <= 55215 || codePoint >= 63744 && codePoint <= 64255 || codePoint >= 65040 && codePoint <= 65049 || codePoint >= 65072 && codePoint <= 65135 || codePoint >= 65280 && codePoint <= 65376 || codePoint >= 65504 && codePoint <= 65510 || codePoint >= 127488 && codePoint <= 131069 || codePoint >= 131072 && codePoint <= 262141) {
716
801
  return Math.min(2, this._cols);
717
802
  }
718
803
  return 1;
719
804
  }
805
+ _appendCombiningMark(ch) {
806
+ const target = this._findCombiningTarget();
807
+ if (target === null) {
808
+ return;
809
+ }
810
+ const cell = this._screen[target.y]?.[target.x];
811
+ if (cell === null || cell === void 0) {
812
+ return;
813
+ }
814
+ cell[1] += ch;
815
+ }
816
+ _findCombiningTarget() {
817
+ const startX = this._pendingWrap ? this._cursorX : this._cursorX - 1;
818
+ for (let y = this._cursorY; y >= 0; y -= 1) {
819
+ const row = this._screen[y];
820
+ if (row === void 0) {
821
+ continue;
822
+ }
823
+ for (let x = y === this._cursorY ? startX : this._cols - 1; x >= 0; x -= 1) {
824
+ if (row[x] !== null) {
825
+ return { x, y };
826
+ }
827
+ }
828
+ }
829
+ return null;
830
+ }
720
831
  _setChar(y, x, ch) {
721
832
  const row = this._screen[y];
722
833
  if (row && x >= 0 && x < this._cols) {
@@ -857,7 +968,8 @@ var TerminalBuffer = class {
857
968
  case "J":
858
969
  if (p0 === 0) {
859
970
  this._eraseLine(this._cursorY, this._cursorX, this._cols - 1);
860
- for (let y = this._cursorY + 1; y < this._rows; y++) this._eraseLine(y, 0, this._cols - 1);
971
+ for (let y = this._cursorY + 1; y < this._rows; y++)
972
+ this._eraseLine(y, 0, this._cols - 1);
861
973
  } else if (p0 === 1) {
862
974
  for (let y = 0; y < this._cursorY; y++) this._eraseLine(y, 0, this._cols - 1);
863
975
  this._eraseLine(this._cursorY, 0, this._cursorX);
@@ -1222,6 +1334,9 @@ function createDefaultStyleState() {
1222
1334
  strikethrough: false
1223
1335
  };
1224
1336
  }
1337
+ function isCombiningCodePoint(codePoint) {
1338
+ return codePoint >= 768 && codePoint <= 879 || codePoint >= 6832 && codePoint <= 6911 || codePoint >= 7616 && codePoint <= 7679 || codePoint >= 8400 && codePoint <= 8447 || codePoint >= 65056 && codePoint <= 65071;
1339
+ }
1225
1340
  function serializeStyleState(state) {
1226
1341
  const codes = [];
1227
1342
  if (state.bold) {
@@ -1254,70 +1369,6 @@ function serializeStyleState(state) {
1254
1369
  return codes.length === 0 ? "" : `\x1B[${codes.join(";")}m`;
1255
1370
  }
1256
1371
 
1257
- // src/keys.ts
1258
- var NAMED_KEY_SEQUENCES = {
1259
- Enter: "\r",
1260
- Tab: " ",
1261
- Escape: "\x1B",
1262
- Backspace: "\x7F",
1263
- Delete: "\x1B[3~",
1264
- ArrowUp: "\x1B[A",
1265
- ArrowDown: "\x1B[B",
1266
- ArrowRight: "\x1B[C",
1267
- ArrowLeft: "\x1B[D",
1268
- Home: "\x1B[H",
1269
- End: "\x1B[F",
1270
- PageUp: "\x1B[5~",
1271
- PageDown: "\x1B[6~",
1272
- Space: " "
1273
- };
1274
- var NAMED_KEY_LOWER = new Map(
1275
- Object.entries(NAMED_KEY_SEQUENCES).map(([k, v]) => [k.toLowerCase(), v])
1276
- );
1277
- var VALID_KEYS_HINT = `Valid keys: ${Object.keys(NAMED_KEY_SEQUENCES).join(", ")}, Control+<letter>, Alt+<key>`;
1278
- function unknownKeyError(key) {
1279
- return new Error(`Unknown terminal key: ${key}. ${VALID_KEYS_HINT}`);
1280
- }
1281
- function keyToSequence(key) {
1282
- const lowerKey = key.toLowerCase();
1283
- const namedSequence = NAMED_KEY_LOWER.get(lowerKey);
1284
- if (namedSequence !== void 0) {
1285
- return namedSequence;
1286
- }
1287
- if (lowerKey.startsWith("control+")) {
1288
- return controlKeyToSequence(key.slice("control+".length));
1289
- }
1290
- if (lowerKey.startsWith("alt+")) {
1291
- const nestedKey = key.slice("alt+".length);
1292
- if (nestedKey.length === 0) {
1293
- throw unknownKeyError(key);
1294
- }
1295
- if (nestedKey.length === 1) {
1296
- return "\x1B" + nestedKey;
1297
- }
1298
- try {
1299
- return "\x1B" + keyToSequence(nestedKey);
1300
- } catch {
1301
- throw unknownKeyError(key);
1302
- }
1303
- }
1304
- if (key.length === 1) {
1305
- return key;
1306
- }
1307
- throw unknownKeyError(key);
1308
- }
1309
- function controlKeyToSequence(controlKey) {
1310
- if (controlKey.length !== 1) {
1311
- throw unknownKeyError(`Control+${controlKey}`);
1312
- }
1313
- const uppercaseLetter = controlKey.toUpperCase();
1314
- const charCode = uppercaseLetter.charCodeAt(0);
1315
- if (charCode < 65 || charCode > 90) {
1316
- throw unknownKeyError(`Control+${controlKey}`);
1317
- }
1318
- return String.fromCharCode(charCode - 64);
1319
- }
1320
-
1321
1372
  // src/terminal-screen.ts
1322
1373
  var TerminalScreen = class {
1323
1374
  lines;
@@ -1756,7 +1807,11 @@ function createTerminalPilotRuntime(options = {}) {
1756
1807
  const pendingNames = /* @__PURE__ */ new Set();
1757
1808
  let pilotPromise;
1758
1809
  function getRequestedName(name, env) {
1759
- return name ?? env?.get(SESSION_ENV_VAR);
1810
+ const requestedName = name ?? env?.get(SESSION_ENV_VAR);
1811
+ if (requestedName !== void 0 && requestedName.trim().length === 0) {
1812
+ throw new UserError("Session name must not be empty.");
1813
+ }
1814
+ return requestedName;
1760
1815
  }
1761
1816
  async function getPilot() {
1762
1817
  pilotPromise ??= launchPilot();
@@ -1792,7 +1847,9 @@ function createTerminalPilotRuntime(options = {}) {
1792
1847
  const sessionId = nameToId.get(name);
1793
1848
  if (sessionId === void 0) {
1794
1849
  const active = await listSessions();
1795
- throw new UserError(`Session "${name}" was not found. ${formatAvailableSessions(active.map((entry) => entry.name))}`);
1850
+ throw new UserError(
1851
+ `Session "${name}" was not found. ${formatAvailableSessions(active.map((entry) => entry.name))}`
1852
+ );
1796
1853
  }
1797
1854
  const pilot = await getPilot();
1798
1855
  try {
@@ -1800,7 +1857,9 @@ function createTerminalPilotRuntime(options = {}) {
1800
1857
  } catch {
1801
1858
  forgetSession(name, sessionId);
1802
1859
  const active = await listSessions();
1803
- throw new UserError(`Session "${name}" was not found. ${formatAvailableSessions(active.map((entry) => entry.name))}`);
1860
+ throw new UserError(
1861
+ `Session "${name}" was not found. ${formatAvailableSessions(active.map((entry) => entry.name))}`
1862
+ );
1804
1863
  }
1805
1864
  }
1806
1865
  async function listSessions() {
@@ -1831,6 +1890,9 @@ function createTerminalPilotRuntime(options = {}) {
1831
1890
  }
1832
1891
  return {
1833
1892
  async createSession(params2, env) {
1893
+ if (params2.command.trim().length === 0) {
1894
+ throw new UserError("Command must not be empty.");
1895
+ }
1834
1896
  const requestedName = getRequestedName(params2.session, env) ?? nextSessionName();
1835
1897
  await discardExitedSessionName(requestedName);
1836
1898
  if (nameToId.has(requestedName) || pendingNames.has(requestedName)) {
@@ -1896,8 +1958,10 @@ function createTerminalPilotRuntime(options = {}) {
1896
1958
 
1897
1959
  // src/commands/press-key.ts
1898
1960
  var params = S.Object({
1899
- key: S.String({ description: "Named key to press" }),
1900
- session: S.Optional(S.String({ short: "s", description: "Session name" }))
1961
+ key: S.String({ description: "Named key to press", pattern: TERMINAL_KEY_PATTERN }),
1962
+ session: S.Optional(
1963
+ S.String({ short: "s", description: "Session name", minLength: 1, pattern: "\\S" })
1964
+ )
1901
1965
  });
1902
1966
  var pressKey = defineCommand({
1903
1967
  name: "press-key",
@@ -1906,11 +1970,22 @@ var pressKey = defineCommand({
1906
1970
  positional: ["key"],
1907
1971
  params,
1908
1972
  handler: async ({ params: params2, env, terminalPilotRuntime }) => {
1909
- const namedSession = await getTerminalPilotRuntime(terminalPilotRuntime).resolveSession(params2.session, env);
1973
+ assertTerminalKey(params2.key);
1974
+ const namedSession = await getTerminalPilotRuntime(terminalPilotRuntime).resolveSession(
1975
+ params2.session,
1976
+ env
1977
+ );
1910
1978
  await namedSession.session.press(params2.key);
1911
1979
  return void 0;
1912
1980
  }
1913
1981
  });
1982
+ function assertTerminalKey(key) {
1983
+ try {
1984
+ keyToSequence(key);
1985
+ } catch (error) {
1986
+ throw new UserError(error instanceof Error ? error.message : String(error));
1987
+ }
1988
+ }
1914
1989
  export {
1915
1990
  pressKey
1916
1991
  };