zerg-ztc 0.1.3 → 0.1.4
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.
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +71 -13
- package/dist/App.js.map +1 -1
- package/dist/agent/agent.d.ts.map +1 -1
- package/dist/agent/agent.js +3 -1
- package/dist/agent/agent.js.map +1 -1
- package/dist/agent/commands/index.d.ts.map +1 -1
- package/dist/agent/commands/index.js +3 -1
- package/dist/agent/commands/index.js.map +1 -1
- package/dist/agent/commands/input_mode.d.ts +3 -0
- package/dist/agent/commands/input_mode.d.ts.map +1 -0
- package/dist/agent/commands/input_mode.js +21 -0
- package/dist/agent/commands/input_mode.js.map +1 -0
- package/dist/agent/commands/keybindings.d.ts +3 -0
- package/dist/agent/commands/keybindings.d.ts.map +1 -0
- package/dist/agent/commands/keybindings.js +38 -0
- package/dist/agent/commands/keybindings.js.map +1 -0
- package/dist/agent/commands/types.d.ts +2 -0
- package/dist/agent/commands/types.d.ts.map +1 -1
- package/dist/cli.js +38 -1
- package/dist/cli.js.map +1 -1
- package/dist/components/FullScreen.d.ts.map +1 -1
- package/dist/components/FullScreen.js +29 -29
- package/dist/components/FullScreen.js.map +1 -1
- package/dist/components/InputArea.d.ts.map +1 -1
- package/dist/components/InputArea.js +476 -19
- package/dist/components/InputArea.js.map +1 -1
- package/dist/components/StatusBar.d.ts +1 -0
- package/dist/components/StatusBar.d.ts.map +1 -1
- package/dist/components/StatusBar.js +2 -1
- package/dist/components/StatusBar.js.map +1 -1
- package/dist/types.d.ts +1 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/ui/core/input_segments.d.ts +1 -0
- package/dist/ui/core/input_segments.d.ts.map +1 -1
- package/dist/ui/core/input_segments.js +46 -14
- package/dist/ui/core/input_segments.js.map +1 -1
- package/dist/ui/core/types.d.ts +1 -0
- package/dist/ui/core/types.d.ts.map +1 -1
- package/dist/ui/ink/render.d.ts +3 -1
- package/dist/ui/ink/render.d.ts.map +1 -1
- package/dist/ui/ink/render.js +7 -5
- package/dist/ui/ink/render.js.map +1 -1
- package/dist/ui/views/app.d.ts +2 -1
- package/dist/ui/views/app.d.ts.map +1 -1
- package/dist/ui/views/app.js +2 -1
- package/dist/ui/views/app.js.map +1 -1
- package/dist/ui/views/header.d.ts.map +1 -1
- package/dist/ui/views/header.js +8 -5
- package/dist/ui/views/header.js.map +1 -1
- package/dist/ui/views/status_bar.d.ts +2 -1
- package/dist/ui/views/status_bar.d.ts.map +1 -1
- package/dist/ui/views/status_bar.js +5 -1
- package/dist/ui/views/status_bar.js.map +1 -1
- package/package.json +1 -1
- package/src/App.tsx +71 -13
- package/src/agent/agent.ts +3 -1
- package/src/agent/commands/index.ts +4 -0
- package/src/agent/commands/input_mode.ts +22 -0
- package/src/agent/commands/keybindings.ts +40 -0
- package/src/agent/commands/types.ts +2 -0
- package/src/cli.tsx +43 -1
- package/src/components/FullScreen.tsx +39 -34
- package/src/components/InputArea.tsx +489 -19
- package/src/components/StatusBar.tsx +3 -0
- package/src/types.ts +1 -0
- package/src/ui/core/input_segments.ts +49 -14
- package/src/ui/core/types.ts +1 -0
- package/src/ui/ink/render.tsx +16 -5
- package/src/ui/views/app.ts +3 -0
- package/src/ui/views/header.ts +8 -5
- package/src/ui/views/status_bar.ts +6 -0
|
@@ -36,20 +36,24 @@ export function clampCursor(segments: InputSegment[], cursor: InputCursor): Inpu
|
|
|
36
36
|
|
|
37
37
|
export function insertText(state: InputState, text: string): InputState {
|
|
38
38
|
if (text.length === 0) return state;
|
|
39
|
-
|
|
39
|
+
// Deep copy segments to avoid mutating original state
|
|
40
|
+
const segments: InputSegment[] = state.segments.map(s => ({ ...s }));
|
|
40
41
|
const cursor = clampCursor(segments, state.cursor);
|
|
41
42
|
|
|
42
43
|
if (cursor.index === segments.length) {
|
|
43
44
|
const last = segments[segments.length - 1];
|
|
44
45
|
if (last && last.type === 'text') {
|
|
45
|
-
last.text
|
|
46
|
+
const newText = last.text + text;
|
|
47
|
+
segments[segments.length - 1] = { ...last, text: newText };
|
|
46
48
|
} else {
|
|
47
49
|
segments.push({ type: 'text', text });
|
|
48
50
|
}
|
|
51
|
+
const finalSegment = segments[segments.length - 1];
|
|
52
|
+
const finalOffset = finalSegment.type === 'text' ? finalSegment.text.length : 1;
|
|
49
53
|
return {
|
|
50
54
|
...state,
|
|
51
55
|
segments: normalizeSegments(segments),
|
|
52
|
-
cursor: { index: segments.length - 1, offset:
|
|
56
|
+
cursor: { index: segments.length - 1, offset: finalOffset }
|
|
53
57
|
};
|
|
54
58
|
}
|
|
55
59
|
|
|
@@ -57,7 +61,8 @@ export function insertText(state: InputState, text: string): InputState {
|
|
|
57
61
|
if (segment.type === 'text') {
|
|
58
62
|
const before = segment.text.slice(0, cursor.offset);
|
|
59
63
|
const after = segment.text.slice(cursor.offset);
|
|
60
|
-
|
|
64
|
+
const newText = before + text + after;
|
|
65
|
+
segments[cursor.index] = { ...segment, text: newText };
|
|
61
66
|
return {
|
|
62
67
|
...state,
|
|
63
68
|
segments: normalizeSegments(segments),
|
|
@@ -94,18 +99,20 @@ export function insertBadge(state: InputState, segment: InputSegment): InputStat
|
|
|
94
99
|
}
|
|
95
100
|
|
|
96
101
|
export function backspace(state: InputState): InputState {
|
|
97
|
-
|
|
102
|
+
// Deep copy segments to avoid mutating original state
|
|
103
|
+
const segments: InputSegment[] = state.segments.map(s => ({ ...s }));
|
|
98
104
|
const cursor = clampCursor(segments, state.cursor);
|
|
99
105
|
|
|
100
106
|
if (cursor.index === segments.length) {
|
|
101
107
|
if (segments.length === 0) return state;
|
|
102
108
|
const last = segments[segments.length - 1];
|
|
103
109
|
if (last.type === 'text' && last.text.length > 0) {
|
|
104
|
-
|
|
110
|
+
const newText = last.text.slice(0, -1);
|
|
111
|
+
segments[segments.length - 1] = { ...last, text: newText };
|
|
105
112
|
return {
|
|
106
113
|
...state,
|
|
107
114
|
segments: normalizeSegments(segments),
|
|
108
|
-
cursor: { index: segments.length - 1, offset:
|
|
115
|
+
cursor: { index: segments.length - 1, offset: newText.length }
|
|
109
116
|
};
|
|
110
117
|
}
|
|
111
118
|
segments.pop();
|
|
@@ -119,7 +126,8 @@ export function backspace(state: InputState): InputState {
|
|
|
119
126
|
const segment = segments[cursor.index];
|
|
120
127
|
if (segment.type === 'text') {
|
|
121
128
|
if (cursor.offset > 0) {
|
|
122
|
-
|
|
129
|
+
const newText = segment.text.slice(0, cursor.offset - 1) + segment.text.slice(cursor.offset);
|
|
130
|
+
segments[cursor.index] = { ...segment, text: newText };
|
|
123
131
|
return {
|
|
124
132
|
...state,
|
|
125
133
|
segments: normalizeSegments(segments),
|
|
@@ -130,7 +138,7 @@ export function backspace(state: InputState): InputState {
|
|
|
130
138
|
const prev = segments[cursor.index - 1];
|
|
131
139
|
if (prev.type === 'text') {
|
|
132
140
|
const prevLen = prev.text.length;
|
|
133
|
-
prev.text
|
|
141
|
+
segments[cursor.index - 1] = { ...prev, text: prev.text + segment.text };
|
|
134
142
|
segments.splice(cursor.index, 1);
|
|
135
143
|
return {
|
|
136
144
|
...state,
|
|
@@ -159,7 +167,7 @@ export function backspace(state: InputState): InputState {
|
|
|
159
167
|
const prev = segments[cursor.index - 1];
|
|
160
168
|
if (prev.type === 'text') {
|
|
161
169
|
const prevLen = prev.text.length;
|
|
162
|
-
|
|
170
|
+
segments[cursor.index - 1] = { ...prev, text: prev.text.slice(0, -1) };
|
|
163
171
|
return {
|
|
164
172
|
...state,
|
|
165
173
|
segments: normalizeSegments(segments),
|
|
@@ -175,7 +183,8 @@ export function backspace(state: InputState): InputState {
|
|
|
175
183
|
}
|
|
176
184
|
|
|
177
185
|
export function deleteForward(state: InputState): InputState {
|
|
178
|
-
|
|
186
|
+
// Deep copy segments to avoid mutating original state
|
|
187
|
+
const segments: InputSegment[] = state.segments.map(s => ({ ...s }));
|
|
179
188
|
const cursor = clampCursor(segments, state.cursor);
|
|
180
189
|
|
|
181
190
|
if (cursor.index === segments.length) return state;
|
|
@@ -183,7 +192,8 @@ export function deleteForward(state: InputState): InputState {
|
|
|
183
192
|
const segment = segments[cursor.index];
|
|
184
193
|
if (segment.type === 'text') {
|
|
185
194
|
if (cursor.offset < segment.text.length) {
|
|
186
|
-
|
|
195
|
+
const newText = segment.text.slice(0, cursor.offset) + segment.text.slice(cursor.offset + 1);
|
|
196
|
+
segments[cursor.index] = { ...segment, text: newText };
|
|
187
197
|
return {
|
|
188
198
|
...state,
|
|
189
199
|
segments: normalizeSegments(segments),
|
|
@@ -193,7 +203,7 @@ export function deleteForward(state: InputState): InputState {
|
|
|
193
203
|
if (cursor.index + 1 >= segments.length) return state;
|
|
194
204
|
const next = segments[cursor.index + 1];
|
|
195
205
|
if (next.type === 'text') {
|
|
196
|
-
segment.text
|
|
206
|
+
segments[cursor.index] = { ...segment, text: segment.text + next.text };
|
|
197
207
|
segments.splice(cursor.index + 1, 1);
|
|
198
208
|
return {
|
|
199
209
|
...state,
|
|
@@ -221,7 +231,7 @@ export function deleteForward(state: InputState): InputState {
|
|
|
221
231
|
if (cursor.index + 1 >= segments.length) return state;
|
|
222
232
|
const next = segments[cursor.index + 1];
|
|
223
233
|
if (next.type === 'text') {
|
|
224
|
-
|
|
234
|
+
segments[cursor.index + 1] = { ...next, text: next.text.slice(1) };
|
|
225
235
|
return {
|
|
226
236
|
...state,
|
|
227
237
|
segments: normalizeSegments(segments),
|
|
@@ -236,6 +246,31 @@ export function deleteForward(state: InputState): InputState {
|
|
|
236
246
|
};
|
|
237
247
|
}
|
|
238
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
|
+
|
|
239
274
|
export function moveLeft(state: InputState): InputState {
|
|
240
275
|
const segments = state.segments;
|
|
241
276
|
const cursor = clampCursor(segments, state.cursor);
|
package/src/ui/core/types.ts
CHANGED
package/src/ui/ink/render.tsx
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { memo } from 'react';
|
|
2
2
|
import { Box, Text, type BoxProps } from 'ink';
|
|
3
3
|
import { LayoutNode, TextNode } from '../core/types.js';
|
|
4
4
|
|
|
5
|
-
function
|
|
5
|
+
const InkText = memo(function InkText({ node }: { node: TextNode }): React.ReactElement {
|
|
6
6
|
const style = node.style || {};
|
|
7
7
|
return (
|
|
8
8
|
<Text
|
|
@@ -15,11 +15,11 @@ function renderText(node: TextNode): React.ReactElement {
|
|
|
15
15
|
{node.text}
|
|
16
16
|
</Text>
|
|
17
17
|
);
|
|
18
|
-
}
|
|
18
|
+
});
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
function InkNodeInner({ node }: { node: LayoutNode }): React.ReactElement {
|
|
21
21
|
if (node.type === 'text') {
|
|
22
|
-
return
|
|
22
|
+
return <InkText node={node} />;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
const style = node.style || {};
|
|
@@ -33,13 +33,22 @@ export function InkNode({ node }: { node: LayoutNode }): React.ReactElement {
|
|
|
33
33
|
padding={style.padding}
|
|
34
34
|
paddingX={style.paddingX}
|
|
35
35
|
paddingY={style.paddingY}
|
|
36
|
+
paddingLeft={style.paddingLeft}
|
|
37
|
+
paddingRight={style.paddingRight}
|
|
38
|
+
paddingTop={style.paddingTop}
|
|
39
|
+
paddingBottom={style.paddingBottom}
|
|
36
40
|
margin={style.margin}
|
|
37
41
|
marginX={style.marginX}
|
|
38
42
|
marginY={style.marginY}
|
|
43
|
+
marginLeft={style.marginLeft}
|
|
44
|
+
marginRight={style.marginRight}
|
|
45
|
+
marginTop={style.marginTop}
|
|
46
|
+
marginBottom={style.marginBottom}
|
|
39
47
|
alignItems={style.alignItems}
|
|
40
48
|
justifyContent={style.justifyContent}
|
|
41
49
|
borderStyle={style.borderStyle as BoxProps['borderStyle']}
|
|
42
50
|
borderColor={style.borderColor}
|
|
51
|
+
overflow={style.overflow}
|
|
43
52
|
>
|
|
44
53
|
{node.children.map((child, index) => (
|
|
45
54
|
<InkNode key={`${child.type}-${index}`} node={child} />
|
|
@@ -47,3 +56,5 @@ export function InkNode({ node }: { node: LayoutNode }): React.ReactElement {
|
|
|
47
56
|
</Box>
|
|
48
57
|
);
|
|
49
58
|
}
|
|
59
|
+
|
|
60
|
+
export const InkNode = memo(InkNodeInner);
|
package/src/ui/views/app.ts
CHANGED
|
@@ -20,6 +20,7 @@ interface AppViewProps {
|
|
|
20
20
|
provider?: string;
|
|
21
21
|
model?: string;
|
|
22
22
|
emulationId?: string;
|
|
23
|
+
inputMode?: 'queue' | 'interrupt';
|
|
23
24
|
toast?: string | null;
|
|
24
25
|
debug?: boolean;
|
|
25
26
|
expandToolOutputs?: boolean;
|
|
@@ -52,6 +53,7 @@ export function buildAppView({
|
|
|
52
53
|
provider,
|
|
53
54
|
model,
|
|
54
55
|
emulationId,
|
|
56
|
+
inputMode,
|
|
55
57
|
toast,
|
|
56
58
|
debug = false,
|
|
57
59
|
expandToolOutputs = false
|
|
@@ -84,6 +86,7 @@ export function buildAppView({
|
|
|
84
86
|
provider,
|
|
85
87
|
model,
|
|
86
88
|
emulationId,
|
|
89
|
+
inputMode,
|
|
87
90
|
toast,
|
|
88
91
|
debug
|
|
89
92
|
}),
|
package/src/ui/views/header.ts
CHANGED
|
@@ -15,13 +15,13 @@ export function buildHeaderView({
|
|
|
15
15
|
showHelp = true,
|
|
16
16
|
debug = false
|
|
17
17
|
}: HeaderProps): LayoutNode {
|
|
18
|
+
// Single-row header to minimize vertical space
|
|
18
19
|
return box([
|
|
19
20
|
box([
|
|
20
21
|
text(title, { bold: true }),
|
|
22
|
+
text(' ', {}),
|
|
21
23
|
text('ZTC', { color: 'gray', dimColor: true })
|
|
22
|
-
], {
|
|
23
|
-
flexDirection: 'column'
|
|
24
|
-
}),
|
|
24
|
+
], { flexDirection: 'row' }),
|
|
25
25
|
showHelp
|
|
26
26
|
? box([
|
|
27
27
|
dateLabel ? text(dateLabel, { color: 'gray', dimColor: true }) : text('', {}),
|
|
@@ -34,9 +34,12 @@ export function buildHeaderView({
|
|
|
34
34
|
], { flexDirection: 'row' })
|
|
35
35
|
: box([], { flexDirection: 'row' })
|
|
36
36
|
], {
|
|
37
|
-
flexDirection: '
|
|
37
|
+
flexDirection: 'row',
|
|
38
38
|
justifyContent: 'space-between',
|
|
39
39
|
paddingX: 1,
|
|
40
|
-
|
|
40
|
+
height: 1,
|
|
41
|
+
flexShrink: 0,
|
|
42
|
+
borderStyle: debug ? 'single' : undefined,
|
|
43
|
+
borderColor: debug ? 'cyan' : undefined
|
|
41
44
|
});
|
|
42
45
|
}
|
|
@@ -11,6 +11,7 @@ interface StatusBarProps {
|
|
|
11
11
|
provider?: string;
|
|
12
12
|
model?: string;
|
|
13
13
|
emulationId?: string;
|
|
14
|
+
inputMode?: 'queue' | 'interrupt';
|
|
14
15
|
toast?: string | null;
|
|
15
16
|
debug?: boolean;
|
|
16
17
|
}
|
|
@@ -36,6 +37,7 @@ export function buildStatusBarView({
|
|
|
36
37
|
provider,
|
|
37
38
|
model,
|
|
38
39
|
emulationId,
|
|
40
|
+
inputMode,
|
|
39
41
|
toast,
|
|
40
42
|
debug = false
|
|
41
43
|
}: StatusBarProps): LayoutNode {
|
|
@@ -78,6 +80,8 @@ export function buildStatusBarView({
|
|
|
78
80
|
(provider || model) ? text(' • ', { color: 'gray', dimColor: true }) : text('', {}),
|
|
79
81
|
emulationId ? text(`emu:${emulationId}`, { color: 'gray', dimColor: true }) : text('', {}),
|
|
80
82
|
emulationId ? text(' • ', { color: 'gray', dimColor: true }) : text('', {}),
|
|
83
|
+
inputMode ? text(`mode:${inputMode}`, { color: 'gray', dimColor: true }) : text('', {}),
|
|
84
|
+
inputMode ? text(' • ', { color: 'gray', dimColor: true }) : text('', {}),
|
|
81
85
|
state.tokensUsed !== undefined && state.tokensUsed > 0
|
|
82
86
|
? text(`${state.tokensUsed.toLocaleString()} tok`, { color: 'gray', dimColor: true })
|
|
83
87
|
: text('', {}),
|
|
@@ -98,6 +102,8 @@ export function buildStatusBarView({
|
|
|
98
102
|
flexDirection: 'row',
|
|
99
103
|
justifyContent: 'space-between',
|
|
100
104
|
paddingX: 1,
|
|
105
|
+
height: 1,
|
|
106
|
+
flexShrink: 0,
|
|
101
107
|
borderStyle: debug ? 'single' : undefined,
|
|
102
108
|
borderColor: debug ? 'gray' : undefined
|
|
103
109
|
});
|