zerg-ztc 0.1.10 → 0.1.12

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 (151) hide show
  1. package/bin/.gitkeep +0 -0
  2. package/bin/ztc-audio-darwin-arm64 +0 -0
  3. package/dist/App.d.ts.map +1 -1
  4. package/dist/App.js +63 -2
  5. package/dist/App.js.map +1 -1
  6. package/dist/agent/commands/dictation.d.ts +3 -0
  7. package/dist/agent/commands/dictation.d.ts.map +1 -0
  8. package/dist/agent/commands/dictation.js +10 -0
  9. package/dist/agent/commands/dictation.js.map +1 -0
  10. package/dist/agent/commands/index.d.ts.map +1 -1
  11. package/dist/agent/commands/index.js +2 -1
  12. package/dist/agent/commands/index.js.map +1 -1
  13. package/dist/agent/commands/types.d.ts +7 -0
  14. package/dist/agent/commands/types.d.ts.map +1 -1
  15. package/dist/components/InputArea.d.ts +1 -0
  16. package/dist/components/InputArea.d.ts.map +1 -1
  17. package/dist/components/InputArea.js +591 -43
  18. package/dist/components/InputArea.js.map +1 -1
  19. package/dist/components/SingleMessage.d.ts.map +1 -1
  20. package/dist/components/SingleMessage.js +157 -7
  21. package/dist/components/SingleMessage.js.map +1 -1
  22. package/dist/config/types.d.ts +6 -0
  23. package/dist/config/types.d.ts.map +1 -1
  24. package/dist/ui/views/status_bar.js +2 -2
  25. package/dist/ui/views/status_bar.js.map +1 -1
  26. package/dist/utils/dictation.d.ts +46 -0
  27. package/dist/utils/dictation.d.ts.map +1 -0
  28. package/dist/utils/dictation.js +409 -0
  29. package/dist/utils/dictation.js.map +1 -0
  30. package/dist/utils/dictation_native.d.ts +51 -0
  31. package/dist/utils/dictation_native.d.ts.map +1 -0
  32. package/dist/utils/dictation_native.js +236 -0
  33. package/dist/utils/dictation_native.js.map +1 -0
  34. package/dist/utils/path_format.d.ts +20 -0
  35. package/dist/utils/path_format.d.ts.map +1 -0
  36. package/dist/utils/path_format.js +90 -0
  37. package/dist/utils/path_format.js.map +1 -0
  38. package/dist/utils/table.d.ts +38 -0
  39. package/dist/utils/table.d.ts.map +1 -0
  40. package/dist/utils/table.js +133 -0
  41. package/dist/utils/table.js.map +1 -0
  42. package/dist/utils/tool_trace.d.ts +7 -2
  43. package/dist/utils/tool_trace.d.ts.map +1 -1
  44. package/dist/utils/tool_trace.js +156 -51
  45. package/dist/utils/tool_trace.js.map +1 -1
  46. package/package.json +5 -1
  47. package/src/App.tsx +0 -813
  48. package/src/agent/agent.ts +0 -534
  49. package/src/agent/backends/anthropic.ts +0 -86
  50. package/src/agent/backends/gemini.ts +0 -119
  51. package/src/agent/backends/inception.ts +0 -23
  52. package/src/agent/backends/index.ts +0 -17
  53. package/src/agent/backends/openai.ts +0 -23
  54. package/src/agent/backends/openai_compatible.ts +0 -143
  55. package/src/agent/backends/types.ts +0 -83
  56. package/src/agent/commands/clipboard.ts +0 -77
  57. package/src/agent/commands/config.ts +0 -204
  58. package/src/agent/commands/debug.ts +0 -23
  59. package/src/agent/commands/emulation.ts +0 -80
  60. package/src/agent/commands/execution.ts +0 -9
  61. package/src/agent/commands/help.ts +0 -20
  62. package/src/agent/commands/history.ts +0 -13
  63. package/src/agent/commands/index.ts +0 -46
  64. package/src/agent/commands/input_mode.ts +0 -22
  65. package/src/agent/commands/keybindings.ts +0 -40
  66. package/src/agent/commands/model.ts +0 -11
  67. package/src/agent/commands/models.ts +0 -116
  68. package/src/agent/commands/permissions.ts +0 -64
  69. package/src/agent/commands/retry.ts +0 -9
  70. package/src/agent/commands/shell.ts +0 -68
  71. package/src/agent/commands/skills.ts +0 -54
  72. package/src/agent/commands/status.ts +0 -19
  73. package/src/agent/commands/types.ts +0 -80
  74. package/src/agent/commands/update.ts +0 -32
  75. package/src/agent/factory.ts +0 -60
  76. package/src/agent/index.ts +0 -20
  77. package/src/agent/runtime/capabilities.ts +0 -7
  78. package/src/agent/runtime/memory.ts +0 -23
  79. package/src/agent/runtime/policy.ts +0 -48
  80. package/src/agent/runtime/session.ts +0 -18
  81. package/src/agent/runtime/tracing.ts +0 -23
  82. package/src/agent/tools/file.ts +0 -178
  83. package/src/agent/tools/index.ts +0 -52
  84. package/src/agent/tools/screenshot.ts +0 -821
  85. package/src/agent/tools/search.ts +0 -138
  86. package/src/agent/tools/shell.ts +0 -69
  87. package/src/agent/tools/skills.ts +0 -28
  88. package/src/agent/tools/types.ts +0 -14
  89. package/src/agent/tools/zerg.ts +0 -50
  90. package/src/cli.tsx +0 -163
  91. package/src/components/ActivityLine.tsx +0 -23
  92. package/src/components/FullScreen.tsx +0 -79
  93. package/src/components/Header.tsx +0 -27
  94. package/src/components/InputArea.tsx +0 -1096
  95. package/src/components/MessageList.tsx +0 -71
  96. package/src/components/SingleMessage.tsx +0 -59
  97. package/src/components/StatusBar.tsx +0 -55
  98. package/src/components/index.tsx +0 -8
  99. package/src/config/types.ts +0 -12
  100. package/src/config.ts +0 -186
  101. package/src/debug/logger.ts +0 -14
  102. package/src/emulation/README.md +0 -24
  103. package/src/emulation/catalog.ts +0 -82
  104. package/src/emulation/trace_style.ts +0 -8
  105. package/src/emulation/types.ts +0 -7
  106. package/src/skills/index.ts +0 -36
  107. package/src/skills/loader.ts +0 -135
  108. package/src/skills/registry.ts +0 -6
  109. package/src/skills/types.ts +0 -10
  110. package/src/types.ts +0 -84
  111. package/src/ui/README.md +0 -44
  112. package/src/ui/core/factory.ts +0 -9
  113. package/src/ui/core/index.ts +0 -4
  114. package/src/ui/core/input.ts +0 -38
  115. package/src/ui/core/input_segments.ts +0 -410
  116. package/src/ui/core/input_state.ts +0 -17
  117. package/src/ui/core/layout_yoga.ts +0 -122
  118. package/src/ui/core/style.ts +0 -38
  119. package/src/ui/core/types.ts +0 -54
  120. package/src/ui/ink/index.tsx +0 -1
  121. package/src/ui/ink/render.tsx +0 -60
  122. package/src/ui/views/activity_line.ts +0 -33
  123. package/src/ui/views/app.ts +0 -111
  124. package/src/ui/views/header.ts +0 -44
  125. package/src/ui/views/input_area.ts +0 -255
  126. package/src/ui/views/message_list.ts +0 -443
  127. package/src/ui/views/status_bar.ts +0 -114
  128. package/src/ui/vue/index.ts +0 -53
  129. package/src/ui/web/frame_render.tsx +0 -148
  130. package/src/ui/web/index.tsx +0 -1
  131. package/src/ui/web/render.tsx +0 -41
  132. package/src/utils/clipboard.ts +0 -39
  133. package/src/utils/clipboard_image.ts +0 -40
  134. package/src/utils/diff.ts +0 -52
  135. package/src/utils/image_preview.ts +0 -36
  136. package/src/utils/models.ts +0 -98
  137. package/src/utils/path_complete.ts +0 -173
  138. package/src/utils/shell.ts +0 -72
  139. package/src/utils/spinner_frames.ts +0 -1
  140. package/src/utils/spinner_verbs.ts +0 -23
  141. package/src/utils/tool_summary.ts +0 -56
  142. package/src/utils/tool_trace.ts +0 -216
  143. package/src/utils/update.ts +0 -44
  144. package/src/utils/version.ts +0 -15
  145. package/src/web/index.html +0 -352
  146. package/src/web/mirror-favicon.svg +0 -4
  147. package/src/web/mirror.html +0 -641
  148. package/src/web/mirror_hook.ts +0 -25
  149. package/src/web/mirror_server.ts +0 -204
  150. package/tsconfig.json +0 -22
  151. package/vite.config.ts +0 -363
@@ -1,410 +0,0 @@
1
- import { InputSegment, InputCursor, InputState } from './input_state.js';
2
-
3
- export const PASTE_BADGE_THRESHOLD = 4;
4
-
5
- export function normalizeSegments(segments: InputSegment[]): InputSegment[] {
6
- const normalized: InputSegment[] = [];
7
- for (const segment of segments) {
8
- if (segment.type === 'text') {
9
- if (segment.text.length === 0) continue;
10
- const last = normalized[normalized.length - 1];
11
- if (last && last.type === 'text') {
12
- last.text += segment.text;
13
- continue;
14
- }
15
- }
16
- normalized.push({ ...segment });
17
- }
18
- return normalized;
19
- }
20
-
21
- export function clampCursor(segments: InputSegment[], cursor: InputCursor): InputCursor {
22
- const maxIndex = segments.length;
23
- let index = Math.max(0, Math.min(cursor.index, maxIndex));
24
- let offset = cursor.offset;
25
- if (index === segments.length) {
26
- return { index, offset: 0 };
27
- }
28
- const segment = segments[index];
29
- if (!segment) return { index, offset: 0 };
30
- if (segment.type === 'text') {
31
- offset = Math.max(0, Math.min(offset, segment.text.length));
32
- return { index, offset };
33
- }
34
- return { index, offset: offset > 0 ? 1 : 0 };
35
- }
36
-
37
- export function insertText(state: InputState, text: string): InputState {
38
- if (text.length === 0) return state;
39
- // Deep copy segments to avoid mutating original state
40
- const segments: InputSegment[] = state.segments.map(s => ({ ...s }));
41
- const cursor = clampCursor(segments, state.cursor);
42
-
43
- if (cursor.index === segments.length) {
44
- const last = segments[segments.length - 1];
45
- if (last && last.type === 'text') {
46
- const newText = last.text + text;
47
- segments[segments.length - 1] = { ...last, text: newText };
48
- } else {
49
- segments.push({ type: 'text', text });
50
- }
51
- const finalSegment = segments[segments.length - 1];
52
- const finalOffset = finalSegment.type === 'text' ? finalSegment.text.length : 1;
53
- return {
54
- ...state,
55
- segments: normalizeSegments(segments),
56
- cursor: { index: segments.length - 1, offset: finalOffset }
57
- };
58
- }
59
-
60
- const segment = segments[cursor.index];
61
- if (segment.type === 'text') {
62
- const before = segment.text.slice(0, cursor.offset);
63
- const after = segment.text.slice(cursor.offset);
64
- const newText = before + text + after;
65
- segments[cursor.index] = { ...segment, text: newText };
66
- return {
67
- ...state,
68
- segments: normalizeSegments(segments),
69
- cursor: { index: cursor.index, offset: cursor.offset + text.length }
70
- };
71
- }
72
-
73
- const insertIndex = cursor.offset === 0 ? cursor.index : cursor.index + 1;
74
- segments.splice(insertIndex, 0, { type: 'text', text });
75
- return {
76
- ...state,
77
- segments: normalizeSegments(segments),
78
- cursor: { index: insertIndex, offset: text.length }
79
- };
80
- }
81
-
82
- export function insertSegment(state: InputState, segment: InputSegment): InputState {
83
- const segments = [...state.segments];
84
- const cursor = clampCursor(segments, state.cursor);
85
- const insertIndex = cursor.index === segments.length
86
- ? segments.length
87
- : (cursor.offset === 0 ? cursor.index : cursor.index + 1);
88
- segments.splice(insertIndex, 0, segment);
89
- return {
90
- ...state,
91
- segments: normalizeSegments(segments),
92
- cursor: { index: insertIndex, offset: segment.type === 'text' ? segment.text.length : 1 }
93
- };
94
- }
95
-
96
- export function insertBadge(state: InputState, segment: InputSegment): InputState {
97
- const next = insertSegment(state, segment);
98
- return insertText(next, ' ');
99
- }
100
-
101
- export function backspace(state: InputState): InputState {
102
- // Deep copy segments to avoid mutating original state
103
- const segments: InputSegment[] = state.segments.map(s => ({ ...s }));
104
- const cursor = clampCursor(segments, state.cursor);
105
-
106
- if (cursor.index === segments.length) {
107
- if (segments.length === 0) return state;
108
- const last = segments[segments.length - 1];
109
- if (last.type === 'text' && last.text.length > 0) {
110
- const newText = last.text.slice(0, -1);
111
- segments[segments.length - 1] = { ...last, text: newText };
112
- return {
113
- ...state,
114
- segments: normalizeSegments(segments),
115
- cursor: { index: segments.length - 1, offset: newText.length }
116
- };
117
- }
118
- segments.pop();
119
- return {
120
- ...state,
121
- segments: normalizeSegments(segments),
122
- cursor: { index: Math.max(0, segments.length - 1), offset: 0 }
123
- };
124
- }
125
-
126
- const segment = segments[cursor.index];
127
- if (segment.type === 'text') {
128
- if (cursor.offset > 0) {
129
- const newText = segment.text.slice(0, cursor.offset - 1) + segment.text.slice(cursor.offset);
130
- segments[cursor.index] = { ...segment, text: newText };
131
- return {
132
- ...state,
133
- segments: normalizeSegments(segments),
134
- cursor: { index: cursor.index, offset: cursor.offset - 1 }
135
- };
136
- }
137
- if (cursor.index === 0) return state;
138
- const prev = segments[cursor.index - 1];
139
- if (prev.type === 'text') {
140
- const prevLen = prev.text.length;
141
- segments[cursor.index - 1] = { ...prev, text: prev.text + segment.text };
142
- segments.splice(cursor.index, 1);
143
- return {
144
- ...state,
145
- segments: normalizeSegments(segments),
146
- cursor: { index: cursor.index - 1, offset: prevLen }
147
- };
148
- }
149
- segments.splice(cursor.index - 1, 1);
150
- return {
151
- ...state,
152
- segments: normalizeSegments(segments),
153
- cursor: { index: cursor.index - 1, offset: 0 }
154
- };
155
- }
156
-
157
- if (cursor.offset === 1) {
158
- segments.splice(cursor.index, 1);
159
- return {
160
- ...state,
161
- segments: normalizeSegments(segments),
162
- cursor: { index: Math.max(0, cursor.index - 1), offset: 0 }
163
- };
164
- }
165
-
166
- if (cursor.index === 0) return state;
167
- const prev = segments[cursor.index - 1];
168
- if (prev.type === 'text') {
169
- const prevLen = prev.text.length;
170
- segments[cursor.index - 1] = { ...prev, text: prev.text.slice(0, -1) };
171
- return {
172
- ...state,
173
- segments: normalizeSegments(segments),
174
- cursor: { index: cursor.index - 1, offset: Math.max(0, prevLen - 1) }
175
- };
176
- }
177
- segments.splice(cursor.index - 1, 1);
178
- return {
179
- ...state,
180
- segments: normalizeSegments(segments),
181
- cursor: { index: cursor.index - 1, offset: 0 }
182
- };
183
- }
184
-
185
- export function deleteForward(state: InputState): InputState {
186
- // Deep copy segments to avoid mutating original state
187
- const segments: InputSegment[] = state.segments.map(s => ({ ...s }));
188
- const cursor = clampCursor(segments, state.cursor);
189
-
190
- if (cursor.index === segments.length) return state;
191
-
192
- const segment = segments[cursor.index];
193
- if (segment.type === 'text') {
194
- if (cursor.offset < segment.text.length) {
195
- const newText = segment.text.slice(0, cursor.offset) + segment.text.slice(cursor.offset + 1);
196
- segments[cursor.index] = { ...segment, text: newText };
197
- return {
198
- ...state,
199
- segments: normalizeSegments(segments),
200
- cursor
201
- };
202
- }
203
- if (cursor.index + 1 >= segments.length) return state;
204
- const next = segments[cursor.index + 1];
205
- if (next.type === 'text') {
206
- segments[cursor.index] = { ...segment, text: segment.text + next.text };
207
- segments.splice(cursor.index + 1, 1);
208
- return {
209
- ...state,
210
- segments: normalizeSegments(segments),
211
- cursor
212
- };
213
- }
214
- segments.splice(cursor.index + 1, 1);
215
- return {
216
- ...state,
217
- segments: normalizeSegments(segments),
218
- cursor
219
- };
220
- }
221
-
222
- if (cursor.offset === 0) {
223
- segments.splice(cursor.index, 1);
224
- return {
225
- ...state,
226
- segments: normalizeSegments(segments),
227
- cursor: { index: Math.min(cursor.index, segments.length), offset: 0 }
228
- };
229
- }
230
-
231
- if (cursor.index + 1 >= segments.length) return state;
232
- const next = segments[cursor.index + 1];
233
- if (next.type === 'text') {
234
- segments[cursor.index + 1] = { ...next, text: next.text.slice(1) };
235
- return {
236
- ...state,
237
- segments: normalizeSegments(segments),
238
- cursor: { index: cursor.index + 1, offset: 0 }
239
- };
240
- }
241
- segments.splice(cursor.index + 1, 1);
242
- return {
243
- ...state,
244
- segments: normalizeSegments(segments),
245
- cursor: { index: cursor.index + 1, offset: 0 }
246
- };
247
- }
248
-
249
- export function killToEnd(state: InputState): InputState {
250
- const segments = [...state.segments];
251
- const cursor = clampCursor(segments, state.cursor);
252
-
253
- if (cursor.index >= segments.length) return state;
254
-
255
- const segment = segments[cursor.index];
256
- if (segment.type === 'text') {
257
- segment.text = segment.text.slice(0, cursor.offset);
258
- segments.splice(cursor.index + 1);
259
- return {
260
- ...state,
261
- segments: normalizeSegments(segments),
262
- cursor: { index: cursor.index, offset: segment.text.length }
263
- };
264
- }
265
-
266
- segments.splice(cursor.index);
267
- return {
268
- ...state,
269
- segments: normalizeSegments(segments),
270
- cursor: { index: Math.min(cursor.index, segments.length), offset: 0 }
271
- };
272
- }
273
-
274
- export function moveLeft(state: InputState): InputState {
275
- const segments = state.segments;
276
- const cursor = clampCursor(segments, state.cursor);
277
- if (cursor.index === segments.length) {
278
- if (segments.length === 0) return state;
279
- const last = segments[segments.length - 1];
280
- if (last.type === 'text') {
281
- return { ...state, cursor: { index: segments.length - 1, offset: last.text.length } };
282
- }
283
- return { ...state, cursor: { index: segments.length - 1, offset: 1 } };
284
- }
285
- const segment = segments[cursor.index];
286
- if (segment.type === 'text') {
287
- if (cursor.offset > 0) return { ...state, cursor: { index: cursor.index, offset: cursor.offset - 1 } };
288
- } else if (cursor.offset === 1) {
289
- return { ...state, cursor: { index: cursor.index, offset: 0 } };
290
- }
291
- if (cursor.index === 0) return state;
292
- const prev = segments[cursor.index - 1];
293
- if (prev.type === 'text') {
294
- return { ...state, cursor: { index: cursor.index - 1, offset: prev.text.length } };
295
- }
296
- return { ...state, cursor: { index: cursor.index - 1, offset: 1 } };
297
- }
298
-
299
- export function moveRight(state: InputState): InputState {
300
- const segments = state.segments;
301
- const cursor = clampCursor(segments, state.cursor);
302
- if (cursor.index === segments.length) return state;
303
- const segment = segments[cursor.index];
304
- if (segment.type === 'text') {
305
- if (cursor.offset < segment.text.length) {
306
- return { ...state, cursor: { index: cursor.index, offset: cursor.offset + 1 } };
307
- }
308
- } else if (cursor.offset === 0) {
309
- return { ...state, cursor: { index: cursor.index, offset: 1 } };
310
- }
311
- if (cursor.index + 1 >= segments.length) {
312
- return { ...state, cursor: { index: segments.length, offset: 0 } };
313
- }
314
- const next = segments[cursor.index + 1];
315
- if (next.type === 'text') {
316
- return { ...state, cursor: { index: cursor.index + 1, offset: 0 } };
317
- }
318
- return { ...state, cursor: { index: cursor.index + 1, offset: 0 } };
319
- }
320
-
321
- export function moveWordLeft(state: InputState): InputState {
322
- const text = getPlainText(state.segments);
323
- if (text.length === 0) return state;
324
- const pos = Math.max(0, textCursorIndex(state.segments, state.cursor));
325
- const before = text.slice(0, pos);
326
- const match = before.match(/\S+\s*$/);
327
- const nextPos = match ? pos - match[0].length : 0;
328
- return { ...state, cursor: cursorFromTextIndex(state.segments, nextPos) };
329
- }
330
-
331
- export function moveWordRight(state: InputState): InputState {
332
- const text = getPlainText(state.segments);
333
- const pos = Math.max(0, textCursorIndex(state.segments, state.cursor));
334
- const after = text.slice(pos);
335
- const match = after.match(/^\s*\S+/);
336
- const nextPos = match ? pos + match[0].length : text.length;
337
- return { ...state, cursor: cursorFromTextIndex(state.segments, nextPos) };
338
- }
339
-
340
- export function textCursorIndex(segments: InputSegment[], cursor: InputCursor): number {
341
- let index = 0;
342
- for (let i = 0; i < segments.length; i += 1) {
343
- const segment = segments[i];
344
- if (i === cursor.index) {
345
- if (segment.type === 'text') return index + cursor.offset;
346
- return index;
347
- }
348
- if (segment.type === 'text') index += segment.text.length;
349
- }
350
- return index;
351
- }
352
-
353
- export function cursorFromTextIndex(segments: InputSegment[], pos: number): InputCursor {
354
- let index = 0;
355
- for (let i = 0; i < segments.length; i += 1) {
356
- const segment = segments[i];
357
- if (segment.type !== 'text') continue;
358
- const next = index + segment.text.length;
359
- if (pos <= next) {
360
- return { index: i, offset: Math.max(0, pos - index) };
361
- }
362
- index = next;
363
- }
364
- return { index: segments.length, offset: 0 };
365
- }
366
-
367
- export function getPlainText(segments: InputSegment[]): string {
368
- return segments.map(segment => {
369
- switch (segment.type) {
370
- case 'text':
371
- return segment.text;
372
- case 'paste':
373
- return segment.text;
374
- case 'file':
375
- return segment.path;
376
- case 'image':
377
- return segment.path;
378
- default:
379
- return '';
380
- }
381
- }).join('');
382
- }
383
-
384
- export function serializeSegments(segments: InputSegment[]): string {
385
- return segments.map(segment => {
386
- switch (segment.type) {
387
- case 'text':
388
- return segment.text;
389
- case 'paste': {
390
- const lines = segment.text.split('\n').length;
391
- return `\n[pasted ${lines} line${lines === 1 ? '' : 's'}]\n${segment.text}\n[/pasted]\n`;
392
- }
393
- case 'file':
394
- return `\n[file ${segment.path}]\n`;
395
- case 'image':
396
- return `\n[image ${segment.path}]\n`;
397
- default:
398
- return '';
399
- }
400
- }).join('');
401
- }
402
-
403
- export function createEmptyState(): InputState {
404
- return {
405
- segments: [],
406
- cursor: { index: 0, offset: 0 },
407
- history: [],
408
- historyIdx: -1
409
- };
410
- }
@@ -1,17 +0,0 @@
1
- export type InputSegment =
2
- | { type: 'text'; text: string }
3
- | { type: 'paste'; text: string }
4
- | { type: 'file'; path: string }
5
- | { type: 'image'; path: string };
6
-
7
- export interface InputCursor {
8
- index: number;
9
- offset: number;
10
- }
11
-
12
- export interface InputState {
13
- segments: InputSegment[];
14
- cursor: InputCursor;
15
- history: string[];
16
- historyIdx: number;
17
- }
@@ -1,122 +0,0 @@
1
- import Yoga, { Node } from 'yoga-layout';
2
- import { LayoutNode, Style, TextNode } from './types.js';
3
-
4
- export interface LayoutFrame {
5
- node: LayoutNode;
6
- x: number;
7
- y: number;
8
- width: number;
9
- height: number;
10
- children: LayoutFrame[];
11
- }
12
-
13
- function applyStyle(node: Node, style?: Style): void {
14
- if (!style) return;
15
-
16
- if (style.flexDirection) {
17
- node.setFlexDirection(
18
- style.flexDirection === 'row' ? Yoga.FLEX_DIRECTION_ROW : Yoga.FLEX_DIRECTION_COLUMN
19
- );
20
- }
21
- if (style.flexGrow !== undefined) node.setFlexGrow(style.flexGrow);
22
- if (style.flexShrink !== undefined) node.setFlexShrink(style.flexShrink);
23
- if (style.width !== undefined) node.setWidth(style.width);
24
- if (style.height !== undefined) node.setHeight(style.height);
25
- if (style.alignItems) {
26
- const map = {
27
- 'flex-start': Yoga.ALIGN_FLEX_START,
28
- 'center': Yoga.ALIGN_CENTER,
29
- 'flex-end': Yoga.ALIGN_FLEX_END,
30
- 'stretch': Yoga.ALIGN_STRETCH
31
- } as const;
32
- node.setAlignItems(map[style.alignItems]);
33
- }
34
- if (style.justifyContent) {
35
- const map = {
36
- 'flex-start': Yoga.JUSTIFY_FLEX_START,
37
- 'center': Yoga.JUSTIFY_CENTER,
38
- 'flex-end': Yoga.JUSTIFY_FLEX_END,
39
- 'space-between': Yoga.JUSTIFY_SPACE_BETWEEN
40
- } as const;
41
- node.setJustifyContent(map[style.justifyContent]);
42
- }
43
-
44
- const paddingX = style.paddingX ?? style.padding ?? 0;
45
- const paddingY = style.paddingY ?? style.padding ?? 0;
46
- const marginX = style.marginX ?? style.margin ?? 0;
47
- const marginY = style.marginY ?? style.margin ?? 0;
48
-
49
- node.setPadding(Yoga.EDGE_LEFT, style.paddingLeft ?? paddingX);
50
- node.setPadding(Yoga.EDGE_RIGHT, style.paddingRight ?? paddingX);
51
- node.setPadding(Yoga.EDGE_TOP, style.paddingTop ?? paddingY);
52
- node.setPadding(Yoga.EDGE_BOTTOM, style.paddingBottom ?? paddingY);
53
-
54
- node.setMargin(Yoga.EDGE_LEFT, style.marginLeft ?? marginX);
55
- node.setMargin(Yoga.EDGE_RIGHT, style.marginRight ?? marginX);
56
- node.setMargin(Yoga.EDGE_TOP, style.marginTop ?? marginY);
57
- node.setMargin(Yoga.EDGE_BOTTOM, style.marginBottom ?? marginY);
58
- }
59
-
60
- function measureText(node: TextNode, width?: number): { width: number; height: number } {
61
- const text = node.text || '';
62
- const wrap = node.style?.wrap ?? 'wrap';
63
- if (!width || width <= 0) {
64
- return { width: text.length, height: 1 };
65
- }
66
-
67
- if (wrap === 'truncate-end') {
68
- return { width: Math.min(text.length, width), height: 1 };
69
- }
70
-
71
- const lines = Math.max(1, Math.ceil(text.length / width));
72
- const lineWidth = Math.min(text.length, width);
73
- return { width: lineWidth, height: lines };
74
- }
75
-
76
- function buildYogaTree(node: LayoutNode): Node {
77
- const yogaNode = Yoga.Node.create();
78
- applyStyle(yogaNode, node.type === 'box' ? node.style : undefined);
79
-
80
- if (node.type === 'text') {
81
- yogaNode.setMeasureFunc((width, widthMode) => {
82
- const availableWidth = widthMode === Yoga.MEASURE_MODE_UNDEFINED ? undefined : width;
83
- const measured = measureText(node, availableWidth);
84
- return { width: measured.width, height: measured.height };
85
- });
86
- } else {
87
- for (const child of node.children) {
88
- yogaNode.insertChild(buildYogaTree(child), yogaNode.getChildCount());
89
- }
90
- }
91
-
92
- return yogaNode;
93
- }
94
-
95
- function buildFrameTree(node: LayoutNode, yogaNode: Node): LayoutFrame {
96
- const children: LayoutFrame[] = [];
97
- if (node.type === 'box') {
98
- for (let i = 0; i < node.children.length; i += 1) {
99
- children.push(buildFrameTree(node.children[i], yogaNode.getChild(i)));
100
- }
101
- }
102
-
103
- return {
104
- node,
105
- x: yogaNode.getComputedLeft(),
106
- y: yogaNode.getComputedTop(),
107
- width: yogaNode.getComputedWidth(),
108
- height: yogaNode.getComputedHeight(),
109
- children
110
- };
111
- }
112
-
113
- export function computeLayout(root: LayoutNode, width: number, height: number): LayoutFrame {
114
- const yogaRoot = buildYogaTree(root);
115
- yogaRoot.setWidth(width);
116
- yogaRoot.setHeight(height);
117
- yogaRoot.calculateLayout(width, height, Yoga.DIRECTION_LTR);
118
-
119
- const frame = buildFrameTree(root, yogaRoot);
120
- yogaRoot.freeRecursive();
121
- return frame;
122
- }
@@ -1,38 +0,0 @@
1
- import { Style } from './types.js';
2
-
3
- export function toCssStyle(style?: Style): Record<string, string | number> {
4
- if (!style) return {};
5
-
6
- const paddingX = style.paddingX ?? style.padding ?? 0;
7
- const paddingY = style.paddingY ?? style.padding ?? 0;
8
- const marginX = style.marginX ?? style.margin ?? 0;
9
- const marginY = style.marginY ?? style.margin ?? 0;
10
-
11
- const css: Record<string, string | number> = {
12
- display: 'flex',
13
- flexDirection: style.flexDirection || 'column',
14
- flexGrow: style.flexGrow ?? 0,
15
- flexShrink: style.flexShrink ?? 0,
16
- paddingLeft: style.paddingLeft ?? paddingX,
17
- paddingRight: style.paddingRight ?? paddingX,
18
- paddingTop: style.paddingTop ?? paddingY,
19
- paddingBottom: style.paddingBottom ?? paddingY,
20
- marginLeft: style.marginLeft ?? marginX,
21
- marginRight: style.marginRight ?? marginX,
22
- marginTop: style.marginTop ?? marginY,
23
- marginBottom: style.marginBottom ?? marginY
24
- };
25
-
26
- if (style.alignItems) css.alignItems = style.alignItems;
27
- if (style.justifyContent) css.justifyContent = style.justifyContent;
28
-
29
- if (style.width !== undefined) css.width = style.width;
30
- if (style.height !== undefined) css.height = style.height;
31
- if (style.borderStyle) {
32
- css.borderStyle = style.borderStyle;
33
- css.borderWidth = 1;
34
- css.borderColor = style.borderColor || 'currentColor';
35
- }
36
-
37
- return css;
38
- }
@@ -1,54 +0,0 @@
1
- export type LayoutNode = BoxNode | TextNode;
2
-
3
- export interface Style {
4
- flexDirection?: 'row' | 'column';
5
- flexGrow?: number;
6
- flexShrink?: number;
7
- width?: number;
8
- height?: number;
9
- padding?: number;
10
- paddingX?: number;
11
- paddingY?: number;
12
- paddingLeft?: number;
13
- paddingRight?: number;
14
- paddingTop?: number;
15
- paddingBottom?: number;
16
- margin?: number;
17
- marginX?: number;
18
- marginY?: number;
19
- marginLeft?: number;
20
- marginRight?: number;
21
- marginTop?: number;
22
- marginBottom?: number;
23
- alignItems?: 'flex-start' | 'center' | 'flex-end' | 'stretch';
24
- justifyContent?: 'flex-start' | 'center' | 'flex-end' | 'space-between';
25
- borderStyle?: string;
26
- borderColor?: string;
27
- overflow?: 'visible' | 'hidden';
28
- }
29
-
30
- export interface TextStyle {
31
- color?: string;
32
- dimColor?: boolean;
33
- bold?: boolean;
34
- inverse?: boolean;
35
- wrap?: 'wrap' | 'truncate-end';
36
- badge?: {
37
- type: 'paste' | 'file' | 'image';
38
- preview: string;
39
- full: string;
40
- path?: string;
41
- };
42
- }
43
-
44
- export interface BoxNode {
45
- type: 'box';
46
- style?: Style;
47
- children: LayoutNode[];
48
- }
49
-
50
- export interface TextNode {
51
- type: 'text';
52
- text: string;
53
- style?: TextStyle;
54
- }
@@ -1 +0,0 @@
1
- export { InkNode } from './render.js';
@@ -1,60 +0,0 @@
1
- import React, { memo } from 'react';
2
- import { Box, Text, type BoxProps } from 'ink';
3
- import { LayoutNode, TextNode } from '../core/types.js';
4
-
5
- const InkText = memo(function InkText({ node }: { node: TextNode }): React.ReactElement {
6
- const style = node.style || {};
7
- return (
8
- <Text
9
- color={style.color}
10
- dimColor={style.dimColor}
11
- bold={style.bold}
12
- inverse={style.inverse}
13
- wrap={style.wrap}
14
- >
15
- {node.text}
16
- </Text>
17
- );
18
- });
19
-
20
- function InkNodeInner({ node }: { node: LayoutNode }): React.ReactElement {
21
- if (node.type === 'text') {
22
- return <InkText node={node} />;
23
- }
24
-
25
- const style = node.style || {};
26
- return (
27
- <Box
28
- flexDirection={style.flexDirection}
29
- flexGrow={style.flexGrow}
30
- flexShrink={style.flexShrink}
31
- width={style.width}
32
- height={style.height}
33
- padding={style.padding}
34
- paddingX={style.paddingX}
35
- paddingY={style.paddingY}
36
- paddingLeft={style.paddingLeft}
37
- paddingRight={style.paddingRight}
38
- paddingTop={style.paddingTop}
39
- paddingBottom={style.paddingBottom}
40
- margin={style.margin}
41
- marginX={style.marginX}
42
- marginY={style.marginY}
43
- marginLeft={style.marginLeft}
44
- marginRight={style.marginRight}
45
- marginTop={style.marginTop}
46
- marginBottom={style.marginBottom}
47
- alignItems={style.alignItems}
48
- justifyContent={style.justifyContent}
49
- borderStyle={style.borderStyle as BoxProps['borderStyle']}
50
- borderColor={style.borderColor}
51
- overflow={style.overflow}
52
- >
53
- {node.children.map((child, index) => (
54
- <InkNode key={`${child.type}-${index}`} node={child} />
55
- ))}
56
- </Box>
57
- );
58
- }
59
-
60
- export const InkNode = memo(InkNodeInner);