@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.
Files changed (67) hide show
  1. package/README.md +85 -154
  2. package/dist/apply.d.ts.map +1 -1
  3. package/dist/apply.js +43 -16
  4. package/dist/apply.js.map +1 -1
  5. package/dist/auth.d.ts.map +1 -1
  6. package/dist/auth.js +5 -1
  7. package/dist/auth.js.map +1 -1
  8. package/dist/commands.d.ts.map +1 -1
  9. package/dist/commands.js +1021 -47
  10. package/dist/commands.js.map +1 -1
  11. package/dist/context/context_builder.d.ts.map +1 -1
  12. package/dist/context/context_builder.js +18 -3
  13. package/dist/context/context_builder.js.map +1 -1
  14. package/dist/context/project_memory.d.ts +2 -2
  15. package/dist/context/project_memory.d.ts.map +1 -1
  16. package/dist/context/project_memory.js +14 -4
  17. package/dist/context/project_memory.js.map +1 -1
  18. package/dist/doctor.d.ts.map +1 -1
  19. package/dist/doctor.js +51 -7
  20. package/dist/doctor.js.map +1 -1
  21. package/dist/history.d.ts.map +1 -1
  22. package/dist/history.js +13 -8
  23. package/dist/history.js.map +1 -1
  24. package/dist/mode_prompts.d.ts.map +1 -1
  25. package/dist/mode_prompts.js +25 -22
  26. package/dist/mode_prompts.js.map +1 -1
  27. package/dist/orchestrator.d.ts.map +1 -1
  28. package/dist/orchestrator.js +69 -11
  29. package/dist/orchestrator.js.map +1 -1
  30. package/dist/planner.js +18 -18
  31. package/dist/planner.js.map +1 -1
  32. package/dist/rollback.d.ts +1 -0
  33. package/dist/rollback.d.ts.map +1 -1
  34. package/dist/rollback.js +28 -4
  35. package/dist/rollback.js.map +1 -1
  36. package/dist/runtime.d.ts.map +1 -1
  37. package/dist/runtime.js +22 -9
  38. package/dist/runtime.js.map +1 -1
  39. package/dist/session.d.ts +1 -0
  40. package/dist/session.d.ts.map +1 -1
  41. package/dist/session.js +8 -1
  42. package/dist/session.js.map +1 -1
  43. package/dist/settings.d.ts +1 -0
  44. package/dist/settings.d.ts.map +1 -1
  45. package/dist/settings.js +8 -3
  46. package/dist/settings.js.map +1 -1
  47. package/dist/shell.d.ts.map +1 -1
  48. package/dist/shell.js +85 -14
  49. package/dist/shell.js.map +1 -1
  50. package/dist/task_runner.d.ts.map +1 -1
  51. package/dist/task_runner.js +20 -2
  52. package/dist/task_runner.js.map +1 -1
  53. package/dist/tui.d.ts.map +1 -1
  54. package/dist/tui.js +709 -107
  55. package/dist/tui.js.map +1 -1
  56. package/dist/ui.d.ts +7 -0
  57. package/dist/ui.d.ts.map +1 -1
  58. package/dist/ui.js +113 -11
  59. package/dist/ui.js.map +1 -1
  60. package/dist/workspace.d.ts.map +1 -1
  61. package/dist/workspace.js +6 -0
  62. package/dist/workspace.js.map +1 -1
  63. package/dist/workspace_model.d.ts +1 -1
  64. package/dist/workspace_model.d.ts.map +1 -1
  65. package/dist/workspace_model.js +8 -1
  66. package/dist/workspace_model.js.map +1 -1
  67. 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
- { name: 'help', description: 'Show all commands' },
46
- { name: 'ask', description: 'Switch to ask mode (read-only)' },
47
- { name: 'plan', description: 'Generate execution plan' },
48
- { name: 'generate', description: 'Create file changes' },
49
- { name: 'diff', description: 'Review pending changes' },
50
- { name: 'apply', description: 'Apply changes' },
51
- { name: 'undo', description: 'Rollback last operation' },
52
- { name: 'model', description: 'Select AI model' },
53
- { name: 'mode', description: 'Set mode' },
54
- { name: 'settings', description: 'Open settings menu' },
55
- { name: 'git', description: 'Show git status' },
56
- { name: 'reset', description: 'Reset session' },
57
- { name: 'exit', description: 'Exit zcode' },
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, // Suppress blessed warnings
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
- // Header with logo
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
- // Tips section
109
- const tips = blessed.box({
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: 4,
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 = (0, settings_1.shouldShowLogo)() ? 12 : 6;
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('You are running in your home directory. It is recommended to run in a project-specific directory.');
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 = contextTop + 2;
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
- // Input box container
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
- // Input prompt symbol
213
- const inputPrompt = blessed.text({
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
- // Input textbox - higher z-index to be on top
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
- // NOTE: Removed placeholder text element entirely to prevent overlap issues
239
- // The tips section already explains how to use the interface
240
- // Status bar at bottom
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 left = `{bold}[${mode}]{/bold}`;
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 - 40) / 2));
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 - NO keys/mouse to prevent stealing focus
554
+ // Command palette - enhanced
266
555
  const palette = blessed.list({
267
556
  bottom: 5,
268
557
  left: 1,
269
- width: 40,
270
- height: 10,
558
+ width: 55,
559
+ height: 12,
271
560
  tags: true,
272
- keys: false, // CRITICAL: Don't let palette handle keys
273
- mouse: false, // CRITICAL: Don't let palette handle mouse
274
- interactive: false, // Not interactive - we handle navigation manually
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
- // Restored message
632
+ // Welcome messages
307
633
  if (restored) {
308
- output.log('{gray-fg}Session restored.{/gray-fg}');
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}agents.md detected and applied{/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
- palette.setItems(filtered.map(c => `{bold}/${c.name}{/bold} {gray-fg}${c.description}{/gray-fg}`));
320
- // Reset selection to top on new filter
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
- output.log(`{cyan-fg}>{/cyan-fg} ${value}`);
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
- output.log('{gray-fg}Processing...{/gray-fg}');
374
- screen.render();
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
- output.log(`{red-fg}Error: ${e?.message || e}{/red-fg}`);
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
- // Manual keypress handling for Palette interaction
394
- input.on('keypress', (ch, key) => {
395
- if (!key) {
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 (key.name === 'escape') {
400
- if (showPalette) {
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 (key.name === 'down') {
407
- if (showPalette) {
408
- palette.down(1);
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
- if (key.name === 'up') {
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
- palette.up(1);
416
- screen.render();
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[selectedIndex]) {
441
- // Check if command needs arguments
442
- const cmd = filteredCommands[selectedIndex];
443
- const hasArgs = ['mode', 'model', 'open', 'file', 'exec'].includes(cmd.name);
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