terminal-pilot 0.0.43 → 0.0.45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/dist/cli.js +3333 -3095
  2. package/dist/cli.js.map +4 -4
  3. package/dist/commands/close-session.js +5 -0
  4. package/dist/commands/close-session.js.map +2 -2
  5. package/dist/commands/create-session.js +5 -0
  6. package/dist/commands/create-session.js.map +2 -2
  7. package/dist/commands/daemon-runtime.js.map +1 -1
  8. package/dist/commands/fill.js +5 -0
  9. package/dist/commands/fill.js.map +2 -2
  10. package/dist/commands/get-session.js +5 -0
  11. package/dist/commands/get-session.js.map +2 -2
  12. package/dist/commands/index.js +5 -0
  13. package/dist/commands/index.js.map +3 -3
  14. package/dist/commands/install.js +5 -0
  15. package/dist/commands/install.js.map +2 -2
  16. package/dist/commands/installer.js.map +1 -1
  17. package/dist/commands/list-sessions.js +5 -0
  18. package/dist/commands/list-sessions.js.map +2 -2
  19. package/dist/commands/press-key.js +5 -0
  20. package/dist/commands/press-key.js.map +2 -2
  21. package/dist/commands/read-history.js +5 -0
  22. package/dist/commands/read-history.js.map +2 -2
  23. package/dist/commands/read-screen.js +5 -0
  24. package/dist/commands/read-screen.js.map +2 -2
  25. package/dist/commands/resize.js +5 -0
  26. package/dist/commands/resize.js.map +2 -2
  27. package/dist/commands/runtime.js.map +1 -1
  28. package/dist/commands/screenshot.js +5 -0
  29. package/dist/commands/screenshot.js.map +2 -2
  30. package/dist/commands/send-signal.js +5 -0
  31. package/dist/commands/send-signal.js.map +2 -2
  32. package/dist/commands/type.js +5 -0
  33. package/dist/commands/type.js.map +2 -2
  34. package/dist/commands/uninstall.js +5 -0
  35. package/dist/commands/uninstall.js.map +2 -2
  36. package/dist/commands/wait-for-exit.js +5 -0
  37. package/dist/commands/wait-for-exit.js.map +2 -2
  38. package/dist/commands/wait-for.js +5 -0
  39. package/dist/commands/wait-for.js.map +2 -2
  40. package/dist/composition.json +1 -1
  41. package/dist/testing/cli-repl.js +3333 -3095
  42. package/dist/testing/cli-repl.js.map +4 -4
  43. package/dist/testing/qa-cli.js +3333 -3095
  44. package/dist/testing/qa-cli.js.map +4 -4
  45. package/node_modules/@poe-code/agent-defs/package.json +1 -3
  46. package/node_modules/@poe-code/config-mutations/package.json +1 -3
  47. package/node_modules/@poe-code/frontmatter/package.json +1 -3
  48. package/node_modules/toolcraft-design/dist/components/file-changes.d.ts +16 -0
  49. package/node_modules/toolcraft-design/dist/components/file-changes.js +162 -0
  50. package/node_modules/toolcraft-design/dist/components/index.d.ts +2 -0
  51. package/node_modules/toolcraft-design/dist/components/index.js +1 -0
  52. package/node_modules/toolcraft-design/dist/explorer/jobs.d.ts +1 -0
  53. package/node_modules/toolcraft-design/dist/explorer/jobs.js +28 -0
  54. package/node_modules/toolcraft-design/dist/explorer/keymap.js +2 -2
  55. package/node_modules/toolcraft-design/dist/explorer/reducer.js +14 -2
  56. package/node_modules/toolcraft-design/dist/explorer/render/detail.js +1 -1
  57. package/node_modules/toolcraft-design/dist/explorer/render/list.js +1 -1
  58. package/node_modules/toolcraft-design/dist/explorer/render/modal.js +2 -1
  59. package/node_modules/toolcraft-design/dist/index.d.ts +2 -0
  60. package/node_modules/toolcraft-design/dist/index.js +1 -0
  61. package/node_modules/toolcraft-design/package.json +1 -0
  62. package/package.json +1 -1
@@ -1,10 +1,8 @@
1
1
  {
2
2
  "name": "@poe-code/agent-defs",
3
3
  "version": "0.0.1",
4
+ "private": true,
4
5
  "license": "MIT",
5
- "publishConfig": {
6
- "access": "public"
7
- },
8
6
  "type": "module",
9
7
  "main": "dist/index.js",
10
8
  "types": "dist/index.d.ts",
@@ -1,10 +1,8 @@
1
1
  {
2
2
  "name": "@poe-code/config-mutations",
3
3
  "version": "0.0.1",
4
+ "private": true,
4
5
  "license": "MIT",
5
- "publishConfig": {
6
- "access": "public"
7
- },
8
6
  "type": "module",
9
7
  "main": "dist/index.js",
10
8
  "types": "dist/index.d.ts",
@@ -1,10 +1,8 @@
1
1
  {
2
2
  "name": "@poe-code/frontmatter",
3
3
  "version": "0.0.1",
4
+ "private": true,
4
5
  "license": "MIT",
5
- "publishConfig": {
6
- "access": "public"
7
- },
8
6
  "type": "module",
9
7
  "main": "dist/index.js",
10
8
  "types": "dist/index.d.ts",
@@ -0,0 +1,16 @@
1
+ export type FileChangeKind = "added" | "modified" | "deleted" | "renamed";
2
+ export type FileChangeDisplayMode = "status" | "diff";
3
+ export type FileChangeOutputFormat = "terminal" | "markdown";
4
+ export interface FileChange {
5
+ path: string;
6
+ kind: FileChangeKind;
7
+ oldPath?: string;
8
+ conflict?: boolean;
9
+ oldContent?: string;
10
+ newContent?: string;
11
+ }
12
+ export interface RenderFileChangesOptions {
13
+ mode?: FileChangeDisplayMode;
14
+ format?: FileChangeOutputFormat;
15
+ }
16
+ export declare function renderFileChanges(changes: readonly FileChange[], options?: RenderFileChangesOptions): string;
@@ -0,0 +1,162 @@
1
+ import { color } from "./color.js";
2
+ const STATUS_MARKERS = {
3
+ added: "A",
4
+ modified: "M",
5
+ deleted: "D",
6
+ renamed: "R"
7
+ };
8
+ const STATUS_LABELS = {
9
+ added: "added",
10
+ modified: "modified",
11
+ deleted: "deleted",
12
+ renamed: "renamed"
13
+ };
14
+ function changePath(change) {
15
+ if (change.kind === "renamed" && change.oldPath) {
16
+ return `${change.oldPath} -> ${change.path}`;
17
+ }
18
+ return change.path;
19
+ }
20
+ function colorStatusMarker(change, marker) {
21
+ if (change.conflict) {
22
+ return color.red.bold(marker);
23
+ }
24
+ switch (change.kind) {
25
+ case "added":
26
+ return color.green(marker);
27
+ case "modified":
28
+ return color.yellow(marker);
29
+ case "deleted":
30
+ return color.red(marker);
31
+ case "renamed":
32
+ return color.cyan(marker);
33
+ }
34
+ }
35
+ function formatSummary(changes) {
36
+ const counts = new Map();
37
+ let conflicts = 0;
38
+ for (const change of changes) {
39
+ counts.set(change.kind, (counts.get(change.kind) ?? 0) + 1);
40
+ if (change.conflict) {
41
+ conflicts += 1;
42
+ }
43
+ }
44
+ const details = Object.keys(STATUS_LABELS)
45
+ .flatMap((kind) => {
46
+ const count = counts.get(kind) ?? 0;
47
+ return count > 0 ? [`${count} ${STATUS_LABELS[kind]}`] : [];
48
+ });
49
+ if (conflicts > 0) {
50
+ details.push(`${conflicts} ${conflicts === 1 ? "conflict" : "conflicts"}`);
51
+ }
52
+ return `${changes.length} ${changes.length === 1 ? "change" : "changes"} (${details.join(", ")})`;
53
+ }
54
+ function renderStatus(changes, format) {
55
+ const lines = changes.map((change) => {
56
+ const marker = `${STATUS_MARKERS[change.kind]}${change.conflict ? "!" : " "}`;
57
+ const renderedMarker = format === "terminal" ? colorStatusMarker(change, marker) : marker;
58
+ return `${renderedMarker} ${changePath(change)}`;
59
+ });
60
+ const output = [...lines, "", formatSummary(changes)].join("\n");
61
+ return format === "markdown" ? `\`\`\`text\n${output}\n\`\`\`` : output;
62
+ }
63
+ function diffPath(prefix, path) {
64
+ return `${prefix}/${path}`;
65
+ }
66
+ function contentLines(content) {
67
+ const lines = content.split("\n");
68
+ if (lines.at(-1) === "") {
69
+ lines.pop();
70
+ }
71
+ return lines;
72
+ }
73
+ function commonPrefixLength(oldLines, newLines) {
74
+ const limit = Math.min(oldLines.length, newLines.length);
75
+ let index = 0;
76
+ while (index < limit && oldLines[index] === newLines[index]) {
77
+ index += 1;
78
+ }
79
+ return index;
80
+ }
81
+ function commonSuffixLength(oldLines, newLines, prefixLength) {
82
+ const limit = Math.min(oldLines.length, newLines.length) - prefixLength;
83
+ let length = 0;
84
+ while (length < limit &&
85
+ oldLines[oldLines.length - length - 1] === newLines[newLines.length - length - 1]) {
86
+ length += 1;
87
+ }
88
+ return length;
89
+ }
90
+ function hunkRange(startIndex, count) {
91
+ const lineNumber = count === 0 ? 0 : startIndex + 1;
92
+ return `${lineNumber},${count}`;
93
+ }
94
+ function createUnifiedHunk(oldContent, newContent) {
95
+ const oldLines = contentLines(oldContent);
96
+ const newLines = contentLines(newContent);
97
+ const prefixLength = commonPrefixLength(oldLines, newLines);
98
+ const suffixLength = commonSuffixLength(oldLines, newLines, prefixLength);
99
+ const context = 3;
100
+ const oldChangeEnd = oldLines.length - suffixLength;
101
+ const newChangeEnd = newLines.length - suffixLength;
102
+ const oldStart = Math.max(0, prefixLength - context);
103
+ const newStart = Math.max(0, prefixLength - context);
104
+ const oldEnd = Math.min(oldLines.length, oldChangeEnd + context);
105
+ const newEnd = Math.min(newLines.length, newChangeEnd + context);
106
+ const oldCount = oldEnd - oldStart;
107
+ const newCount = newEnd - newStart;
108
+ const before = oldLines.slice(oldStart, prefixLength).map((line) => ` ${line}`);
109
+ const removed = oldLines.slice(prefixLength, oldChangeEnd).map((line) => `-${line}`);
110
+ const added = newLines.slice(prefixLength, newChangeEnd).map((line) => `+${line}`);
111
+ const after = oldLines.slice(oldChangeEnd, oldEnd).map((line) => ` ${line}`);
112
+ return [
113
+ `@@ -${hunkRange(oldStart, oldCount)} +${hunkRange(newStart, newCount)} @@`,
114
+ ...before,
115
+ ...removed,
116
+ ...added,
117
+ ...after
118
+ ].join("\n");
119
+ }
120
+ function createChangePatch(change) {
121
+ const oldPath = change.kind === "added"
122
+ ? "/dev/null"
123
+ : diffPath("a", change.oldPath ?? change.path);
124
+ const newPath = change.kind === "deleted" ? "/dev/null" : diffPath("b", change.path);
125
+ const oldContent = change.kind === "added" ? "" : (change.oldContent ?? "");
126
+ const newContent = change.kind === "deleted" ? "" : (change.newContent ?? "");
127
+ const headers = [`--- ${oldPath}`, `+++ ${newPath}`];
128
+ if (oldContent !== newContent) {
129
+ return [...headers, createUnifiedHunk(oldContent, newContent)].join("\n");
130
+ }
131
+ return headers.join("\n");
132
+ }
133
+ function colorDiffLine(line) {
134
+ if (line.startsWith("@@")) {
135
+ return color.cyan(line);
136
+ }
137
+ if (line.startsWith("+++") || line.startsWith("---")) {
138
+ return color.bold(line);
139
+ }
140
+ if (line.startsWith("+")) {
141
+ return color.green(line);
142
+ }
143
+ if (line.startsWith("-")) {
144
+ return color.red(line);
145
+ }
146
+ return line;
147
+ }
148
+ function renderDiff(changes, format) {
149
+ const output = changes.map(createChangePatch).join("\n\n");
150
+ if (format === "markdown") {
151
+ return `\`\`\`diff\n${output}\n\`\`\``;
152
+ }
153
+ return output.split("\n").map(colorDiffLine).join("\n");
154
+ }
155
+ export function renderFileChanges(changes, options = {}) {
156
+ if (changes.length === 0) {
157
+ return "No file changes.";
158
+ }
159
+ const mode = options.mode ?? "status";
160
+ const format = options.format ?? "terminal";
161
+ return mode === "diff" ? renderDiff(changes, format) : renderStatus(changes, format);
162
+ }
@@ -10,6 +10,8 @@ export { formatCommandNotFound } from "./command-errors.js";
10
10
  export { formatCommandNotFoundPanel } from "./command-errors.js";
11
11
  export { renderTable } from "./table.js";
12
12
  export type { TableColumn, RenderTableOptions } from "./table.js";
13
+ export { renderFileChanges } from "./file-changes.js";
14
+ export type { FileChange, FileChangeDisplayMode, FileChangeKind, FileChangeOutputFormat, RenderFileChangesOptions } from "./file-changes.js";
13
15
  export { renderCatalog } from "./catalog.js";
14
16
  export type { CatalogGroup, CatalogItem, CatalogMetric, CatalogTone, RenderCatalogOptions } from "./catalog.js";
15
17
  export { getTemplatePartialNames, renderTemplate, resolveTemplatePartials } from "./template.js";
@@ -6,5 +6,6 @@ export { helpFormatter, formatColumns, formatCommand, formatUsage, formatOption,
6
6
  export { formatCommandNotFound } from "./command-errors.js";
7
7
  export { formatCommandNotFoundPanel } from "./command-errors.js";
8
8
  export { renderTable } from "./table.js";
9
+ export { renderFileChanges } from "./file-changes.js";
9
10
  export { renderCatalog } from "./catalog.js";
10
11
  export { getTemplatePartialNames, renderTemplate, resolveTemplatePartials } from "./template.js";
@@ -1,6 +1,7 @@
1
1
  import type { ExplorerEvent } from "./events.js";
2
2
  import type { DetailCtx, DetailItem } from "./state.js";
3
3
  export declare const LOADING_INDICATOR_MS = 150;
4
+ export declare const DETAIL_DEBOUNCE_MS = 100;
4
5
  export declare function createDetailJobs(emit: (event: ExplorerEvent) => void): {
5
6
  schedule: (rowId: string, items: (ctx: DetailCtx) => Promise<DetailItem[]>, ctx: DetailCtx) => Promise<void>;
6
7
  abort: () => void;
@@ -1,6 +1,8 @@
1
1
  export const LOADING_INDICATOR_MS = 150;
2
+ export const DETAIL_DEBOUNCE_MS = 100;
2
3
  export function createDetailJobs(emit) {
3
4
  let token = 0;
5
+ let lastScheduleAt = 0;
4
6
  let current = null;
5
7
  const abortedTokens = new Set();
6
8
  return {
@@ -9,6 +11,9 @@ export function createDetailJobs(emit) {
9
11
  current.controller.abort();
10
12
  clearTimeout(current.loadingTimer);
11
13
  }
14
+ const scheduledAt = Date.now();
15
+ const debounce = scheduledAt - lastScheduleAt < DETAIL_DEBOUNCE_MS;
16
+ lastScheduleAt = scheduledAt;
12
17
  const nextToken = ++token;
13
18
  const controller = new AbortController();
14
19
  let finished = false;
@@ -19,6 +24,12 @@ export function createDetailJobs(emit) {
19
24
  }, LOADING_INDICATOR_MS);
20
25
  current = { controller, loadingTimer, token: nextToken };
21
26
  try {
27
+ if (debounce) {
28
+ await waitUnlessAborted(DETAIL_DEBOUNCE_MS, controller.signal);
29
+ if (controller.signal.aborted || nextToken !== token) {
30
+ return;
31
+ }
32
+ }
22
33
  const loadedItems = await items({ ...ctx, signal: controller.signal });
23
34
  finished = true;
24
35
  if (!abortedTokens.has(nextToken)) {
@@ -51,6 +62,23 @@ export function createDetailJobs(emit) {
51
62
  }
52
63
  };
53
64
  }
65
+ function waitUnlessAborted(ms, signal) {
66
+ return new Promise((resolve) => {
67
+ if (signal.aborted) {
68
+ resolve();
69
+ return;
70
+ }
71
+ const onAbort = () => {
72
+ clearTimeout(timer);
73
+ resolve();
74
+ };
75
+ const timer = setTimeout(() => {
76
+ signal.removeEventListener("abort", onAbort);
77
+ resolve();
78
+ }, ms);
79
+ signal.addEventListener("abort", onAbort, { once: true });
80
+ });
81
+ }
54
82
  function toError(error) {
55
83
  if (error instanceof Error) {
56
84
  return error;
@@ -7,8 +7,8 @@ const builtinBindings = {
7
7
  cursorDown: ["down", "j"],
8
8
  top: ["home", "gg"],
9
9
  bottom: ["end", "G"],
10
- pageUp: ["Ctrl+u"],
11
- pageDown: ["Ctrl+d"],
10
+ pageUp: ["pageup", "Ctrl+u"],
11
+ pageDown: ["pagedown", "Ctrl+d"],
12
12
  focusNext: ["tab"],
13
13
  escape: ["escape"],
14
14
  confirm: ["return", "enter"],
@@ -77,9 +77,13 @@ function stepKey(state, key, runtimeHandles) {
77
77
  case "bottom":
78
78
  return setCursor(state, state.filtered.length - 1);
79
79
  case "pageUp":
80
- return moveCursor(state, -pageSize(state));
80
+ return isDetailBlobFocused(state)
81
+ ? detailScroll(state, -detailBodyHeight(state))
82
+ : moveCursor(state, -pageSize(state));
81
83
  case "pageDown":
82
- return moveCursor(state, pageSize(state));
84
+ return isDetailBlobFocused(state)
85
+ ? detailScroll(state, detailBodyHeight(state))
86
+ : moveCursor(state, pageSize(state));
83
87
  case "focusNext":
84
88
  return focusNext(state);
85
89
  case "escape":
@@ -328,8 +332,16 @@ function moveCursor(state, delta) {
328
332
  if (state.focused === "detail" && hasDetailCursor(state)) {
329
333
  return moveDetailCursor(state, delta);
330
334
  }
335
+ if (isDetailBlobFocused(state)) {
336
+ return detailScroll(state, delta);
337
+ }
331
338
  return setCursor(state, state.cursor + delta);
332
339
  }
340
+ function isDetailBlobFocused(state) {
341
+ return (state.focused === "detail" &&
342
+ !hasDetailCursor(state) &&
343
+ (state.detail.items?.length ?? 0) > 0);
344
+ }
333
345
  function moveDetailCursor(state, delta) {
334
346
  const max = Math.max(0, (state.detail.items?.length ?? 0) - 1);
335
347
  const cursor = clamp(state.detail.cursor + delta, 0, max);
@@ -26,7 +26,7 @@ function renderDetailBody(state, screen, rect, row) {
26
26
  return;
27
27
  }
28
28
  if (items === null) {
29
- writeLine(screen, rect, 0, state.detail.loading ? "Loading detail..." : state.emptyHint, styles.muted);
29
+ writeLine(screen, rect, 0, row === null ? state.emptyHint : "Loading detail...", styles.muted);
30
30
  return;
31
31
  }
32
32
  if (items.length === 0) {
@@ -13,7 +13,7 @@ export function renderList(state, screen, layout) {
13
13
  writeLine(screen, rect, 0, "Terminal too narrow", styles.muted);
14
14
  return;
15
15
  }
16
- drawPaneFrame(screen, rect, "Plans", state.focused === "list" ? styles.borderFocused : styles.border);
16
+ drawPaneFrame(screen, rect, state.title, state.focused === "list" ? styles.borderFocused : styles.border);
17
17
  const bodyRect = paneBodyRect(rect);
18
18
  if (bodyRect.width <= 0 || bodyRect.height <= 0) {
19
19
  return;
@@ -1,3 +1,4 @@
1
+ import { stripAnsi } from "../../internal/strip-ansi.js";
1
2
  import { getExplorerStyles } from "../theme.js";
2
3
  import { fitToWidth, padEndCells } from "./text.js";
3
4
  export function renderModal(state, screen) {
@@ -12,7 +13,7 @@ export function renderModal(state, screen) {
12
13
  drawBox(screen, x, y, width, height, title(state), styles.borderFocused);
13
14
  const lines = modalLines(state);
14
15
  for (let row = 0; row < Math.min(lines.length, height - 2); row += 1) {
15
- screen.put(x + 2, y + 1 + row, fitToWidth(lines[row], width - 4, x + 2), row === 1 ? styles.accent : {});
16
+ screen.put(x + 2, y + 1 + row, fitToWidth(stripAnsi(lines[row]), width - 4, x + 2), row === 1 ? styles.accent : {});
16
17
  }
17
18
  }
18
19
  function modalLines(state) {
@@ -19,6 +19,8 @@ export { formatCommandNotFound } from "./components/command-errors.js";
19
19
  export { formatCommandNotFoundPanel } from "./components/command-errors.js";
20
20
  export { renderTable } from "./components/table.js";
21
21
  export type { TableColumn, RenderTableOptions } from "./components/table.js";
22
+ export { renderFileChanges } from "./components/file-changes.js";
23
+ export type { FileChange, FileChangeDisplayMode, FileChangeKind, FileChangeOutputFormat, RenderFileChangesOptions } from "./components/file-changes.js";
22
24
  export { renderCatalog } from "./components/catalog.js";
23
25
  export type { CatalogGroup, CatalogItem, CatalogMetric, CatalogTone, RenderCatalogOptions } from "./components/catalog.js";
24
26
  export { renderDetailCard } from "./components/detail-card.js";
@@ -15,6 +15,7 @@ export * as helpFormatterPlain from "./components/help-formatter-plain.js";
15
15
  export { formatCommandNotFound } from "./components/command-errors.js";
16
16
  export { formatCommandNotFoundPanel } from "./components/command-errors.js";
17
17
  export { renderTable } from "./components/table.js";
18
+ export { renderFileChanges } from "./components/file-changes.js";
18
19
  export { renderCatalog } from "./components/catalog.js";
19
20
  export { renderDetailCard } from "./components/detail-card.js";
20
21
  export { renderInspectorCard } from "./components/inspector-card.js";
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "toolcraft-design",
3
3
  "version": "0.0.2",
4
+ "private": true,
4
5
  "license": "MIT",
5
6
  "type": "module",
6
7
  "main": "dist/index.js",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "terminal-pilot",
3
- "version": "0.0.43",
3
+ "version": "0.0.45",
4
4
  "description": "Playwright-like SDK and CLI for automating interactive CLI apps through a real PTY",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",