zerg-ztc 0.1.11 → 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 (122) hide show
  1. package/bin/ztc-audio-darwin-arm64 +0 -0
  2. package/dist/utils/dictation_native.d.ts.map +1 -1
  3. package/dist/utils/dictation_native.js +43 -23
  4. package/dist/utils/dictation_native.js.map +1 -1
  5. package/package.json +5 -4
  6. package/packages/ztc-dictation/Cargo.toml +0 -43
  7. package/packages/ztc-dictation/README.md +0 -65
  8. package/packages/ztc-dictation/index.d.ts +0 -16
  9. package/packages/ztc-dictation/index.js +0 -74
  10. package/packages/ztc-dictation/package.json +0 -41
  11. package/packages/ztc-dictation/src/main.rs +0 -430
  12. package/src/App.tsx +0 -910
  13. package/src/agent/agent.ts +0 -534
  14. package/src/agent/backends/anthropic.ts +0 -86
  15. package/src/agent/backends/gemini.ts +0 -119
  16. package/src/agent/backends/inception.ts +0 -23
  17. package/src/agent/backends/index.ts +0 -17
  18. package/src/agent/backends/openai.ts +0 -23
  19. package/src/agent/backends/openai_compatible.ts +0 -143
  20. package/src/agent/backends/types.ts +0 -83
  21. package/src/agent/commands/clipboard.ts +0 -77
  22. package/src/agent/commands/config.ts +0 -204
  23. package/src/agent/commands/debug.ts +0 -23
  24. package/src/agent/commands/dictation.ts +0 -11
  25. package/src/agent/commands/emulation.ts +0 -80
  26. package/src/agent/commands/execution.ts +0 -9
  27. package/src/agent/commands/help.ts +0 -20
  28. package/src/agent/commands/history.ts +0 -13
  29. package/src/agent/commands/index.ts +0 -48
  30. package/src/agent/commands/input_mode.ts +0 -22
  31. package/src/agent/commands/keybindings.ts +0 -40
  32. package/src/agent/commands/model.ts +0 -11
  33. package/src/agent/commands/models.ts +0 -116
  34. package/src/agent/commands/permissions.ts +0 -64
  35. package/src/agent/commands/retry.ts +0 -9
  36. package/src/agent/commands/shell.ts +0 -68
  37. package/src/agent/commands/skills.ts +0 -54
  38. package/src/agent/commands/status.ts +0 -19
  39. package/src/agent/commands/types.ts +0 -88
  40. package/src/agent/commands/update.ts +0 -32
  41. package/src/agent/factory.ts +0 -60
  42. package/src/agent/index.ts +0 -20
  43. package/src/agent/runtime/capabilities.ts +0 -7
  44. package/src/agent/runtime/memory.ts +0 -23
  45. package/src/agent/runtime/policy.ts +0 -48
  46. package/src/agent/runtime/session.ts +0 -18
  47. package/src/agent/runtime/tracing.ts +0 -23
  48. package/src/agent/tools/file.ts +0 -178
  49. package/src/agent/tools/index.ts +0 -52
  50. package/src/agent/tools/screenshot.ts +0 -821
  51. package/src/agent/tools/search.ts +0 -138
  52. package/src/agent/tools/shell.ts +0 -69
  53. package/src/agent/tools/skills.ts +0 -28
  54. package/src/agent/tools/types.ts +0 -14
  55. package/src/agent/tools/zerg.ts +0 -50
  56. package/src/cli.tsx +0 -163
  57. package/src/components/ActivityLine.tsx +0 -23
  58. package/src/components/FullScreen.tsx +0 -79
  59. package/src/components/Header.tsx +0 -27
  60. package/src/components/InputArea.tsx +0 -1660
  61. package/src/components/MessageList.tsx +0 -71
  62. package/src/components/SingleMessage.tsx +0 -298
  63. package/src/components/StatusBar.tsx +0 -55
  64. package/src/components/index.tsx +0 -8
  65. package/src/config/types.ts +0 -19
  66. package/src/config.ts +0 -186
  67. package/src/debug/logger.ts +0 -14
  68. package/src/emulation/README.md +0 -24
  69. package/src/emulation/catalog.ts +0 -82
  70. package/src/emulation/trace_style.ts +0 -8
  71. package/src/emulation/types.ts +0 -7
  72. package/src/skills/index.ts +0 -36
  73. package/src/skills/loader.ts +0 -135
  74. package/src/skills/registry.ts +0 -6
  75. package/src/skills/types.ts +0 -10
  76. package/src/types.ts +0 -84
  77. package/src/ui/README.md +0 -44
  78. package/src/ui/core/factory.ts +0 -9
  79. package/src/ui/core/index.ts +0 -4
  80. package/src/ui/core/input.ts +0 -38
  81. package/src/ui/core/input_segments.ts +0 -410
  82. package/src/ui/core/input_state.ts +0 -17
  83. package/src/ui/core/layout_yoga.ts +0 -122
  84. package/src/ui/core/style.ts +0 -38
  85. package/src/ui/core/types.ts +0 -54
  86. package/src/ui/ink/index.tsx +0 -1
  87. package/src/ui/ink/render.tsx +0 -60
  88. package/src/ui/views/activity_line.ts +0 -33
  89. package/src/ui/views/app.ts +0 -111
  90. package/src/ui/views/header.ts +0 -44
  91. package/src/ui/views/input_area.ts +0 -255
  92. package/src/ui/views/message_list.ts +0 -443
  93. package/src/ui/views/status_bar.ts +0 -114
  94. package/src/ui/vue/index.ts +0 -53
  95. package/src/ui/web/frame_render.tsx +0 -148
  96. package/src/ui/web/index.tsx +0 -1
  97. package/src/ui/web/render.tsx +0 -41
  98. package/src/utils/clipboard.ts +0 -39
  99. package/src/utils/clipboard_image.ts +0 -40
  100. package/src/utils/dictation.ts +0 -467
  101. package/src/utils/dictation_native.ts +0 -258
  102. package/src/utils/diff.ts +0 -52
  103. package/src/utils/image_preview.ts +0 -36
  104. package/src/utils/models.ts +0 -98
  105. package/src/utils/path_complete.ts +0 -173
  106. package/src/utils/path_format.ts +0 -99
  107. package/src/utils/shell.ts +0 -72
  108. package/src/utils/spinner_frames.ts +0 -1
  109. package/src/utils/spinner_verbs.ts +0 -23
  110. package/src/utils/table.ts +0 -171
  111. package/src/utils/tool_summary.ts +0 -56
  112. package/src/utils/tool_trace.ts +0 -346
  113. package/src/utils/update.ts +0 -44
  114. package/src/utils/version.ts +0 -15
  115. package/src/web/index.html +0 -352
  116. package/src/web/mirror-favicon.svg +0 -4
  117. package/src/web/mirror.html +0 -641
  118. package/src/web/mirror_hook.ts +0 -25
  119. package/src/web/mirror_server.ts +0 -204
  120. package/tsconfig.json +0 -22
  121. package/vite.config.ts +0 -363
  122. /package/{packages/ztc-dictation/bin → bin}/.gitkeep +0 -0
@@ -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);