@staticpayload/zai-code 1.2.15 → 1.4.1
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/README.md +85 -154
- package/dist/apply.d.ts.map +1 -1
- package/dist/apply.js +43 -16
- package/dist/apply.js.map +1 -1
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +5 -1
- package/dist/auth.js.map +1 -1
- package/dist/commands.d.ts.map +1 -1
- package/dist/commands.js +1021 -47
- package/dist/commands.js.map +1 -1
- package/dist/context/context_builder.d.ts.map +1 -1
- package/dist/context/context_builder.js +18 -3
- package/dist/context/context_builder.js.map +1 -1
- package/dist/context/project_memory.d.ts +2 -2
- package/dist/context/project_memory.d.ts.map +1 -1
- package/dist/context/project_memory.js +14 -4
- package/dist/context/project_memory.js.map +1 -1
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +51 -7
- package/dist/doctor.js.map +1 -1
- package/dist/history.d.ts.map +1 -1
- package/dist/history.js +13 -8
- package/dist/history.js.map +1 -1
- package/dist/mode_prompts.d.ts.map +1 -1
- package/dist/mode_prompts.js +25 -22
- package/dist/mode_prompts.js.map +1 -1
- package/dist/orchestrator.d.ts.map +1 -1
- package/dist/orchestrator.js +69 -11
- package/dist/orchestrator.js.map +1 -1
- package/dist/planner.js +18 -18
- package/dist/planner.js.map +1 -1
- package/dist/rollback.d.ts +1 -0
- package/dist/rollback.d.ts.map +1 -1
- package/dist/rollback.js +28 -4
- package/dist/rollback.js.map +1 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +22 -9
- package/dist/runtime.js.map +1 -1
- package/dist/session.d.ts +1 -0
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +8 -1
- package/dist/session.js.map +1 -1
- package/dist/settings.d.ts +1 -0
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js +8 -3
- package/dist/settings.js.map +1 -1
- package/dist/shell.d.ts.map +1 -1
- package/dist/shell.js +85 -14
- package/dist/shell.js.map +1 -1
- package/dist/task_runner.d.ts.map +1 -1
- package/dist/task_runner.js +20 -2
- package/dist/task_runner.js.map +1 -1
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +709 -107
- package/dist/tui.js.map +1 -1
- package/dist/ui.d.ts +7 -0
- package/dist/ui.d.ts.map +1 -1
- package/dist/ui.js +113 -11
- package/dist/ui.js.map +1 -1
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +6 -0
- package/dist/workspace.js.map +1 -1
- package/dist/workspace_model.d.ts +1 -1
- package/dist/workspace_model.d.ts.map +1 -1
- package/dist/workspace_model.js +8 -1
- package/dist/workspace_model.js.map +1 -1
- package/package.json +4 -2
package/dist/tui.js
CHANGED
|
@@ -39,33 +39,148 @@ const session_1 = require("./session");
|
|
|
39
39
|
const orchestrator_1 = require("./orchestrator");
|
|
40
40
|
const git_1 = require("./git");
|
|
41
41
|
const settings_1 = require("./settings");
|
|
42
|
+
const workspace_model_1 = require("./workspace_model");
|
|
42
43
|
const agents_1 = require("./agents");
|
|
43
|
-
// Command definitions
|
|
44
|
+
// Command definitions with categories and shortcuts
|
|
44
45
|
const COMMANDS = [
|
|
45
|
-
|
|
46
|
-
{ name: '
|
|
47
|
-
{ name: '
|
|
48
|
-
{ name: '
|
|
49
|
-
{ name: '
|
|
50
|
-
|
|
51
|
-
{ name: '
|
|
52
|
-
{ name: '
|
|
53
|
-
{ name: '
|
|
54
|
-
{ name: '
|
|
55
|
-
{ name: '
|
|
56
|
-
{ name: '
|
|
57
|
-
{ name: '
|
|
46
|
+
// Quick actions (most used)
|
|
47
|
+
{ name: 'do', description: 'Quick: plan + generate', category: 'quick', shortcut: 'Ctrl+D' },
|
|
48
|
+
{ name: 'run', description: 'Auto: plan + generate + apply', category: 'quick', shortcut: 'Ctrl+R' },
|
|
49
|
+
{ name: 'ask', description: 'Quick question', category: 'quick', shortcut: 'Ctrl+A' },
|
|
50
|
+
{ name: 'fix', description: 'Debug mode task', category: 'quick', shortcut: 'Ctrl+F' },
|
|
51
|
+
// Workflow
|
|
52
|
+
{ name: 'plan', description: 'Generate execution plan', category: 'workflow', shortcut: 'Ctrl+P' },
|
|
53
|
+
{ name: 'generate', description: 'Create file changes', category: 'workflow', shortcut: 'Ctrl+G' },
|
|
54
|
+
{ name: 'diff', description: 'Review pending changes', category: 'workflow' },
|
|
55
|
+
{ name: 'apply', description: 'Apply changes', category: 'workflow' },
|
|
56
|
+
{ name: 'undo', description: 'Rollback last operation', category: 'workflow', shortcut: 'Ctrl+Z' },
|
|
57
|
+
{ name: 'retry', description: 'Retry last failed operation', category: 'workflow' },
|
|
58
|
+
{ name: 'clear', description: 'Clear current task', category: 'workflow' },
|
|
59
|
+
// Files
|
|
60
|
+
{ name: 'open', description: 'Add file to context', category: 'files' },
|
|
61
|
+
{ name: 'close', description: 'Remove file from context', category: 'files' },
|
|
62
|
+
{ name: 'files', description: 'List open files', category: 'files' },
|
|
63
|
+
{ name: 'search', description: 'Search files', category: 'files' },
|
|
64
|
+
{ name: 'read', description: 'View file contents', category: 'files' },
|
|
65
|
+
{ name: 'tree', description: 'Show file tree', category: 'files' },
|
|
66
|
+
// Modes
|
|
67
|
+
{ name: 'mode', description: 'Set mode (edit/ask/auto/debug)', category: 'modes' },
|
|
68
|
+
{ name: 'model', description: 'Select AI model', category: 'modes' },
|
|
69
|
+
{ name: 'dry-run', description: 'Toggle dry-run mode', category: 'modes' },
|
|
70
|
+
// Git
|
|
71
|
+
{ name: 'git', description: 'Git operations', category: 'git' },
|
|
72
|
+
{ name: 'commit', description: 'AI-powered commit', category: 'git' },
|
|
73
|
+
// System
|
|
74
|
+
{ name: 'help', description: 'Show all commands', category: 'system' },
|
|
75
|
+
{ name: 'settings', description: 'Open settings menu', category: 'system' },
|
|
76
|
+
{ name: 'status', description: 'Show session status', category: 'system' },
|
|
77
|
+
{ name: 'doctor', description: 'System health check', category: 'system' },
|
|
78
|
+
{ name: 'version', description: 'Show version', category: 'system' },
|
|
79
|
+
{ name: 'reset', description: 'Reset session', category: 'system' },
|
|
80
|
+
{ name: 'exit', description: 'Exit zcode', category: 'system', shortcut: 'Ctrl+C' },
|
|
81
|
+
];
|
|
82
|
+
// Smart suggestions based on context
|
|
83
|
+
function getSmartSuggestions() {
|
|
84
|
+
const session = (0, session_1.getSession)();
|
|
85
|
+
const suggestions = [];
|
|
86
|
+
// Based on current state
|
|
87
|
+
if (session.pendingActions || session.lastDiff) {
|
|
88
|
+
suggestions.push('/diff - Review changes');
|
|
89
|
+
suggestions.push('/apply - Apply changes');
|
|
90
|
+
}
|
|
91
|
+
else if (session.lastPlan && session.lastPlan.length > 0) {
|
|
92
|
+
suggestions.push('/generate - Create changes');
|
|
93
|
+
}
|
|
94
|
+
else if (session.currentIntent) {
|
|
95
|
+
suggestions.push('/plan - Create plan');
|
|
96
|
+
}
|
|
97
|
+
// Based on mode
|
|
98
|
+
if (session.mode === 'edit' && !session.currentIntent) {
|
|
99
|
+
suggestions.push('Type a task to get started');
|
|
100
|
+
}
|
|
101
|
+
// Git suggestions
|
|
102
|
+
const gitInfo = (0, git_1.getGitInfo)(session.workingDirectory);
|
|
103
|
+
if (gitInfo.isRepo && gitInfo.isDirty) {
|
|
104
|
+
suggestions.push('/commit - Commit changes');
|
|
105
|
+
}
|
|
106
|
+
return suggestions.slice(0, 3);
|
|
107
|
+
}
|
|
108
|
+
// Spinner frames for loading animation
|
|
109
|
+
const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
110
|
+
const PROGRESS_CHARS = ['▱', '▰'];
|
|
111
|
+
// Get contextual placeholder based on mode
|
|
112
|
+
function getPlaceholder() {
|
|
113
|
+
const session = (0, session_1.getSession)();
|
|
114
|
+
const mode = session.mode;
|
|
115
|
+
switch (mode) {
|
|
116
|
+
case 'auto':
|
|
117
|
+
return 'Type a task to execute automatically...';
|
|
118
|
+
case 'ask':
|
|
119
|
+
return 'Ask a question about your code...';
|
|
120
|
+
case 'debug':
|
|
121
|
+
return 'Describe the bug or error...';
|
|
122
|
+
case 'review':
|
|
123
|
+
return 'What would you like reviewed?';
|
|
124
|
+
case 'explain':
|
|
125
|
+
return 'What would you like explained?';
|
|
126
|
+
default:
|
|
127
|
+
if (session.pendingActions)
|
|
128
|
+
return '/apply to execute, /diff to review';
|
|
129
|
+
if (session.lastPlan?.length)
|
|
130
|
+
return '/generate to create changes';
|
|
131
|
+
if (session.currentIntent)
|
|
132
|
+
return '/plan to create execution plan';
|
|
133
|
+
return 'Describe what you want to build...';
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
// Recent commands history
|
|
137
|
+
const commandHistory = [];
|
|
138
|
+
let historyIndex = -1;
|
|
139
|
+
// Animated Robot Mascot Frames
|
|
140
|
+
const MASCOT_FRAMES = [
|
|
141
|
+
// Frame 1 - looking right
|
|
142
|
+
`{cyan-fg} ╭───╮ {/cyan-fg}
|
|
143
|
+
{cyan-fg} │{/cyan-fg}{bold}{white-fg}◉ ◉{/white-fg}{/bold}{cyan-fg}│ {/cyan-fg}
|
|
144
|
+
{cyan-fg} │{/cyan-fg}{yellow-fg} ▽ {/yellow-fg}{cyan-fg}│ {/cyan-fg}
|
|
145
|
+
{cyan-fg} ╭┴───┴╮ {/cyan-fg}
|
|
146
|
+
{cyan-fg} │ {/cyan-fg}{blue-fg}ZAI{/blue-fg}{cyan-fg} │ {/cyan-fg}
|
|
147
|
+
{cyan-fg} ╰┬───┬╯ {/cyan-fg}`,
|
|
148
|
+
// Frame 2 - looking left
|
|
149
|
+
`{cyan-fg} ╭───╮ {/cyan-fg}
|
|
150
|
+
{cyan-fg} │{/cyan-fg}{bold}{white-fg}◉ ◉{/white-fg}{/bold}{cyan-fg}│ {/cyan-fg}
|
|
151
|
+
{cyan-fg} │{/cyan-fg}{yellow-fg} ◡ {/yellow-fg}{cyan-fg}│ {/cyan-fg}
|
|
152
|
+
{cyan-fg} ╭┴───┴╮ {/cyan-fg}
|
|
153
|
+
{cyan-fg} │ {/cyan-fg}{blue-fg}ZAI{/blue-fg}{cyan-fg} │ {/cyan-fg}
|
|
154
|
+
{cyan-fg} ╰┬───┬╯ {/cyan-fg}`,
|
|
155
|
+
// Frame 3 - blinking
|
|
156
|
+
`{cyan-fg} ╭───╮ {/cyan-fg}
|
|
157
|
+
{cyan-fg} │{/cyan-fg}{bold}{white-fg}─ ─{/white-fg}{/bold}{cyan-fg}│ {/cyan-fg}
|
|
158
|
+
{cyan-fg} │{/cyan-fg}{yellow-fg} ◡ {/yellow-fg}{cyan-fg}│ {/cyan-fg}
|
|
159
|
+
{cyan-fg} ╭┴───┴╮ {/cyan-fg}
|
|
160
|
+
{cyan-fg} │ {/cyan-fg}{blue-fg}ZAI{/blue-fg}{cyan-fg} │ {/cyan-fg}
|
|
161
|
+
{cyan-fg} ╰┬───┬╯ {/cyan-fg}`,
|
|
162
|
+
// Frame 4 - happy
|
|
163
|
+
`{cyan-fg} ╭───╮ {/cyan-fg}
|
|
164
|
+
{cyan-fg} │{/cyan-fg}{bold}{white-fg}◠ ◠{/white-fg}{/bold}{cyan-fg}│ {/cyan-fg}
|
|
165
|
+
{cyan-fg} │{/cyan-fg}{yellow-fg} ◡ {/yellow-fg}{cyan-fg}│ {/cyan-fg}
|
|
166
|
+
{cyan-fg} ╭┴───┴╮ {/cyan-fg}
|
|
167
|
+
{cyan-fg} │ {/cyan-fg}{blue-fg}ZAI{/blue-fg}{cyan-fg} │ {/cyan-fg}
|
|
168
|
+
{cyan-fg} ╰┬───┬╯ {/cyan-fg}`,
|
|
169
|
+
];
|
|
170
|
+
// Compact header with mascot
|
|
171
|
+
const HEADER_WITH_MASCOT = `{bold}{cyan-fg}zai{/cyan-fg}{blue-fg}·{/blue-fg}{cyan-fg}code{/cyan-fg}{/bold} {gray-fg}v1.4.0{/gray-fg}`;
|
|
172
|
+
const MINIMAL_LOGO = '{bold}{cyan-fg}⚡ zai·code{/cyan-fg}{/bold} {gray-fg}AI-native editor{/gray-fg}';
|
|
173
|
+
// Welcome tips - rotate through these
|
|
174
|
+
const WELCOME_TIPS = [
|
|
175
|
+
'Type a task naturally, like "add error handling to auth.ts"',
|
|
176
|
+
'Use /do <task> for quick plan+generate in one step',
|
|
177
|
+
'Use /run <task> for full auto execution (YOLO mode)',
|
|
178
|
+
'Use /ask for quick questions without changing mode',
|
|
179
|
+
'Try /fix <problem> to quickly debug issues',
|
|
180
|
+
'/commit generates AI-powered commit messages',
|
|
181
|
+
'Use /mode auto for autonomous execution',
|
|
182
|
+
'Use ↑↓ to navigate commands, Tab to complete',
|
|
58
183
|
];
|
|
59
|
-
// ASCII Logo
|
|
60
|
-
const ASCII_LOGO = `
|
|
61
|
-
{bold}{blue-fg}███████╗{/blue-fg}{cyan-fg} █████╗ {/cyan-fg}{blue-fg}██╗{/blue-fg} {cyan-fg} ██████╗ ██████╗ ██████╗ ███████╗{/cyan-fg}{/bold}
|
|
62
|
-
{bold}{blue-fg}╚══███╔╝{/blue-fg}{cyan-fg}██╔══██╗{/cyan-fg}{blue-fg}██║{/blue-fg} {cyan-fg}██╔════╝██╔═══██╗██╔══██╗██╔════╝{/cyan-fg}{/bold}
|
|
63
|
-
{bold}{blue-fg} ███╔╝ {/blue-fg}{cyan-fg}███████║{/cyan-fg}{blue-fg}██║{/blue-fg} {cyan-fg}██║ ██║ ██║██║ ██║█████╗ {/cyan-fg}{/bold}
|
|
64
|
-
{bold}{blue-fg} ███╔╝ {/blue-fg}{cyan-fg}██╔══██║{/cyan-fg}{blue-fg}██║{/blue-fg} {cyan-fg}██║ ██║ ██║██║ ██║██╔══╝ {/cyan-fg}{/bold}
|
|
65
|
-
{bold}{blue-fg}███████╗{/blue-fg}{cyan-fg}██║ ██║{/cyan-fg}{blue-fg}██║{/blue-fg} {cyan-fg}╚██████╗╚██████╔╝██████╔╝███████╗{/cyan-fg}{/bold}
|
|
66
|
-
{bold}{blue-fg}╚══════╝{/blue-fg}{cyan-fg}╚═╝ ╚═╝{/cyan-fg}{blue-fg}╚═╝{/blue-fg} {cyan-fg} ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝{/cyan-fg}{/bold}
|
|
67
|
-
`;
|
|
68
|
-
const MINIMAL_LOGO = '{bold}{blue-fg}zai{/blue-fg} {cyan-fg}code{/cyan-fg}{/bold}';
|
|
69
184
|
async function startTUI(options) {
|
|
70
185
|
const { projectName, restored, onExit } = options;
|
|
71
186
|
const session = (0, session_1.getSession)();
|
|
@@ -82,48 +197,163 @@ async function startTUI(options) {
|
|
|
82
197
|
fullUnicode: true,
|
|
83
198
|
dockBorders: true,
|
|
84
199
|
autoPadding: true,
|
|
85
|
-
warnings: false,
|
|
200
|
+
warnings: false,
|
|
86
201
|
});
|
|
87
|
-
// Theme colors
|
|
202
|
+
// Theme colors - modern dark theme
|
|
88
203
|
const theme = {
|
|
89
204
|
bg: 'black',
|
|
90
205
|
fg: 'white',
|
|
91
206
|
border: 'blue',
|
|
92
207
|
highlight: 'cyan',
|
|
208
|
+
accent: 'magenta',
|
|
209
|
+
success: 'green',
|
|
210
|
+
warning: 'yellow',
|
|
211
|
+
error: 'red',
|
|
93
212
|
gray: 'gray'
|
|
94
213
|
};
|
|
95
|
-
//
|
|
214
|
+
// State for spinner and processing
|
|
215
|
+
let spinnerInterval = null;
|
|
216
|
+
let spinnerFrame = 0;
|
|
217
|
+
let isProcessing = false;
|
|
218
|
+
let currentTip = Math.floor(Math.random() * WELCOME_TIPS.length);
|
|
219
|
+
let mascotFrame = 0;
|
|
220
|
+
let mascotInterval = null;
|
|
221
|
+
// Header with animated mascot
|
|
96
222
|
const header = blessed.box({
|
|
97
223
|
top: 0,
|
|
98
224
|
left: 0,
|
|
99
225
|
width: '100%',
|
|
100
226
|
height: (0, settings_1.shouldShowLogo)() ? 8 : 2,
|
|
101
227
|
tags: true,
|
|
102
|
-
content: (0, settings_1.shouldShowLogo)() ? ASCII_LOGO : MINIMAL_LOGO,
|
|
103
228
|
style: {
|
|
104
229
|
fg: theme.fg,
|
|
105
230
|
bg: theme.bg,
|
|
106
231
|
},
|
|
107
232
|
});
|
|
108
|
-
//
|
|
109
|
-
|
|
233
|
+
// Update header with mascot animation
|
|
234
|
+
function updateHeader() {
|
|
235
|
+
if ((0, settings_1.shouldShowLogo)()) {
|
|
236
|
+
const mascot = MASCOT_FRAMES[mascotFrame];
|
|
237
|
+
const title = `{bold}{cyan-fg}zai{/cyan-fg}{blue-fg}·{/blue-fg}{cyan-fg}code{/cyan-fg}{/bold} {gray-fg}v1.4.0{/gray-fg}`;
|
|
238
|
+
const subtitle = '{gray-fg}AI-native code editor{/gray-fg}';
|
|
239
|
+
// Combine mascot with title
|
|
240
|
+
const mascotLines = mascot.split('\n');
|
|
241
|
+
const content = mascotLines.map((line, i) => {
|
|
242
|
+
if (i === 1)
|
|
243
|
+
return line + ' ' + title;
|
|
244
|
+
if (i === 2)
|
|
245
|
+
return line + ' ' + subtitle;
|
|
246
|
+
return line;
|
|
247
|
+
}).join('\n');
|
|
248
|
+
header.setContent(content);
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
header.setContent(MINIMAL_LOGO);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
// Start mascot animation
|
|
255
|
+
function startMascotAnimation() {
|
|
256
|
+
if (mascotInterval)
|
|
257
|
+
return;
|
|
258
|
+
mascotInterval = setInterval(() => {
|
|
259
|
+
mascotFrame = (mascotFrame + 1) % MASCOT_FRAMES.length;
|
|
260
|
+
updateHeader();
|
|
261
|
+
screen.render();
|
|
262
|
+
}, 2000); // Change every 2 seconds
|
|
263
|
+
}
|
|
264
|
+
// Stop mascot animation
|
|
265
|
+
function stopMascotAnimation() {
|
|
266
|
+
if (mascotInterval) {
|
|
267
|
+
clearInterval(mascotInterval);
|
|
268
|
+
mascotInterval = null;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
// Initialize header
|
|
272
|
+
updateHeader();
|
|
273
|
+
if ((0, settings_1.shouldShowLogo)()) {
|
|
274
|
+
startMascotAnimation();
|
|
275
|
+
}
|
|
276
|
+
// Quick actions bar - keyboard shortcuts
|
|
277
|
+
const quickActions = blessed.box({
|
|
110
278
|
top: (0, settings_1.shouldShowLogo)() ? 8 : 2,
|
|
111
279
|
left: 0,
|
|
112
280
|
width: '100%',
|
|
113
|
-
height:
|
|
281
|
+
height: 1,
|
|
282
|
+
tags: true,
|
|
283
|
+
content: '{gray-fg}↑↓{/gray-fg} navigate {gray-fg}Tab{/gray-fg} complete {gray-fg}Enter{/gray-fg} select {gray-fg}Esc{/gray-fg} close',
|
|
284
|
+
style: {
|
|
285
|
+
fg: theme.fg,
|
|
286
|
+
bg: theme.bg,
|
|
287
|
+
},
|
|
288
|
+
padding: { left: 1 },
|
|
289
|
+
});
|
|
290
|
+
// Context/status line
|
|
291
|
+
const contextTop = (0, settings_1.shouldShowLogo)() ? 9 : 3;
|
|
292
|
+
const contextLine = blessed.box({
|
|
293
|
+
top: contextTop,
|
|
294
|
+
left: 0,
|
|
295
|
+
width: '100%',
|
|
296
|
+
height: 1,
|
|
297
|
+
tags: true,
|
|
298
|
+
style: {
|
|
299
|
+
fg: theme.fg,
|
|
300
|
+
bg: theme.bg,
|
|
301
|
+
},
|
|
302
|
+
padding: { left: 1 },
|
|
303
|
+
});
|
|
304
|
+
// Update context line with current state
|
|
305
|
+
function updateContextLine() {
|
|
306
|
+
const gitInfo = (0, git_1.getGitInfo)(session.workingDirectory);
|
|
307
|
+
const model = (0, settings_1.getModel)();
|
|
308
|
+
const mode = session.mode;
|
|
309
|
+
const intent = (0, session_1.getIntent)();
|
|
310
|
+
const fileCount = session.openFiles.length;
|
|
311
|
+
let parts = [];
|
|
312
|
+
parts.push(`{bold}${projectName}{/bold}`);
|
|
313
|
+
if (gitInfo.isRepo) {
|
|
314
|
+
const dirty = gitInfo.isDirty ? '{yellow-fg}*{/yellow-fg}' : '';
|
|
315
|
+
parts.push(`{gray-fg}git:{/gray-fg}${gitInfo.branch}${dirty}`);
|
|
316
|
+
}
|
|
317
|
+
const modeColors = {
|
|
318
|
+
'auto': 'magenta', 'edit': 'cyan', 'ask': 'green',
|
|
319
|
+
'debug': 'red', 'review': 'yellow', 'explain': 'blue'
|
|
320
|
+
};
|
|
321
|
+
const modeColor = modeColors[mode] || 'cyan';
|
|
322
|
+
parts.push(`{${modeColor}-fg}${mode}{/${modeColor}-fg}`);
|
|
323
|
+
parts.push(`{gray-fg}${model}{/gray-fg}`);
|
|
324
|
+
if (fileCount > 0)
|
|
325
|
+
parts.push(`{gray-fg}${fileCount} file(s){/gray-fg}`);
|
|
326
|
+
if (intent) {
|
|
327
|
+
const truncated = intent.length > 30 ? intent.substring(0, 30) + '...' : intent;
|
|
328
|
+
parts.push(`{yellow-fg}→ ${truncated}{/yellow-fg}`);
|
|
329
|
+
}
|
|
330
|
+
contextLine.setContent(parts.join(' {gray-fg}│{/gray-fg} '));
|
|
331
|
+
}
|
|
332
|
+
// Tips section - smart suggestions
|
|
333
|
+
const tipsTop = contextTop + 1;
|
|
334
|
+
const tips = blessed.box({
|
|
335
|
+
top: tipsTop,
|
|
336
|
+
left: 0,
|
|
337
|
+
width: '100%',
|
|
338
|
+
height: 2,
|
|
114
339
|
tags: true,
|
|
115
|
-
content: `{bold}Tips for getting started:{/bold}
|
|
116
|
-
1. Type a task or question to begin.
|
|
117
|
-
2. Use {bold}/commands{/bold} for direct actions.
|
|
118
|
-
3. {bold}/help{/bold} for more information.`,
|
|
119
340
|
style: {
|
|
120
341
|
fg: theme.fg,
|
|
121
342
|
bg: theme.bg,
|
|
122
343
|
},
|
|
123
344
|
padding: { left: 1 },
|
|
124
345
|
});
|
|
346
|
+
function updateTips() {
|
|
347
|
+
const smartSuggestions = getSmartSuggestions();
|
|
348
|
+
if (smartSuggestions.length > 0) {
|
|
349
|
+
tips.setContent(`{gray-fg}💡 ${smartSuggestions.join(' │ ')}{/gray-fg}`);
|
|
350
|
+
}
|
|
351
|
+
else {
|
|
352
|
+
tips.setContent(`{gray-fg}💡 ${WELCOME_TIPS[currentTip]}{/gray-fg}`);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
125
355
|
// Warning box (if in home directory)
|
|
126
|
-
const warningTop =
|
|
356
|
+
const warningTop = tipsTop + 2;
|
|
127
357
|
const warning = blessed.box({
|
|
128
358
|
top: warningTop,
|
|
129
359
|
left: 0,
|
|
@@ -147,30 +377,15 @@ async function startTUI(options) {
|
|
|
147
377
|
// Check warnings
|
|
148
378
|
const gitInfo = (0, git_1.getGitInfo)(session.workingDirectory);
|
|
149
379
|
if (session.workingDirectory === require('os').homedir()) {
|
|
150
|
-
warning.setContent('
|
|
380
|
+
warning.setContent('{yellow-fg}⚠{/yellow-fg} Running in home directory. Consider using a project directory.');
|
|
151
381
|
warning.show();
|
|
152
382
|
}
|
|
153
383
|
else if (!gitInfo.isRepo) {
|
|
154
|
-
warning.setContent('Not a git repository. Changes cannot be tracked.');
|
|
384
|
+
warning.setContent('{yellow-fg}⚠{/yellow-fg} Not a git repository. Changes cannot be tracked.');
|
|
155
385
|
warning.show();
|
|
156
386
|
}
|
|
157
|
-
// Context line (Using: X files)
|
|
158
|
-
const contextTop = warning.hidden ? warningTop : warningTop + 3;
|
|
159
|
-
const context = blessed.box({
|
|
160
|
-
top: contextTop,
|
|
161
|
-
left: 0,
|
|
162
|
-
width: '100%',
|
|
163
|
-
height: 1,
|
|
164
|
-
tags: true,
|
|
165
|
-
content: `{bold}${projectName}{/bold} · ${session.openFiles.length || 0} file(s) in context`,
|
|
166
|
-
style: {
|
|
167
|
-
fg: theme.fg,
|
|
168
|
-
bg: theme.bg,
|
|
169
|
-
},
|
|
170
|
-
padding: { left: 1 },
|
|
171
|
-
});
|
|
172
387
|
// Main output area
|
|
173
|
-
const outputTop =
|
|
388
|
+
const outputTop = warning.hidden ? warningTop : warningTop + 3;
|
|
174
389
|
const output = blessed.log({
|
|
175
390
|
top: outputTop,
|
|
176
391
|
left: 0,
|
|
@@ -192,7 +407,38 @@ async function startTUI(options) {
|
|
|
192
407
|
},
|
|
193
408
|
padding: { left: 1, right: 1 },
|
|
194
409
|
});
|
|
195
|
-
//
|
|
410
|
+
// Processing spinner indicator
|
|
411
|
+
const processingIndicator = blessed.box({
|
|
412
|
+
bottom: 5,
|
|
413
|
+
right: 2,
|
|
414
|
+
width: 25,
|
|
415
|
+
height: 1,
|
|
416
|
+
tags: true,
|
|
417
|
+
style: {
|
|
418
|
+
fg: theme.highlight,
|
|
419
|
+
bg: theme.bg,
|
|
420
|
+
},
|
|
421
|
+
hidden: true,
|
|
422
|
+
});
|
|
423
|
+
function startSpinner(message = 'Processing') {
|
|
424
|
+
isProcessing = true;
|
|
425
|
+
processingIndicator.show();
|
|
426
|
+
spinnerInterval = setInterval(() => {
|
|
427
|
+
spinnerFrame = (spinnerFrame + 1) % SPINNER_FRAMES.length;
|
|
428
|
+
processingIndicator.setContent(`{cyan-fg}${SPINNER_FRAMES[spinnerFrame]} ${message}...{/cyan-fg}`);
|
|
429
|
+
screen.render();
|
|
430
|
+
}, 80);
|
|
431
|
+
}
|
|
432
|
+
function stopSpinner() {
|
|
433
|
+
isProcessing = false;
|
|
434
|
+
if (spinnerInterval) {
|
|
435
|
+
clearInterval(spinnerInterval);
|
|
436
|
+
spinnerInterval = null;
|
|
437
|
+
}
|
|
438
|
+
processingIndicator.hide();
|
|
439
|
+
screen.render();
|
|
440
|
+
}
|
|
441
|
+
// Input box container with mode indicator
|
|
196
442
|
const inputContainer = blessed.box({
|
|
197
443
|
bottom: 2,
|
|
198
444
|
left: 0,
|
|
@@ -209,18 +455,30 @@ async function startTUI(options) {
|
|
|
209
455
|
},
|
|
210
456
|
},
|
|
211
457
|
});
|
|
212
|
-
//
|
|
213
|
-
const
|
|
458
|
+
// Mode indicator icon
|
|
459
|
+
const modeIndicator = blessed.text({
|
|
214
460
|
parent: inputContainer,
|
|
215
461
|
left: 1,
|
|
216
462
|
top: 0,
|
|
217
|
-
content: `{bold}{blue-fg}❯{/blue-fg}{/bold}`,
|
|
218
463
|
tags: true,
|
|
219
464
|
style: {
|
|
220
465
|
bg: theme.bg
|
|
221
466
|
}
|
|
222
467
|
});
|
|
223
|
-
|
|
468
|
+
function updateModeIndicator() {
|
|
469
|
+
const mode = session.mode;
|
|
470
|
+
const modeColors = {
|
|
471
|
+
'auto': 'magenta', 'edit': 'cyan', 'ask': 'green',
|
|
472
|
+
'debug': 'red', 'review': 'yellow', 'explain': 'blue'
|
|
473
|
+
};
|
|
474
|
+
const color = modeColors[mode] || 'cyan';
|
|
475
|
+
const icons = {
|
|
476
|
+
'auto': '⚡', 'edit': '❯', 'ask': '?', 'debug': '🔧', 'review': '👁', 'explain': '📖'
|
|
477
|
+
};
|
|
478
|
+
const icon = icons[mode] || '❯';
|
|
479
|
+
modeIndicator.setContent(`{bold}{${color}-fg}${icon}{/${color}-fg}{/bold}`);
|
|
480
|
+
}
|
|
481
|
+
// Input textbox
|
|
224
482
|
const input = blessed.textbox({
|
|
225
483
|
parent: inputContainer,
|
|
226
484
|
left: 3,
|
|
@@ -235,9 +493,28 @@ async function startTUI(options) {
|
|
|
235
493
|
bg: theme.bg,
|
|
236
494
|
},
|
|
237
495
|
});
|
|
238
|
-
//
|
|
239
|
-
|
|
240
|
-
|
|
496
|
+
// Placeholder text
|
|
497
|
+
const placeholder = blessed.text({
|
|
498
|
+
parent: inputContainer,
|
|
499
|
+
left: 4,
|
|
500
|
+
top: 0,
|
|
501
|
+
tags: true,
|
|
502
|
+
style: {
|
|
503
|
+
fg: theme.gray,
|
|
504
|
+
bg: theme.bg,
|
|
505
|
+
},
|
|
506
|
+
});
|
|
507
|
+
function updatePlaceholder() {
|
|
508
|
+
const val = input.getValue();
|
|
509
|
+
if (!val || val.length === 0) {
|
|
510
|
+
placeholder.setContent(`{gray-fg}${getPlaceholder()}{/gray-fg}`);
|
|
511
|
+
placeholder.show();
|
|
512
|
+
}
|
|
513
|
+
else {
|
|
514
|
+
placeholder.hide();
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
// Status bar at bottom - more informative
|
|
241
518
|
const statusBar = blessed.box({
|
|
242
519
|
bottom: 0,
|
|
243
520
|
left: 0,
|
|
@@ -250,31 +527,44 @@ async function startTUI(options) {
|
|
|
250
527
|
},
|
|
251
528
|
padding: { left: 1, right: 1 },
|
|
252
529
|
});
|
|
253
|
-
// Update status bar
|
|
530
|
+
// Update status bar with state
|
|
254
531
|
function updateStatusBar() {
|
|
255
532
|
const model = (0, settings_1.getModel)();
|
|
256
533
|
const mode = (0, session_1.getSession)().mode;
|
|
257
534
|
const gitStatus = gitInfo.isRepo ? `${gitInfo.branch}${gitInfo.isDirty ? '*' : ''}` : 'no-git';
|
|
258
|
-
const
|
|
535
|
+
const dryRun = session.dryRun ? ' {yellow-fg}[DRY]{/yellow-fg}' : '';
|
|
536
|
+
// State indicator
|
|
537
|
+
let state = '{green-fg}ready{/green-fg}';
|
|
538
|
+
if (session.pendingActions || session.lastDiff) {
|
|
539
|
+
state = '{yellow-fg}pending{/yellow-fg}';
|
|
540
|
+
}
|
|
541
|
+
else if (session.lastPlan && session.lastPlan.length > 0) {
|
|
542
|
+
state = '{cyan-fg}planned{/cyan-fg}';
|
|
543
|
+
}
|
|
544
|
+
else if (session.currentIntent) {
|
|
545
|
+
state = '{blue-fg}intent{/blue-fg}';
|
|
546
|
+
}
|
|
547
|
+
const left = `{bold}[${mode}]{/bold} ${state}${dryRun}`;
|
|
259
548
|
const center = `${gitStatus}`;
|
|
260
|
-
const right = `{cyan-fg}${model}{/cyan-fg}`;
|
|
549
|
+
const right = `{cyan-fg}${model}{/cyan-fg} {gray-fg}/help{/gray-fg}`;
|
|
261
550
|
const width = screen.width || 80;
|
|
262
|
-
const padding = Math.max(0, Math.floor((width -
|
|
551
|
+
const padding = Math.max(0, Math.floor((width - 50) / 2));
|
|
263
552
|
statusBar.setContent(`${left}${' '.repeat(padding)}${center}${' '.repeat(padding)}${right}`);
|
|
264
553
|
}
|
|
265
|
-
// Command palette -
|
|
554
|
+
// Command palette - enhanced
|
|
266
555
|
const palette = blessed.list({
|
|
267
556
|
bottom: 5,
|
|
268
557
|
left: 1,
|
|
269
|
-
width:
|
|
270
|
-
height:
|
|
558
|
+
width: 55,
|
|
559
|
+
height: 12,
|
|
271
560
|
tags: true,
|
|
272
|
-
keys: false,
|
|
273
|
-
mouse: false,
|
|
274
|
-
interactive: false,
|
|
561
|
+
keys: false,
|
|
562
|
+
mouse: false,
|
|
563
|
+
interactive: false,
|
|
275
564
|
border: {
|
|
276
565
|
type: 'line',
|
|
277
566
|
},
|
|
567
|
+
label: ' Commands ',
|
|
278
568
|
style: {
|
|
279
569
|
fg: theme.fg,
|
|
280
570
|
bg: theme.bg,
|
|
@@ -290,44 +580,105 @@ async function startTUI(options) {
|
|
|
290
580
|
},
|
|
291
581
|
hidden: true,
|
|
292
582
|
});
|
|
583
|
+
// File autocomplete
|
|
584
|
+
const fileAutocomplete = blessed.list({
|
|
585
|
+
bottom: 5,
|
|
586
|
+
left: 1,
|
|
587
|
+
width: 60,
|
|
588
|
+
height: 10,
|
|
589
|
+
tags: true,
|
|
590
|
+
keys: false,
|
|
591
|
+
mouse: false,
|
|
592
|
+
interactive: false,
|
|
593
|
+
border: {
|
|
594
|
+
type: 'line',
|
|
595
|
+
},
|
|
596
|
+
label: ' Files ',
|
|
597
|
+
style: {
|
|
598
|
+
fg: theme.fg,
|
|
599
|
+
bg: theme.bg,
|
|
600
|
+
border: {
|
|
601
|
+
fg: theme.success,
|
|
602
|
+
bg: theme.bg
|
|
603
|
+
},
|
|
604
|
+
selected: {
|
|
605
|
+
bg: theme.success,
|
|
606
|
+
fg: theme.bg,
|
|
607
|
+
bold: true,
|
|
608
|
+
},
|
|
609
|
+
},
|
|
610
|
+
hidden: true,
|
|
611
|
+
});
|
|
293
612
|
// Add all elements to screen
|
|
294
613
|
screen.append(header);
|
|
614
|
+
screen.append(quickActions);
|
|
615
|
+
screen.append(contextLine);
|
|
295
616
|
screen.append(tips);
|
|
296
617
|
screen.append(warning);
|
|
297
|
-
screen.append(context);
|
|
298
618
|
screen.append(output);
|
|
619
|
+
screen.append(processingIndicator);
|
|
299
620
|
screen.append(inputContainer);
|
|
300
621
|
screen.append(statusBar);
|
|
301
622
|
screen.append(palette);
|
|
623
|
+
screen.append(fileAutocomplete);
|
|
302
624
|
// Initial render
|
|
625
|
+
updateContextLine();
|
|
626
|
+
updateTips();
|
|
303
627
|
updateStatusBar();
|
|
628
|
+
updateModeIndicator();
|
|
629
|
+
updatePlaceholder();
|
|
304
630
|
input.focus();
|
|
305
631
|
screen.render();
|
|
306
|
-
//
|
|
632
|
+
// Welcome messages
|
|
307
633
|
if (restored) {
|
|
308
|
-
output.log('{
|
|
634
|
+
output.log('{green-fg}✓{/green-fg} Session restored');
|
|
635
|
+
if (session.currentIntent) {
|
|
636
|
+
output.log(`{gray-fg} Task: ${session.currentIntent.substring(0, 60)}...{/gray-fg}`);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
else {
|
|
640
|
+
output.log('{cyan-fg}Welcome to zai·code!{/cyan-fg} {gray-fg}Type a task or /help{/gray-fg}');
|
|
309
641
|
}
|
|
310
642
|
// agents.md notice
|
|
311
643
|
if ((0, agents_1.hasAgentsConfig)(session.workingDirectory)) {
|
|
312
|
-
output.log('{green-fg}
|
|
644
|
+
output.log('{green-fg}✓{/green-fg} agents.md detected');
|
|
313
645
|
}
|
|
646
|
+
output.log('');
|
|
314
647
|
// --- LOGIC ---
|
|
315
648
|
let showPalette = false;
|
|
649
|
+
let showFileAutocomplete = false;
|
|
650
|
+
let autocompleteFiles = [];
|
|
316
651
|
function updatePalette(filter) {
|
|
317
652
|
const query = filter.replace(/^\//, '').toLowerCase();
|
|
318
|
-
const filtered = COMMANDS.filter(c => c.name.startsWith(query));
|
|
319
|
-
|
|
320
|
-
|
|
653
|
+
const filtered = COMMANDS.filter(c => c.name.startsWith(query) || c.description.toLowerCase().includes(query));
|
|
654
|
+
const items = filtered.slice(0, 10).map(c => {
|
|
655
|
+
const shortcut = c.shortcut ? ` {gray-fg}${c.shortcut}{/gray-fg}` : '';
|
|
656
|
+
return `{bold}{cyan-fg}/${c.name}{/cyan-fg}{/bold} ${c.description}${shortcut}`;
|
|
657
|
+
});
|
|
658
|
+
palette.setItems(items);
|
|
321
659
|
if (filtered.length > 0) {
|
|
322
660
|
palette.select(0);
|
|
323
661
|
}
|
|
324
662
|
}
|
|
663
|
+
function updateFileAutocomplete(query) {
|
|
664
|
+
const ws = (0, workspace_model_1.getWorkspace)(session.workingDirectory);
|
|
665
|
+
ws.indexFileTree();
|
|
666
|
+
const files = ws.getFileIndex();
|
|
667
|
+
const pattern = query.toLowerCase();
|
|
668
|
+
autocompleteFiles = files
|
|
669
|
+
.filter(f => f.path.toLowerCase().includes(pattern))
|
|
670
|
+
.slice(0, 10)
|
|
671
|
+
.map(f => f.path);
|
|
672
|
+
fileAutocomplete.setItems(autocompleteFiles.map(f => `{green-fg}📄{/green-fg} ${f}`));
|
|
673
|
+
if (autocompleteFiles.length > 0) {
|
|
674
|
+
fileAutocomplete.select(0);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
325
677
|
function togglePalette(show, filter = '/') {
|
|
326
678
|
showPalette = show;
|
|
327
679
|
if (show) {
|
|
328
680
|
updatePalette(filter);
|
|
329
681
|
palette.show();
|
|
330
|
-
// CRITICAL: Do NOT focus palette. Keep input focused.
|
|
331
682
|
screen.render();
|
|
332
683
|
}
|
|
333
684
|
else {
|
|
@@ -335,11 +686,33 @@ async function startTUI(options) {
|
|
|
335
686
|
screen.render();
|
|
336
687
|
}
|
|
337
688
|
}
|
|
689
|
+
function toggleFileAutocomplete(show, query = '') {
|
|
690
|
+
showFileAutocomplete = show;
|
|
691
|
+
if (show && query) {
|
|
692
|
+
updateFileAutocomplete(query);
|
|
693
|
+
fileAutocomplete.show();
|
|
694
|
+
screen.render();
|
|
695
|
+
}
|
|
696
|
+
else {
|
|
697
|
+
fileAutocomplete.hide();
|
|
698
|
+
screen.render();
|
|
699
|
+
}
|
|
700
|
+
}
|
|
338
701
|
// Process input
|
|
339
702
|
async function processInput(value) {
|
|
340
703
|
if (!value.trim())
|
|
341
704
|
return;
|
|
342
|
-
|
|
705
|
+
// Add to command history
|
|
706
|
+
commandHistory.unshift(value);
|
|
707
|
+
if (commandHistory.length > 50)
|
|
708
|
+
commandHistory.pop();
|
|
709
|
+
historyIndex = -1;
|
|
710
|
+
// Format input display
|
|
711
|
+
const isCommand = value.startsWith('/');
|
|
712
|
+
const inputDisplay = isCommand
|
|
713
|
+
? `{cyan-fg}❯{/cyan-fg} {bold}${value}{/bold}`
|
|
714
|
+
: `{cyan-fg}❯{/cyan-fg} ${value}`;
|
|
715
|
+
output.log(inputDisplay);
|
|
343
716
|
screen.render();
|
|
344
717
|
const trimmed = value.trim();
|
|
345
718
|
if (trimmed === '/exit' || trimmed === 'exit' || trimmed === 'quit') {
|
|
@@ -370,62 +743,189 @@ async function startTUI(options) {
|
|
|
370
743
|
screen.render();
|
|
371
744
|
};
|
|
372
745
|
try {
|
|
373
|
-
|
|
374
|
-
|
|
746
|
+
// Determine spinner message based on input
|
|
747
|
+
let spinnerMsg = 'Processing';
|
|
748
|
+
if (trimmed.startsWith('/plan') || trimmed === '/p')
|
|
749
|
+
spinnerMsg = 'Planning';
|
|
750
|
+
else if (trimmed.startsWith('/generate') || trimmed === '/g')
|
|
751
|
+
spinnerMsg = 'Generating';
|
|
752
|
+
else if (trimmed.startsWith('/apply') || trimmed === '/a')
|
|
753
|
+
spinnerMsg = 'Applying';
|
|
754
|
+
else if (trimmed.startsWith('/do '))
|
|
755
|
+
spinnerMsg = 'Executing';
|
|
756
|
+
else if (trimmed.startsWith('/run '))
|
|
757
|
+
spinnerMsg = 'Running';
|
|
758
|
+
else if (trimmed.startsWith('/ask') || trimmed.startsWith('/commit'))
|
|
759
|
+
spinnerMsg = 'Thinking';
|
|
760
|
+
else if (!trimmed.startsWith('/'))
|
|
761
|
+
spinnerMsg = 'Thinking';
|
|
762
|
+
startSpinner(spinnerMsg);
|
|
375
763
|
await (0, orchestrator_1.orchestrate)(value);
|
|
764
|
+
stopSpinner();
|
|
376
765
|
}
|
|
377
766
|
catch (e) {
|
|
378
|
-
|
|
767
|
+
stopSpinner();
|
|
768
|
+
output.log(`{red-fg}✗ Error: ${e?.message || e}{/red-fg}`);
|
|
379
769
|
}
|
|
380
770
|
console.log = originalLog;
|
|
381
771
|
console.error = originalError;
|
|
382
772
|
console.warn = originalWarn;
|
|
773
|
+
// Update all UI elements
|
|
774
|
+
updateContextLine();
|
|
775
|
+
updateTips();
|
|
383
776
|
updateStatusBar();
|
|
777
|
+
updateModeIndicator();
|
|
778
|
+
updatePlaceholder();
|
|
779
|
+
// Rotate tip
|
|
780
|
+
currentTip = (currentTip + 1) % WELCOME_TIPS.length;
|
|
781
|
+
output.log('');
|
|
384
782
|
screen.render();
|
|
385
783
|
}
|
|
386
784
|
// Input events
|
|
387
785
|
input.on('focus', () => {
|
|
786
|
+
updatePlaceholder();
|
|
388
787
|
screen.render();
|
|
389
788
|
});
|
|
390
789
|
input.on('blur', () => {
|
|
391
790
|
screen.render();
|
|
392
791
|
});
|
|
393
|
-
//
|
|
394
|
-
|
|
395
|
-
|
|
792
|
+
// Track palette selection index manually
|
|
793
|
+
let paletteSelectedIndex = 0;
|
|
794
|
+
let fileSelectedIndex = 0;
|
|
795
|
+
// Screen-level key handling for arrow keys (works better than input keypress)
|
|
796
|
+
screen.key(['up'], () => {
|
|
797
|
+
if (showPalette) {
|
|
798
|
+
paletteSelectedIndex = Math.max(0, paletteSelectedIndex - 1);
|
|
799
|
+
palette.select(paletteSelectedIndex);
|
|
396
800
|
screen.render();
|
|
397
801
|
return;
|
|
398
802
|
}
|
|
399
|
-
if (
|
|
400
|
-
|
|
803
|
+
if (showFileAutocomplete) {
|
|
804
|
+
fileSelectedIndex = Math.max(0, fileSelectedIndex - 1);
|
|
805
|
+
fileAutocomplete.select(fileSelectedIndex);
|
|
806
|
+
screen.render();
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
809
|
+
// Command history navigation
|
|
810
|
+
if (commandHistory.length > 0 && historyIndex < commandHistory.length - 1) {
|
|
811
|
+
historyIndex++;
|
|
812
|
+
input.setValue(commandHistory[historyIndex]);
|
|
813
|
+
updatePlaceholder();
|
|
814
|
+
screen.render();
|
|
815
|
+
}
|
|
816
|
+
});
|
|
817
|
+
screen.key(['down'], () => {
|
|
818
|
+
if (showPalette) {
|
|
819
|
+
const maxIndex = Math.min(COMMANDS.length - 1, 9);
|
|
820
|
+
paletteSelectedIndex = Math.min(maxIndex, paletteSelectedIndex + 1);
|
|
821
|
+
palette.select(paletteSelectedIndex);
|
|
822
|
+
screen.render();
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
825
|
+
if (showFileAutocomplete) {
|
|
826
|
+
const maxIndex = Math.min(autocompleteFiles.length - 1, 9);
|
|
827
|
+
fileSelectedIndex = Math.min(maxIndex, fileSelectedIndex + 1);
|
|
828
|
+
fileAutocomplete.select(fileSelectedIndex);
|
|
829
|
+
screen.render();
|
|
830
|
+
return;
|
|
831
|
+
}
|
|
832
|
+
// Command history navigation (go back to newer)
|
|
833
|
+
if (historyIndex > 0) {
|
|
834
|
+
historyIndex--;
|
|
835
|
+
input.setValue(commandHistory[historyIndex]);
|
|
836
|
+
updatePlaceholder();
|
|
837
|
+
screen.render();
|
|
838
|
+
}
|
|
839
|
+
else if (historyIndex === 0) {
|
|
840
|
+
historyIndex = -1;
|
|
841
|
+
input.setValue('');
|
|
842
|
+
updatePlaceholder();
|
|
843
|
+
screen.render();
|
|
844
|
+
}
|
|
845
|
+
});
|
|
846
|
+
// Tab completion
|
|
847
|
+
screen.key(['tab'], () => {
|
|
848
|
+
if (showPalette) {
|
|
849
|
+
const filter = (input.getValue() || '').replace(/^\//, '').toLowerCase();
|
|
850
|
+
const filtered = COMMANDS.filter(c => c.name.startsWith(filter) || c.description.toLowerCase().includes(filter));
|
|
851
|
+
if (filtered[paletteSelectedIndex]) {
|
|
852
|
+
const cmd = filtered[paletteSelectedIndex];
|
|
853
|
+
input.setValue('/' + cmd.name + ' ');
|
|
401
854
|
togglePalette(false);
|
|
855
|
+
paletteSelectedIndex = 0;
|
|
856
|
+
updatePlaceholder();
|
|
857
|
+
input.focus();
|
|
858
|
+
screen.render();
|
|
402
859
|
}
|
|
403
|
-
screen.render();
|
|
404
860
|
return;
|
|
405
861
|
}
|
|
406
|
-
if (
|
|
407
|
-
|
|
408
|
-
|
|
862
|
+
if (showFileAutocomplete && autocompleteFiles.length > 0) {
|
|
863
|
+
const selectedFile = autocompleteFiles[fileSelectedIndex];
|
|
864
|
+
if (selectedFile) {
|
|
865
|
+
const currentVal = input.getValue() || '';
|
|
866
|
+
const parts = currentVal.split(' ');
|
|
867
|
+
parts[parts.length - 1] = selectedFile;
|
|
868
|
+
input.setValue(parts.join(' '));
|
|
869
|
+
toggleFileAutocomplete(false);
|
|
870
|
+
fileSelectedIndex = 0;
|
|
871
|
+
updatePlaceholder();
|
|
872
|
+
input.focus();
|
|
409
873
|
screen.render();
|
|
410
|
-
return;
|
|
411
874
|
}
|
|
412
875
|
}
|
|
413
|
-
|
|
876
|
+
});
|
|
877
|
+
// Manual keypress handling for other keys
|
|
878
|
+
input.on('keypress', (ch, key) => {
|
|
879
|
+
if (!key) {
|
|
880
|
+
updatePlaceholder();
|
|
881
|
+
screen.render();
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
if (key.name === 'escape') {
|
|
414
885
|
if (showPalette) {
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
return;
|
|
886
|
+
togglePalette(false);
|
|
887
|
+
paletteSelectedIndex = 0;
|
|
418
888
|
}
|
|
889
|
+
if (showFileAutocomplete) {
|
|
890
|
+
toggleFileAutocomplete(false);
|
|
891
|
+
fileSelectedIndex = 0;
|
|
892
|
+
}
|
|
893
|
+
screen.render();
|
|
894
|
+
return;
|
|
419
895
|
}
|
|
420
896
|
// Check input content on next tick to see if we should show palette
|
|
421
897
|
setImmediate(() => {
|
|
422
898
|
const val = input.getValue();
|
|
899
|
+
updatePlaceholder();
|
|
423
900
|
if (val && val.startsWith('/')) {
|
|
901
|
+
toggleFileAutocomplete(false);
|
|
902
|
+
fileSelectedIndex = 0;
|
|
903
|
+
// Reset palette selection when filter changes
|
|
904
|
+
paletteSelectedIndex = 0;
|
|
424
905
|
togglePalette(true, val);
|
|
906
|
+
// Check for file commands that need autocomplete
|
|
907
|
+
const fileCommands = ['/open ', '/read ', '/cat ', '/close '];
|
|
908
|
+
for (const cmd of fileCommands) {
|
|
909
|
+
if (val.startsWith(cmd)) {
|
|
910
|
+
const query = val.substring(cmd.length);
|
|
911
|
+
if (query.length > 0) {
|
|
912
|
+
togglePalette(false);
|
|
913
|
+
paletteSelectedIndex = 0;
|
|
914
|
+
toggleFileAutocomplete(true, query);
|
|
915
|
+
}
|
|
916
|
+
break;
|
|
917
|
+
}
|
|
918
|
+
}
|
|
425
919
|
}
|
|
426
920
|
else {
|
|
427
|
-
if (showPalette)
|
|
921
|
+
if (showPalette) {
|
|
428
922
|
togglePalette(false);
|
|
923
|
+
paletteSelectedIndex = 0;
|
|
924
|
+
}
|
|
925
|
+
if (showFileAutocomplete) {
|
|
926
|
+
toggleFileAutocomplete(false);
|
|
927
|
+
fileSelectedIndex = 0;
|
|
928
|
+
}
|
|
429
929
|
}
|
|
430
930
|
screen.render();
|
|
431
931
|
});
|
|
@@ -434,26 +934,53 @@ async function startTUI(options) {
|
|
|
434
934
|
input.on('submit', async (value) => {
|
|
435
935
|
const inputValue = value || input.getValue() || '';
|
|
436
936
|
if (showPalette && inputValue.startsWith('/')) {
|
|
437
|
-
const selectedIndex = palette.selected || 0;
|
|
438
937
|
const filter = inputValue.replace(/^\//, '').toLowerCase();
|
|
439
|
-
const filteredCommands = COMMANDS.filter(c => c.name.startsWith(filter));
|
|
440
|
-
if (filteredCommands[
|
|
441
|
-
|
|
442
|
-
const
|
|
443
|
-
|
|
444
|
-
if (hasArgs && !inputValue.includes(' ')) {
|
|
445
|
-
// Command needs args - autocomplete and wait for user to type args
|
|
938
|
+
const filteredCommands = COMMANDS.filter(c => c.name.startsWith(filter) || c.description.toLowerCase().includes(filter));
|
|
939
|
+
if (filteredCommands[paletteSelectedIndex]) {
|
|
940
|
+
const cmd = filteredCommands[paletteSelectedIndex];
|
|
941
|
+
const needsArgs = ['mode', 'model', 'open', 'read', 'cat', 'close', 'do', 'run', 'ask', 'fix', 'exec', 'search'].includes(cmd.name);
|
|
942
|
+
if (needsArgs && !inputValue.includes(' ')) {
|
|
446
943
|
input.setValue('/' + cmd.name + ' ');
|
|
447
944
|
togglePalette(false);
|
|
945
|
+
paletteSelectedIndex = 0;
|
|
448
946
|
input.focus();
|
|
947
|
+
updatePlaceholder();
|
|
449
948
|
screen.render();
|
|
450
949
|
return;
|
|
451
950
|
}
|
|
452
951
|
}
|
|
453
952
|
}
|
|
953
|
+
// Handle file autocomplete selection
|
|
954
|
+
if (showFileAutocomplete && autocompleteFiles.length > 0) {
|
|
955
|
+
const selectedFile = autocompleteFiles[fileSelectedIndex];
|
|
956
|
+
if (selectedFile) {
|
|
957
|
+
const currentVal = inputValue;
|
|
958
|
+
const parts = currentVal.split(' ');
|
|
959
|
+
parts[parts.length - 1] = selectedFile;
|
|
960
|
+
const newValue = parts.join(' ');
|
|
961
|
+
input.clearValue();
|
|
962
|
+
toggleFileAutocomplete(false);
|
|
963
|
+
togglePalette(false);
|
|
964
|
+
fileSelectedIndex = 0;
|
|
965
|
+
paletteSelectedIndex = 0;
|
|
966
|
+
updatePlaceholder();
|
|
967
|
+
screen.render();
|
|
968
|
+
if (newValue && newValue.trim()) {
|
|
969
|
+
await processInput(newValue);
|
|
970
|
+
}
|
|
971
|
+
input.focus();
|
|
972
|
+
updatePlaceholder();
|
|
973
|
+
screen.render();
|
|
974
|
+
return;
|
|
975
|
+
}
|
|
976
|
+
}
|
|
454
977
|
// Clear input and palette before processing
|
|
455
978
|
input.clearValue();
|
|
456
979
|
togglePalette(false);
|
|
980
|
+
toggleFileAutocomplete(false);
|
|
981
|
+
paletteSelectedIndex = 0;
|
|
982
|
+
fileSelectedIndex = 0;
|
|
983
|
+
updatePlaceholder();
|
|
457
984
|
screen.render();
|
|
458
985
|
// Process the input
|
|
459
986
|
if (inputValue && inputValue.trim()) {
|
|
@@ -461,6 +988,7 @@ async function startTUI(options) {
|
|
|
461
988
|
}
|
|
462
989
|
// Refocus input for next command
|
|
463
990
|
input.focus();
|
|
991
|
+
updatePlaceholder();
|
|
464
992
|
screen.render();
|
|
465
993
|
});
|
|
466
994
|
// SETTINGS MODAL
|
|
@@ -583,12 +1111,82 @@ async function startTUI(options) {
|
|
|
583
1111
|
}
|
|
584
1112
|
// Global Key Bindings
|
|
585
1113
|
screen.key(['C-c'], () => {
|
|
1114
|
+
stopMascotAnimation();
|
|
586
1115
|
onExit?.();
|
|
587
1116
|
return process.exit(0);
|
|
588
1117
|
});
|
|
1118
|
+
// Quick action shortcuts
|
|
1119
|
+
screen.key(['C-d'], async () => {
|
|
1120
|
+
// Quick /do - need to prompt for task
|
|
1121
|
+
input.setValue('/do ');
|
|
1122
|
+
input.focus();
|
|
1123
|
+
updatePlaceholder();
|
|
1124
|
+
screen.render();
|
|
1125
|
+
});
|
|
1126
|
+
screen.key(['C-r'], async () => {
|
|
1127
|
+
// Quick /run
|
|
1128
|
+
input.setValue('/run ');
|
|
1129
|
+
input.focus();
|
|
1130
|
+
updatePlaceholder();
|
|
1131
|
+
screen.render();
|
|
1132
|
+
});
|
|
1133
|
+
screen.key(['C-p'], async () => {
|
|
1134
|
+
// Quick /plan
|
|
1135
|
+
if (screen.focused === input && !input.getValue()) {
|
|
1136
|
+
input.clearValue();
|
|
1137
|
+
togglePalette(false);
|
|
1138
|
+
updatePlaceholder();
|
|
1139
|
+
screen.render();
|
|
1140
|
+
await processInput('/plan');
|
|
1141
|
+
input.focus();
|
|
1142
|
+
updatePlaceholder();
|
|
1143
|
+
screen.render();
|
|
1144
|
+
}
|
|
1145
|
+
});
|
|
1146
|
+
screen.key(['C-g'], async () => {
|
|
1147
|
+
// Quick /generate
|
|
1148
|
+
if (screen.focused === input && !input.getValue()) {
|
|
1149
|
+
input.clearValue();
|
|
1150
|
+
togglePalette(false);
|
|
1151
|
+
updatePlaceholder();
|
|
1152
|
+
screen.render();
|
|
1153
|
+
await processInput('/generate');
|
|
1154
|
+
input.focus();
|
|
1155
|
+
updatePlaceholder();
|
|
1156
|
+
screen.render();
|
|
1157
|
+
}
|
|
1158
|
+
});
|
|
1159
|
+
screen.key(['C-z'], async () => {
|
|
1160
|
+
// Quick /undo
|
|
1161
|
+
if (screen.focused === input && !input.getValue()) {
|
|
1162
|
+
input.clearValue();
|
|
1163
|
+
togglePalette(false);
|
|
1164
|
+
updatePlaceholder();
|
|
1165
|
+
screen.render();
|
|
1166
|
+
await processInput('/undo');
|
|
1167
|
+
input.focus();
|
|
1168
|
+
updatePlaceholder();
|
|
1169
|
+
screen.render();
|
|
1170
|
+
}
|
|
1171
|
+
});
|
|
1172
|
+
screen.key(['C-a'], async () => {
|
|
1173
|
+
// Quick /ask
|
|
1174
|
+
input.setValue('/ask ');
|
|
1175
|
+
input.focus();
|
|
1176
|
+
updatePlaceholder();
|
|
1177
|
+
screen.render();
|
|
1178
|
+
});
|
|
1179
|
+
screen.key(['C-f'], async () => {
|
|
1180
|
+
// Quick /fix
|
|
1181
|
+
input.setValue('/fix ');
|
|
1182
|
+
input.focus();
|
|
1183
|
+
updatePlaceholder();
|
|
1184
|
+
screen.render();
|
|
1185
|
+
});
|
|
589
1186
|
screen.key(['q'], () => {
|
|
590
1187
|
// Only quit if not focused on input
|
|
591
1188
|
if (screen.focused !== input) {
|
|
1189
|
+
stopMascotAnimation();
|
|
592
1190
|
onExit?.();
|
|
593
1191
|
return process.exit(0);
|
|
594
1192
|
}
|
|
@@ -601,6 +1199,10 @@ async function startTUI(options) {
|
|
|
601
1199
|
output.log(`{red-fg}Error: ${err.message}{/red-fg}`);
|
|
602
1200
|
screen.render();
|
|
603
1201
|
});
|
|
1202
|
+
// Cleanup on exit
|
|
1203
|
+
process.on('exit', () => {
|
|
1204
|
+
stopMascotAnimation();
|
|
1205
|
+
});
|
|
604
1206
|
screen.render();
|
|
605
1207
|
}
|
|
606
1208
|
//# sourceMappingURL=tui.js.map
|