toolcraft 0.0.231 → 0.0.233

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 (180) hide show
  1. package/composition.json +3 -3
  2. package/dist/applied-default.d.ts +3 -0
  3. package/dist/applied-default.js +16 -0
  4. package/dist/cli.js +508 -204
  5. package/dist/composition.json +3 -3
  6. package/dist/discriminator.d.ts +9 -0
  7. package/dist/discriminator.js +32 -0
  8. package/dist/error-report.d.ts +2 -18
  9. package/dist/error-report.js +12 -14
  10. package/dist/http.js +8 -1
  11. package/dist/human-in-loop/gate.js +6 -0
  12. package/dist/index.compile-check.js +22 -5
  13. package/dist/index.d.ts +12 -5
  14. package/dist/index.js +50 -0
  15. package/dist/json-schema-converter.d.ts +14 -6
  16. package/dist/json-schema-converter.js +150 -20
  17. package/dist/mcp-proxy-metadata.d.ts +2 -0
  18. package/dist/mcp-proxy-metadata.js +13 -0
  19. package/dist/mcp-proxy.d.ts +1 -0
  20. package/dist/mcp-proxy.js +65 -47
  21. package/dist/mcp-result.d.ts +3 -0
  22. package/dist/mcp-result.js +10 -0
  23. package/dist/mcp.js +211 -153
  24. package/dist/redaction.js +26 -17
  25. package/dist/renderer.js +40 -29
  26. package/dist/schema-member-names.d.ts +2 -0
  27. package/dist/schema-member-names.js +39 -0
  28. package/dist/schema-scope.d.ts +1 -0
  29. package/dist/schema-scope.js +29 -16
  30. package/dist/schema.d.ts +1 -1
  31. package/dist/sdk.d.ts +2 -1
  32. package/dist/sdk.js +164 -50
  33. package/dist/stream.js +24 -23
  34. package/dist/testing/diagnostic-value.d.ts +1 -0
  35. package/dist/testing/diagnostic-value.js +57 -0
  36. package/dist/testing/fakes.js +2 -1
  37. package/dist/testing/harness.js +1 -0
  38. package/dist/testing/memory-fs.js +42 -1
  39. package/dist/testing/parity.js +50 -6
  40. package/dist/union-validation.d.ts +5 -0
  41. package/dist/union-validation.js +36 -0
  42. package/node_modules/@poe-code/process-runner/dist/types.d.ts +2 -0
  43. package/node_modules/mcp-oauth-server/dist/index.js +47 -6
  44. package/node_modules/tiny-http-mcp-server/dist/cli.js +14 -2
  45. package/node_modules/tiny-http-mcp-server/dist/composition.json +1 -1
  46. package/node_modules/tiny-http-mcp-server/dist/http-server.d.ts +7 -5
  47. package/node_modules/tiny-http-mcp-server/dist/http-server.js +3 -11
  48. package/node_modules/tiny-http-mcp-server/dist/http-transport.d.ts +6 -1
  49. package/node_modules/tiny-http-mcp-server/dist/http-transport.js +196 -15
  50. package/node_modules/tiny-http-mcp-server/dist/index.d.ts +2 -2
  51. package/node_modules/tiny-http-mcp-server/dist/modern-headers.d.ts +3 -0
  52. package/node_modules/tiny-http-mcp-server/dist/modern-headers.js +35 -0
  53. package/node_modules/tiny-http-mcp-server/dist/parse-body.d.ts +1 -0
  54. package/node_modules/tiny-http-mcp-server/dist/parse-body.js +9 -5
  55. package/node_modules/tiny-http-mcp-server/dist/test-support.js +17 -0
  56. package/node_modules/tiny-http-mcp-server/dist/testing.d.ts +1 -1
  57. package/node_modules/tiny-http-mcp-server/dist/testing.js +3 -2
  58. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/default-oauth-client-provider.js +21 -4
  59. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/token-endpoint.d.ts +1 -1
  60. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/token-endpoint.js +14 -6
  61. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/http-fetch.d.ts +1 -0
  62. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/http-fetch.js +8 -0
  63. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/http-response.d.ts +1 -0
  64. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/http-response.js +50 -0
  65. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/index.d.ts +2 -0
  66. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/index.js +2 -0
  67. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/server/jwks-token-verifier.js +21 -3
  68. package/node_modules/tiny-http-mcp-server/package.json +1 -1
  69. package/node_modules/tiny-mcp-client/README.md +1 -0
  70. package/node_modules/tiny-mcp-client/dist/index.d.ts +178 -101
  71. package/node_modules/tiny-mcp-client/dist/index.js +4696 -291
  72. package/node_modules/tiny-mcp-client/package.json +2 -1
  73. package/node_modules/tiny-stdio-mcp-server/dist/base64.d.ts +1 -0
  74. package/node_modules/tiny-stdio-mcp-server/dist/base64.js +5 -0
  75. package/node_modules/tiny-stdio-mcp-server/dist/composition.json +1 -1
  76. package/node_modules/tiny-stdio-mcp-server/dist/content/audio.d.ts +3 -0
  77. package/node_modules/tiny-stdio-mcp-server/dist/content/convert.d.ts +5 -1
  78. package/node_modules/tiny-stdio-mcp-server/dist/content/convert.js +41 -6
  79. package/node_modules/tiny-stdio-mcp-server/dist/content/file.d.ts +5 -0
  80. package/node_modules/tiny-stdio-mcp-server/dist/content/image.d.ts +3 -0
  81. package/node_modules/tiny-stdio-mcp-server/dist/headers.d.ts +10 -0
  82. package/node_modules/tiny-stdio-mcp-server/dist/headers.js +180 -0
  83. package/node_modules/tiny-stdio-mcp-server/dist/index.d.ts +5 -5
  84. package/node_modules/tiny-stdio-mcp-server/dist/index.js +1 -1
  85. package/node_modules/tiny-stdio-mcp-server/dist/input-request-schema.d.ts +220 -0
  86. package/node_modules/tiny-stdio-mcp-server/dist/input-request-schema.js +1995 -0
  87. package/node_modules/tiny-stdio-mcp-server/dist/jsonrpc.d.ts +1 -0
  88. package/node_modules/tiny-stdio-mcp-server/dist/jsonrpc.js +38 -25
  89. package/node_modules/tiny-stdio-mcp-server/dist/metadata.d.ts +2 -0
  90. package/node_modules/tiny-stdio-mcp-server/dist/metadata.js +37 -0
  91. package/node_modules/tiny-stdio-mcp-server/dist/protocol-validation.d.ts +4 -0
  92. package/node_modules/tiny-stdio-mcp-server/dist/protocol-validation.js +53 -0
  93. package/node_modules/tiny-stdio-mcp-server/dist/protocol.d.ts +17 -0
  94. package/node_modules/tiny-stdio-mcp-server/dist/protocol.js +228 -0
  95. package/node_modules/tiny-stdio-mcp-server/dist/request-cancellation.d.ts +1 -0
  96. package/node_modules/tiny-stdio-mcp-server/dist/request-cancellation.js +18 -0
  97. package/node_modules/tiny-stdio-mcp-server/dist/sampling-messages.d.ts +18 -0
  98. package/node_modules/tiny-stdio-mcp-server/dist/sampling-messages.js +34 -0
  99. package/node_modules/tiny-stdio-mcp-server/dist/schema.d.ts +4 -1
  100. package/node_modules/tiny-stdio-mcp-server/dist/server.d.ts +9 -4
  101. package/node_modules/tiny-stdio-mcp-server/dist/server.js +334 -135
  102. package/node_modules/tiny-stdio-mcp-server/dist/stdio-input.d.ts +3 -0
  103. package/node_modules/tiny-stdio-mcp-server/dist/stdio-input.js +50 -28
  104. package/node_modules/tiny-stdio-mcp-server/dist/subscriptions.d.ts +11 -0
  105. package/node_modules/tiny-stdio-mcp-server/dist/subscriptions.js +128 -0
  106. package/node_modules/tiny-stdio-mcp-server/dist/types.d.ts +58 -18
  107. package/node_modules/tiny-stdio-mcp-server/dist/types.js +2 -1
  108. package/node_modules/tiny-stdio-mcp-server/dist/uri.d.ts +1 -0
  109. package/node_modules/tiny-stdio-mcp-server/dist/uri.js +41 -0
  110. package/node_modules/tiny-stdio-mcp-server/package.json +8 -0
  111. package/node_modules/toolcraft-design/dist/command-registry.d.ts +11 -0
  112. package/node_modules/toolcraft-design/dist/command-registry.js +24 -0
  113. package/node_modules/toolcraft-design/dist/dashboard/ansi.d.ts +2 -0
  114. package/node_modules/toolcraft-design/dist/dashboard/ansi.js +81 -27
  115. package/node_modules/toolcraft-design/dist/dashboard/buffer.js +39 -25
  116. package/node_modules/toolcraft-design/dist/dashboard/components/border.js +6 -5
  117. package/node_modules/toolcraft-design/dist/dashboard/components/footer.js +26 -21
  118. package/node_modules/toolcraft-design/dist/dashboard/components/output-pane.d.ts +1 -1
  119. package/node_modules/toolcraft-design/dist/dashboard/components/output-pane.js +12 -6
  120. package/node_modules/toolcraft-design/dist/dashboard/components/stats-pane.d.ts +2 -1
  121. package/node_modules/toolcraft-design/dist/dashboard/components/stats-pane.js +51 -10
  122. package/node_modules/toolcraft-design/dist/dashboard/dashboard.d.ts +4 -0
  123. package/node_modules/toolcraft-design/dist/dashboard/dashboard.js +77 -4
  124. package/node_modules/toolcraft-design/dist/dashboard/index.d.ts +2 -0
  125. package/node_modules/toolcraft-design/dist/dashboard/index.js +2 -0
  126. package/node_modules/toolcraft-design/dist/dashboard/keymap.js +26 -6
  127. package/node_modules/toolcraft-design/dist/dashboard/layout.d.ts +1 -0
  128. package/node_modules/toolcraft-design/dist/dashboard/layout.js +19 -3
  129. package/node_modules/toolcraft-design/dist/dashboard/line-buffer.d.ts +9 -0
  130. package/node_modules/toolcraft-design/dist/dashboard/line-buffer.js +63 -0
  131. package/node_modules/toolcraft-design/dist/dashboard/output-preview.d.ts +9 -0
  132. package/node_modules/toolcraft-design/dist/dashboard/output-preview.js +70 -0
  133. package/node_modules/toolcraft-design/dist/dashboard/snapshot.js +3 -1
  134. package/node_modules/toolcraft-design/dist/dashboard/store.js +9 -3
  135. package/node_modules/toolcraft-design/dist/dashboard/terminal-strings.d.ts +6 -0
  136. package/node_modules/toolcraft-design/dist/dashboard/terminal-strings.js +108 -0
  137. package/node_modules/toolcraft-design/dist/dashboard/terminal-width.js +3 -1
  138. package/node_modules/toolcraft-design/dist/dashboard/terminal.js +11 -1
  139. package/node_modules/toolcraft-design/dist/dashboard/testing/pipeline-scenario.d.ts +1 -0
  140. package/node_modules/toolcraft-design/dist/dashboard/testing/pipeline-scenario.js +126 -0
  141. package/node_modules/toolcraft-design/dist/dashboard/types.d.ts +3 -1
  142. package/node_modules/toolcraft-design/dist/event-groups.d.ts +19 -0
  143. package/node_modules/toolcraft-design/dist/event-groups.js +49 -0
  144. package/node_modules/toolcraft-design/dist/index.d.ts +6 -1
  145. package/node_modules/toolcraft-design/dist/index.js +5 -1
  146. package/node_modules/toolcraft-design/dist/inline-notice.d.ts +14 -0
  147. package/node_modules/toolcraft-design/dist/inline-notice.js +28 -0
  148. package/node_modules/toolcraft-design/dist/metric.d.ts +8 -0
  149. package/node_modules/toolcraft-design/dist/metric.js +21 -0
  150. package/node_modules/toolcraft-design/dist/overlay-manager.d.ts +6 -0
  151. package/node_modules/toolcraft-design/dist/overlay-manager.js +10 -0
  152. package/node_modules/toolcraft-design/dist/primitives.d.ts +12 -0
  153. package/node_modules/toolcraft-design/dist/primitives.js +7 -0
  154. package/node_modules/toolcraft-design/dist/progress-group.d.ts +7 -0
  155. package/node_modules/toolcraft-design/dist/progress-group.js +10 -0
  156. package/node_modules/toolcraft-design/dist/render-performance.d.ts +33 -0
  157. package/node_modules/toolcraft-design/dist/render-performance.js +71 -0
  158. package/node_modules/toolcraft-design/dist/task-tree.d.ts +22 -0
  159. package/node_modules/toolcraft-design/dist/task-tree.js +79 -0
  160. package/node_modules/toolcraft-design/dist/viewport.d.ts +18 -0
  161. package/node_modules/toolcraft-design/dist/viewport.js +56 -0
  162. package/node_modules/toolcraft-schema/dist/clone-default.d.ts +1 -0
  163. package/node_modules/toolcraft-schema/dist/clone-default.js +26 -0
  164. package/node_modules/toolcraft-schema/dist/index.compile-check.js +1 -0
  165. package/node_modules/toolcraft-schema/dist/index.d.ts +18 -9
  166. package/node_modules/toolcraft-schema/dist/index.js +46 -8
  167. package/node_modules/toolcraft-schema/dist/json-schema/compiler.d.ts +1 -0
  168. package/node_modules/toolcraft-schema/dist/json-schema/compiler.js +6 -0
  169. package/node_modules/toolcraft-schema/dist/json-schema/evaluate.js +6 -3
  170. package/node_modules/toolcraft-schema/dist/json-schema/normalize-nullability.d.ts +1 -0
  171. package/node_modules/toolcraft-schema/dist/json-schema/normalize-nullability.js +69 -0
  172. package/node_modules/toolcraft-schema/dist/json-schema/types.d.ts +1 -0
  173. package/node_modules/toolcraft-schema/dist/json.d.ts +4 -1
  174. package/node_modules/toolcraft-schema/dist/json.js +57 -1
  175. package/node_modules/toolcraft-schema/dist/native-json-schema.d.ts +10 -0
  176. package/node_modules/toolcraft-schema/dist/native-json-schema.js +11 -0
  177. package/node_modules/toolcraft-schema/dist/validate.d.ts +5 -1
  178. package/node_modules/toolcraft-schema/dist/validate.js +61 -47
  179. package/node_modules/toolcraft-schema/package.json +1 -1
  180. package/package.json +6 -3
@@ -1,10 +1,11 @@
1
+ import { createRenderPerformanceMonitor, formatRenderPerformance } from "../render-performance.js";
1
2
  import { createLogger } from "../components/logger.js";
2
3
  import { resolveOutputFormat } from "../internal/output-format.js";
3
4
  import { ScreenBuffer, diff } from "./buffer.js";
4
5
  import { renderBorder } from "./components/border.js";
5
6
  import { defaultHints, renderFooter } from "./components/footer.js";
6
7
  import { renderOutputPane } from "./components/output-pane.js";
7
- import { renderStatsPane } from "./components/stats-pane.js";
8
+ import { renderCompactStatsPane, renderStatsPane } from "./components/stats-pane.js";
8
9
  import { createKeymap } from "./keymap.js";
9
10
  import { computeDashboardLayout } from "./layout.js";
10
11
  import { createStore } from "./store.js";
@@ -32,6 +33,11 @@ export function createDashboard(opts = {}) {
32
33
  let unsubscribeResize;
33
34
  let started = false;
34
35
  let destroyed = false;
36
+ const performanceMonitor = createRenderPerformanceMonitor();
37
+ let showPerformance = false;
38
+ let scrollOffset = 0;
39
+ let heldOutput;
40
+ let renderTimer;
35
41
  function appendOutput(item) {
36
42
  if (destroyed) {
37
43
  return;
@@ -61,21 +67,74 @@ export function createDashboard(opts = {}) {
61
67
  driver.hideCursor();
62
68
  render();
63
69
  const activeStore = getStore();
70
+ let previousStats = activeStore.getState().stats;
71
+ let lastStatsPaint = -Infinity;
64
72
  unsubscribeStore = activeStore.onChange(() => {
65
- render();
73
+ performanceMonitor.request("update");
74
+ const stats = activeStore.getState().stats;
75
+ if (stats !== previousStats) {
76
+ const statusChanged = stats.status !== previousStats.status;
77
+ previousStats = stats;
78
+ if (statusChanged || Date.now() - lastStatsPaint >= 16) {
79
+ lastStatsPaint = Date.now();
80
+ render();
81
+ }
82
+ else {
83
+ renderTimer ??= setTimeout(render, 16);
84
+ }
85
+ }
86
+ else if (heldOutput === undefined && renderTimer === undefined) {
87
+ renderTimer = setTimeout(render, 16);
88
+ }
66
89
  });
67
90
  unsubscribeKeypress = driver.onKeypress((event) => {
68
91
  const command = resolveCommand(event);
69
92
  if (command === undefined) {
70
93
  return;
71
94
  }
95
+ performanceMonitor.request("input");
96
+ if (command === "render-stats") {
97
+ showPerformance = !showPerformance;
98
+ render();
99
+ return;
100
+ }
101
+ if (command === "follow") {
102
+ heldOutput = undefined;
103
+ scrollOffset = 0;
104
+ render();
105
+ return;
106
+ }
107
+ if (command === "scroll-up" ||
108
+ command === "scroll-down" ||
109
+ command === "page-up" ||
110
+ command === "page-down") {
111
+ const page = Math.max(1, computeDashboardLayout({
112
+ totalWidth: driver.getSize().cols,
113
+ totalHeight: driver.getSize().rows,
114
+ rightPaneWidth
115
+ }).leftPane.height);
116
+ const amount = command === "page-up" || command === "page-down" ? page : 1;
117
+ const direction = command === "scroll-up" || command === "page-up" ? 1 : -1;
118
+ if (direction > 0)
119
+ heldOutput ??= getStore().getState().output;
120
+ scrollOffset = Math.max(0, scrollOffset + amount * direction);
121
+ if (scrollOffset === 0)
122
+ heldOutput = undefined;
123
+ renderTimer ??= setTimeout(render, 16);
124
+ return;
125
+ }
72
126
  emitCommand(command);
73
127
  });
74
128
  unsubscribeResize = driver.onResize(() => {
129
+ performanceMonitor.request("resize");
130
+ previousBuffer = new ScreenBuffer(0, 0);
131
+ driver?.write("\u001b[2J");
75
132
  render();
76
133
  });
77
134
  }
78
135
  function stop() {
136
+ clearTimeout(renderTimer);
137
+ renderTimer = undefined;
79
138
  unsubscribeStore?.();
80
139
  unsubscribeKeypress?.();
81
140
  unsubscribeResize?.();
@@ -111,9 +170,12 @@ export function createDashboard(opts = {}) {
111
170
  return store;
112
171
  }
113
172
  function render() {
173
+ clearTimeout(renderTimer);
174
+ renderTimer = undefined;
114
175
  if (driver === undefined) {
115
176
  return;
116
177
  }
178
+ const startedAt = performanceMonitor.begin();
117
179
  const { cols, rows } = driver.getSize();
118
180
  const layout = computeDashboardLayout({
119
181
  totalWidth: cols,
@@ -127,10 +189,20 @@ export function createDashboard(opts = {}) {
127
189
  rightTitle: statsTitle,
128
190
  style: { dim: true }
129
191
  });
130
- renderOutputPane(nextBuffer, layout.leftPane, state.output);
192
+ scrollOffset = renderOutputPane(nextBuffer, { ...layout.leftPane, height: Math.max(0, layout.leftPane.height - (showPerformance ? 1 : 0)) }, heldOutput ?? state.output, scrollOffset);
193
+ if (scrollOffset === 0)
194
+ heldOutput = undefined;
131
195
  renderStatsPane(nextBuffer, layout.rightPane, state.stats);
196
+ if (layout.summary)
197
+ renderCompactStatsPane(nextBuffer, layout.summary, state.stats);
132
198
  renderFooter(nextBuffer, layout.footer, footerHints);
133
- driver.flush(diff(previousBuffer, nextBuffer));
199
+ if (showPerformance && layout.leftPane.height > 0) {
200
+ nextBuffer.putInRect(layout.leftPane, layout.leftPane.height - 1, formatRenderPerformance(performanceMonitor.snapshot(), layout.leftPane.width), { dim: true });
201
+ }
202
+ const changes = diff(previousBuffer, nextBuffer);
203
+ driver.flush(changes);
204
+ performanceMonitor.end(startedAt, { changedCells: changes.length });
205
+ opts.onPerformance?.(performanceMonitor.snapshot());
134
206
  previousBuffer = nextBuffer;
135
207
  }
136
208
  function emitCommand(command) {
@@ -159,6 +231,7 @@ export function createDashboard(opts = {}) {
159
231
  appendOutput,
160
232
  updateStats,
161
233
  onCommand,
234
+ getPerformance: performanceMonitor.snapshot,
162
235
  destroy
163
236
  };
164
237
  }
@@ -1,4 +1,6 @@
1
1
  export { createDashboard } from "./dashboard.js";
2
+ export { createDashboardLineBuffer, createStreamingDashboardLineBuffer } from "./line-buffer.js";
3
+ export { limitOutputPreview, createOutputPreviewBuffer } from "./output-preview.js";
2
4
  export { shouldUseInteractiveDashboard } from "./should-use-dashboard.js";
3
5
  export type { Dashboard, DashboardOptions } from "./dashboard.js";
4
6
  export { renderDashboardSnapshot } from "./snapshot.js";
@@ -1,4 +1,6 @@
1
1
  export { createDashboard } from "./dashboard.js";
2
+ export { createDashboardLineBuffer, createStreamingDashboardLineBuffer } from "./line-buffer.js";
3
+ export { limitOutputPreview, createOutputPreviewBuffer } from "./output-preview.js";
2
4
  export { shouldUseInteractiveDashboard } from "./should-use-dashboard.js";
3
5
  export { renderDashboardSnapshot } from "./snapshot.js";
4
6
  export { defaultHints } from "./components/footer.js";
@@ -1,11 +1,30 @@
1
- const commands = ["forceQuit", "quit", "edit", "pause", "retry", "view-log"];
1
+ const commands = [
2
+ "forceQuit",
3
+ "quit",
4
+ "edit",
5
+ "pause",
6
+ "retry",
7
+ "view-log",
8
+ "scroll-up",
9
+ "scroll-down",
10
+ "page-up",
11
+ "page-down",
12
+ "follow",
13
+ "render-stats"
14
+ ];
2
15
  const defaultBindings = {
3
16
  forceQuit: ["Ctrl+C"],
4
17
  quit: ["q"],
5
18
  edit: ["e"],
6
19
  pause: ["p"],
7
20
  retry: ["r"],
8
- "view-log": ["l"]
21
+ "view-log": ["l"],
22
+ "scroll-up": ["up"],
23
+ "scroll-down": ["down"],
24
+ "page-up": ["pageup"],
25
+ "page-down": ["pagedown"],
26
+ follow: ["f", "F", "end"],
27
+ "render-stats": ["Ctrl+g"]
9
28
  };
10
29
  export function createKeymap(overrides, options) {
11
30
  const resolvedCommands = options?.commands ?? commands;
@@ -85,7 +104,10 @@ function parseBinding(binding) {
85
104
  if (value.length === 0) {
86
105
  return undefined;
87
106
  }
88
- const parts = value.split("+").map((part) => part.trim()).filter(Boolean);
107
+ const parts = value
108
+ .split("+")
109
+ .map((part) => part.trim())
110
+ .filter(Boolean);
89
111
  if (parts.length === 0) {
90
112
  return undefined;
91
113
  }
@@ -146,9 +168,7 @@ function matchesSingleKey(binding, event) {
146
168
  if (binding.sequence !== undefined) {
147
169
  return false;
148
170
  }
149
- if (binding.ctrl !== event.ctrl ||
150
- binding.meta !== event.meta ||
151
- binding.shift !== event.shift) {
171
+ if (binding.ctrl !== event.ctrl || binding.meta !== event.meta || binding.shift !== event.shift) {
152
172
  return false;
153
173
  }
154
174
  if (binding.ch !== undefined) {
@@ -7,6 +7,7 @@ export type LayoutOptions = {
7
7
  borderWidth?: number;
8
8
  };
9
9
  export type DashboardLayout = {
10
+ summary?: Rect;
10
11
  outerBorder: Rect;
11
12
  leftPane: Rect;
12
13
  rightPane: Rect;
@@ -2,6 +2,7 @@ const DEFAULT_RIGHT_PANE_WIDTH = 25;
2
2
  const DEFAULT_FOOTER_HEIGHT = 1;
3
3
  const DEFAULT_BORDER_WIDTH = 1;
4
4
  const MIN_LEFT_PANE_WIDTH = 20;
5
+ const MIN_READABLE_OUTPUT_WIDTH = 40;
5
6
  export function computeDashboardLayout(opts) {
6
7
  const totalWidth = normalizeSize(opts.totalWidth);
7
8
  const totalHeight = normalizeSize(opts.totalHeight);
@@ -11,8 +12,8 @@ export function computeDashboardLayout(opts) {
11
12
  const maxX = Math.max(0, totalWidth - 1);
12
13
  const maxY = Math.max(0, totalHeight - 1);
13
14
  const outerBorder = { x: 0, y: 0, width: totalWidth, height: totalHeight };
14
- const innerWidth = Math.max(0, totalWidth - (borderWidth * 2));
15
- const innerHeight = Math.max(0, totalHeight - (borderWidth * 2));
15
+ const innerWidth = Math.max(0, totalWidth - borderWidth * 2);
16
+ const innerHeight = Math.max(0, totalHeight - borderWidth * 2);
16
17
  const innerX = clampCoordinate(borderWidth, maxX);
17
18
  const innerY = clampCoordinate(borderWidth, maxY);
18
19
  const dividerWidth = innerWidth > 0 ? 1 : 0;
@@ -41,7 +42,7 @@ export function computeDashboardLayout(opts) {
41
42
  const footerY = clampCoordinate(footerDividerY + footerDividerHeight, maxY);
42
43
  const footerDividerLeft = innerX;
43
44
  const footerDividerRight = clampCoordinate(Math.max(innerX, totalWidth - borderWidth - 1), maxX);
44
- return {
45
+ const layout = {
45
46
  outerBorder,
46
47
  leftPane,
47
48
  rightPane,
@@ -62,6 +63,21 @@ export function computeDashboardLayout(opts) {
62
63
  right: footerDividerRight
63
64
  }
64
65
  };
66
+ if (innerWidth > 0 &&
67
+ requestedRightPaneWidth > 0 &&
68
+ availablePaneWidth < MIN_READABLE_OUTPUT_WIDTH + requestedRightPaneWidth) {
69
+ const summaryHeight = Math.min(2, contentHeight);
70
+ layout.summary = { x: innerX, y: innerY, width: innerWidth, height: summaryHeight };
71
+ layout.leftPane = {
72
+ x: innerX,
73
+ y: innerY + summaryHeight,
74
+ width: innerWidth,
75
+ height: Math.max(0, contentHeight - summaryHeight)
76
+ };
77
+ layout.rightPane = { x: maxX, y: innerY, width: 0, height: 0 };
78
+ layout.divider = { x: maxX, top: innerY, bottom: innerY };
79
+ }
80
+ return layout;
65
81
  }
66
82
  function computeLeftPaneWidth(availablePaneWidth, requestedRightPaneWidth) {
67
83
  if (availablePaneWidth <= 0) {
@@ -0,0 +1,9 @@
1
+ export declare function createDashboardLineBuffer(emit: (line: string) => void): {
2
+ push(chunk: string): void;
3
+ flush(): void;
4
+ preview(): string;
5
+ };
6
+ export declare function createStreamingDashboardLineBuffer(emit: (line: string, id: string) => void): {
7
+ push(chunk: string): void;
8
+ flush(): void;
9
+ };
@@ -0,0 +1,63 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { limitOutputPreview, MAX_OUTPUT_PREVIEW_CHARS, OUTPUT_TRUNCATION_NOTICE, retainOutputTail } from "./output-preview.js";
3
+ import { createTerminalStringFilter } from "./terminal-strings.js";
4
+ export function createDashboardLineBuffer(emit) {
5
+ let pending = "";
6
+ let omitted = false;
7
+ const strings = createTerminalStringFilter();
8
+ return {
9
+ preview() {
10
+ const line = pending.endsWith("\r") ? pending.slice(0, -1) : pending;
11
+ return (omitted ? OUTPUT_TRUNCATION_NOTICE : "") + line;
12
+ },
13
+ push(chunk) {
14
+ const text = pending + strings.push(chunk);
15
+ let start = 0;
16
+ let newline = text.indexOf("\n");
17
+ while (newline !== -1) {
18
+ const raw = text.slice(start, newline);
19
+ const line = raw.endsWith("\r") ? raw.slice(0, -1) : raw;
20
+ emit(limitOutputPreview((omitted ? OUTPUT_TRUNCATION_NOTICE : "") + line));
21
+ omitted = false;
22
+ start = newline + 1;
23
+ newline = text.indexOf("\n", start);
24
+ }
25
+ const remaining = text.slice(start);
26
+ if (remaining.length > MAX_OUTPUT_PREVIEW_CHARS)
27
+ omitted = true;
28
+ const budget = MAX_OUTPUT_PREVIEW_CHARS - (omitted ? OUTPUT_TRUNCATION_NOTICE.length : 0);
29
+ pending = retainOutputTail(remaining, budget);
30
+ },
31
+ flush() {
32
+ if (pending.length === 0)
33
+ return;
34
+ const line = pending.endsWith("\r") ? pending.slice(0, -1) : pending;
35
+ emit((omitted ? OUTPUT_TRUNCATION_NOTICE : "") + line);
36
+ pending = "";
37
+ omitted = false;
38
+ }
39
+ };
40
+ }
41
+ export function createStreamingDashboardLineBuffer(emit) {
42
+ let id = randomUUID();
43
+ let lastPreview = "";
44
+ const lines = createDashboardLineBuffer(line => {
45
+ if (line.length === 0 || line !== lastPreview)
46
+ emit(line, id);
47
+ id = randomUUID();
48
+ lastPreview = "";
49
+ });
50
+ return {
51
+ push(chunk) {
52
+ if (chunk.length === 0)
53
+ return;
54
+ lines.push(chunk);
55
+ const preview = lines.preview();
56
+ if (preview.length > 0 && preview !== lastPreview) {
57
+ lastPreview = preview;
58
+ emit(preview, id);
59
+ }
60
+ },
61
+ flush: lines.flush
62
+ };
63
+ }
@@ -0,0 +1,9 @@
1
+ export declare const MAX_OUTPUT_PREVIEW_CHARS = 16384;
2
+ export declare const OUTPUT_TRUNCATION_NOTICE = "[Output truncated: showing latest text]\n";
3
+ export declare function retainOutputTail(text: string, maxChars: number): string;
4
+ export declare function limitOutputPreview(text: string): string;
5
+ /** Keep live deltas bounded without rebuilding the complete preview for every delta. */
6
+ export declare function createOutputPreviewBuffer(): {
7
+ push(text: string): void;
8
+ text(): string;
9
+ };
@@ -0,0 +1,70 @@
1
+ import { Buffer } from "node:buffer";
2
+ import { createTerminalStringFilter, terminalControlTailStart } from "./terminal-strings.js";
3
+ export const MAX_OUTPUT_PREVIEW_CHARS = 16_384;
4
+ export const OUTPUT_TRUNCATION_NOTICE = "[Output truncated: showing latest text]\n";
5
+ export function retainOutputTail(text, maxChars) {
6
+ let start = Math.max(0, text.length - maxChars);
7
+ if (start > 0) {
8
+ const newline = text.indexOf("\n", start);
9
+ if (newline !== -1 && newline < text.length - 1)
10
+ start = newline + 1;
11
+ start = terminalControlTailStart(text, start);
12
+ const firstCodeUnit = text.charCodeAt(start);
13
+ if (firstCodeUnit >= 0xdc00 && firstCodeUnit <= 0xdfff)
14
+ start += 1;
15
+ }
16
+ // Materialize the bounded tail rather than retaining a large substring backing store.
17
+ // UTF-16 preserves lone surrogate code units as well as complete Unicode text.
18
+ return Buffer.from(text.slice(start), "utf16le").toString("utf16le");
19
+ }
20
+ export function limitOutputPreview(text) {
21
+ text = createTerminalStringFilter().push(text);
22
+ if (text.length <= MAX_OUTPUT_PREVIEW_CHARS)
23
+ return text;
24
+ return (OUTPUT_TRUNCATION_NOTICE +
25
+ retainOutputTail(text, MAX_OUTPUT_PREVIEW_CHARS - OUTPUT_TRUNCATION_NOTICE.length));
26
+ }
27
+ /** Keep live deltas bounded without rebuilding the complete preview for every delta. */
28
+ export function createOutputPreviewBuffer() {
29
+ const strings = createTerminalStringFilter();
30
+ const chunks = [];
31
+ let chars = 0;
32
+ let omitted = false;
33
+ const tailBudget = MAX_OUTPUT_PREVIEW_CHARS - OUTPUT_TRUNCATION_NOTICE.length + 1;
34
+ return {
35
+ push(text) {
36
+ text = strings.push(text);
37
+ if (text.length === 0)
38
+ return;
39
+ if (text.length > MAX_OUTPUT_PREVIEW_CHARS) {
40
+ chunks.length = 0;
41
+ chars = 0;
42
+ omitted = true;
43
+ text = retainOutputTail(text, tailBudget);
44
+ }
45
+ chunks.push(text);
46
+ chars += text.length;
47
+ if (chars > MAX_OUTPUT_PREVIEW_CHARS)
48
+ omitted = true;
49
+ if (!omitted)
50
+ return;
51
+ while (chars > tailBudget) {
52
+ const first = chunks[0];
53
+ const excess = chars - tailBudget;
54
+ if (first.length <= excess) {
55
+ chars -= first.length;
56
+ chunks.shift();
57
+ }
58
+ else {
59
+ const start = terminalControlTailStart(first, excess);
60
+ chunks[0] = first.slice(start);
61
+ chars -= start;
62
+ }
63
+ }
64
+ },
65
+ text() {
66
+ const text = chunks.join("");
67
+ return limitOutputPreview((omitted ? OUTPUT_TRUNCATION_NOTICE : "") + text);
68
+ }
69
+ };
70
+ }
@@ -2,7 +2,7 @@ import { ScreenBuffer, cellToAnsi } from "./buffer.js";
2
2
  import { renderBorder } from "./components/border.js";
3
3
  import { defaultHints, renderFooter } from "./components/footer.js";
4
4
  import { renderOutputPane } from "./components/output-pane.js";
5
- import { renderStatsPane } from "./components/stats-pane.js";
5
+ import { renderCompactStatsPane, renderStatsPane } from "./components/stats-pane.js";
6
6
  import { computeDashboardLayout } from "./layout.js";
7
7
  const SNAPSHOT_WIDTH = 80;
8
8
  const SNAPSHOT_HEIGHT = 20;
@@ -27,6 +27,8 @@ export function renderDashboardSnapshot(opts = {}) {
27
27
  });
28
28
  renderOutputPane(buffer, layout.leftPane, items);
29
29
  renderStatsPane(buffer, layout.rightPane, stats);
30
+ if (layout.summary)
31
+ renderCompactStatsPane(buffer, layout.summary, stats);
30
32
  renderFooter(buffer, layout.footer, defaultHints());
31
33
  return bufferToAnsi(buffer);
32
34
  }
@@ -1,3 +1,4 @@
1
+ import { limitOutputPreview } from "./output-preview.js";
1
2
  const MAX_RETAINED_OUTPUT = 256;
2
3
  export function createStore() {
3
4
  let state = {
@@ -20,9 +21,14 @@ export function createStore() {
20
21
  return state;
21
22
  }
22
23
  function appendOutput(item) {
23
- const next = state.output.length >= MAX_RETAINED_OUTPUT
24
- ? [...state.output.slice(state.output.length - MAX_RETAINED_OUTPUT + 1), item]
25
- : [...state.output, item];
24
+ const preview = limitOutputPreview(item.text);
25
+ const retainedItem = preview === item.text ? item : { ...item, text: preview };
26
+ const existing = item.id === undefined ? -1 : state.output.findIndex((entry) => entry.id === item.id);
27
+ const next = existing !== -1
28
+ ? state.output.map((entry, index) => index === existing ? retainedItem : entry)
29
+ : state.output.length >= MAX_RETAINED_OUTPUT
30
+ ? [...state.output.slice(state.output.length - MAX_RETAINED_OUTPUT + 1), retainedItem]
31
+ : [...state.output, retainedItem];
26
32
  state = { ...state, output: next };
27
33
  notify();
28
34
  }
@@ -0,0 +1,6 @@
1
+ /** Remove nonprinting strings and retain complete CSI controls before presentation trims them. */
2
+ export declare function createTerminalStringFilter(): {
3
+ push(text: string): string;
4
+ };
5
+ /** Advance a tail boundary past a control if it would retain only its parameters. */
6
+ export declare function terminalControlTailStart(text: string, start: number): number;
@@ -0,0 +1,108 @@
1
+ // Normal SGR/cursor sequences are short; malformed controls must not grow retained state.
2
+ const MAX_CONTROL_SEQUENCE_CHARS = 1_024;
3
+ /** Remove nonprinting strings and retain complete CSI controls before presentation trims them. */
4
+ export function createTerminalStringFilter() {
5
+ let hidden = false;
6
+ let allowBell = false;
7
+ let escape = false;
8
+ let csi;
9
+ let oversizedCsi = false;
10
+ return {
11
+ push(text) {
12
+ if (!hidden && !escape && csi === undefined && !text.includes("\u001b") && !text.includes("\u009b") && !text.includes("\u0090") &&
13
+ !text.includes("\u0098") && !text.includes("\u009d") && !text.includes("\u009e") && !text.includes("\u009f"))
14
+ return text;
15
+ let output = "";
16
+ for (const ch of text) {
17
+ if ((ch === "\u0018" || ch === "\u001a") && (hidden || escape || csi !== undefined)) {
18
+ hidden = false;
19
+ escape = false;
20
+ csi = undefined;
21
+ oversizedCsi = false;
22
+ continue;
23
+ }
24
+ if (csi !== undefined) {
25
+ if (ch === "\u001b") {
26
+ csi = undefined;
27
+ oversizedCsi = false;
28
+ escape = true;
29
+ continue;
30
+ }
31
+ if (!oversizedCsi) {
32
+ csi += ch;
33
+ if (csi.length > MAX_CONTROL_SEQUENCE_CHARS) {
34
+ oversizedCsi = true;
35
+ csi = "";
36
+ }
37
+ }
38
+ const code = ch.charCodeAt(0);
39
+ if (code >= 0x40 && code <= 0x7e) {
40
+ if (!oversizedCsi)
41
+ output += csi;
42
+ csi = undefined;
43
+ oversizedCsi = false;
44
+ }
45
+ continue;
46
+ }
47
+ if (hidden) {
48
+ if (ch === "\u009c" || (allowBell && ch === "\u0007") || (escape && ch === "\\")) {
49
+ hidden = false;
50
+ escape = false;
51
+ }
52
+ else
53
+ escape = ch === "\u001b";
54
+ continue;
55
+ }
56
+ if (escape) {
57
+ escape = false;
58
+ if (ch === "[") {
59
+ csi = "\u001b[";
60
+ continue;
61
+ }
62
+ if (ch === "]" || ch === "P" || ch === "X" || ch === "^" || ch === "_") {
63
+ hidden = true;
64
+ allowBell = ch === "]";
65
+ continue;
66
+ }
67
+ output += "\u001b";
68
+ }
69
+ if (ch === "\u001b")
70
+ escape = true;
71
+ else if (ch === "\u009b")
72
+ csi = ch;
73
+ else if (ch === "\u0090" || ch === "\u0098" || ch === "\u009d" || ch === "\u009e" || ch === "\u009f") {
74
+ hidden = true;
75
+ allowBell = ch === "\u009d";
76
+ }
77
+ else
78
+ output += ch;
79
+ }
80
+ return output;
81
+ }
82
+ };
83
+ }
84
+ /** Advance a tail boundary past a control if it would retain only its parameters. */
85
+ export function terminalControlTailStart(text, start) {
86
+ if (!text.includes("\u001b") && !text.includes("\u009b"))
87
+ return start;
88
+ for (let index = 0; index < start; index++) {
89
+ const ch = text[index];
90
+ if (ch !== "\u001b" && ch !== "\u009b")
91
+ continue;
92
+ let end = index + 1;
93
+ if (ch === "\u009b" || text[end] === "[") {
94
+ if (ch === "\u001b")
95
+ end++;
96
+ while (end < text.length) {
97
+ const code = text.charCodeAt(end);
98
+ if (code >= 0x40 && code <= 0x7e)
99
+ break;
100
+ end++;
101
+ }
102
+ }
103
+ if (start <= end)
104
+ return Math.min(end + 1, text.length);
105
+ index = end;
106
+ }
107
+ return start;
108
+ }
@@ -2,7 +2,7 @@ const graphemeSegmenter = new Intl.Segmenter(undefined, { granularity: "grapheme
2
2
  export function graphemes(value) {
3
3
  for (let index = 0; index < value.length; index += 1) {
4
4
  const code = value.charCodeAt(index);
5
- if (code < 0x20 || code > 0x7e) {
5
+ if ((code < 0x20 || code > 0x7e) && (code < 0x4e00 || code > 0x9fff)) {
6
6
  return Array.from(graphemeSegmenter.segment(value), ({ segment }) => segment);
7
7
  }
8
8
  }
@@ -20,6 +20,8 @@ export function displayWidth(value, startColumn = 0) {
20
20
  return column - startColumn;
21
21
  }
22
22
  export function expandTabs(value, startColumn = 0) {
23
+ if (!value.includes("\t"))
24
+ return value;
23
25
  let column = startColumn;
24
26
  let expanded = "";
25
27
  for (const segment of graphemes(value)) {
@@ -1,6 +1,7 @@
1
1
  import readline from "node:readline";
2
2
  import { PassThrough } from "node:stream";
3
3
  import { cellToAnsi } from "./buffer.js";
4
+ import { graphemeWidth } from "./terminal-width.js";
4
5
  export function createTerminalDriver(opts) {
5
6
  const stdin = (opts?.stdin ?? process.stdin);
6
7
  const stdout = (opts?.stdout ?? process.stdout);
@@ -87,8 +88,17 @@ export function createTerminalDriver(opts) {
87
88
  return;
88
89
  }
89
90
  let output = "";
91
+ let cursorX;
92
+ let cursorY;
90
93
  for (const change of changes) {
91
- output += `${cursorPositionAnsi(change.x, change.y)}${cellToAnsi(change.cell)}`;
94
+ if (change.cell.ch.length === 0)
95
+ continue;
96
+ if (change.x !== cursorX || change.y !== cursorY) {
97
+ output += cursorPositionAnsi(change.x, change.y);
98
+ }
99
+ output += cellToAnsi(change.cell);
100
+ cursorX = change.x + graphemeWidth(change.cell.ch);
101
+ cursorY = change.y;
92
102
  }
93
103
  write(output);
94
104
  }