toolcraft 0.0.232 → 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,4 +1,16 @@
1
+ import { graphemes, graphemeWidth } from "./terminal-width.js";
2
+ import { createTerminalStringFilter } from "./terminal-strings.js";
1
3
  const ESC = "\u001b";
4
+ /** Visible text for a plain screen row; styles are supplied by the caller. */
5
+ export function plainTerminalText(text) {
6
+ for (let index = 0; index < text.length; index++) {
7
+ const code = text.charCodeAt(index);
8
+ if ((code < 0x20 && code !== 0x09) || (code >= 0x7f && code <= 0x9f)) {
9
+ return parseAnsi(text).map(line => line.segments.map(segment => segment.text).join("")).join(" ");
10
+ }
11
+ }
12
+ return text;
13
+ }
2
14
  export function hasAnsi(text) {
3
15
  return text.includes(ESC);
4
16
  }
@@ -11,12 +23,38 @@ export function hasAnsi(text) {
11
23
  * `baseStyle` is used as the initial style and as the restore target for SGR reset / default color.
12
24
  */
13
25
  export function parseAnsi(text, baseStyle) {
26
+ text = createTerminalStringFilter().push(text);
14
27
  const base = normalizeStyle(baseStyle);
15
28
  let style = { ...base };
16
29
  let concealed = false;
17
30
  const lines = [];
18
31
  let cells = [];
19
32
  let column = 0;
33
+ const clearCell = (position) => {
34
+ if (cells[position]?.ch === "" && position > 0) {
35
+ cells[position - 1] = { ch: " ", style: { ...style } };
36
+ }
37
+ if (cells[position + 1]?.ch === "") {
38
+ cells[position + 1] = { ch: " ", style: { ...style } };
39
+ }
40
+ cells[position] = undefined;
41
+ };
42
+ const writeGrapheme = (ch) => {
43
+ const width = graphemeWidth(ch);
44
+ if (width === 0) {
45
+ const previous = cells[column - 1]?.ch === "" ? column - 2 : column - 1;
46
+ if (!concealed && cells[previous])
47
+ cells[previous].ch += ch;
48
+ return;
49
+ }
50
+ clearCell(column);
51
+ if (width === 2)
52
+ clearCell(column + 1);
53
+ cells[column] = { ch: concealed ? " " : ch, style: { ...style } };
54
+ if (width === 2)
55
+ cells[column + 1] = { ch: concealed ? " " : "", style: { ...style } };
56
+ column += width;
57
+ };
20
58
  const finishLine = () => {
21
59
  lines.push({ segments: cellsToSegments(cells) });
22
60
  cells = [];
@@ -25,8 +63,8 @@ export function parseAnsi(text, baseStyle) {
25
63
  let index = 0;
26
64
  while (index < text.length) {
27
65
  const ch = text[index];
28
- if (ch === ESC && text[index + 1] === "[") {
29
- const paramsStart = index + 2;
66
+ if ((ch === ESC && text[index + 1] === "[") || ch === "\u009b") {
67
+ const paramsStart = index + (ch === ESC ? 2 : 1);
30
68
  let cursor = paramsStart;
31
69
  while (cursor < text.length && !isCsiFinalByte(text[cursor])) {
32
70
  cursor += 1;
@@ -42,18 +80,30 @@ export function parseAnsi(text, baseStyle) {
42
80
  style = sgr.style;
43
81
  concealed = sgr.concealed;
44
82
  }
45
- else if (finalByte === "K" && parseParams(params)[0] === 2) {
46
- cells = [];
83
+ else if (finalByte === "K") {
84
+ const mode = parseParams(params)[0];
85
+ if (mode === 0) {
86
+ clearCell(column);
87
+ cells.length = Math.min(cells.length, column);
88
+ }
89
+ else if (mode === 1) {
90
+ const end = Math.min(column, cells.length - 1);
91
+ for (let position = 0; position <= end; position += 1)
92
+ clearCell(position);
93
+ }
94
+ else if (mode === 2) {
95
+ cells = [];
96
+ }
47
97
  }
48
98
  index = cursor + 1;
49
99
  continue;
50
100
  }
101
+ if (ch === "\u0085") {
102
+ finishLine();
103
+ index += 1;
104
+ continue;
105
+ }
51
106
  if (ch === ESC) {
52
- const next = text[index + 1];
53
- if (next === "]" || next === "P" || next === "X" || next === "^" || next === "_") {
54
- index = skipStringTerminated(text, index + 2);
55
- continue;
56
- }
57
107
  index += 2;
58
108
  continue;
59
109
  }
@@ -72,14 +122,32 @@ export function parseAnsi(text, baseStyle) {
72
122
  index += 1;
73
123
  continue;
74
124
  }
125
+ if (ch === "\t") {
126
+ const spaces = 8 - (column % 8);
127
+ for (let offset = 0; offset < spaces; offset++) {
128
+ if (cells[column] === undefined)
129
+ writeGrapheme(" ");
130
+ else
131
+ column += 1;
132
+ }
133
+ index += 1;
134
+ continue;
135
+ }
75
136
  const code = ch.charCodeAt(0);
76
- if (code < 0x20 && ch !== "\t") {
137
+ if (code < 0x20 || (code >= 0x7f && code <= 0x9f)) {
77
138
  index += 1;
78
139
  continue;
79
140
  }
80
- cells[column] = { ch: concealed ? " " : ch, style: { ...style } };
81
- column += 1;
82
- index += 1;
141
+ let end = index + 1;
142
+ while (end < text.length) {
143
+ const next = text.charCodeAt(end);
144
+ if (next < 0x20 || (next >= 0x7f && next <= 0x9f))
145
+ break;
146
+ end += 1;
147
+ }
148
+ for (const grapheme of graphemes(text.slice(index, end)))
149
+ writeGrapheme(grapheme);
150
+ index = end;
83
151
  }
84
152
  finishLine();
85
153
  return lines;
@@ -102,20 +170,6 @@ function isCsiFinalByte(ch) {
102
170
  const code = ch.charCodeAt(0);
103
171
  return code >= 0x40 && code <= 0x7e;
104
172
  }
105
- function skipStringTerminated(text, start) {
106
- let index = start;
107
- while (index < text.length) {
108
- const ch = text[index];
109
- if (ch === "\u0007") {
110
- return index + 1;
111
- }
112
- if (ch === ESC && text[index + 1] === "\\") {
113
- return index + 2;
114
- }
115
- index += 1;
116
- }
117
- return index;
118
- }
119
173
  function parseParams(params) {
120
174
  if (params.length === 0) {
121
175
  return [0];
@@ -1,5 +1,6 @@
1
1
  import { color } from "../components/color.js";
2
2
  import { expandTabs, graphemes, graphemeWidth } from "./terminal-width.js";
3
+ import { parseAnsi } from "./ansi.js";
3
4
  const EMPTY_CELL = { ch: " ", style: {} };
4
5
  export class ScreenBuffer {
5
6
  _width;
@@ -22,17 +23,19 @@ export class ScreenBuffer {
22
23
  }
23
24
  const normalizedStyle = normalizeStyle(style);
24
25
  let offset = 0;
25
- for (const ch of graphemes(expandTabs(text, Math.max(0, x)))) {
26
- const targetX = x + offset;
27
- const width = graphemeWidth(ch);
28
- offset += width;
29
- if (!this.isInBoundsX(targetX)) {
30
- continue;
31
- }
32
- this._cells[this.index(targetX, y)] = { ch, style: normalizedStyle };
33
- for (let continuation = 1; continuation < width; continuation += 1) {
34
- if (this.isInBoundsX(targetX + continuation)) {
35
- this._cells[this.index(targetX + continuation, y)] = { ch: "", style: normalizedStyle };
26
+ for (const segment of screenSegments(text, normalizedStyle)) {
27
+ for (const ch of graphemes(expandTabs(segment.text, Math.max(0, x + offset)))) {
28
+ const targetX = x + offset;
29
+ const width = graphemeWidth(ch);
30
+ offset += width;
31
+ if (!this.isInBoundsX(targetX)) {
32
+ continue;
33
+ }
34
+ this._cells[this.index(targetX, y)] = { ch, style: segment.style };
35
+ for (let continuation = 1; continuation < width; continuation += 1) {
36
+ if (this.isInBoundsX(targetX + continuation)) {
37
+ this._cells[this.index(targetX + continuation, y)] = { ch: "", style: segment.style };
38
+ }
36
39
  }
37
40
  }
38
41
  }
@@ -84,20 +87,22 @@ export class ScreenBuffer {
84
87
  const normalizedStyle = normalizeStyle(style);
85
88
  const rectEndX = rect.x + rect.width;
86
89
  let offset = 0;
87
- for (const ch of graphemes(expandTabs(text))) {
88
- const targetX = rect.x + offset;
89
- const width = graphemeWidth(ch);
90
- offset += width;
91
- if (targetX + width > rectEndX) {
92
- break;
93
- }
94
- if (!this.isInBoundsX(targetX)) {
95
- continue;
96
- }
97
- this._cells[this.index(targetX, y)] = { ch, style: normalizedStyle };
98
- for (let continuation = 1; continuation < width; continuation += 1) {
99
- if (this.isInBoundsX(targetX + continuation)) {
100
- this._cells[this.index(targetX + continuation, y)] = { ch: "", style: normalizedStyle };
90
+ for (const segment of screenSegments(text, normalizedStyle)) {
91
+ for (const ch of graphemes(expandTabs(segment.text, offset))) {
92
+ const targetX = rect.x + offset;
93
+ const width = graphemeWidth(ch);
94
+ offset += width;
95
+ if (targetX + width > rectEndX) {
96
+ break;
97
+ }
98
+ if (!this.isInBoundsX(targetX)) {
99
+ continue;
100
+ }
101
+ this._cells[this.index(targetX, y)] = { ch, style: segment.style };
102
+ for (let continuation = 1; continuation < width; continuation += 1) {
103
+ if (this.isInBoundsX(targetX + continuation)) {
104
+ this._cells[this.index(targetX + continuation, y)] = { ch: "", style: segment.style };
105
+ }
101
106
  }
102
107
  }
103
108
  }
@@ -115,6 +120,15 @@ export class ScreenBuffer {
115
120
  return y >= 0 && y < this._height;
116
121
  }
117
122
  }
123
+ function screenSegments(text, style) {
124
+ for (let index = 0; index < text.length; index++) {
125
+ const code = text.charCodeAt(index);
126
+ if ((code < 0x20 && code !== 0x09) || (code >= 0x7f && code <= 0x9f)) {
127
+ return parseAnsi(text, style).flatMap((line, row) => row === 0 ? line.segments : [{ text: " ", style }, ...line.segments]);
128
+ }
129
+ }
130
+ return [{ text, style }];
131
+ }
118
132
  export function diff(prev, next) {
119
133
  const changes = [];
120
134
  const width = Math.max(prev.width, next.width);
@@ -1,3 +1,5 @@
1
+ import { plainTerminalText } from "../ansi.js";
2
+ import { displayWidth, truncateToWidth } from "../terminal-width.js";
1
3
  export function renderBorder(buffer, layout, opts) {
2
4
  const outer = layout.outerBorder;
3
5
  if (outer.width <= 0 || outer.height <= 0) {
@@ -100,17 +102,16 @@ function renderFooterDivider(buffer, left, right, bottom, layout, style) {
100
102
  }
101
103
  }
102
104
  function renderTopSegment(width, title) {
105
+ if (title !== undefined)
106
+ title = plainTerminalText(title);
103
107
  if (width <= 0) {
104
108
  return "";
105
109
  }
106
110
  if (!title) {
107
111
  return "─".repeat(width);
108
112
  }
109
- const content = `─ ${title} `;
110
- if (content.length >= width) {
111
- return content.slice(0, width);
112
- }
113
- return `${content}${"─".repeat(width - content.length)}`;
113
+ const content = truncateToWidth(`─ ${title} `, width);
114
+ return `${content}${"─".repeat(width - displayWidth(content))}`;
114
115
  }
115
116
  function hasInteriorRows(top, bottom) {
116
117
  return (bottom - top) > 1;
@@ -1,14 +1,32 @@
1
1
  import { getTheme } from "../../internal/theme-detect.js";
2
+ import { displayWidth, graphemes, graphemeWidth, truncateToWidth } from "../terminal-width.js";
3
+ import { plainTerminalText } from "../ansi.js";
2
4
  export function renderFooter(buffer, rect, hints) {
3
5
  buffer.clearRect(rect);
4
6
  if (rect.width <= 0 || rect.height <= 0 || hints.length === 0) {
5
7
  return;
6
8
  }
7
- const cells = truncateCells(hintsToCells(hints), rect.width);
8
- const startX = rect.x + Math.floor((rect.width - cells.length) / 2);
9
+ const fitted = [];
10
+ hints = hints.map(hint => ({ key: plainTerminalText(hint.key), label: plainTerminalText(hint.label) }));
11
+ let width = 0;
12
+ for (const hint of hints) {
13
+ const nextWidth = displayWidth(`${hint.key} ${hint.label}`) + (fitted.length > 0 ? 2 : 0);
14
+ if (width + nextWidth > rect.width)
15
+ break;
16
+ fitted.push(hint);
17
+ width += nextWidth;
18
+ }
19
+ if (fitted.length === 0) {
20
+ const key = truncateToWidth(hints[0].key, rect.width);
21
+ fitted.push({ key, label: "" });
22
+ width = displayWidth(key);
23
+ }
24
+ const cells = hintsToCells(fitted);
25
+ let x = rect.x + Math.floor((rect.width - width) / 2);
9
26
  const y = rect.y + Math.floor(rect.height / 2);
10
- cells.forEach((cell, index) => {
11
- buffer.put(startX + index, y, cell.ch, cell.style);
27
+ cells.forEach((cell) => {
28
+ buffer.put(x, y, cell.ch, cell.style);
29
+ x += graphemeWidth(cell.ch);
12
30
  });
13
31
  }
14
32
  export function defaultHints() {
@@ -27,30 +45,17 @@ function hintsToCells(hints) {
27
45
  if (hintIndex > 0) {
28
46
  cells.push({ ch: " ", style: {} }, { ch: " ", style: {} });
29
47
  }
30
- for (const ch of hint.key) {
48
+ for (const ch of graphemes(hint.key)) {
31
49
  cells.push({ ch, style: accentStyle });
32
50
  }
33
- cells.push({ ch: " ", style: {} });
34
- for (const ch of hint.label) {
51
+ if (hint.label.length > 0)
52
+ cells.push({ ch: " ", style: {} });
53
+ for (const ch of graphemes(hint.label)) {
35
54
  cells.push({ ch, style: {} });
36
55
  }
37
56
  });
38
57
  return cells;
39
58
  }
40
- function truncateCells(cells, width) {
41
- if (cells.length <= width) {
42
- return cells;
43
- }
44
- if (width <= 3) {
45
- return Array.from({ length: Math.max(0, width) }, () => ({ ch: ".", style: {} }));
46
- }
47
- return [
48
- ...cells.slice(0, width - 3),
49
- { ch: ".", style: {} },
50
- { ch: ".", style: {} },
51
- { ch: ".", style: {} }
52
- ];
53
- }
54
59
  function getAccentStyle() {
55
60
  return getTheme().styles.accent;
56
61
  }
@@ -8,5 +8,5 @@ export type VisualLine = {
8
8
  prefixStyle: CellStyle;
9
9
  segments?: StyledSegment[];
10
10
  };
11
- export declare function renderOutputPane(buffer: ScreenBuffer, rect: Rect, items: OutputItem[]): void;
11
+ export declare function renderOutputPane(buffer: ScreenBuffer, rect: Rect, items: OutputItem[], scrollOffset?: number): number;
12
12
  export declare function computeVisualLines(items: OutputItem[], width: number): VisualLine[];
@@ -1,15 +1,15 @@
1
+ import { selectViewportTail } from "../../viewport.js";
1
2
  import { getTheme } from "../../internal/theme-detect.js";
2
3
  import { hasAnsi, parseAnsi } from "../ansi.js";
3
4
  import { displayWidth, expandTabs, graphemes, graphemeWidth } from "../terminal-width.js";
4
5
  const TEXT_OFFSET = 3;
5
6
  const CONTINUATION_PREFIX = "│";
6
- export function renderOutputPane(buffer, rect, items) {
7
+ export function renderOutputPane(buffer, rect, items, scrollOffset = 0) {
7
8
  buffer.clearRect(rect);
8
9
  if (rect.width <= 0 || rect.height <= 0) {
9
- return;
10
+ return 0;
10
11
  }
11
- const visualLines = computeVisualLines(items, rect.width);
12
- const startLine = Math.max(visualLines.length - rect.height, 0);
12
+ const { rows: visualLines, offset: actualOffset } = selectViewportTail(items, rect.height, scrollOffset, item => computeVisualLines([item], rect.width));
13
13
  const textRect = {
14
14
  x: rect.x + TEXT_OFFSET,
15
15
  y: rect.y,
@@ -17,7 +17,7 @@ export function renderOutputPane(buffer, rect, items) {
17
17
  height: rect.height
18
18
  };
19
19
  for (let row = 0; row < rect.height; row += 1) {
20
- const line = visualLines[startLine + row];
20
+ const line = visualLines[row];
21
21
  if (line === undefined) {
22
22
  continue;
23
23
  }
@@ -44,6 +44,7 @@ export function renderOutputPane(buffer, rect, items) {
44
44
  }
45
45
  buffer.putInRect(textRect, row, line.text, line.style);
46
46
  }
47
+ return actualOffset;
47
48
  }
48
49
  export function computeVisualLines(items, width) {
49
50
  if (width <= 0) {
@@ -85,7 +86,12 @@ export function computeVisualLines(items, width) {
85
86
  return visualLines;
86
87
  }
87
88
  function hasCursorControls(text) {
88
- return text.includes("\r") || text.includes("\b");
89
+ for (let index = 0; index < text.length; index += 1) {
90
+ const code = text.charCodeAt(index);
91
+ if ((code < 0x20 && code !== 0x0a && code !== 0x09) || (code >= 0x7f && code <= 0x9f))
92
+ return true;
93
+ }
94
+ return false;
89
95
  }
90
96
  function hardWrapSegments(segments, width) {
91
97
  if (width <= 0) {
@@ -1,6 +1,7 @@
1
1
  import { ScreenBuffer } from "../buffer.js";
2
2
  import type { DashboardStats, Rect } from "../types.js";
3
- import type { VisualLine } from "./output-pane.js";
3
+ import { type VisualLine } from "./output-pane.js";
4
+ export declare function renderCompactStatsPane(buffer: ScreenBuffer, rect: Rect, stats: DashboardStats): void;
4
5
  export declare function renderStatsPane(buffer: ScreenBuffer, rect: Rect, stats: DashboardStats): void;
5
6
  export declare function formatElapsed(ms: number): string;
6
7
  export declare function formatNumber(n: number): string;
@@ -1,10 +1,42 @@
1
1
  import { getTheme } from "../../internal/theme-detect.js";
2
+ import { plainTerminalText } from "../ansi.js";
3
+ import { displayWidth, graphemes, graphemeWidth, truncateToWidth } from "../terminal-width.js";
4
+ import { computeVisualLines } from "./output-pane.js";
5
+ export function renderCompactStatsPane(buffer, rect, stats) {
6
+ buffer.clearRect(rect);
7
+ if (rect.width <= 0 || rect.height <= 0)
8
+ return;
9
+ const status = formatStatus(stats.status);
10
+ const metrics = `${status} · ${stats.iterationsLabel ?? "Iteration"} ${formatNumber(stats.iterations)} · ${formatElapsed(stats.elapsedMs)} · ${formatNumber(stats.tokensIn + stats.tokensOut)} tokens`;
11
+ const firstLine = rect.height === 1 && stats.currentAction ? `${status} · ${stats.currentAction}` : metrics;
12
+ const messages = [firstLine, stats.currentAction ?? ""].map(plainTerminalText);
13
+ buffer.putInRect(rect, 0, truncateToWidth(messages[0], rect.width), getStatusStyle(stats.status));
14
+ if (rect.height > 1 && stats.currentAction !== undefined) {
15
+ buffer.putInRect(rect, 1, truncateToWidth(messages[1], rect.width), getToneStyle("muted"));
16
+ }
17
+ }
2
18
  export function renderStatsPane(buffer, rect, stats) {
3
19
  buffer.clearRect(rect);
4
20
  if (rect.width <= 0 || rect.height <= 0) {
5
21
  return;
6
22
  }
7
- const lines = statsToLines(stats, rect.width);
23
+ let lines = statsToLines(stats, rect.width);
24
+ if (lines.length > rect.height) {
25
+ const actions = stats.currentAction === undefined ? [] : lines.slice(9);
26
+ const visibleActions = actions.slice(0, Math.max(0, rect.height - 1));
27
+ if (visibleActions.length > 0 && visibleActions.length < actions.length) {
28
+ const last = visibleActions[visibleActions.length - 1];
29
+ visibleActions[visibleActions.length - 1] = {
30
+ ...last,
31
+ text: truncateToWidth(`${last.text}…`, Math.max(0, rect.width - displayWidth(last.prefix)))
32
+ };
33
+ }
34
+ lines = [
35
+ lines[0],
36
+ ...visibleActions,
37
+ ...lines.slice(1, 7).filter((line) => line.prefix.length > 0 || line.text.length > 0)
38
+ ];
39
+ }
8
40
  for (let row = 0; row < rect.height; row += 1) {
9
41
  const line = lines[row];
10
42
  if (line === undefined) {
@@ -16,7 +48,7 @@ export function renderStatsPane(buffer, rect, stats) {
16
48
  if (line.text.length === 0) {
17
49
  continue;
18
50
  }
19
- const textStart = Math.min(line.prefix.length, rect.width);
51
+ const textStart = Math.min(displayWidth(line.prefix), rect.width);
20
52
  buffer.putInRect({ x: rect.x + textStart, y: rect.y + row, width: rect.width - textStart, height: 1 }, 0, line.text, line.style);
21
53
  }
22
54
  }
@@ -37,7 +69,7 @@ export function statsToLines(stats, width) {
37
69
  }
38
70
  const mutedStyle = getToneStyle("muted");
39
71
  const totalTokens = stats.tokensIn + stats.tokensOut;
40
- const iterationsLabel = stats.iterationsLabel ?? "Iteration";
72
+ const iterationsLabel = plainTerminalText(stats.iterationsLabel ?? "Iteration");
41
73
  const lines = [
42
74
  createKeyValueLine("Status", formatStatus(stats.status), width, getStatusStyle(stats.status)),
43
75
  createKeyValueLine(iterationsLabel, formatNumber(stats.iterations), width),
@@ -53,12 +85,12 @@ export function statsToLines(stats, width) {
53
85
  prefixStyle: {},
54
86
  style: {},
55
87
  text: ""
56
- }, {
57
- prefix: width > 0 ? clipText(" ", width) : "",
88
+ }, ...computeVisualLines([{ kind: "status", text: stats.currentAction, ts: 0 }], width + 1).map((line) => ({
89
+ prefix: clipText(" ", width),
58
90
  prefixStyle: mutedStyle,
59
91
  style: mutedStyle,
60
- text: clipText(stats.currentAction, Math.max(width - 2, 0))
61
- });
92
+ text: line.text
93
+ })));
62
94
  }
63
95
  return lines;
64
96
  }
@@ -72,17 +104,26 @@ function createBlankLine() {
72
104
  }
73
105
  function createKeyValueLine(label, value, width, valueStyle = {}) {
74
106
  const clippedValue = clipText(value, width);
75
- const availableBeforeValue = Math.max(width - clippedValue.length, 0);
107
+ const availableBeforeValue = Math.max(width - displayWidth(clippedValue), 0);
76
108
  const clippedLabel = clipText(label, Math.max(availableBeforeValue - 1, 0));
77
109
  return {
78
- prefix: clippedLabel + " ".repeat(Math.max(availableBeforeValue - clippedLabel.length, 0)),
110
+ prefix: clippedLabel + " ".repeat(Math.max(availableBeforeValue - displayWidth(clippedLabel), 0)),
79
111
  prefixStyle: {},
80
112
  style: valueStyle,
81
113
  text: clippedValue
82
114
  };
83
115
  }
84
116
  function clipText(value, width) {
85
- return width <= 0 ? "" : value.slice(0, width);
117
+ let result = "";
118
+ let cells = 0;
119
+ for (const grapheme of graphemes(value)) {
120
+ const size = graphemeWidth(grapheme);
121
+ if (cells + size > width)
122
+ break;
123
+ result += grapheme;
124
+ cells += size;
125
+ }
126
+ return result;
86
127
  }
87
128
  function formatStatus(status) {
88
129
  return `${status.slice(0, 1).toUpperCase()}${status.slice(1)}`;
@@ -1,3 +1,4 @@
1
+ import { type RenderPerformanceSnapshot } from "../render-performance.js";
1
2
  import type { FooterHint } from "./components/footer.js";
2
3
  import type { Command, DashboardStats, OutputItem } from "./types.js";
3
4
  export type DashboardOptions = {
@@ -8,6 +9,8 @@ export type DashboardOptions = {
8
9
  hints?: FooterHint[];
9
10
  stdin?: NodeJS.ReadStream;
10
11
  stdout?: NodeJS.WriteStream;
12
+ /** Observe bounded performance snapshots without forcing idle repaints. */
13
+ onPerformance?: (stats: RenderPerformanceSnapshot) => void;
11
14
  };
12
15
  export type Dashboard = {
13
16
  start(): void;
@@ -16,5 +19,6 @@ export type Dashboard = {
16
19
  updateStats(stats: Partial<DashboardStats>): void;
17
20
  onCommand(handler: (cmd: Command) => void): void;
18
21
  destroy(): void;
22
+ getPerformance(): RenderPerformanceSnapshot;
19
23
  };
20
24
  export declare function createDashboard(opts?: DashboardOptions): Dashboard;