terminal-pilot 0.0.4 → 0.0.6

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 (86) hide show
  1. package/README.md +40 -0
  2. package/dist/ansi.js +68 -63
  3. package/dist/ansi.js.map +7 -0
  4. package/dist/cli.d.ts +2 -0
  5. package/dist/cli.js +1807 -0
  6. package/dist/cli.js.map +7 -0
  7. package/dist/commands/close-session.d.ts +12 -0
  8. package/dist/commands/close-session.js +1264 -0
  9. package/dist/commands/close-session.js.map +7 -0
  10. package/dist/commands/create-session.d.ts +26 -0
  11. package/dist/commands/create-session.js +1271 -0
  12. package/dist/commands/create-session.js.map +7 -0
  13. package/dist/commands/fill.d.ts +10 -0
  14. package/dist/commands/fill.js +1267 -0
  15. package/dist/commands/fill.js.map +7 -0
  16. package/dist/commands/get-session.d.ts +18 -0
  17. package/dist/commands/get-session.js +1269 -0
  18. package/dist/commands/get-session.js.map +7 -0
  19. package/dist/commands/index.d.ts +242 -0
  20. package/dist/commands/index.js +1781 -0
  21. package/dist/commands/index.js.map +7 -0
  22. package/dist/commands/install.d.ts +20 -0
  23. package/dist/commands/install.js +119 -0
  24. package/dist/commands/install.js.map +7 -0
  25. package/dist/commands/installer.d.ts +43 -0
  26. package/dist/commands/installer.js +123 -0
  27. package/dist/commands/installer.js.map +7 -0
  28. package/dist/commands/list-sessions.d.ts +16 -0
  29. package/dist/commands/list-sessions.js +1268 -0
  30. package/dist/commands/list-sessions.js.map +7 -0
  31. package/dist/commands/press-key.d.ts +10 -0
  32. package/dist/commands/press-key.js +1267 -0
  33. package/dist/commands/press-key.js.map +7 -0
  34. package/dist/commands/read-history.d.ts +16 -0
  35. package/dist/commands/read-history.js +1266 -0
  36. package/dist/commands/read-history.js.map +7 -0
  37. package/dist/commands/read-screen.d.ts +21 -0
  38. package/dist/commands/read-screen.js +1270 -0
  39. package/dist/commands/read-screen.js.map +7 -0
  40. package/dist/commands/resize.d.ts +12 -0
  41. package/dist/commands/resize.js +1267 -0
  42. package/dist/commands/resize.js.map +7 -0
  43. package/dist/commands/runtime.d.ts +36 -0
  44. package/dist/commands/runtime.js +1256 -0
  45. package/dist/commands/runtime.js.map +7 -0
  46. package/dist/commands/screenshot.d.ts +14 -0
  47. package/dist/commands/screenshot.js +1274 -0
  48. package/dist/commands/screenshot.js.map +7 -0
  49. package/dist/commands/send-signal.d.ts +10 -0
  50. package/dist/commands/send-signal.js +1267 -0
  51. package/dist/commands/send-signal.js.map +7 -0
  52. package/dist/commands/type.d.ts +10 -0
  53. package/dist/commands/type.js +1267 -0
  54. package/dist/commands/type.js.map +7 -0
  55. package/dist/commands/uninstall.d.ts +14 -0
  56. package/dist/commands/uninstall.js +124 -0
  57. package/dist/commands/uninstall.js.map +7 -0
  58. package/dist/commands/wait-for-exit.d.ts +14 -0
  59. package/dist/commands/wait-for-exit.js +1268 -0
  60. package/dist/commands/wait-for-exit.js.map +7 -0
  61. package/dist/commands/wait-for.d.ts +20 -0
  62. package/dist/commands/wait-for.js +1275 -0
  63. package/dist/commands/wait-for.js.map +7 -0
  64. package/dist/exports.compile-check.js +1 -1
  65. package/dist/exports.compile-check.js.map +7 -0
  66. package/dist/index.js +1118 -5
  67. package/dist/index.js.map +7 -0
  68. package/dist/keys.js +58 -49
  69. package/dist/keys.js.map +7 -0
  70. package/dist/templates/terminal-pilot.md +45 -0
  71. package/dist/terminal-buffer.d.ts +9 -1
  72. package/dist/terminal-buffer.js +525 -425
  73. package/dist/terminal-buffer.js.map +7 -0
  74. package/dist/terminal-pilot.js +1105 -35
  75. package/dist/terminal-pilot.js.map +7 -0
  76. package/dist/terminal-screen.js +108 -26
  77. package/dist/terminal-screen.js.map +7 -0
  78. package/dist/terminal-session.js +1020 -292
  79. package/dist/terminal-session.js.map +7 -0
  80. package/dist/testing/cli-repl.d.ts +15 -0
  81. package/dist/testing/cli-repl.js +1911 -0
  82. package/dist/testing/cli-repl.js.map +7 -0
  83. package/dist/testing/qa-cli.d.ts +1 -0
  84. package/dist/testing/qa-cli.js +2828 -0
  85. package/dist/testing/qa-cli.js.map +7 -0
  86. package/package.json +26 -5
@@ -0,0 +1,2828 @@
1
+ // src/testing/qa-cli.ts
2
+ import assert from "node:assert/strict";
3
+ import { rm } from "node:fs/promises";
4
+ import os2 from "node:os";
5
+ import path3 from "node:path";
6
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
7
+
8
+ // src/ansi.ts
9
+ var ESC = 27;
10
+ var BEL = 7;
11
+ var ST = 156;
12
+ var CSI = 155;
13
+ var OSC = 157;
14
+ var DCS = 144;
15
+ var SOS = 152;
16
+ var PM = 158;
17
+ var APC = 159;
18
+ function stripAnsi(input) {
19
+ let output = "";
20
+ for (let index = 0; index < input.length; index += 1) {
21
+ const code = input.charCodeAt(index);
22
+ if (code === ESC) {
23
+ const nextCode = input.charCodeAt(index + 1);
24
+ if (nextCode === 91) {
25
+ index = consumeCsi(input, index + 2);
26
+ continue;
27
+ }
28
+ if (nextCode === 93) {
29
+ index = consumeTerminatedString(input, index + 2, true);
30
+ continue;
31
+ }
32
+ if (nextCode === 80 || nextCode === 88 || nextCode === 94 || nextCode === 95) {
33
+ index = consumeTerminatedString(input, index + 2, false);
34
+ continue;
35
+ }
36
+ if (!Number.isNaN(nextCode)) {
37
+ index += 1;
38
+ }
39
+ continue;
40
+ }
41
+ if (code === CSI) {
42
+ index = consumeCsi(input, index + 1);
43
+ continue;
44
+ }
45
+ if (code === OSC) {
46
+ index = consumeTerminatedString(input, index + 1, true);
47
+ continue;
48
+ }
49
+ if (code === DCS || code === SOS || code === PM || code === APC) {
50
+ index = consumeTerminatedString(input, index + 1, false);
51
+ continue;
52
+ }
53
+ output += input[index];
54
+ }
55
+ return output;
56
+ }
57
+ function consumeCsi(input, index) {
58
+ while (index < input.length) {
59
+ const code = input.charCodeAt(index);
60
+ if (code >= 64 && code <= 126) {
61
+ return index;
62
+ }
63
+ index += 1;
64
+ }
65
+ return input.length;
66
+ }
67
+ function consumeTerminatedString(input, index, allowBellTerminator) {
68
+ while (index < input.length) {
69
+ const code = input.charCodeAt(index);
70
+ if (code === ST || allowBellTerminator && code === BEL) {
71
+ return index;
72
+ }
73
+ if (code === ESC && input.charCodeAt(index + 1) === 92) {
74
+ return index + 1;
75
+ }
76
+ index += 1;
77
+ }
78
+ return input.length;
79
+ }
80
+
81
+ // src/cli.ts
82
+ import { realpath } from "node:fs/promises";
83
+ import path2 from "node:path";
84
+ import { fileURLToPath } from "node:url";
85
+ import { runCLI } from "@poe-code/cmdkit/cli";
86
+
87
+ // src/commands/index.ts
88
+ import { defineGroup } from "@poe-code/cmdkit";
89
+
90
+ // src/commands/close-session.ts
91
+ import { defineCommand, S } from "@poe-code/cmdkit";
92
+
93
+ // src/commands/runtime.ts
94
+ import { UserError } from "@poe-code/cmdkit";
95
+
96
+ // src/terminal-pilot.ts
97
+ import { randomUUID } from "node:crypto";
98
+
99
+ // src/terminal-session.ts
100
+ import { spawn as spawnChildProcess } from "node:child_process";
101
+ import { EventEmitter } from "node:events";
102
+ import * as nodePty from "node-pty";
103
+
104
+ // src/terminal-buffer.ts
105
+ var RESET_SGR = "\x1B[0m";
106
+ var TerminalBuffer = class {
107
+ _cols;
108
+ _rows;
109
+ _screen;
110
+ _cursorX = 0;
111
+ _cursorY = 0;
112
+ _savedCursor = { x: 0, y: 0 };
113
+ _scrollTop = 0;
114
+ _scrollBottom;
115
+ _state = 0 /* Normal */;
116
+ _csiParams = "";
117
+ _csiPrivate = "";
118
+ _style = createDefaultStyleState();
119
+ _styleSequence = "";
120
+ displayBuffer;
121
+ constructor(cols, rows) {
122
+ this._cols = cols;
123
+ this._rows = rows;
124
+ this._scrollBottom = rows - 1;
125
+ this._screen = this._makeScreen(cols, rows);
126
+ this.displayBuffer = Object.defineProperties(
127
+ {},
128
+ {
129
+ cursorX: { get: () => this._cursorX, enumerable: true },
130
+ cursorY: { get: () => this._cursorY, enumerable: true },
131
+ data: { get: () => this._screen, enumerable: true }
132
+ }
133
+ );
134
+ }
135
+ write(data) {
136
+ for (const ch of data) {
137
+ this._feed(ch);
138
+ }
139
+ }
140
+ renderLine(row) {
141
+ const cells = this._screen[row] ?? [];
142
+ let lastVisibleCell = -1;
143
+ for (let index = cells.length - 1; index >= 0; index -= 1) {
144
+ if (cells[index] !== null) {
145
+ lastVisibleCell = index;
146
+ break;
147
+ }
148
+ }
149
+ if (lastVisibleCell === -1) {
150
+ return "";
151
+ }
152
+ let line = "";
153
+ let activeStyle = "";
154
+ for (let index = 0; index <= lastVisibleCell; index += 1) {
155
+ const cell = cells[index];
156
+ const cellStyle = cell?.style ?? "";
157
+ if (cellStyle !== activeStyle) {
158
+ line += cellStyle.length > 0 ? cellStyle : RESET_SGR;
159
+ activeStyle = cellStyle;
160
+ }
161
+ line += cell?.[1] ?? " ";
162
+ }
163
+ if (activeStyle.length > 0) {
164
+ line += RESET_SGR;
165
+ }
166
+ return line;
167
+ }
168
+ resize(cols, rows) {
169
+ while (this._screen.length < rows) {
170
+ this._screen.push(this._makeRow(cols));
171
+ }
172
+ this._screen.length = rows;
173
+ for (let y = 0; y < rows; y++) {
174
+ const row = this._screen[y] ?? this._makeRow(cols);
175
+ while (row.length < cols) row.push(null);
176
+ row.length = cols;
177
+ this._screen[y] = row;
178
+ }
179
+ this._cols = cols;
180
+ this._rows = rows;
181
+ this._scrollTop = 0;
182
+ this._scrollBottom = rows - 1;
183
+ this._cursorX = this._clamp(this._cursorX, 0, cols - 1);
184
+ this._cursorY = this._clamp(this._cursorY, 0, rows - 1);
185
+ }
186
+ _makeScreen(cols, rows) {
187
+ return Array.from({ length: rows }, () => this._makeRow(cols));
188
+ }
189
+ _makeRow(cols) {
190
+ return Array(cols).fill(null);
191
+ }
192
+ _clamp(value, min, max) {
193
+ return Math.max(min, Math.min(max, value));
194
+ }
195
+ _setChar(y, x, ch) {
196
+ const row = this._screen[y];
197
+ if (row && x >= 0 && x < this._cols) {
198
+ const cell = [ch.charCodeAt(0), ch];
199
+ if (this._styleSequence.length > 0) {
200
+ Object.defineProperty(cell, "style", {
201
+ value: this._styleSequence,
202
+ writable: true,
203
+ configurable: true
204
+ });
205
+ }
206
+ row[x] = cell;
207
+ }
208
+ }
209
+ _eraseLine(y, fromX, toX) {
210
+ const row = this._screen[y];
211
+ if (!row) return;
212
+ for (let x = fromX; x <= toX && x < this._cols; x++) {
213
+ row[x] = null;
214
+ }
215
+ }
216
+ _scrollUp(count) {
217
+ for (let i = 0; i < count; i++) {
218
+ this._screen.splice(this._scrollTop, 1);
219
+ this._screen.splice(this._scrollBottom, 0, this._makeRow(this._cols));
220
+ }
221
+ }
222
+ _scrollDown(count) {
223
+ for (let i = 0; i < count; i++) {
224
+ this._screen.splice(this._scrollBottom, 1);
225
+ this._screen.splice(this._scrollTop, 0, this._makeRow(this._cols));
226
+ }
227
+ }
228
+ _newline() {
229
+ if (this._cursorY === this._scrollBottom) {
230
+ this._scrollUp(1);
231
+ } else {
232
+ this._cursorY = Math.min(this._cursorY + 1, this._rows - 1);
233
+ }
234
+ }
235
+ _parseCsiParams() {
236
+ if (!this._csiParams) return [];
237
+ return this._csiParams.split(";").map((s) => s === "" ? 0 : parseInt(s, 10));
238
+ }
239
+ _execCsi(final) {
240
+ const params17 = this._parseCsiParams();
241
+ const p0 = params17[0] ?? 0;
242
+ const p1 = params17[1] ?? 0;
243
+ if (this._csiPrivate === "?") {
244
+ if (final === "h" || final === "l") {
245
+ if (params17.includes(1049)) {
246
+ if (final === "h") {
247
+ this._screen = this._makeScreen(this._cols, this._rows);
248
+ this._cursorX = 0;
249
+ this._cursorY = 0;
250
+ } else {
251
+ this._screen = this._makeScreen(this._cols, this._rows);
252
+ this._cursorX = 0;
253
+ this._cursorY = 0;
254
+ }
255
+ this._resetStyle();
256
+ }
257
+ }
258
+ return;
259
+ }
260
+ switch (final) {
261
+ case "A":
262
+ this._cursorY = this._clamp(this._cursorY - Math.max(1, p0), 0, this._rows - 1);
263
+ break;
264
+ case "B":
265
+ this._cursorY = this._clamp(this._cursorY + Math.max(1, p0), 0, this._rows - 1);
266
+ break;
267
+ case "C":
268
+ // cursor forward
269
+ case "a":
270
+ this._cursorX = this._clamp(this._cursorX + Math.max(1, p0), 0, this._cols - 1);
271
+ break;
272
+ case "D":
273
+ this._cursorX = this._clamp(this._cursorX - Math.max(1, p0), 0, this._cols - 1);
274
+ break;
275
+ case "E":
276
+ this._cursorY = this._clamp(this._cursorY + Math.max(1, p0), 0, this._rows - 1);
277
+ this._cursorX = 0;
278
+ break;
279
+ case "F":
280
+ this._cursorY = this._clamp(this._cursorY - Math.max(1, p0), 0, this._rows - 1);
281
+ this._cursorX = 0;
282
+ break;
283
+ case "G":
284
+ // cursor horizontal absolute
285
+ case "`":
286
+ this._cursorX = this._clamp(Math.max(1, p0) - 1, 0, this._cols - 1);
287
+ break;
288
+ case "H":
289
+ // cursor position
290
+ case "f":
291
+ this._cursorY = this._clamp(Math.max(1, p0) - 1, 0, this._rows - 1);
292
+ this._cursorX = this._clamp(Math.max(1, p1) - 1, 0, this._cols - 1);
293
+ break;
294
+ case "I":
295
+ for (let i = 0; i < Math.max(1, p0); i++) {
296
+ this._cursorX = Math.min(this._cols - 1, (Math.floor(this._cursorX / 8) + 1) * 8);
297
+ }
298
+ break;
299
+ case "J":
300
+ if (p0 === 0) {
301
+ this._eraseLine(this._cursorY, this._cursorX, this._cols - 1);
302
+ for (let y = this._cursorY + 1; y < this._rows; y++) this._eraseLine(y, 0, this._cols - 1);
303
+ } else if (p0 === 1) {
304
+ for (let y = 0; y < this._cursorY; y++) this._eraseLine(y, 0, this._cols - 1);
305
+ this._eraseLine(this._cursorY, 0, this._cursorX);
306
+ } else if (p0 === 2 || p0 === 3) {
307
+ for (let y = 0; y < this._rows; y++) this._eraseLine(y, 0, this._cols - 1);
308
+ }
309
+ break;
310
+ case "K":
311
+ if (p0 === 0) this._eraseLine(this._cursorY, this._cursorX, this._cols - 1);
312
+ else if (p0 === 1) this._eraseLine(this._cursorY, 0, this._cursorX);
313
+ else if (p0 === 2) this._eraseLine(this._cursorY, 0, this._cols - 1);
314
+ break;
315
+ case "X":
316
+ this._eraseLine(this._cursorY, this._cursorX, this._cursorX + Math.max(1, p0) - 1);
317
+ break;
318
+ case "L": {
319
+ const n = Math.max(1, p0);
320
+ for (let i = 0; i < n; i++) {
321
+ this._screen.splice(this._scrollBottom, 1);
322
+ this._screen.splice(this._cursorY, 0, this._makeRow(this._cols));
323
+ }
324
+ break;
325
+ }
326
+ case "M": {
327
+ const n = Math.max(1, p0);
328
+ for (let i = 0; i < n; i++) {
329
+ this._screen.splice(this._cursorY, 1);
330
+ this._screen.splice(this._scrollBottom, 0, this._makeRow(this._cols));
331
+ }
332
+ break;
333
+ }
334
+ case "P": {
335
+ const row = this._screen[this._cursorY];
336
+ if (row) {
337
+ const n = Math.max(1, p0);
338
+ row.splice(this._cursorX, n);
339
+ while (row.length < this._cols) row.push(null);
340
+ }
341
+ break;
342
+ }
343
+ case "@": {
344
+ const row = this._screen[this._cursorY];
345
+ if (row) {
346
+ const n = Math.max(1, p0);
347
+ for (let i = 0; i < n; i++) row.splice(this._cursorX, 0, null);
348
+ row.splice(this._cols);
349
+ }
350
+ break;
351
+ }
352
+ case "S":
353
+ this._scrollUp(Math.max(1, p0));
354
+ break;
355
+ case "T":
356
+ if (params17.length <= 1) this._scrollDown(Math.max(1, p0));
357
+ break;
358
+ case "Z": {
359
+ const n = Math.max(1, p0);
360
+ for (let i = 0; i < n; i++) {
361
+ this._cursorX = Math.max(0, (Math.ceil(this._cursorX / 8) - 1) * 8);
362
+ }
363
+ break;
364
+ }
365
+ case "d":
366
+ this._cursorY = this._clamp(Math.max(1, p0) - 1, 0, this._rows - 1);
367
+ break;
368
+ case "e":
369
+ this._cursorY = this._clamp(this._cursorY + Math.max(1, p0), 0, this._rows - 1);
370
+ break;
371
+ case "r": {
372
+ const top = this._clamp(Math.max(1, p0) - 1, 0, this._rows - 1);
373
+ const bottom = this._clamp((p1 === 0 ? this._rows : p1) - 1, 0, this._rows - 1);
374
+ if (top < bottom) {
375
+ this._scrollTop = top;
376
+ this._scrollBottom = bottom;
377
+ }
378
+ this._cursorX = 0;
379
+ this._cursorY = 0;
380
+ break;
381
+ }
382
+ case "s":
383
+ this._savedCursor = { x: this._cursorX, y: this._cursorY };
384
+ break;
385
+ case "u":
386
+ this._cursorX = this._clamp(this._savedCursor.x, 0, this._cols - 1);
387
+ this._cursorY = this._clamp(this._savedCursor.y, 0, this._rows - 1);
388
+ break;
389
+ case "m":
390
+ this._applySgr(params17);
391
+ break;
392
+ default:
393
+ break;
394
+ }
395
+ }
396
+ _feed(ch) {
397
+ const code = ch.charCodeAt(0);
398
+ switch (this._state) {
399
+ case 0 /* Normal */:
400
+ this._feedNormal(ch, code);
401
+ break;
402
+ case 1 /* Escape */:
403
+ this._feedEscape(ch, code);
404
+ break;
405
+ case 2 /* Csi */:
406
+ this._feedCsi(ch, code);
407
+ break;
408
+ case 3 /* Osc */:
409
+ if (code === 7 || code === 156) {
410
+ this._state = 0 /* Normal */;
411
+ } else if (code === 27) {
412
+ this._state = 0 /* Normal */;
413
+ }
414
+ break;
415
+ case 4 /* Str */:
416
+ if (code === 156 || code === 7) {
417
+ this._state = 0 /* Normal */;
418
+ } else if (code === 27) {
419
+ this._state = 0 /* Normal */;
420
+ }
421
+ break;
422
+ case 5 /* EscCharset */:
423
+ this._state = 0 /* Normal */;
424
+ break;
425
+ case 6 /* EscHash */:
426
+ this._state = 0 /* Normal */;
427
+ break;
428
+ }
429
+ }
430
+ _feedNormal(ch, code) {
431
+ if (code === 27) {
432
+ this._state = 1 /* Escape */;
433
+ } else if (code === 155) {
434
+ this._csiParams = "";
435
+ this._csiPrivate = "";
436
+ this._state = 2 /* Csi */;
437
+ } else if (code === 157) {
438
+ this._state = 3 /* Osc */;
439
+ } else if (code === 144 || code === 152 || code === 158 || code === 159) {
440
+ this._state = 4 /* Str */;
441
+ } else if (code === 7 || code === 5 || code === 6) {
442
+ } else if (code === 8) {
443
+ if (this._cursorX > 0) this._cursorX--;
444
+ } else if (code === 127) {
445
+ if (this._cursorX > 0) {
446
+ this._cursorX--;
447
+ this._setChar(this._cursorY, this._cursorX, " ");
448
+ }
449
+ } else if (code === 9) {
450
+ this._cursorX = Math.min(this._cols - 1, (Math.floor(this._cursorX / 8) + 1) * 8);
451
+ } else if (code === 10 || code === 11 || code === 12) {
452
+ this._newline();
453
+ } else if (code === 13) {
454
+ this._cursorX = 0;
455
+ } else if (code === 14 || code === 15) {
456
+ } else if (code >= 32 && code !== 127) {
457
+ this._setChar(this._cursorY, this._cursorX, ch);
458
+ this._cursorX++;
459
+ if (this._cursorX >= this._cols) {
460
+ this._cursorX = 0;
461
+ this._newline();
462
+ }
463
+ }
464
+ }
465
+ _feedEscape(ch, code) {
466
+ this._state = 0 /* Normal */;
467
+ if (code === 91) {
468
+ this._csiParams = "";
469
+ this._csiPrivate = "";
470
+ this._state = 2 /* Csi */;
471
+ } else if (code === 93) {
472
+ this._state = 3 /* Osc */;
473
+ } else if (code === 80 || code === 88 || code === 94 || code === 95) {
474
+ this._state = 4 /* Str */;
475
+ } else if (code === 40 || code === 41 || code === 42 || code === 43 || code === 45 || code === 46) {
476
+ this._state = 5 /* EscCharset */;
477
+ } else if (code === 35) {
478
+ this._state = 6 /* EscHash */;
479
+ } else if (code === 55) {
480
+ this._savedCursor = { x: this._cursorX, y: this._cursorY };
481
+ } else if (code === 56) {
482
+ this._cursorX = this._clamp(this._savedCursor.x, 0, this._cols - 1);
483
+ this._cursorY = this._clamp(this._savedCursor.y, 0, this._rows - 1);
484
+ } else if (code === 68) {
485
+ this._newline();
486
+ } else if (code === 69) {
487
+ this._cursorX = 0;
488
+ this._newline();
489
+ } else if (code === 77) {
490
+ if (this._cursorY === this._scrollTop) {
491
+ this._scrollDown(1);
492
+ } else {
493
+ this._cursorY = Math.max(0, this._cursorY - 1);
494
+ }
495
+ } else if (code === 72) {
496
+ } else if (code === 99) {
497
+ this._screen = this._makeScreen(this._cols, this._rows);
498
+ this._cursorX = 0;
499
+ this._cursorY = 0;
500
+ this._savedCursor = { x: 0, y: 0 };
501
+ this._scrollTop = 0;
502
+ this._scrollBottom = this._rows - 1;
503
+ this._resetStyle();
504
+ }
505
+ }
506
+ _feedCsi(ch, code) {
507
+ if (code >= 64 && code <= 126) {
508
+ this._execCsi(ch);
509
+ this._state = 0 /* Normal */;
510
+ } else if (code === 63 || code === 33 || code === 62 || code === 32) {
511
+ this._csiPrivate = ch;
512
+ } else if (code >= 48 && code <= 57 || code === 59) {
513
+ this._csiParams += ch;
514
+ }
515
+ }
516
+ _resetStyle() {
517
+ this._style = createDefaultStyleState();
518
+ this._styleSequence = "";
519
+ }
520
+ _applySgr(params17) {
521
+ const normalizedParams = params17.length === 0 ? [0] : params17;
522
+ for (let index = 0; index < normalizedParams.length; index += 1) {
523
+ const value = normalizedParams[index] ?? 0;
524
+ switch (value) {
525
+ case 0:
526
+ this._resetStyle();
527
+ break;
528
+ case 1:
529
+ this._style.bold = true;
530
+ break;
531
+ case 2:
532
+ this._style.dim = true;
533
+ break;
534
+ case 3:
535
+ this._style.italic = true;
536
+ break;
537
+ case 4:
538
+ this._style.underline = true;
539
+ break;
540
+ case 7:
541
+ this._style.inverse = true;
542
+ break;
543
+ case 9:
544
+ this._style.strikethrough = true;
545
+ break;
546
+ case 21:
547
+ case 22:
548
+ this._style.bold = false;
549
+ this._style.dim = false;
550
+ break;
551
+ case 23:
552
+ this._style.italic = false;
553
+ break;
554
+ case 24:
555
+ this._style.underline = false;
556
+ break;
557
+ case 27:
558
+ this._style.inverse = false;
559
+ break;
560
+ case 29:
561
+ this._style.strikethrough = false;
562
+ break;
563
+ case 39:
564
+ this._style.fg = void 0;
565
+ break;
566
+ case 49:
567
+ this._style.bg = void 0;
568
+ break;
569
+ case 38:
570
+ case 48:
571
+ index = this._applyExtendedColor(value, normalizedParams, index);
572
+ break;
573
+ default:
574
+ if (value >= 30 && value <= 37 || value >= 90 && value <= 97) {
575
+ this._style.fg = [value];
576
+ } else if (value >= 40 && value <= 47 || value >= 100 && value <= 107) {
577
+ this._style.bg = [value];
578
+ }
579
+ break;
580
+ }
581
+ }
582
+ this._styleSequence = serializeStyleState(this._style);
583
+ }
584
+ _applyExtendedColor(control, params17, index) {
585
+ const mode = params17[index + 1];
586
+ const target = control === 38 ? "fg" : "bg";
587
+ if (mode === 5) {
588
+ const paletteIndex = params17[index + 2];
589
+ if (paletteIndex !== void 0) {
590
+ this._style[target] = [control, 5, paletteIndex];
591
+ return index + 2;
592
+ }
593
+ return index;
594
+ }
595
+ if (mode === 2) {
596
+ const red = params17[index + 2];
597
+ const green = params17[index + 3];
598
+ const blue = params17[index + 4];
599
+ if (red !== void 0 && green !== void 0 && blue !== void 0) {
600
+ this._style[target] = [control, 2, red, green, blue];
601
+ return index + 4;
602
+ }
603
+ }
604
+ return index;
605
+ }
606
+ };
607
+ function createDefaultStyleState() {
608
+ return {
609
+ bold: false,
610
+ dim: false,
611
+ italic: false,
612
+ underline: false,
613
+ inverse: false,
614
+ strikethrough: false
615
+ };
616
+ }
617
+ function serializeStyleState(state) {
618
+ const codes = [];
619
+ if (state.bold) {
620
+ codes.push(1);
621
+ }
622
+ if (state.dim) {
623
+ codes.push(2);
624
+ }
625
+ if (state.italic) {
626
+ codes.push(3);
627
+ }
628
+ if (state.underline) {
629
+ codes.push(4);
630
+ }
631
+ if (state.inverse) {
632
+ codes.push(7);
633
+ }
634
+ if (state.strikethrough) {
635
+ codes.push(9);
636
+ }
637
+ if (state.fg !== void 0) {
638
+ codes.push(...state.fg);
639
+ }
640
+ if (state.bg !== void 0) {
641
+ codes.push(...state.bg);
642
+ }
643
+ return codes.length === 0 ? "" : `\x1B[${codes.join(";")}m`;
644
+ }
645
+
646
+ // src/keys.ts
647
+ var NAMED_KEY_SEQUENCES = {
648
+ Enter: "\r",
649
+ Tab: " ",
650
+ Escape: "\x1B",
651
+ Backspace: "\x7F",
652
+ Delete: "\x1B[3~",
653
+ ArrowUp: "\x1B[A",
654
+ ArrowDown: "\x1B[B",
655
+ ArrowRight: "\x1B[C",
656
+ ArrowLeft: "\x1B[D",
657
+ Home: "\x1B[H",
658
+ End: "\x1B[F",
659
+ PageUp: "\x1B[5~",
660
+ PageDown: "\x1B[6~",
661
+ Space: " "
662
+ };
663
+ var NAMED_KEY_LOWER = new Map(
664
+ Object.entries(NAMED_KEY_SEQUENCES).map(([k, v]) => [k.toLowerCase(), v])
665
+ );
666
+ var VALID_KEYS_HINT = `Valid keys: ${Object.keys(NAMED_KEY_SEQUENCES).join(", ")}, Control+<letter>, Alt+<key>`;
667
+ function unknownKeyError(key) {
668
+ return new Error(`Unknown terminal key: ${key}. ${VALID_KEYS_HINT}`);
669
+ }
670
+ function keyToSequence(key) {
671
+ const lowerKey = key.toLowerCase();
672
+ const namedSequence = NAMED_KEY_LOWER.get(lowerKey);
673
+ if (namedSequence !== void 0) {
674
+ return namedSequence;
675
+ }
676
+ if (lowerKey.startsWith("control+")) {
677
+ return controlKeyToSequence(key.slice("control+".length));
678
+ }
679
+ if (lowerKey.startsWith("alt+")) {
680
+ const nestedKey = key.slice("alt+".length);
681
+ if (nestedKey.length === 0) {
682
+ throw unknownKeyError(key);
683
+ }
684
+ if (nestedKey.length === 1) {
685
+ return "\x1B" + nestedKey;
686
+ }
687
+ try {
688
+ return "\x1B" + keyToSequence(nestedKey);
689
+ } catch {
690
+ throw unknownKeyError(key);
691
+ }
692
+ }
693
+ if (key.length === 1) {
694
+ return key;
695
+ }
696
+ throw unknownKeyError(key);
697
+ }
698
+ function controlKeyToSequence(controlKey) {
699
+ if (controlKey.length !== 1) {
700
+ throw unknownKeyError(`Control+${controlKey}`);
701
+ }
702
+ const uppercaseLetter = controlKey.toUpperCase();
703
+ const charCode = uppercaseLetter.charCodeAt(0);
704
+ if (charCode < 65 || charCode > 90) {
705
+ throw unknownKeyError(`Control+${controlKey}`);
706
+ }
707
+ return String.fromCharCode(charCode - 64);
708
+ }
709
+
710
+ // src/terminal-screen.ts
711
+ var TerminalScreen = class {
712
+ lines;
713
+ rawLines;
714
+ cursor;
715
+ size;
716
+ constructor({
717
+ lines,
718
+ rawLines,
719
+ cursor,
720
+ size
721
+ }) {
722
+ this.lines = Object.freeze(lines.map((line) => stripAnsi(line)));
723
+ this.rawLines = Object.freeze([...rawLines]);
724
+ this.cursor = Object.freeze({ ...cursor });
725
+ this.size = Object.freeze({ ...size });
726
+ Object.freeze(this);
727
+ }
728
+ get text() {
729
+ return this.lines.join("\n");
730
+ }
731
+ contains(substring) {
732
+ return this.text.includes(substring);
733
+ }
734
+ line(index) {
735
+ const normalizedIndex = index < 0 ? this.lines.length + index : index;
736
+ const line = this.lines[normalizedIndex];
737
+ if (line === void 0) {
738
+ throw new RangeError(`Line index out of bounds: ${index}`);
739
+ }
740
+ return line;
741
+ }
742
+ };
743
+
744
+ // src/terminal-session.ts
745
+ var DEFAULT_COLS = 120;
746
+ var DEFAULT_ROWS = 40;
747
+ var DEFAULT_TIMEOUT_MS = 1e4;
748
+ var WAIT_FOR_POLL_MS = 10;
749
+ var TYPE_DELAY_MS = 15;
750
+ var CLOSE_AFTER_SIGNAL_GRACE_MS = 250;
751
+ var CLOSE_AFTER_SIGTERM_MS = 1e3;
752
+ var TerminalSession = class {
753
+ id;
754
+ command;
755
+ pid;
756
+ exitCode = null;
757
+ pty;
758
+ terminal;
759
+ emitter = new EventEmitter();
760
+ exitPromise;
761
+ rawBuffer = "";
762
+ lastDataAt = Date.now();
763
+ currentCols;
764
+ currentRows;
765
+ closeRequested = false;
766
+ signalRequested = false;
767
+ constructor({
768
+ id,
769
+ command,
770
+ args = [],
771
+ cwd = process.cwd(),
772
+ env = process.env,
773
+ cols = DEFAULT_COLS,
774
+ rows = DEFAULT_ROWS,
775
+ observe = false
776
+ }) {
777
+ this.id = id;
778
+ this.command = command;
779
+ this.currentCols = cols;
780
+ this.currentRows = rows;
781
+ this.terminal = new TerminalBuffer(cols, rows);
782
+ this.pty = createPtyProcess({ command, args, cwd, env, cols, rows });
783
+ this.pid = this.pty.pid;
784
+ const dataSubscription = this.pty.onData((chunk) => {
785
+ this.rawBuffer += chunk;
786
+ this.lastDataAt = Date.now();
787
+ this.terminal.write(chunk);
788
+ if (observe) {
789
+ process.stderr.write(chunk);
790
+ }
791
+ });
792
+ let exitSubscription;
793
+ this.exitPromise = new Promise((resolve) => {
794
+ exitSubscription = this.pty.onExit(({ exitCode }) => {
795
+ if (this.exitCode !== null) {
796
+ resolve(this.exitCode);
797
+ return;
798
+ }
799
+ this.exitCode = exitCode;
800
+ dataSubscription.dispose();
801
+ exitSubscription?.dispose();
802
+ this.emitter.emit("exit", exitCode);
803
+ resolve(exitCode);
804
+ });
805
+ });
806
+ }
807
+ async type(text) {
808
+ for (const character of text) {
809
+ await this.send(character);
810
+ await sleep(TYPE_DELAY_MS);
811
+ }
812
+ }
813
+ async fill(text) {
814
+ await this.send(text.replace(/\r?\n/g, "\r"));
815
+ }
816
+ async press(key) {
817
+ await this.send(keyToSequence(key));
818
+ }
819
+ async send(raw) {
820
+ if (this.exitCode !== null) {
821
+ return;
822
+ }
823
+ this.pty.write(raw);
824
+ }
825
+ async signal(sig) {
826
+ if (this.exitCode !== null) {
827
+ return;
828
+ }
829
+ this.signalRequested = true;
830
+ this.pty.kill(sig);
831
+ }
832
+ async waitFor(pattern, opts) {
833
+ const timeout = opts?.timeout ?? DEFAULT_TIMEOUT_MS;
834
+ const startedAt = Date.now();
835
+ while (Date.now() - startedAt <= timeout) {
836
+ const matched = matchPattern(this.rawBuffer, pattern);
837
+ if (matched !== null) {
838
+ return matched;
839
+ }
840
+ await sleep(WAIT_FOR_POLL_MS);
841
+ }
842
+ throw new Error(`Timed out waiting for pattern after ${timeout}ms: ${String(pattern)}`);
843
+ }
844
+ async waitForQuiet(ms) {
845
+ while (true) {
846
+ const remaining = ms - (Date.now() - this.lastDataAt);
847
+ if (remaining <= 0) {
848
+ return;
849
+ }
850
+ await sleep(remaining);
851
+ }
852
+ }
853
+ async screen() {
854
+ const rawLines = [];
855
+ for (let row = 0; row < this.currentRows; row += 1) {
856
+ rawLines.push(this.terminal.renderLine(row));
857
+ }
858
+ return new TerminalScreen({
859
+ lines: rawLines,
860
+ rawLines,
861
+ cursor: {
862
+ row: this.terminal.displayBuffer.cursorY,
863
+ col: this.terminal.displayBuffer.cursorX
864
+ },
865
+ size: {
866
+ rows: this.currentRows,
867
+ cols: this.currentCols
868
+ }
869
+ });
870
+ }
871
+ async history(opts) {
872
+ const normalized = normalizeHistoryBuffer(stripAnsi(this.rawBuffer));
873
+ const lines = splitHistoryLines(normalized);
874
+ if (opts?.last === void 0) {
875
+ return lines;
876
+ }
877
+ const start = Math.max(0, lines.length - opts.last);
878
+ return lines.slice(start);
879
+ }
880
+ async resize(cols, rows) {
881
+ this.currentCols = cols;
882
+ this.currentRows = rows;
883
+ if (this.exitCode === null) {
884
+ this.pty.resize(cols, rows);
885
+ }
886
+ this.terminal.resize(cols, rows);
887
+ }
888
+ async waitForExit(opts) {
889
+ if (this.exitCode !== null) {
890
+ return this.exitCode;
891
+ }
892
+ if (opts?.timeout !== void 0) {
893
+ const result = await waitForExit(this.exitPromise, opts.timeout);
894
+ if (result === null) {
895
+ throw new Error(`Timed out waiting for process to exit after ${opts.timeout}ms`);
896
+ }
897
+ return result;
898
+ }
899
+ return this.exitPromise;
900
+ }
901
+ async close() {
902
+ if (this.exitCode !== null) {
903
+ return this.exitCode;
904
+ }
905
+ if (!this.closeRequested) {
906
+ this.closeRequested = true;
907
+ const gracefulExitCode = await waitForExit(this.exitPromise, CLOSE_AFTER_SIGNAL_GRACE_MS);
908
+ if (gracefulExitCode !== null) {
909
+ return gracefulExitCode;
910
+ }
911
+ if (this.signalRequested) {
912
+ return this.exitPromise;
913
+ }
914
+ if (this.exitCode === null) {
915
+ this.pty.kill("SIGTERM");
916
+ const afterSigterm = await waitForExit(this.exitPromise, CLOSE_AFTER_SIGTERM_MS);
917
+ if (afterSigterm !== null) {
918
+ return afterSigterm;
919
+ }
920
+ }
921
+ if (this.exitCode === null) {
922
+ this.pty.kill("SIGKILL");
923
+ }
924
+ }
925
+ return this.exitPromise;
926
+ }
927
+ on(event, cb) {
928
+ this.emitter.on(event, cb);
929
+ }
930
+ };
931
+ function createPtyProcess({
932
+ command,
933
+ args,
934
+ cwd,
935
+ env,
936
+ cols,
937
+ rows
938
+ }) {
939
+ try {
940
+ return nodePty.spawn(command, args, {
941
+ cwd,
942
+ env,
943
+ cols,
944
+ rows,
945
+ encoding: "utf8"
946
+ });
947
+ } catch {
948
+ return createChildProcessFallback({ command, args, cwd, env });
949
+ }
950
+ }
951
+ function createChildProcessFallback({
952
+ command,
953
+ args,
954
+ cwd,
955
+ env
956
+ }) {
957
+ const child = spawnChildProcess(command, args, {
958
+ cwd,
959
+ env,
960
+ stdio: ["pipe", "pipe", "pipe"]
961
+ });
962
+ return new ChildProcessFallback(child);
963
+ }
964
+ var ChildProcessFallback = class {
965
+ pid;
966
+ child;
967
+ dataEmitter = new EventEmitter();
968
+ exitEmitter = new EventEmitter();
969
+ constructor(child) {
970
+ this.child = child;
971
+ this.pid = child.pid ?? -1;
972
+ child.stdout.setEncoding("utf8");
973
+ child.stderr.setEncoding("utf8");
974
+ child.stdout.on("data", this.handleData);
975
+ child.stderr.on("data", this.handleData);
976
+ child.on("exit", (exitCode, signal) => {
977
+ this.exitEmitter.emit("exit", {
978
+ exitCode: exitCode ?? signalToExitCode(signal),
979
+ signal: void 0
980
+ });
981
+ });
982
+ }
983
+ write(data) {
984
+ this.child.stdin.write(data);
985
+ }
986
+ resize() {
987
+ }
988
+ kill(signal) {
989
+ this.child.kill(signal);
990
+ }
991
+ onData(listener) {
992
+ this.dataEmitter.on("data", listener);
993
+ return {
994
+ dispose: () => {
995
+ this.dataEmitter.off("data", listener);
996
+ }
997
+ };
998
+ }
999
+ onExit(listener) {
1000
+ this.exitEmitter.on("exit", listener);
1001
+ return {
1002
+ dispose: () => {
1003
+ this.exitEmitter.off("exit", listener);
1004
+ }
1005
+ };
1006
+ }
1007
+ handleData = (chunk) => {
1008
+ this.dataEmitter.emit("data", String(chunk));
1009
+ };
1010
+ };
1011
+ function signalToExitCode(signal) {
1012
+ if (signal === null) {
1013
+ return 0;
1014
+ }
1015
+ const signalNumbers = {
1016
+ SIGTERM: 15,
1017
+ SIGINT: 2,
1018
+ SIGHUP: 1,
1019
+ SIGKILL: 9
1020
+ };
1021
+ const signalNumber = signalNumbers[signal];
1022
+ if (signalNumber === void 0) {
1023
+ return 1;
1024
+ }
1025
+ return 128 + signalNumber;
1026
+ }
1027
+ function matchPattern(buffer, pattern) {
1028
+ const clean = normalizeHistoryBuffer(stripAnsi(buffer));
1029
+ for (const line of clean.split("\n")) {
1030
+ if (typeof pattern === "string") {
1031
+ if (line.includes(pattern)) return line;
1032
+ } else {
1033
+ const flags = removeCharacter(pattern.flags, "g");
1034
+ if (new RegExp(pattern.source, flags).test(line)) return line;
1035
+ }
1036
+ }
1037
+ return null;
1038
+ }
1039
+ function removeCharacter(input, charToRemove) {
1040
+ let output = "";
1041
+ for (const character of input) {
1042
+ if (character !== charToRemove) {
1043
+ output += character;
1044
+ }
1045
+ }
1046
+ return output;
1047
+ }
1048
+ function splitHistoryLines(input) {
1049
+ const lines = input.split("\n");
1050
+ if (lines.length > 0 && lines[lines.length - 1] === "") {
1051
+ lines.pop();
1052
+ }
1053
+ return lines;
1054
+ }
1055
+ function normalizeHistoryBuffer(input) {
1056
+ let output = "";
1057
+ for (const character of input) {
1058
+ if (character === "\r" || character === "\b") {
1059
+ continue;
1060
+ }
1061
+ output += character;
1062
+ }
1063
+ return output;
1064
+ }
1065
+ function sleep(ms) {
1066
+ return new Promise((resolve) => {
1067
+ setTimeout(resolve, ms);
1068
+ });
1069
+ }
1070
+ async function waitForExit(exitPromise, timeout) {
1071
+ return new Promise((resolve) => {
1072
+ let settled = false;
1073
+ const timer = setTimeout(() => {
1074
+ if (settled) {
1075
+ return;
1076
+ }
1077
+ settled = true;
1078
+ resolve(null);
1079
+ }, timeout);
1080
+ void exitPromise.then((code) => {
1081
+ if (settled) {
1082
+ return;
1083
+ }
1084
+ settled = true;
1085
+ clearTimeout(timer);
1086
+ resolve(code);
1087
+ });
1088
+ });
1089
+ }
1090
+
1091
+ // src/terminal-pilot.ts
1092
+ var DEFAULT_COLS2 = 120;
1093
+ var DEFAULT_ROWS2 = 40;
1094
+ var TerminalPilot = class _TerminalPilot {
1095
+ sessionMap = /* @__PURE__ */ new Map();
1096
+ static async launch() {
1097
+ return new _TerminalPilot();
1098
+ }
1099
+ async newSession(opts) {
1100
+ const session = new TerminalSession({
1101
+ id: randomUUID(),
1102
+ command: opts.command,
1103
+ args: opts.args,
1104
+ cwd: opts.cwd,
1105
+ env: opts.env,
1106
+ cols: opts.cols ?? DEFAULT_COLS2,
1107
+ rows: opts.rows ?? DEFAULT_ROWS2,
1108
+ observe: opts.observe ?? false
1109
+ });
1110
+ this.sessionMap.set(session.id, session);
1111
+ return session;
1112
+ }
1113
+ getSession(id) {
1114
+ const session = this.sessionMap.get(id);
1115
+ if (session === void 0) {
1116
+ throw new Error(`Session not found: ${id}`);
1117
+ }
1118
+ return session;
1119
+ }
1120
+ deleteSession(id) {
1121
+ this.sessionMap.delete(id);
1122
+ }
1123
+ sessions() {
1124
+ return [...this.sessionMap.values()].filter((s) => s.exitCode === null);
1125
+ }
1126
+ async close() {
1127
+ const sessions = [...this.sessionMap.values()];
1128
+ try {
1129
+ await Promise.all(sessions.map((session) => session.close()));
1130
+ } finally {
1131
+ this.sessionMap.clear();
1132
+ }
1133
+ }
1134
+ };
1135
+
1136
+ // src/commands/runtime.ts
1137
+ var SESSION_ENV_VAR = "TERMINAL_PILOT_SESSION";
1138
+ var sharedRuntime;
1139
+ function getTerminalPilotRuntime(runtime) {
1140
+ if (runtime !== void 0) {
1141
+ return runtime;
1142
+ }
1143
+ sharedRuntime ??= createTerminalPilotRuntime();
1144
+ return sharedRuntime;
1145
+ }
1146
+ function createTerminalPilotRuntime(options = {}) {
1147
+ const launchPilot = options.launchPilot ?? TerminalPilot.launch;
1148
+ const nameToId = /* @__PURE__ */ new Map();
1149
+ const idToName = /* @__PURE__ */ new Map();
1150
+ let pilotPromise;
1151
+ function getRequestedName(name, env) {
1152
+ return name ?? env?.get(SESSION_ENV_VAR);
1153
+ }
1154
+ async function getPilot() {
1155
+ pilotPromise ??= launchPilot();
1156
+ return pilotPromise;
1157
+ }
1158
+ function nextSessionName() {
1159
+ let index = 1;
1160
+ while (nameToId.has(`s${index}`)) {
1161
+ index += 1;
1162
+ }
1163
+ return `s${index}`;
1164
+ }
1165
+ function rememberSession(name, session) {
1166
+ nameToId.set(name, session.id);
1167
+ idToName.set(session.id, name);
1168
+ return { name, session };
1169
+ }
1170
+ function forgetSession(name, sessionId) {
1171
+ nameToId.delete(name);
1172
+ idToName.delete(sessionId);
1173
+ }
1174
+ function formatAvailableSessions(names) {
1175
+ if (names.length === 0) {
1176
+ return "No active sessions are available.";
1177
+ }
1178
+ return `Available sessions: ${names.join(", ")}.`;
1179
+ }
1180
+ async function lookupNamedSession(name) {
1181
+ const sessionId = nameToId.get(name);
1182
+ if (sessionId === void 0) {
1183
+ const active = await listSessions2();
1184
+ throw new UserError(`Session "${name}" was not found. ${formatAvailableSessions(active.map((entry) => entry.name))}`);
1185
+ }
1186
+ const pilot = await getPilot();
1187
+ try {
1188
+ return { name, session: pilot.getSession(sessionId) };
1189
+ } catch {
1190
+ forgetSession(name, sessionId);
1191
+ const active = await listSessions2();
1192
+ throw new UserError(`Session "${name}" was not found. ${formatAvailableSessions(active.map((entry) => entry.name))}`);
1193
+ }
1194
+ }
1195
+ async function listSessions2() {
1196
+ const pilot = await getPilot();
1197
+ return pilot.sessions().flatMap((session) => {
1198
+ const name = idToName.get(session.id);
1199
+ if (name === void 0) {
1200
+ return [];
1201
+ }
1202
+ return [{ name, session }];
1203
+ });
1204
+ }
1205
+ return {
1206
+ async createSession(params17, env) {
1207
+ const requestedName = getRequestedName(params17.session, env) ?? nextSessionName();
1208
+ if (nameToId.has(requestedName)) {
1209
+ throw new UserError(`Session "${requestedName}" already exists.`);
1210
+ }
1211
+ const pilot = await getPilot();
1212
+ const session = await pilot.newSession({
1213
+ command: params17.command,
1214
+ args: params17.args,
1215
+ cwd: params17.cwd,
1216
+ cols: params17.cols,
1217
+ rows: params17.rows,
1218
+ observe: params17.observe
1219
+ });
1220
+ return rememberSession(requestedName, session);
1221
+ },
1222
+ async resolveSession(name, env) {
1223
+ const requestedName = getRequestedName(name, env);
1224
+ if (requestedName !== void 0) {
1225
+ return lookupNamedSession(requestedName);
1226
+ }
1227
+ const active = await listSessions2();
1228
+ if (active.length === 1) {
1229
+ return active[0];
1230
+ }
1231
+ if (active.length === 0) {
1232
+ throw new UserError("No active sessions. Create one with create-session.");
1233
+ }
1234
+ throw new UserError(
1235
+ `Multiple active sessions require an explicit session name. Pass --session or set ${SESSION_ENV_VAR}. ${formatAvailableSessions(active.map((entry) => entry.name))}`
1236
+ );
1237
+ },
1238
+ async closeSession(name, env) {
1239
+ const namedSession = await this.resolveSession(name, env);
1240
+ const exitCode = await namedSession.session.close();
1241
+ const pilot = await getPilot();
1242
+ pilot.deleteSession(namedSession.session.id);
1243
+ forgetSession(namedSession.name, namedSession.session.id);
1244
+ return {
1245
+ exitCode,
1246
+ name: namedSession.name
1247
+ };
1248
+ },
1249
+ listSessions: listSessions2,
1250
+ async close() {
1251
+ if (pilotPromise === void 0) {
1252
+ return;
1253
+ }
1254
+ const pilot = await pilotPromise;
1255
+ await pilot.close();
1256
+ pilotPromise = void 0;
1257
+ nameToId.clear();
1258
+ idToName.clear();
1259
+ }
1260
+ };
1261
+ }
1262
+ async function closeSharedTerminalPilotRuntime() {
1263
+ if (sharedRuntime === void 0) {
1264
+ return;
1265
+ }
1266
+ await sharedRuntime.close();
1267
+ sharedRuntime = void 0;
1268
+ }
1269
+
1270
+ // src/commands/close-session.ts
1271
+ var params = S.Object({
1272
+ session: S.Optional(S.String({ short: "s", description: "Session name" }))
1273
+ });
1274
+ var closeSession = defineCommand({
1275
+ name: "close-session",
1276
+ description: "Close an active terminal session",
1277
+ scope: ["cli", "mcp", "sdk"],
1278
+ params,
1279
+ handler: async ({ params: params17, env, terminalPilotRuntime }) => {
1280
+ const { exitCode } = await getTerminalPilotRuntime(terminalPilotRuntime).closeSession(params17.session, env);
1281
+ return { exitCode };
1282
+ }
1283
+ });
1284
+
1285
+ // src/commands/create-session.ts
1286
+ import { defineCommand as defineCommand2, S as S2 } from "@poe-code/cmdkit";
1287
+ var params2 = S2.Object({
1288
+ command: S2.String({ description: "Command to execute" }),
1289
+ args: S2.Optional(S2.Array(S2.String(), { description: "Command arguments" })),
1290
+ session: S2.Optional(S2.String({ short: "s", description: "Session name" })),
1291
+ cwd: S2.Optional(S2.String({ description: "Working directory" })),
1292
+ cols: S2.Optional(S2.Number({ description: "Terminal width in columns" })),
1293
+ rows: S2.Optional(S2.Number({ description: "Terminal height in rows" })),
1294
+ observe: S2.Optional(S2.Boolean({ description: "Mirror PTY output to stderr" }))
1295
+ });
1296
+ var createSession = defineCommand2({
1297
+ name: "create-session",
1298
+ description: "Spawn an interactive CLI in a PTY",
1299
+ scope: ["cli", "mcp", "sdk"],
1300
+ positional: ["command", "args"],
1301
+ params: params2,
1302
+ handler: async ({ params: params17, env, terminalPilotRuntime }) => {
1303
+ const namedSession = await getTerminalPilotRuntime(terminalPilotRuntime).createSession(params17, env);
1304
+ return { session: namedSession.name, pid: namedSession.session.pid };
1305
+ }
1306
+ });
1307
+
1308
+ // src/commands/fill.ts
1309
+ import { defineCommand as defineCommand3, S as S3 } from "@poe-code/cmdkit";
1310
+ var params3 = S3.Object({
1311
+ text: S3.String({ description: "Text to write to the session" }),
1312
+ session: S3.Optional(S3.String({ short: "s", description: "Session name" }))
1313
+ });
1314
+ var fill = defineCommand3({
1315
+ name: "fill",
1316
+ description: "Write text to an active terminal session all at once (replaces \\n with \\r)",
1317
+ scope: ["cli", "mcp", "sdk"],
1318
+ positional: ["text"],
1319
+ params: params3,
1320
+ handler: async ({ params: params17, env, terminalPilotRuntime }) => {
1321
+ const namedSession = await getTerminalPilotRuntime(terminalPilotRuntime).resolveSession(params17.session, env);
1322
+ await namedSession.session.fill(params17.text);
1323
+ return void 0;
1324
+ }
1325
+ });
1326
+
1327
+ // src/commands/get-session.ts
1328
+ import { defineCommand as defineCommand4, S as S4 } from "@poe-code/cmdkit";
1329
+ var params4 = S4.Object({
1330
+ session: S4.Optional(S4.String({ short: "s", description: "Session name" }))
1331
+ });
1332
+ var getSession = defineCommand4({
1333
+ name: "get-session",
1334
+ description: "Get session metadata (name, pid, command, exitCode)",
1335
+ scope: ["cli", "mcp", "sdk"],
1336
+ params: params4,
1337
+ handler: async ({ params: params17, env, terminalPilotRuntime }) => {
1338
+ const namedSession = await getTerminalPilotRuntime(terminalPilotRuntime).resolveSession(params17.session, env);
1339
+ return {
1340
+ session: namedSession.name,
1341
+ pid: namedSession.session.pid,
1342
+ command: namedSession.session.command,
1343
+ exitCode: namedSession.session.exitCode
1344
+ };
1345
+ }
1346
+ });
1347
+
1348
+ // src/commands/install.ts
1349
+ import { installSkill } from "@poe-code/agent-skill-config";
1350
+ import { defineCommand as defineCommand5, S as S5 } from "@poe-code/cmdkit";
1351
+
1352
+ // src/commands/installer.ts
1353
+ import os from "node:os";
1354
+ import path from "node:path";
1355
+ import * as nodeFs from "node:fs/promises";
1356
+ import { readFile } from "node:fs/promises";
1357
+ import { UserError as UserError2 } from "@poe-code/cmdkit";
1358
+ import {
1359
+ getAgentConfig,
1360
+ resolveAgentSupport as resolveSkillAgentSupport,
1361
+ supportedAgents as skillSupportedAgents
1362
+ } from "@poe-code/agent-skill-config";
1363
+ var DEFAULT_INSTALL_AGENT = "claude-code";
1364
+ var DEFAULT_INSTALL_SCOPE = "local";
1365
+ var TERMINAL_PILOT_SKILL_NAME = "terminal-pilot";
1366
+ var installableAgents = skillSupportedAgents;
1367
+ function isNotFoundError(error) {
1368
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
1369
+ }
1370
+ function resolveInstallerServices(installer) {
1371
+ return {
1372
+ fs: installer?.fs ?? nodeFs,
1373
+ cwd: installer?.cwd ?? process.cwd(),
1374
+ homeDir: installer?.homeDir ?? os.homedir(),
1375
+ platform: installer?.platform ?? process.platform
1376
+ };
1377
+ }
1378
+ function throwUnsupportedAgent(agent) {
1379
+ throw new UserError2(`Unsupported agent: ${agent}`);
1380
+ }
1381
+ function resolveInstallableAgent(agent) {
1382
+ const skillSupport = resolveSkillAgentSupport(agent);
1383
+ if (skillSupport.status !== "supported" || !skillSupport.id) {
1384
+ throwUnsupportedAgent(agent);
1385
+ }
1386
+ return skillSupport.id;
1387
+ }
1388
+ function resolveInstallScope(input) {
1389
+ if (input.local && input.global) {
1390
+ throw new UserError2("Use either --local or --global, not both.");
1391
+ }
1392
+ if (input.local) {
1393
+ return "local";
1394
+ }
1395
+ if (input.global) {
1396
+ return "global";
1397
+ }
1398
+ return DEFAULT_INSTALL_SCOPE;
1399
+ }
1400
+ var terminalPilotTemplateCache;
1401
+ async function loadTerminalPilotTemplate() {
1402
+ if (terminalPilotTemplateCache !== void 0) {
1403
+ return terminalPilotTemplateCache;
1404
+ }
1405
+ const candidates = [
1406
+ new URL("./templates/terminal-pilot.md", import.meta.url),
1407
+ new URL("../templates/terminal-pilot.md", import.meta.url),
1408
+ new URL("../../../agent-skill-config/src/templates/terminal-pilot.md", import.meta.url)
1409
+ ];
1410
+ for (const candidate of candidates) {
1411
+ try {
1412
+ terminalPilotTemplateCache = await readFile(candidate, "utf8");
1413
+ return terminalPilotTemplateCache;
1414
+ } catch (error) {
1415
+ if (!isNotFoundError(error)) {
1416
+ throw error;
1417
+ }
1418
+ }
1419
+ }
1420
+ throw new UserError2("terminal-pilot skill template is missing.");
1421
+ }
1422
+ function resolveHomeRelativePath(targetPath, homeDir) {
1423
+ if (targetPath === "~") {
1424
+ return homeDir;
1425
+ }
1426
+ if (targetPath.startsWith("~/")) {
1427
+ return path.join(homeDir, targetPath.slice(2));
1428
+ }
1429
+ return targetPath;
1430
+ }
1431
+ function getSkillFolderWithHome(agent, scope, cwd, homeDir) {
1432
+ const config = getAgentConfig(agent);
1433
+ if (!config) {
1434
+ throwUnsupportedAgent(agent);
1435
+ }
1436
+ return {
1437
+ displayPath: path.join(
1438
+ scope === "global" ? config.globalSkillDir : config.localSkillDir,
1439
+ TERMINAL_PILOT_SKILL_NAME
1440
+ ),
1441
+ fullPath: path.join(
1442
+ scope === "global" ? resolveHomeRelativePath(config.globalSkillDir, homeDir) : path.resolve(cwd, config.localSkillDir),
1443
+ TERMINAL_PILOT_SKILL_NAME
1444
+ )
1445
+ };
1446
+ }
1447
+ async function removeSkillFolder(fs, folderPath) {
1448
+ try {
1449
+ await fs.stat(folderPath);
1450
+ } catch (error) {
1451
+ if (isNotFoundError(error)) {
1452
+ return false;
1453
+ }
1454
+ throw error;
1455
+ }
1456
+ if (typeof fs.rm !== "function") {
1457
+ throw new UserError2("The configured filesystem does not support removing directories.");
1458
+ }
1459
+ await fs.rm(folderPath, { recursive: true, force: true });
1460
+ return true;
1461
+ }
1462
+
1463
+ // src/commands/install.ts
1464
+ var params5 = S5.Object({
1465
+ agent: S5.Enum(installableAgents, {
1466
+ description: "Agent to install terminal-pilot for",
1467
+ default: DEFAULT_INSTALL_AGENT
1468
+ }),
1469
+ local: S5.Optional(S5.Boolean({ description: "Install the skill in the current project" })),
1470
+ global: S5.Optional(S5.Boolean({ description: "Install the skill in the user home directory" }))
1471
+ });
1472
+ var install = defineCommand5({
1473
+ name: "install",
1474
+ description: "Install the terminal-pilot CLI skill.",
1475
+ scope: ["cli"],
1476
+ positional: ["agent"],
1477
+ params: params5,
1478
+ handler: async ({ params: params17, terminalPilotInstaller }) => {
1479
+ const services = resolveInstallerServices(terminalPilotInstaller);
1480
+ const agent = resolveInstallableAgent(params17.agent);
1481
+ const scope = resolveInstallScope(params17);
1482
+ const template = await loadTerminalPilotTemplate();
1483
+ const skillResult = await installSkill(
1484
+ agent,
1485
+ {
1486
+ name: TERMINAL_PILOT_SKILL_NAME,
1487
+ content: template
1488
+ },
1489
+ {
1490
+ fs: services.fs,
1491
+ cwd: services.cwd,
1492
+ homeDir: services.homeDir,
1493
+ scope
1494
+ }
1495
+ );
1496
+ return {
1497
+ agent,
1498
+ scope,
1499
+ skillPath: skillResult.displayPath
1500
+ };
1501
+ }
1502
+ });
1503
+
1504
+ // src/commands/list-sessions.ts
1505
+ import { defineCommand as defineCommand6, S as S6 } from "@poe-code/cmdkit";
1506
+ var params6 = S6.Object({});
1507
+ var listSessions = defineCommand6({
1508
+ name: "list-sessions",
1509
+ description: "List active terminal sessions",
1510
+ scope: ["cli", "mcp", "sdk"],
1511
+ params: params6,
1512
+ handler: async ({ terminalPilotRuntime }) => {
1513
+ const sessions = await getTerminalPilotRuntime(terminalPilotRuntime).listSessions();
1514
+ return {
1515
+ sessions: sessions.map((namedSession) => ({
1516
+ session: namedSession.name,
1517
+ command: namedSession.session.command,
1518
+ pid: namedSession.session.pid
1519
+ }))
1520
+ };
1521
+ }
1522
+ });
1523
+
1524
+ // src/commands/press-key.ts
1525
+ import { defineCommand as defineCommand7, S as S7 } from "@poe-code/cmdkit";
1526
+ var params7 = S7.Object({
1527
+ key: S7.String({ description: "Named key to press" }),
1528
+ session: S7.Optional(S7.String({ short: "s", description: "Session name" }))
1529
+ });
1530
+ var pressKey = defineCommand7({
1531
+ name: "press-key",
1532
+ description: "Send a named key press to an active terminal session",
1533
+ scope: ["cli", "mcp", "sdk"],
1534
+ positional: ["key"],
1535
+ params: params7,
1536
+ handler: async ({ params: params17, env, terminalPilotRuntime }) => {
1537
+ const namedSession = await getTerminalPilotRuntime(terminalPilotRuntime).resolveSession(params17.session, env);
1538
+ await namedSession.session.press(params17.key);
1539
+ return void 0;
1540
+ }
1541
+ });
1542
+
1543
+ // src/commands/read-history.ts
1544
+ import { defineCommand as defineCommand8, S as S8 } from "@poe-code/cmdkit";
1545
+ var params8 = S8.Object({
1546
+ session: S8.Optional(S8.String({ short: "s", description: "Session name" })),
1547
+ last: S8.Optional(S8.Number({ short: "n", description: "Return only the last N lines" }))
1548
+ });
1549
+ var readHistory = defineCommand8({
1550
+ name: "read-history",
1551
+ description: "Read terminal output history",
1552
+ scope: ["cli", "mcp", "sdk"],
1553
+ params: params8,
1554
+ handler: async ({ params: params17, env, terminalPilotRuntime }) => {
1555
+ const namedSession = await getTerminalPilotRuntime(terminalPilotRuntime).resolveSession(params17.session, env);
1556
+ const lines = await namedSession.session.history({ last: params17.last });
1557
+ return { lines, exitCode: namedSession.session.exitCode };
1558
+ }
1559
+ });
1560
+
1561
+ // src/commands/read-screen.ts
1562
+ import { defineCommand as defineCommand9, S as S9 } from "@poe-code/cmdkit";
1563
+ var params9 = S9.Object({
1564
+ session: S9.Optional(S9.String({ short: "s", description: "Session name" }))
1565
+ });
1566
+ var readScreen = defineCommand9({
1567
+ name: "read-screen",
1568
+ description: "Read the current visible terminal screen",
1569
+ scope: ["cli", "mcp", "sdk"],
1570
+ params: params9,
1571
+ handler: async ({ params: params17, env, terminalPilotRuntime }) => {
1572
+ const namedSession = await getTerminalPilotRuntime(terminalPilotRuntime).resolveSession(params17.session, env);
1573
+ const screen = await namedSession.session.screen();
1574
+ return {
1575
+ lines: [...screen.lines],
1576
+ cursor: { ...screen.cursor },
1577
+ size: { ...screen.size },
1578
+ exitCode: namedSession.session.exitCode
1579
+ };
1580
+ }
1581
+ });
1582
+
1583
+ // src/commands/resize.ts
1584
+ import { defineCommand as defineCommand10, S as S10 } from "@poe-code/cmdkit";
1585
+ var params10 = S10.Object({
1586
+ cols: S10.Number({ description: "Terminal width in columns" }),
1587
+ rows: S10.Number({ description: "Terminal height in rows" }),
1588
+ session: S10.Optional(S10.String({ short: "s", description: "Session name" }))
1589
+ });
1590
+ var resize = defineCommand10({
1591
+ name: "resize",
1592
+ description: "Resize an active terminal session",
1593
+ scope: ["cli", "mcp", "sdk"],
1594
+ params: params10,
1595
+ handler: async ({ params: params17, env, terminalPilotRuntime }) => {
1596
+ const namedSession = await getTerminalPilotRuntime(terminalPilotRuntime).resolveSession(params17.session, env);
1597
+ await namedSession.session.resize(params17.cols, params17.rows);
1598
+ return void 0;
1599
+ }
1600
+ });
1601
+
1602
+ // src/commands/screenshot.ts
1603
+ import { defineCommand as defineCommand11, S as S11 } from "@poe-code/cmdkit";
1604
+ import { renderTerminalPng } from "terminal-png";
1605
+ var params11 = S11.Object({
1606
+ session: S11.Optional(S11.String({ short: "s", description: "Session name" })),
1607
+ output: S11.String({ short: "o", description: "Path to the output PNG file" }),
1608
+ window: S11.Optional(S11.Boolean({ description: "Include terminal window chrome", default: true })),
1609
+ padding: S11.Optional(S11.Number({ short: "p", description: "Padding around terminal content" }))
1610
+ });
1611
+ var screenshot = defineCommand11({
1612
+ name: "screenshot",
1613
+ description: "Capture the current terminal screen as a PNG image",
1614
+ scope: ["cli"],
1615
+ params: params11,
1616
+ handler: async ({ params: params17, env, terminalPilotRuntime }) => {
1617
+ const namedSession = await getTerminalPilotRuntime(terminalPilotRuntime).resolveSession(params17.session, env);
1618
+ const screen = await namedSession.session.screen();
1619
+ await renderTerminalPng(screen.rawLines.join("\n"), {
1620
+ output: params17.output,
1621
+ window: params17.window,
1622
+ padding: params17.padding
1623
+ });
1624
+ return void 0;
1625
+ }
1626
+ });
1627
+
1628
+ // src/commands/send-signal.ts
1629
+ import { defineCommand as defineCommand12, S as S12 } from "@poe-code/cmdkit";
1630
+ var params12 = S12.Object({
1631
+ signal: S12.String({ description: "Signal to send to the session process" }),
1632
+ session: S12.Optional(S12.String({ short: "s", description: "Session name" }))
1633
+ });
1634
+ var sendSignal = defineCommand12({
1635
+ name: "send-signal",
1636
+ description: "Send a process signal to an active terminal session",
1637
+ scope: ["cli", "mcp", "sdk"],
1638
+ positional: ["signal"],
1639
+ params: params12,
1640
+ handler: async ({ params: params17, env, terminalPilotRuntime }) => {
1641
+ const namedSession = await getTerminalPilotRuntime(terminalPilotRuntime).resolveSession(params17.session, env);
1642
+ await namedSession.session.signal(params17.signal);
1643
+ return void 0;
1644
+ }
1645
+ });
1646
+
1647
+ // src/commands/type.ts
1648
+ import { defineCommand as defineCommand13, S as S13 } from "@poe-code/cmdkit";
1649
+ var params13 = S13.Object({
1650
+ text: S13.String({ description: "Text to write to the session" }),
1651
+ session: S13.Optional(S13.String({ short: "s", description: "Session name" }))
1652
+ });
1653
+ var type = defineCommand13({
1654
+ name: "type",
1655
+ description: "Write text to an active terminal session character-by-character with delay",
1656
+ scope: ["cli", "mcp", "sdk"],
1657
+ positional: ["text"],
1658
+ params: params13,
1659
+ handler: async ({ params: params17, env, terminalPilotRuntime }) => {
1660
+ const namedSession = await getTerminalPilotRuntime(terminalPilotRuntime).resolveSession(params17.session, env);
1661
+ await namedSession.session.type(params17.text);
1662
+ return void 0;
1663
+ }
1664
+ });
1665
+
1666
+ // src/commands/uninstall.ts
1667
+ import { defineCommand as defineCommand14, S as S14 } from "@poe-code/cmdkit";
1668
+ var params14 = S14.Object({
1669
+ agent: S14.Enum(installableAgents, {
1670
+ description: "Agent to uninstall terminal-pilot from",
1671
+ default: DEFAULT_INSTALL_AGENT
1672
+ })
1673
+ });
1674
+ var uninstall = defineCommand14({
1675
+ name: "uninstall",
1676
+ description: "Remove the terminal-pilot CLI skill.",
1677
+ scope: ["cli"],
1678
+ positional: ["agent"],
1679
+ params: params14,
1680
+ handler: async ({ params: params17, terminalPilotInstaller }) => {
1681
+ const services = resolveInstallerServices(terminalPilotInstaller);
1682
+ const agent = resolveInstallableAgent(params17.agent);
1683
+ const localSkill = getSkillFolderWithHome(
1684
+ agent,
1685
+ "local",
1686
+ services.cwd,
1687
+ services.homeDir
1688
+ );
1689
+ const globalSkill = getSkillFolderWithHome(
1690
+ agent,
1691
+ "global",
1692
+ services.cwd,
1693
+ services.homeDir
1694
+ );
1695
+ const removedSkillPaths = [];
1696
+ if (await removeSkillFolder(services.fs, localSkill.fullPath)) {
1697
+ removedSkillPaths.push(localSkill.displayPath);
1698
+ }
1699
+ if (await removeSkillFolder(services.fs, globalSkill.fullPath)) {
1700
+ removedSkillPaths.push(globalSkill.displayPath);
1701
+ }
1702
+ return {
1703
+ agent,
1704
+ removedSkillPaths
1705
+ };
1706
+ }
1707
+ });
1708
+
1709
+ // src/commands/wait-for.ts
1710
+ import { defineCommand as defineCommand15, S as S15 } from "@poe-code/cmdkit";
1711
+ var params15 = S15.Object({
1712
+ pattern: S15.String({ description: "Regular expression pattern to wait for" }),
1713
+ session: S15.Optional(S15.String({ short: "s", description: "Session name" })),
1714
+ timeout: S15.Optional(S15.Number({ short: "t", description: "Maximum wait time in milliseconds" })),
1715
+ literal: S15.Optional(
1716
+ S15.Boolean({
1717
+ short: "l",
1718
+ description: "When true, treat pattern as a literal string instead of a regex"
1719
+ })
1720
+ )
1721
+ });
1722
+ var waitFor = defineCommand15({
1723
+ name: "wait-for",
1724
+ description: "Wait for terminal output to match a pattern",
1725
+ scope: ["cli", "mcp", "sdk"],
1726
+ positional: ["pattern"],
1727
+ params: params15,
1728
+ handler: async ({ params: params17, env, terminalPilotRuntime }) => {
1729
+ const namedSession = await getTerminalPilotRuntime(terminalPilotRuntime).resolveSession(params17.session, env);
1730
+ const pattern = params17.literal === true ? params17.pattern : new RegExp(params17.pattern);
1731
+ const line = params17.timeout === void 0 ? await namedSession.session.waitFor(pattern) : await namedSession.session.waitFor(pattern, { timeout: params17.timeout });
1732
+ return { matched: true, line };
1733
+ }
1734
+ });
1735
+
1736
+ // src/commands/wait-for-exit.ts
1737
+ import { defineCommand as defineCommand16, S as S16 } from "@poe-code/cmdkit";
1738
+ var params16 = S16.Object({
1739
+ session: S16.Optional(S16.String({ short: "s", description: "Session name" })),
1740
+ timeout: S16.Optional(S16.Number({ short: "t", description: "Maximum wait time in milliseconds" }))
1741
+ });
1742
+ var waitForExit2 = defineCommand16({
1743
+ name: "wait-for-exit",
1744
+ description: "Wait for a terminal session process to finish",
1745
+ scope: ["cli", "mcp", "sdk"],
1746
+ params: params16,
1747
+ handler: async ({ params: params17, env, terminalPilotRuntime }) => {
1748
+ const namedSession = await getTerminalPilotRuntime(terminalPilotRuntime).resolveSession(params17.session, env);
1749
+ const exitCode = await namedSession.session.waitForExit(
1750
+ params17.timeout === void 0 ? void 0 : { timeout: params17.timeout }
1751
+ );
1752
+ return { exitCode };
1753
+ }
1754
+ });
1755
+
1756
+ // src/commands/index.ts
1757
+ var children = [
1758
+ install,
1759
+ uninstall,
1760
+ createSession,
1761
+ fill,
1762
+ type,
1763
+ pressKey,
1764
+ sendSignal,
1765
+ waitFor,
1766
+ waitForExit2,
1767
+ readScreen,
1768
+ screenshot,
1769
+ readHistory,
1770
+ resize,
1771
+ closeSession,
1772
+ getSession,
1773
+ listSessions
1774
+ ];
1775
+ var terminalPilotGroup = defineGroup({
1776
+ name: "terminal-pilot",
1777
+ scope: ["cli", "mcp", "sdk"],
1778
+ children
1779
+ });
1780
+
1781
+ // src/cli.ts
1782
+ function normalizeArgv(argv) {
1783
+ if (argv.includes("--json") && !argv.some((argument) => argument === "--output" || argument.startsWith("--output="))) {
1784
+ return argv.flatMap((argument) => argument === "--json" ? ["--output", "json"] : [argument]);
1785
+ }
1786
+ return argv;
1787
+ }
1788
+ async function main(argv = process.argv) {
1789
+ const originalArgv = process.argv;
1790
+ process.argv = normalizeArgv(argv);
1791
+ try {
1792
+ await runCLI(terminalPilotGroup);
1793
+ } finally {
1794
+ process.argv = originalArgv;
1795
+ }
1796
+ }
1797
+ async function isDirectExecution(argv) {
1798
+ const entryPoint = argv[1];
1799
+ if (typeof entryPoint !== "string" || entryPoint.length === 0) {
1800
+ return false;
1801
+ }
1802
+ try {
1803
+ const modulePath = fileURLToPath(import.meta.url);
1804
+ const [resolvedEntryPoint, resolvedModulePath] = await Promise.all([
1805
+ realpath(path2.resolve(entryPoint)),
1806
+ realpath(modulePath)
1807
+ ]);
1808
+ return resolvedEntryPoint === resolvedModulePath;
1809
+ } catch {
1810
+ return false;
1811
+ }
1812
+ }
1813
+ if (await isDirectExecution(process.argv)) {
1814
+ await main();
1815
+ }
1816
+
1817
+ // src/testing/cli-repl.ts
1818
+ function appendJsonOutputFlag(args) {
1819
+ if (args.includes("--json") || args.includes("--output") || args.some((arg) => arg.startsWith("--output="))) {
1820
+ return [...args];
1821
+ }
1822
+ const ddashIndex = args.indexOf("--");
1823
+ if (ddashIndex === -1) {
1824
+ return [...args, "--output", "json"];
1825
+ }
1826
+ return [...args.slice(0, ddashIndex), "--output", "json", "--", ...args.slice(ddashIndex + 1)];
1827
+ }
1828
+ function toText(chunk) {
1829
+ if (typeof chunk === "string") {
1830
+ return chunk;
1831
+ }
1832
+ if (chunk instanceof Uint8Array) {
1833
+ return Buffer.from(chunk).toString("utf8");
1834
+ }
1835
+ return String(chunk);
1836
+ }
1837
+ function normalizeOutput(text) {
1838
+ return stripAnsi(text).replaceAll("\r", "").trim();
1839
+ }
1840
+ function canParseJson(text) {
1841
+ if (text.length === 0) {
1842
+ return false;
1843
+ }
1844
+ try {
1845
+ JSON.parse(text);
1846
+ return true;
1847
+ } catch {
1848
+ return false;
1849
+ }
1850
+ }
1851
+ function createTerminalPilotCliRepl() {
1852
+ return {
1853
+ async run(args) {
1854
+ const stdoutChunks = [];
1855
+ const stderrChunks = [];
1856
+ const originalStdoutWrite = process.stdout.write.bind(process.stdout);
1857
+ const originalStderrWrite = process.stderr.write.bind(process.stderr);
1858
+ const previousExitCode = process.exitCode;
1859
+ process.exitCode = 0;
1860
+ process.stdout.write = ((chunk, ...rest) => {
1861
+ stdoutChunks.push(toText(chunk));
1862
+ const callback = typeof rest.at(-1) === "function" ? rest.at(-1) : void 0;
1863
+ callback?.();
1864
+ return true;
1865
+ });
1866
+ process.stderr.write = ((chunk, ...rest) => {
1867
+ stderrChunks.push(toText(chunk));
1868
+ const callback = typeof rest.at(-1) === "function" ? rest.at(-1) : void 0;
1869
+ callback?.();
1870
+ return true;
1871
+ });
1872
+ try {
1873
+ await main(["node", "terminal-pilot", ...args]);
1874
+ } finally {
1875
+ process.stdout.write = originalStdoutWrite;
1876
+ process.stderr.write = originalStderrWrite;
1877
+ }
1878
+ const exitCode = process.exitCode ?? 0;
1879
+ process.exitCode = previousExitCode;
1880
+ let stdout = normalizeOutput(stdoutChunks.join(""));
1881
+ let stderr = normalizeOutput(stderrChunks.join(""));
1882
+ if (exitCode !== 0 && stderr.length === 0 && !canParseJson(stdout)) {
1883
+ stderr = stdout;
1884
+ stdout = "";
1885
+ }
1886
+ return {
1887
+ exitCode,
1888
+ stdout,
1889
+ stderr
1890
+ };
1891
+ },
1892
+ async runJson(args) {
1893
+ const result = await this.run(appendJsonOutputFlag(args));
1894
+ if (result.exitCode !== 0) {
1895
+ return {
1896
+ exitCode: result.exitCode,
1897
+ stdout: void 0,
1898
+ stderr: result.stderr
1899
+ };
1900
+ }
1901
+ if (result.stdout.length === 0) {
1902
+ throw new Error(`Expected JSON output for terminal-pilot ${args.join(" ")}, but stdout was empty.`);
1903
+ }
1904
+ return {
1905
+ exitCode: result.exitCode,
1906
+ stdout: JSON.parse(result.stdout),
1907
+ stderr: result.stderr
1908
+ };
1909
+ },
1910
+ async close() {
1911
+ await closeSharedTerminalPilotRuntime();
1912
+ }
1913
+ };
1914
+ }
1915
+
1916
+ // src/testing/qa-cli.ts
1917
+ var testingDirectory = path3.dirname(fileURLToPath2(import.meta.url));
1918
+ var repoRoot = path3.resolve(testingDirectory, "../../../..");
1919
+ var tsxPath = path3.join(repoRoot, "node_modules", ".bin", "tsx");
1920
+ var testCliPath = path3.join(repoRoot, "packages/terminal-pilot/src/testing/test-cli.js");
1921
+ var menuCliPath = path3.join(repoRoot, "packages/terminal-pilot/src/testing/menu-cli.js");
1922
+ var vimSavedFile = path3.join(os2.tmpdir(), "terminal-pilot-qa-test.txt");
1923
+ var vimDiscardFile = path3.join(os2.tmpdir(), "terminal-pilot-qa-discard.txt");
1924
+ var QaContext = class {
1925
+ repl = createTerminalPilotCliRepl();
1926
+ async close() {
1927
+ await this.repl.close();
1928
+ }
1929
+ async expectOk(args) {
1930
+ const result = await this.repl.runJson(args);
1931
+ if (result.exitCode !== 0) {
1932
+ throw new Error(
1933
+ `Command failed (${result.exitCode}): terminal-pilot ${args.join(" ")}
1934
+ ${result.stderr || "<no error output>"}`
1935
+ );
1936
+ }
1937
+ return result.stdout;
1938
+ }
1939
+ async expectError(args) {
1940
+ const result = await this.repl.run(args);
1941
+ if (result.exitCode === 0) {
1942
+ throw new Error(
1943
+ `Expected command to fail: terminal-pilot ${args.join(" ")}
1944
+ stdout: ${result.stdout || "<empty>"}`
1945
+ );
1946
+ }
1947
+ return result.stderr || result.stdout;
1948
+ }
1949
+ async listSessions() {
1950
+ return this.expectOk(["list-sessions"]);
1951
+ }
1952
+ async getSessionIfExists(session) {
1953
+ const result = await this.repl.runJson(["get-session", "-s", session]);
1954
+ if (result.exitCode !== 0) return null;
1955
+ return result.stdout;
1956
+ }
1957
+ async closeSessionIfExists(session) {
1958
+ const existing = await this.getSessionIfExists(session);
1959
+ if (existing !== null) {
1960
+ await this.expectOk(["close-session", "-s", session]);
1961
+ }
1962
+ }
1963
+ async ensurePromptSession(session) {
1964
+ const existing = await this.getSessionIfExists(session);
1965
+ if (existing?.exitCode !== null) {
1966
+ await this.expectOk(["close-session", "-s", session]);
1967
+ }
1968
+ if (existing === null || existing.exitCode !== null) {
1969
+ const created = await this.expectOk([
1970
+ "create-session",
1971
+ "-s",
1972
+ session,
1973
+ tsxPath,
1974
+ testCliPath
1975
+ ]);
1976
+ assert.equal(created.session, session);
1977
+ assert.ok(created.pid > 0);
1978
+ }
1979
+ const screen = await this.expectOk(["read-screen", "-s", session]);
1980
+ if (!screen.lines.some((line) => line.includes("What is your name?"))) {
1981
+ const prompt = await this.expectOk([
1982
+ "wait-for",
1983
+ "-s",
1984
+ session,
1985
+ "-l",
1986
+ "What is your name?"
1987
+ ]);
1988
+ assert.equal(prompt.matched, true);
1989
+ }
1990
+ }
1991
+ async ensureGreetingSession(session, name) {
1992
+ await this.ensurePromptSession(session);
1993
+ const history = await this.expectOk(["read-history", "-s", session]);
1994
+ if (history.lines.some((line) => line.includes(`Hello, ${name}!`))) {
1995
+ return;
1996
+ }
1997
+ await this.expectOk(["fill", "-s", session, `${name}
1998
+ `]);
1999
+ const greeted = await this.expectOk([
2000
+ "wait-for",
2001
+ "-s",
2002
+ session,
2003
+ "-l",
2004
+ `Hello, ${name}!`
2005
+ ]);
2006
+ assert.equal(greeted.matched, true);
2007
+ }
2008
+ };
2009
+ function formatError(error) {
2010
+ if (error instanceof Error) {
2011
+ return error.message;
2012
+ }
2013
+ return String(error);
2014
+ }
2015
+ async function removeIfExists(filePath) {
2016
+ await rm(filePath, { force: true });
2017
+ }
2018
+ async function closeSessions(ctx, sessions) {
2019
+ for (const session of sessions) {
2020
+ await ctx.closeSessionIfExists(session);
2021
+ }
2022
+ }
2023
+ var cases = [
2024
+ {
2025
+ id: 1,
2026
+ title: "Empty session list",
2027
+ async run(ctx) {
2028
+ await ctx.closeSessionIfExists("S1");
2029
+ const listed = await ctx.listSessions();
2030
+ assert.deepEqual(listed.sessions, []);
2031
+ }
2032
+ },
2033
+ {
2034
+ id: 2,
2035
+ title: "Create a session",
2036
+ async run(ctx) {
2037
+ await ctx.closeSessionIfExists("S1");
2038
+ const created = await ctx.expectOk([
2039
+ "create-session",
2040
+ "-s",
2041
+ "S1",
2042
+ tsxPath,
2043
+ testCliPath
2044
+ ]);
2045
+ assert.equal(created.session, "S1");
2046
+ assert.ok(created.pid > 0);
2047
+ }
2048
+ },
2049
+ {
2050
+ id: 3,
2051
+ title: "Session appears in list",
2052
+ async run(ctx) {
2053
+ await ctx.ensurePromptSession("S1");
2054
+ const listed = await ctx.listSessions();
2055
+ assert.equal(listed.sessions.length, 1);
2056
+ assert.equal(listed.sessions[0]?.session, "S1");
2057
+ }
2058
+ },
2059
+ {
2060
+ id: 4,
2061
+ title: "Get session metadata",
2062
+ async run(ctx) {
2063
+ await ctx.ensurePromptSession("S1");
2064
+ const session = await ctx.expectOk(["get-session", "-s", "S1"]);
2065
+ assert.equal(session.session, "S1");
2066
+ assert.ok(session.pid > 0);
2067
+ assert.match(session.command, /tsx/);
2068
+ assert.equal(session.exitCode, null);
2069
+ }
2070
+ },
2071
+ {
2072
+ id: 5,
2073
+ title: "Wait for prompt",
2074
+ async run(ctx) {
2075
+ await ctx.ensurePromptSession("S1");
2076
+ const waited = await ctx.expectOk([
2077
+ "wait-for",
2078
+ "-s",
2079
+ "S1",
2080
+ "-l",
2081
+ "What is your name?"
2082
+ ]);
2083
+ assert.equal(waited.matched, true);
2084
+ assert.match(waited.line, /What is your name\?/);
2085
+ }
2086
+ },
2087
+ {
2088
+ id: 6,
2089
+ title: "Fill text",
2090
+ async run(ctx) {
2091
+ await ctx.ensurePromptSession("S1");
2092
+ const result = await ctx.expectOk(["fill", "-s", "S1", "Alice\n"]);
2093
+ assert.deepEqual(result, { ok: true });
2094
+ }
2095
+ },
2096
+ {
2097
+ id: 7,
2098
+ title: "Wait for greeting",
2099
+ async run(ctx) {
2100
+ await ctx.ensureGreetingSession("S1", "Alice");
2101
+ const waited = await ctx.expectOk([
2102
+ "wait-for",
2103
+ "-s",
2104
+ "S1",
2105
+ "-l",
2106
+ "Hello, Alice!"
2107
+ ]);
2108
+ assert.equal(waited.matched, true);
2109
+ }
2110
+ },
2111
+ {
2112
+ id: 8,
2113
+ title: "Read screen includes exitCode null",
2114
+ async run(ctx) {
2115
+ await ctx.ensureGreetingSession("S1", "Alice");
2116
+ const screen = await ctx.expectOk(["read-screen", "-s", "S1"]);
2117
+ assert.ok(Array.isArray(screen.lines));
2118
+ assert.equal(typeof screen.cursor.row, "number");
2119
+ assert.equal(typeof screen.cursor.col, "number");
2120
+ assert.equal(typeof screen.size.rows, "number");
2121
+ assert.equal(typeof screen.size.cols, "number");
2122
+ assert.ok(screen.exitCode === null || screen.exitCode === 0);
2123
+ }
2124
+ },
2125
+ {
2126
+ id: 9,
2127
+ title: "Read history includes all output, exitCode null",
2128
+ async run(ctx) {
2129
+ await ctx.ensureGreetingSession("S1", "Alice");
2130
+ const history = await ctx.expectOk(["read-history", "-s", "S1"]);
2131
+ assert.ok(history.lines.length > 0);
2132
+ assert.ok(history.lines.some((line) => line.includes("Hello, Alice!")));
2133
+ assert.ok(history.exitCode === null || history.exitCode === 0);
2134
+ }
2135
+ },
2136
+ {
2137
+ id: 10,
2138
+ title: "Read history with last N",
2139
+ async run(ctx) {
2140
+ await ctx.ensureGreetingSession("S1", "Alice");
2141
+ const history = await ctx.expectOk(["read-history", "-s", "S1", "-n", "2"]);
2142
+ assert.ok(history.lines.length <= 2);
2143
+ }
2144
+ },
2145
+ {
2146
+ id: 11,
2147
+ title: "Close session",
2148
+ async run(ctx) {
2149
+ await ctx.ensureGreetingSession("S1", "Alice");
2150
+ const closed = await ctx.expectOk(["close-session", "-s", "S1"]);
2151
+ assert.equal(typeof closed.exitCode, "number");
2152
+ const listed = await ctx.listSessions();
2153
+ assert.deepEqual(listed.sessions, []);
2154
+ const error = await ctx.expectError(["get-session", "-s", "S1"]);
2155
+ assert.match(error, /not found/);
2156
+ }
2157
+ },
2158
+ {
2159
+ id: 12,
2160
+ title: "terminal_type character-by-character",
2161
+ async run(ctx) {
2162
+ await ctx.closeSessionIfExists("S2");
2163
+ try {
2164
+ const created = await ctx.expectOk([
2165
+ "create-session",
2166
+ "-s",
2167
+ "S2",
2168
+ tsxPath,
2169
+ testCliPath
2170
+ ]);
2171
+ assert.equal(created.session, "S2");
2172
+ await ctx.expectOk(["wait-for", "-s", "S2", "-l", "What is your name?"]);
2173
+ await ctx.expectOk(["type", "-s", "S2", "Bob"]);
2174
+ await ctx.expectOk(["press-key", "-s", "S2", "Enter"]);
2175
+ const greeted = await ctx.expectOk([
2176
+ "wait-for",
2177
+ "-s",
2178
+ "S2",
2179
+ "-l",
2180
+ "Hello, Bob!"
2181
+ ]);
2182
+ assert.equal(greeted.matched, true);
2183
+ } finally {
2184
+ await ctx.closeSessionIfExists("S2");
2185
+ }
2186
+ }
2187
+ },
2188
+ {
2189
+ id: 13,
2190
+ title: "terminal_wait_for with literal flag",
2191
+ async run(ctx) {
2192
+ await ctx.closeSessionIfExists("S3");
2193
+ try {
2194
+ await ctx.expectOk(["create-session", "-s", "S3", tsxPath, testCliPath]);
2195
+ const prompt = await ctx.expectOk([
2196
+ "wait-for",
2197
+ "-s",
2198
+ "S3",
2199
+ "-l",
2200
+ "What is your name?"
2201
+ ]);
2202
+ assert.equal(prompt.matched, true);
2203
+ await ctx.expectOk(["fill", "-s", "S3", "Carol\n"]);
2204
+ const greeted = await ctx.expectOk([
2205
+ "wait-for",
2206
+ "-s",
2207
+ "S3",
2208
+ "-l",
2209
+ "Hello, Carol!"
2210
+ ]);
2211
+ assert.equal(greeted.matched, true);
2212
+ } finally {
2213
+ await ctx.closeSessionIfExists("S3");
2214
+ }
2215
+ }
2216
+ },
2217
+ {
2218
+ id: 14,
2219
+ title: "terminal_wait_for regex",
2220
+ async run(ctx) {
2221
+ await ctx.closeSessionIfExists("S4");
2222
+ try {
2223
+ await ctx.expectOk(["create-session", "-s", "S4", tsxPath, testCliPath]);
2224
+ const prompt = await ctx.expectOk([
2225
+ "wait-for",
2226
+ "-s",
2227
+ "S4",
2228
+ "What is your name\\?"
2229
+ ]);
2230
+ assert.equal(prompt.matched, true);
2231
+ await ctx.expectOk(["fill", "-s", "S4", "Dan\n"]);
2232
+ const greeted = await ctx.expectOk([
2233
+ "wait-for",
2234
+ "-s",
2235
+ "S4",
2236
+ "Hello,\\s+Dan"
2237
+ ]);
2238
+ assert.equal(greeted.matched, true);
2239
+ } finally {
2240
+ await ctx.closeSessionIfExists("S4");
2241
+ }
2242
+ }
2243
+ },
2244
+ {
2245
+ id: 15,
2246
+ title: "terminal_wait_for timeout exceeded",
2247
+ async run(ctx) {
2248
+ await ctx.closeSessionIfExists("S5");
2249
+ try {
2250
+ await ctx.expectOk(["create-session", "-s", "S5", tsxPath, testCliPath]);
2251
+ const error = await ctx.expectError([
2252
+ "wait-for",
2253
+ "-s",
2254
+ "S5",
2255
+ "-t",
2256
+ "500",
2257
+ "THIS_WILL_NEVER_APPEAR"
2258
+ ]);
2259
+ assert.match(error, /Timed out waiting for pattern/i);
2260
+ } finally {
2261
+ await ctx.closeSessionIfExists("S5");
2262
+ }
2263
+ }
2264
+ },
2265
+ {
2266
+ id: 16,
2267
+ title: "terminal_wait_for_exit natural exit",
2268
+ async run(ctx) {
2269
+ await ctx.closeSessionIfExists("S6");
2270
+ try {
2271
+ await ctx.expectOk(["create-session", "-s", "S6", tsxPath, testCliPath]);
2272
+ await ctx.expectOk(["wait-for", "-s", "S6", "-l", "What is your name?"]);
2273
+ await ctx.expectOk(["fill", "-s", "S6", "Eve\n"]);
2274
+ const exited = await ctx.expectOk([
2275
+ "wait-for-exit",
2276
+ "-s",
2277
+ "S6",
2278
+ "-t",
2279
+ "5000"
2280
+ ]);
2281
+ assert.equal(exited.exitCode, 0);
2282
+ const session = await ctx.expectOk(["get-session", "-s", "S6"]);
2283
+ assert.equal(session.exitCode, 0);
2284
+ const exitedAgain = await ctx.expectOk(["wait-for-exit", "-s", "S6"]);
2285
+ assert.equal(exitedAgain.exitCode, 0);
2286
+ } finally {
2287
+ await ctx.closeSessionIfExists("S6");
2288
+ }
2289
+ }
2290
+ },
2291
+ {
2292
+ id: 17,
2293
+ title: "terminal_wait_for_exit timeout exceeded",
2294
+ async run(ctx) {
2295
+ await ctx.closeSessionIfExists("S7");
2296
+ try {
2297
+ await ctx.expectOk(["create-session", "-s", "S7", tsxPath, testCliPath]);
2298
+ await ctx.expectOk(["wait-for", "-s", "S7", "-l", "What is your name?"]);
2299
+ const error = await ctx.expectError([
2300
+ "wait-for-exit",
2301
+ "-s",
2302
+ "S7",
2303
+ "-t",
2304
+ "300"
2305
+ ]);
2306
+ assert.match(error, /Timed out waiting for process to exit/i);
2307
+ } finally {
2308
+ await ctx.closeSessionIfExists("S7");
2309
+ }
2310
+ }
2311
+ },
2312
+ {
2313
+ id: 18,
2314
+ title: "terminal_send_signal SIGINT",
2315
+ async run(ctx) {
2316
+ await ctx.closeSessionIfExists("S8");
2317
+ try {
2318
+ await ctx.expectOk(["create-session", "-s", "S8", tsxPath, testCliPath]);
2319
+ await ctx.expectOk(["wait-for", "-s", "S8", "-l", "What is your name?"]);
2320
+ await ctx.expectOk(["send-signal", "-s", "S8", "SIGINT"]);
2321
+ const exited = await ctx.expectOk([
2322
+ "wait-for-exit",
2323
+ "-s",
2324
+ "S8",
2325
+ "-t",
2326
+ "3000"
2327
+ ]);
2328
+ assert.equal(typeof exited.exitCode, "number");
2329
+ const screen = await ctx.expectOk(["read-screen", "-s", "S8"]);
2330
+ assert.equal(screen.exitCode, exited.exitCode);
2331
+ } finally {
2332
+ await ctx.closeSessionIfExists("S8");
2333
+ }
2334
+ }
2335
+ },
2336
+ {
2337
+ id: 19,
2338
+ title: "terminal_resize",
2339
+ async run(ctx) {
2340
+ await ctx.closeSessionIfExists("S9");
2341
+ try {
2342
+ await ctx.expectOk([
2343
+ "create-session",
2344
+ "-s",
2345
+ "S9",
2346
+ "--cols",
2347
+ "80",
2348
+ "--rows",
2349
+ "24",
2350
+ tsxPath,
2351
+ testCliPath
2352
+ ]);
2353
+ const initialScreen = await ctx.expectOk(["read-screen", "-s", "S9"]);
2354
+ assert.equal(initialScreen.size.cols, 80);
2355
+ assert.equal(initialScreen.size.rows, 24);
2356
+ await ctx.expectOk(["resize", "-s", "S9", "--cols", "120", "--rows", "40"]);
2357
+ const resizedScreen = await ctx.expectOk(["read-screen", "-s", "S9"]);
2358
+ assert.equal(resizedScreen.size.cols, 120);
2359
+ assert.equal(resizedScreen.size.rows, 40);
2360
+ await ctx.expectOk(["wait-for", "-s", "S9", "-l", "What is your name?"]);
2361
+ await ctx.expectOk(["fill", "-s", "S9", "Frank\n"]);
2362
+ const greeted = await ctx.expectOk([
2363
+ "wait-for",
2364
+ "-s",
2365
+ "S9",
2366
+ "-l",
2367
+ "Hello, Frank!"
2368
+ ]);
2369
+ assert.equal(greeted.matched, true);
2370
+ } finally {
2371
+ await ctx.closeSessionIfExists("S9");
2372
+ }
2373
+ }
2374
+ },
2375
+ {
2376
+ id: 20,
2377
+ title: "Full flow menu-cli with ArrowDown navigation",
2378
+ async run(ctx) {
2379
+ await ctx.closeSessionIfExists("S10");
2380
+ try {
2381
+ await ctx.expectOk(["create-session", "-s", "S10", tsxPath, menuCliPath]);
2382
+ await ctx.expectOk(["wait-for", "-s", "S10", "-l", "Select an option:"]);
2383
+ await ctx.expectOk(["press-key", "-s", "S10", "ArrowDown"]);
2384
+ await ctx.expectOk(["press-key", "-s", "S10", "ArrowDown"]);
2385
+ await ctx.expectOk(["press-key", "-s", "S10", "Enter"]);
2386
+ const selected = await ctx.expectOk([
2387
+ "wait-for",
2388
+ "-s",
2389
+ "S10",
2390
+ "-l",
2391
+ "You selected: Option 3"
2392
+ ]);
2393
+ assert.equal(selected.matched, true);
2394
+ const exited = await ctx.expectOk([
2395
+ "wait-for-exit",
2396
+ "-s",
2397
+ "S10",
2398
+ "-t",
2399
+ "3000"
2400
+ ]);
2401
+ assert.equal(exited.exitCode, 0);
2402
+ } finally {
2403
+ await ctx.closeSessionIfExists("S10");
2404
+ }
2405
+ }
2406
+ },
2407
+ {
2408
+ id: 21,
2409
+ title: "Full flow menu-cli ArrowUp wrap-around",
2410
+ async run(ctx) {
2411
+ await ctx.closeSessionIfExists("S11");
2412
+ try {
2413
+ await ctx.expectOk(["create-session", "-s", "S11", tsxPath, menuCliPath]);
2414
+ await ctx.expectOk(["wait-for", "-s", "S11", "-l", "Select an option:"]);
2415
+ await ctx.expectOk(["press-key", "-s", "S11", "ArrowUp"]);
2416
+ await ctx.expectOk(["press-key", "-s", "S11", "Enter"]);
2417
+ const selected = await ctx.expectOk([
2418
+ "wait-for",
2419
+ "-s",
2420
+ "S11",
2421
+ "You selected: Option [123]"
2422
+ ]);
2423
+ assert.equal(selected.matched, true);
2424
+ } finally {
2425
+ await ctx.closeSessionIfExists("S11");
2426
+ }
2427
+ }
2428
+ },
2429
+ {
2430
+ id: 22,
2431
+ title: "Multiple concurrent sessions isolation",
2432
+ async run(ctx) {
2433
+ await closeSessions(ctx, ["SA", "SB", "SC"]);
2434
+ try {
2435
+ await ctx.expectOk(["create-session", "-s", "SA", tsxPath, testCliPath]);
2436
+ await ctx.expectOk(["create-session", "-s", "SB", tsxPath, testCliPath]);
2437
+ await ctx.expectOk(["create-session", "-s", "SC", tsxPath, menuCliPath]);
2438
+ const listed = await ctx.listSessions();
2439
+ assert.equal(listed.sessions.length, 3);
2440
+ await ctx.expectOk(["wait-for", "-s", "SA", "-l", "What is your name?"]);
2441
+ await ctx.expectOk(["fill", "-s", "SA", "Session-A\n"]);
2442
+ await ctx.expectOk(["wait-for", "-s", "SB", "-l", "What is your name?"]);
2443
+ await ctx.expectOk(["fill", "-s", "SB", "Session-B\n"]);
2444
+ await ctx.expectOk(["wait-for", "-s", "SC", "-l", "Select an option:"]);
2445
+ await ctx.expectOk(["press-key", "-s", "SC", "Enter"]);
2446
+ await ctx.expectOk(["wait-for", "-s", "SA", "-l", "Hello, Session-A!"]);
2447
+ await ctx.expectOk(["wait-for", "-s", "SB", "-l", "Hello, Session-B!"]);
2448
+ await ctx.expectOk(["wait-for", "-s", "SC", "-l", "You selected: Option 1"]);
2449
+ const historyA = await ctx.expectOk(["read-history", "-s", "SA"]);
2450
+ assert.ok(historyA.lines.some((line) => line.includes("Session-A")));
2451
+ assert.ok(historyA.lines.every((line) => !line.includes("Session-B")));
2452
+ const historyB = await ctx.expectOk(["read-history", "-s", "SB"]);
2453
+ assert.ok(historyB.lines.some((line) => line.includes("Session-B")));
2454
+ assert.ok(historyB.lines.every((line) => !line.includes("Session-A")));
2455
+ await ctx.expectOk(["close-session", "-s", "SB"]);
2456
+ const afterClose = await ctx.listSessions();
2457
+ assert.ok(!afterClose.sessions.some((s) => s.session === "SB"));
2458
+ } finally {
2459
+ await closeSessions(ctx, ["SA", "SB", "SC"]);
2460
+ }
2461
+ }
2462
+ },
2463
+ {
2464
+ id: 23,
2465
+ title: "Final state",
2466
+ async run(ctx) {
2467
+ const listed = await ctx.listSessions();
2468
+ assert.deepEqual(listed.sessions, []);
2469
+ }
2470
+ },
2471
+ {
2472
+ id: 24,
2473
+ title: "Unknown session error",
2474
+ async run(ctx) {
2475
+ const readScreenError = await ctx.expectError(["read-screen", "-s", "does-not-exist"]);
2476
+ assert.match(readScreenError, /not found/);
2477
+ const closeError = await ctx.expectError(["close-session", "-s", "does-not-exist"]);
2478
+ assert.match(closeError, /not found/);
2479
+ }
2480
+ },
2481
+ {
2482
+ id: 25,
2483
+ title: "Bash interactive shell",
2484
+ async run(ctx) {
2485
+ await ctx.closeSessionIfExists("BASH");
2486
+ try {
2487
+ await ctx.expectOk([
2488
+ "create-session",
2489
+ "-s",
2490
+ "BASH",
2491
+ "--cols",
2492
+ "120",
2493
+ "--rows",
2494
+ "40",
2495
+ "bash",
2496
+ "--",
2497
+ "--norc",
2498
+ "--noprofile"
2499
+ ]);
2500
+ await ctx.expectOk(["wait-for", "-s", "BASH", "-t", "3000", "\\$\\s*$"]);
2501
+ await ctx.expectOk(["fill", "-s", "BASH", "echo hello-from-bash\n"]);
2502
+ await ctx.expectOk(["wait-for", "-s", "BASH", "-l", "hello-from-bash"]);
2503
+ await ctx.expectOk(["fill", "-s", "BASH", "echo $((6 * 7))\n"]);
2504
+ await ctx.expectOk(["wait-for", "-s", "BASH", "-l", "42"]);
2505
+ await ctx.expectOk(["fill", "-s", "BASH", "sleep 60\n"]);
2506
+ await ctx.expectOk(["wait-for", "-s", "BASH", "-t", "2000", "sleep"]);
2507
+ await ctx.expectOk(["press-key", "-s", "BASH", "Control+c"]);
2508
+ await ctx.expectOk(["wait-for", "-s", "BASH", "-t", "3000", "\\$\\s*$"]);
2509
+ await ctx.expectOk(["fill", "-s", "BASH", "exit 0\n"]);
2510
+ const exited = await ctx.expectOk([
2511
+ "wait-for-exit",
2512
+ "-s",
2513
+ "BASH",
2514
+ "-t",
2515
+ "3000"
2516
+ ]);
2517
+ assert.equal(exited.exitCode, 0);
2518
+ } finally {
2519
+ await ctx.closeSessionIfExists("BASH");
2520
+ }
2521
+ }
2522
+ },
2523
+ {
2524
+ id: 26,
2525
+ title: "Bash long output read_history line count",
2526
+ async run(ctx) {
2527
+ await ctx.closeSessionIfExists("BASH2");
2528
+ try {
2529
+ await ctx.expectOk([
2530
+ "create-session",
2531
+ "-s",
2532
+ "BASH2",
2533
+ "bash",
2534
+ "--",
2535
+ "--norc",
2536
+ "--noprofile"
2537
+ ]);
2538
+ await ctx.expectOk(["wait-for", "-s", "BASH2", "-t", "3000", "\\$\\s*$"]);
2539
+ await ctx.expectOk([
2540
+ "fill",
2541
+ "-s",
2542
+ "BASH2",
2543
+ 'for i in $(seq 1 50); do echo "line $i"; done\n'
2544
+ ]);
2545
+ await ctx.expectOk(["wait-for", "-s", "BASH2", "-l", "line 50"]);
2546
+ const history = await ctx.expectOk(["read-history", "-s", "BASH2"]);
2547
+ for (let index = 1; index <= 50; index += 1) {
2548
+ assert.ok(history.lines.some((line) => line.includes(`line ${index}`)));
2549
+ }
2550
+ const tail = await ctx.expectOk(["read-history", "-s", "BASH2", "-n", "10"]);
2551
+ assert.ok(tail.lines.length <= 10);
2552
+ assert.ok(tail.lines.some((line) => /line 50/.test(line)));
2553
+ } finally {
2554
+ await ctx.closeSessionIfExists("BASH2");
2555
+ }
2556
+ }
2557
+ },
2558
+ {
2559
+ id: 27,
2560
+ title: "Python REPL expression evaluation and multi-line input",
2561
+ async run(ctx) {
2562
+ await ctx.closeSessionIfExists("PY");
2563
+ try {
2564
+ await ctx.expectOk([
2565
+ "create-session",
2566
+ "-s",
2567
+ "PY",
2568
+ "--cols",
2569
+ "120",
2570
+ "--rows",
2571
+ "40",
2572
+ "python3",
2573
+ "--",
2574
+ "-q"
2575
+ ]);
2576
+ await ctx.expectOk(["wait-for", "-s", "PY", "-l", ">>>"]);
2577
+ await ctx.expectOk(["fill", "-s", "PY", "2 ** 10\n"]);
2578
+ await ctx.expectOk(["wait-for", "-s", "PY", "-l", "1024"]);
2579
+ await ctx.expectOk(["fill", "-s", "PY", "[x*x for x in range(5)]\n"]);
2580
+ await ctx.expectOk([
2581
+ "wait-for",
2582
+ "-s",
2583
+ "PY",
2584
+ "\\[0, 1, 4, 9, 16\\]"
2585
+ ]);
2586
+ await ctx.expectOk(["fill", "-s", "PY", "def greet(name):\n"]);
2587
+ await ctx.expectOk(["wait-for", "-s", "PY", "\\.\\.\\."]);
2588
+ await ctx.expectOk(["fill", "-s", "PY", " return f'hi {name}'\n"]);
2589
+ await ctx.expectOk(["wait-for", "-s", "PY", "\\.\\.\\."]);
2590
+ await ctx.expectOk(["fill", "-s", "PY", "\n"]);
2591
+ await ctx.expectOk(["wait-for", "-s", "PY", "-l", ">>>"]);
2592
+ await ctx.expectOk(["fill", "-s", "PY", "greet('world')\n"]);
2593
+ await ctx.expectOk(["wait-for", "-s", "PY", "-l", "hi world"]);
2594
+ await ctx.expectOk(["press-key", "-s", "PY", "Control+d"]);
2595
+ const exited = await ctx.expectOk([
2596
+ "wait-for-exit",
2597
+ "-s",
2598
+ "PY",
2599
+ "-t",
2600
+ "3000"
2601
+ ]);
2602
+ assert.equal(exited.exitCode, 0);
2603
+ } finally {
2604
+ await ctx.closeSessionIfExists("PY");
2605
+ }
2606
+ }
2607
+ },
2608
+ {
2609
+ id: 28,
2610
+ title: "Node.js REPL evaluation",
2611
+ async run(ctx) {
2612
+ await ctx.closeSessionIfExists("NODE");
2613
+ try {
2614
+ await ctx.expectOk([
2615
+ "create-session",
2616
+ "-s",
2617
+ "NODE",
2618
+ "--cols",
2619
+ "120",
2620
+ "--rows",
2621
+ "40",
2622
+ "node"
2623
+ ]);
2624
+ await ctx.expectOk(["wait-for", "-s", "NODE", "-l", ">"]);
2625
+ await ctx.expectOk(["fill", "-s", "NODE", "Math.PI.toFixed(4)\n"]);
2626
+ await ctx.expectOk(["wait-for", "-s", "NODE", "-l", "3.1416"]);
2627
+ await ctx.expectOk(["fill", "-s", "NODE", "[1,2,3].map(x => x * 2)\n"]);
2628
+ await ctx.expectOk(["wait-for", "-s", "NODE", "\\[ 2, 4, 6 \\]"]);
2629
+ await ctx.expectOk(["fill", "-s", "NODE", "process.version\n"]);
2630
+ await ctx.expectOk(["wait-for", "-s", "NODE", "v\\d+"]);
2631
+ await ctx.expectOk(["press-key", "-s", "NODE", "Control+d"]);
2632
+ const exited = await ctx.expectOk([
2633
+ "wait-for-exit",
2634
+ "-s",
2635
+ "NODE",
2636
+ "-t",
2637
+ "3000"
2638
+ ]);
2639
+ assert.equal(exited.exitCode, 0);
2640
+ } finally {
2641
+ await ctx.closeSessionIfExists("NODE");
2642
+ }
2643
+ }
2644
+ },
2645
+ {
2646
+ id: 29,
2647
+ title: "vim open, edit, save and quit",
2648
+ async run(ctx) {
2649
+ await removeIfExists(vimSavedFile);
2650
+ await closeSessions(ctx, ["VIM", "CAT"]);
2651
+ try {
2652
+ await ctx.expectOk([
2653
+ "create-session",
2654
+ "-s",
2655
+ "VIM",
2656
+ "--cols",
2657
+ "120",
2658
+ "--rows",
2659
+ "40",
2660
+ "vim",
2661
+ vimSavedFile
2662
+ ]);
2663
+ await ctx.expectOk([
2664
+ "wait-for",
2665
+ "-s",
2666
+ "VIM",
2667
+ "-t",
2668
+ "5000",
2669
+ path3.basename(vimSavedFile)
2670
+ ]);
2671
+ await ctx.expectOk(["press-key", "-s", "VIM", "i"]);
2672
+ await ctx.expectOk(["type", "-s", "VIM", "hello from terminal-pilot"]);
2673
+ await ctx.expectOk(["press-key", "-s", "VIM", "Escape"]);
2674
+ const screen = await ctx.expectOk(["read-screen", "-s", "VIM"]);
2675
+ assert.ok(screen.lines.some((line) => line.includes("hello from terminal-pilot")));
2676
+ assert.equal(screen.exitCode, null);
2677
+ await ctx.expectOk(["fill", "-s", "VIM", ":wq\n"]);
2678
+ const exited = await ctx.expectOk([
2679
+ "wait-for-exit",
2680
+ "-s",
2681
+ "VIM",
2682
+ "-t",
2683
+ "5000"
2684
+ ]);
2685
+ assert.equal(exited.exitCode, 0);
2686
+ await ctx.expectOk(["create-session", "-s", "CAT", "cat", vimSavedFile]);
2687
+ await ctx.expectOk([
2688
+ "wait-for",
2689
+ "-s",
2690
+ "CAT",
2691
+ "-l",
2692
+ "hello from terminal-pilot"
2693
+ ]);
2694
+ } finally {
2695
+ await closeSessions(ctx, ["VIM", "CAT"]);
2696
+ await removeIfExists(vimSavedFile);
2697
+ }
2698
+ }
2699
+ },
2700
+ {
2701
+ id: 30,
2702
+ title: "vim quit without saving",
2703
+ async run(ctx) {
2704
+ await removeIfExists(vimDiscardFile);
2705
+ await ctx.closeSessionIfExists("VIM2");
2706
+ try {
2707
+ await ctx.expectOk([
2708
+ "create-session",
2709
+ "-s",
2710
+ "VIM2",
2711
+ "--cols",
2712
+ "80",
2713
+ "--rows",
2714
+ "24",
2715
+ "vim",
2716
+ vimDiscardFile
2717
+ ]);
2718
+ await ctx.expectOk([
2719
+ "wait-for",
2720
+ "-s",
2721
+ "VIM2",
2722
+ "-t",
2723
+ "5000",
2724
+ path3.basename(vimDiscardFile)
2725
+ ]);
2726
+ await ctx.expectOk(["press-key", "-s", "VIM2", "i"]);
2727
+ await ctx.expectOk(["type", "-s", "VIM2", "this should not be saved"]);
2728
+ await ctx.expectOk(["press-key", "-s", "VIM2", "Escape"]);
2729
+ await ctx.expectOk(["fill", "-s", "VIM2", ":q!\n"]);
2730
+ const exited = await ctx.expectOk([
2731
+ "wait-for-exit",
2732
+ "-s",
2733
+ "VIM2",
2734
+ "-t",
2735
+ "5000"
2736
+ ]);
2737
+ assert.equal(exited.exitCode, 0);
2738
+ } finally {
2739
+ await ctx.closeSessionIfExists("VIM2");
2740
+ await removeIfExists(vimDiscardFile);
2741
+ }
2742
+ }
2743
+ },
2744
+ {
2745
+ id: 31,
2746
+ title: "poe-code help output",
2747
+ skipped: true
2748
+ },
2749
+ {
2750
+ id: 32,
2751
+ title: "poe-code configure interactive agent selection",
2752
+ skipped: true
2753
+ },
2754
+ {
2755
+ id: 33,
2756
+ title: "Claude Code version and help",
2757
+ skipped: true
2758
+ },
2759
+ {
2760
+ id: 34,
2761
+ title: "Claude Code single prompt",
2762
+ skipped: true
2763
+ },
2764
+ {
2765
+ id: 35,
2766
+ title: "Claude Code interactive REPL mode",
2767
+ skipped: true
2768
+ },
2769
+ {
2770
+ id: 36,
2771
+ title: "Final state all sessions closed",
2772
+ async run(ctx) {
2773
+ const listed = await ctx.listSessions();
2774
+ assert.deepEqual(listed.sessions, []);
2775
+ }
2776
+ }
2777
+ ];
2778
+ async function main2() {
2779
+ const ctx = new QaContext();
2780
+ const failures = [];
2781
+ try {
2782
+ for (const testCase of cases) {
2783
+ if (testCase.skipped === true) {
2784
+ process.stdout.write(`SKIP ${testCase.id}. ${testCase.title}
2785
+ `);
2786
+ continue;
2787
+ }
2788
+ process.stdout.write(`RUN ${testCase.id}. ${testCase.title}
2789
+ `);
2790
+ try {
2791
+ await testCase.run?.(ctx);
2792
+ process.stdout.write(`PASS ${testCase.id}. ${testCase.title}
2793
+ `);
2794
+ } catch (error) {
2795
+ const reason = formatError(error);
2796
+ failures.push({
2797
+ id: testCase.id,
2798
+ title: testCase.title,
2799
+ reason
2800
+ });
2801
+ process.stdout.write(`FAIL ${testCase.id}. ${testCase.title}
2802
+ `);
2803
+ process.stdout.write(`${reason}
2804
+ `);
2805
+ }
2806
+ }
2807
+ } finally {
2808
+ await ctx.close();
2809
+ }
2810
+ process.stdout.write(`
2811
+ Summary: ${cases.filter((entry) => !entry.skipped).length - failures.length} passed, ${failures.length} failed, ${cases.filter((entry) => entry.skipped).length} skipped.
2812
+ `);
2813
+ if (failures.length > 0) {
2814
+ process.stdout.write("\nFailures:\n");
2815
+ for (const failure of failures) {
2816
+ process.stdout.write(`${failure.id}. ${failure.title}: ${failure.reason}
2817
+ `);
2818
+ }
2819
+ process.exitCode = 1;
2820
+ }
2821
+ }
2822
+ if (process.argv[1] === fileURLToPath2(import.meta.url)) {
2823
+ await main2();
2824
+ }
2825
+ export {
2826
+ main2 as main
2827
+ };
2828
+ //# sourceMappingURL=qa-cli.js.map