@staticpayload/zai-code 1.2.14 → 1.4.0

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 +107 -39
  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 +566 -88
  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,125 @@ 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' },
58
81
  ];
59
- // ASCII Logo
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
+ // ASCII Logo - cleaner, more modern
60
140
  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}
141
+ {bold}{cyan-fg}╭─────────────────────────────────────╮{/cyan-fg}{/bold}
142
+ {bold}{cyan-fg}│{/cyan-fg} {blue-fg}███████{/blue-fg} {cyan-fg}█████{/cyan-fg} {blue-fg}██{/blue-fg} {cyan-fg}code{/cyan-fg} {cyan-fg}│{/cyan-fg}{/bold}
143
+ {bold}{cyan-fg}│{/cyan-fg} {blue-fg}███{/blue-fg} {cyan-fg}██{/cyan-fg} {cyan-fg}██{/cyan-fg} {blue-fg}██{/blue-fg} {gray-fg}v1.3.0{/gray-fg} {cyan-fg}│{/cyan-fg}{/bold}
144
+ {bold}{cyan-fg}│{/cyan-fg} {blue-fg}███{/blue-fg} {cyan-fg}█████{/cyan-fg} {blue-fg}██{/blue-fg} {cyan-fg}{/cyan-fg}{/bold}
145
+ {bold}{cyan-fg}│{/cyan-fg} {blue-fg}███{/blue-fg} {cyan-fg}██{/cyan-fg} {cyan-fg}██{/cyan-fg} {blue-fg}██{/blue-fg} {gray-fg}AI-native{/gray-fg} {cyan-fg}{/cyan-fg}{/bold}
146
+ {bold}{cyan-fg}│{/cyan-fg} {blue-fg}███████{/blue-fg} {cyan-fg}██{/cyan-fg} {cyan-fg}██{/cyan-fg} {blue-fg}██{/blue-fg} {gray-fg}code editor{/gray-fg}{cyan-fg}{/cyan-fg}{/bold}
147
+ {bold}{cyan-fg}╰─────────────────────────────────────╯{/cyan-fg}{/bold}
67
148
  `;
68
- const MINIMAL_LOGO = '{bold}{blue-fg}zai{/blue-fg} {cyan-fg}code{/cyan-fg}{/bold}';
149
+ const MINIMAL_LOGO = '{bold}{blue-fg}{/blue-fg} {cyan-fg}zai·code{/cyan-fg}{/bold} {gray-fg}AI-native code editor{/gray-fg}';
150
+ // Welcome tips - rotate through these
151
+ const WELCOME_TIPS = [
152
+ 'Type a task naturally, like "add error handling to auth.ts"',
153
+ 'Use /do <task> for quick plan+generate in one step',
154
+ 'Use /run <task> for full auto execution (YOLO mode)',
155
+ 'Press Ctrl+P to quickly plan, Ctrl+G to generate',
156
+ 'Use /ask for quick questions without changing mode',
157
+ 'Try /fix <problem> to quickly debug issues',
158
+ '/commit generates AI-powered commit messages',
159
+ 'Use /mode auto for autonomous execution',
160
+ ];
69
161
  async function startTUI(options) {
70
162
  const { projectName, restored, onExit } = options;
71
163
  const session = (0, session_1.getSession)();
@@ -82,22 +174,31 @@ async function startTUI(options) {
82
174
  fullUnicode: true,
83
175
  dockBorders: true,
84
176
  autoPadding: true,
85
- warnings: false, // Suppress blessed warnings
177
+ warnings: false,
86
178
  });
87
- // Theme colors
179
+ // Theme colors - modern dark theme
88
180
  const theme = {
89
181
  bg: 'black',
90
182
  fg: 'white',
91
183
  border: 'blue',
92
184
  highlight: 'cyan',
185
+ accent: 'magenta',
186
+ success: 'green',
187
+ warning: 'yellow',
188
+ error: 'red',
93
189
  gray: 'gray'
94
190
  };
191
+ // State for spinner and processing
192
+ let spinnerInterval = null;
193
+ let spinnerFrame = 0;
194
+ let isProcessing = false;
195
+ let currentTip = Math.floor(Math.random() * WELCOME_TIPS.length);
95
196
  // Header with logo
96
197
  const header = blessed.box({
97
198
  top: 0,
98
199
  left: 0,
99
200
  width: '100%',
100
- height: (0, settings_1.shouldShowLogo)() ? 8 : 2,
201
+ height: (0, settings_1.shouldShowLogo)() ? 9 : 2,
101
202
  tags: true,
102
203
  content: (0, settings_1.shouldShowLogo)() ? ASCII_LOGO : MINIMAL_LOGO,
103
204
  style: {
@@ -105,25 +206,87 @@ async function startTUI(options) {
105
206
  bg: theme.bg,
106
207
  },
107
208
  });
108
- // Tips section
209
+ // Quick actions bar - keyboard shortcuts
210
+ const quickActions = blessed.box({
211
+ top: (0, settings_1.shouldShowLogo)() ? 9 : 2,
212
+ left: 0,
213
+ width: '100%',
214
+ height: 1,
215
+ tags: true,
216
+ content: '{gray-fg}Quick:{/gray-fg} {cyan-fg}^D{/cyan-fg}do {cyan-fg}^R{/cyan-fg}run {cyan-fg}^P{/cyan-fg}plan {cyan-fg}^G{/cyan-fg}gen {cyan-fg}^Z{/cyan-fg}undo',
217
+ style: {
218
+ fg: theme.fg,
219
+ bg: theme.bg,
220
+ },
221
+ padding: { left: 1 },
222
+ });
223
+ // Context/status line
224
+ const contextTop = (0, settings_1.shouldShowLogo)() ? 10 : 3;
225
+ const contextLine = blessed.box({
226
+ top: contextTop,
227
+ left: 0,
228
+ width: '100%',
229
+ height: 1,
230
+ tags: true,
231
+ style: {
232
+ fg: theme.fg,
233
+ bg: theme.bg,
234
+ },
235
+ padding: { left: 1 },
236
+ });
237
+ // Update context line with current state
238
+ function updateContextLine() {
239
+ const gitInfo = (0, git_1.getGitInfo)(session.workingDirectory);
240
+ const model = (0, settings_1.getModel)();
241
+ const mode = session.mode;
242
+ const intent = (0, session_1.getIntent)();
243
+ const fileCount = session.openFiles.length;
244
+ let parts = [];
245
+ parts.push(`{bold}${projectName}{/bold}`);
246
+ if (gitInfo.isRepo) {
247
+ const dirty = gitInfo.isDirty ? '{yellow-fg}*{/yellow-fg}' : '';
248
+ parts.push(`{gray-fg}git:{/gray-fg}${gitInfo.branch}${dirty}`);
249
+ }
250
+ const modeColors = {
251
+ 'auto': 'magenta', 'edit': 'cyan', 'ask': 'green',
252
+ 'debug': 'red', 'review': 'yellow', 'explain': 'blue'
253
+ };
254
+ const modeColor = modeColors[mode] || 'cyan';
255
+ parts.push(`{${modeColor}-fg}${mode}{/${modeColor}-fg}`);
256
+ parts.push(`{gray-fg}${model}{/gray-fg}`);
257
+ if (fileCount > 0)
258
+ parts.push(`{gray-fg}${fileCount} file(s){/gray-fg}`);
259
+ if (intent) {
260
+ const truncated = intent.length > 30 ? intent.substring(0, 30) + '...' : intent;
261
+ parts.push(`{yellow-fg}→ ${truncated}{/yellow-fg}`);
262
+ }
263
+ contextLine.setContent(parts.join(' {gray-fg}│{/gray-fg} '));
264
+ }
265
+ // Tips section - smart suggestions
266
+ const tipsTop = contextTop + 1;
109
267
  const tips = blessed.box({
110
- top: (0, settings_1.shouldShowLogo)() ? 8 : 2,
268
+ top: tipsTop,
111
269
  left: 0,
112
270
  width: '100%',
113
- height: 4,
271
+ height: 2,
114
272
  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
273
  style: {
120
274
  fg: theme.fg,
121
275
  bg: theme.bg,
122
276
  },
123
277
  padding: { left: 1 },
124
278
  });
279
+ function updateTips() {
280
+ const smartSuggestions = getSmartSuggestions();
281
+ if (smartSuggestions.length > 0) {
282
+ tips.setContent(`{gray-fg}💡 ${smartSuggestions.join(' │ ')}{/gray-fg}`);
283
+ }
284
+ else {
285
+ tips.setContent(`{gray-fg}💡 ${WELCOME_TIPS[currentTip]}{/gray-fg}`);
286
+ }
287
+ }
125
288
  // Warning box (if in home directory)
126
- const warningTop = (0, settings_1.shouldShowLogo)() ? 12 : 6;
289
+ const warningTop = tipsTop + 2;
127
290
  const warning = blessed.box({
128
291
  top: warningTop,
129
292
  left: 0,
@@ -147,30 +310,15 @@ async function startTUI(options) {
147
310
  // Check warnings
148
311
  const gitInfo = (0, git_1.getGitInfo)(session.workingDirectory);
149
312
  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.');
313
+ warning.setContent('{yellow-fg}⚠{/yellow-fg} Running in home directory. Consider using a project directory.');
151
314
  warning.show();
152
315
  }
153
316
  else if (!gitInfo.isRepo) {
154
- warning.setContent('Not a git repository. Changes cannot be tracked.');
317
+ warning.setContent('{yellow-fg}⚠{/yellow-fg} Not a git repository. Changes cannot be tracked.');
155
318
  warning.show();
156
319
  }
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
320
  // Main output area
173
- const outputTop = contextTop + 2;
321
+ const outputTop = warning.hidden ? warningTop : warningTop + 3;
174
322
  const output = blessed.log({
175
323
  top: outputTop,
176
324
  left: 0,
@@ -192,7 +340,38 @@ async function startTUI(options) {
192
340
  },
193
341
  padding: { left: 1, right: 1 },
194
342
  });
195
- // Input box container
343
+ // Processing spinner indicator
344
+ const processingIndicator = blessed.box({
345
+ bottom: 5,
346
+ right: 2,
347
+ width: 25,
348
+ height: 1,
349
+ tags: true,
350
+ style: {
351
+ fg: theme.highlight,
352
+ bg: theme.bg,
353
+ },
354
+ hidden: true,
355
+ });
356
+ function startSpinner(message = 'Processing') {
357
+ isProcessing = true;
358
+ processingIndicator.show();
359
+ spinnerInterval = setInterval(() => {
360
+ spinnerFrame = (spinnerFrame + 1) % SPINNER_FRAMES.length;
361
+ processingIndicator.setContent(`{cyan-fg}${SPINNER_FRAMES[spinnerFrame]} ${message}...{/cyan-fg}`);
362
+ screen.render();
363
+ }, 80);
364
+ }
365
+ function stopSpinner() {
366
+ isProcessing = false;
367
+ if (spinnerInterval) {
368
+ clearInterval(spinnerInterval);
369
+ spinnerInterval = null;
370
+ }
371
+ processingIndicator.hide();
372
+ screen.render();
373
+ }
374
+ // Input box container with mode indicator
196
375
  const inputContainer = blessed.box({
197
376
  bottom: 2,
198
377
  left: 0,
@@ -209,18 +388,30 @@ async function startTUI(options) {
209
388
  },
210
389
  },
211
390
  });
212
- // Input prompt symbol
213
- const inputPrompt = blessed.text({
391
+ // Mode indicator icon
392
+ const modeIndicator = blessed.text({
214
393
  parent: inputContainer,
215
394
  left: 1,
216
395
  top: 0,
217
- content: `{bold}{blue-fg}❯{/blue-fg}{/bold}`,
218
396
  tags: true,
219
397
  style: {
220
398
  bg: theme.bg
221
399
  }
222
400
  });
223
- // Input textbox - higher z-index to be on top
401
+ function updateModeIndicator() {
402
+ const mode = session.mode;
403
+ const modeColors = {
404
+ 'auto': 'magenta', 'edit': 'cyan', 'ask': 'green',
405
+ 'debug': 'red', 'review': 'yellow', 'explain': 'blue'
406
+ };
407
+ const color = modeColors[mode] || 'cyan';
408
+ const icons = {
409
+ 'auto': '⚡', 'edit': '❯', 'ask': '?', 'debug': '🔧', 'review': '👁', 'explain': '📖'
410
+ };
411
+ const icon = icons[mode] || '❯';
412
+ modeIndicator.setContent(`{bold}{${color}-fg}${icon}{/${color}-fg}{/bold}`);
413
+ }
414
+ // Input textbox
224
415
  const input = blessed.textbox({
225
416
  parent: inputContainer,
226
417
  left: 3,
@@ -235,9 +426,28 @@ async function startTUI(options) {
235
426
  bg: theme.bg,
236
427
  },
237
428
  });
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
429
+ // Placeholder text
430
+ const placeholder = blessed.text({
431
+ parent: inputContainer,
432
+ left: 4,
433
+ top: 0,
434
+ tags: true,
435
+ style: {
436
+ fg: theme.gray,
437
+ bg: theme.bg,
438
+ },
439
+ });
440
+ function updatePlaceholder() {
441
+ const val = input.getValue();
442
+ if (!val || val.length === 0) {
443
+ placeholder.setContent(`{gray-fg}${getPlaceholder()}{/gray-fg}`);
444
+ placeholder.show();
445
+ }
446
+ else {
447
+ placeholder.hide();
448
+ }
449
+ }
450
+ // Status bar at bottom - more informative
241
451
  const statusBar = blessed.box({
242
452
  bottom: 0,
243
453
  left: 0,
@@ -250,31 +460,44 @@ async function startTUI(options) {
250
460
  },
251
461
  padding: { left: 1, right: 1 },
252
462
  });
253
- // Update status bar
463
+ // Update status bar with state
254
464
  function updateStatusBar() {
255
465
  const model = (0, settings_1.getModel)();
256
466
  const mode = (0, session_1.getSession)().mode;
257
467
  const gitStatus = gitInfo.isRepo ? `${gitInfo.branch}${gitInfo.isDirty ? '*' : ''}` : 'no-git';
258
- const left = `{bold}[${mode}]{/bold}`;
468
+ const dryRun = session.dryRun ? ' {yellow-fg}[DRY]{/yellow-fg}' : '';
469
+ // State indicator
470
+ let state = '{green-fg}ready{/green-fg}';
471
+ if (session.pendingActions || session.lastDiff) {
472
+ state = '{yellow-fg}pending{/yellow-fg}';
473
+ }
474
+ else if (session.lastPlan && session.lastPlan.length > 0) {
475
+ state = '{cyan-fg}planned{/cyan-fg}';
476
+ }
477
+ else if (session.currentIntent) {
478
+ state = '{blue-fg}intent{/blue-fg}';
479
+ }
480
+ const left = `{bold}[${mode}]{/bold} ${state}${dryRun}`;
259
481
  const center = `${gitStatus}`;
260
- const right = `{cyan-fg}${model}{/cyan-fg}`;
482
+ const right = `{cyan-fg}${model}{/cyan-fg} {gray-fg}/help{/gray-fg}`;
261
483
  const width = screen.width || 80;
262
- const padding = Math.max(0, Math.floor((width - 40) / 2));
484
+ const padding = Math.max(0, Math.floor((width - 50) / 2));
263
485
  statusBar.setContent(`${left}${' '.repeat(padding)}${center}${' '.repeat(padding)}${right}`);
264
486
  }
265
- // Command palette - NO keys/mouse to prevent stealing focus
487
+ // Command palette - enhanced
266
488
  const palette = blessed.list({
267
489
  bottom: 5,
268
490
  left: 1,
269
- width: 40,
270
- height: 10,
491
+ width: 55,
492
+ height: 12,
271
493
  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
494
+ keys: false,
495
+ mouse: false,
496
+ interactive: false,
275
497
  border: {
276
498
  type: 'line',
277
499
  },
500
+ label: ' Commands ',
278
501
  style: {
279
502
  fg: theme.fg,
280
503
  bg: theme.bg,
@@ -290,44 +513,105 @@ async function startTUI(options) {
290
513
  },
291
514
  hidden: true,
292
515
  });
516
+ // File autocomplete
517
+ const fileAutocomplete = blessed.list({
518
+ bottom: 5,
519
+ left: 1,
520
+ width: 60,
521
+ height: 10,
522
+ tags: true,
523
+ keys: false,
524
+ mouse: false,
525
+ interactive: false,
526
+ border: {
527
+ type: 'line',
528
+ },
529
+ label: ' Files ',
530
+ style: {
531
+ fg: theme.fg,
532
+ bg: theme.bg,
533
+ border: {
534
+ fg: theme.success,
535
+ bg: theme.bg
536
+ },
537
+ selected: {
538
+ bg: theme.success,
539
+ fg: theme.bg,
540
+ bold: true,
541
+ },
542
+ },
543
+ hidden: true,
544
+ });
293
545
  // Add all elements to screen
294
546
  screen.append(header);
547
+ screen.append(quickActions);
548
+ screen.append(contextLine);
295
549
  screen.append(tips);
296
550
  screen.append(warning);
297
- screen.append(context);
298
551
  screen.append(output);
552
+ screen.append(processingIndicator);
299
553
  screen.append(inputContainer);
300
554
  screen.append(statusBar);
301
555
  screen.append(palette);
556
+ screen.append(fileAutocomplete);
302
557
  // Initial render
558
+ updateContextLine();
559
+ updateTips();
303
560
  updateStatusBar();
561
+ updateModeIndicator();
562
+ updatePlaceholder();
304
563
  input.focus();
305
564
  screen.render();
306
- // Restored message
565
+ // Welcome messages
307
566
  if (restored) {
308
- output.log('{gray-fg}Session restored.{/gray-fg}');
567
+ output.log('{green-fg}{/green-fg} Session restored');
568
+ if (session.currentIntent) {
569
+ output.log(`{gray-fg} Task: ${session.currentIntent.substring(0, 60)}...{/gray-fg}`);
570
+ }
571
+ }
572
+ else {
573
+ output.log('{cyan-fg}Welcome to zai·code!{/cyan-fg} {gray-fg}Type a task or /help{/gray-fg}');
309
574
  }
310
575
  // agents.md notice
311
576
  if ((0, agents_1.hasAgentsConfig)(session.workingDirectory)) {
312
- output.log('{green-fg}agents.md detected and applied{/green-fg}');
577
+ output.log('{green-fg}{/green-fg} agents.md detected');
313
578
  }
579
+ output.log('');
314
580
  // --- LOGIC ---
315
581
  let showPalette = false;
582
+ let showFileAutocomplete = false;
583
+ let autocompleteFiles = [];
316
584
  function updatePalette(filter) {
317
585
  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
586
+ const filtered = COMMANDS.filter(c => c.name.startsWith(query) || c.description.toLowerCase().includes(query));
587
+ const items = filtered.slice(0, 10).map(c => {
588
+ const shortcut = c.shortcut ? ` {gray-fg}${c.shortcut}{/gray-fg}` : '';
589
+ return `{bold}{cyan-fg}/${c.name}{/cyan-fg}{/bold} ${c.description}${shortcut}`;
590
+ });
591
+ palette.setItems(items);
321
592
  if (filtered.length > 0) {
322
593
  palette.select(0);
323
594
  }
324
595
  }
596
+ function updateFileAutocomplete(query) {
597
+ const ws = (0, workspace_model_1.getWorkspace)(session.workingDirectory);
598
+ ws.indexFileTree();
599
+ const files = ws.getFileIndex();
600
+ const pattern = query.toLowerCase();
601
+ autocompleteFiles = files
602
+ .filter(f => f.path.toLowerCase().includes(pattern))
603
+ .slice(0, 10)
604
+ .map(f => f.path);
605
+ fileAutocomplete.setItems(autocompleteFiles.map(f => `{green-fg}📄{/green-fg} ${f}`));
606
+ if (autocompleteFiles.length > 0) {
607
+ fileAutocomplete.select(0);
608
+ }
609
+ }
325
610
  function togglePalette(show, filter = '/') {
326
611
  showPalette = show;
327
612
  if (show) {
328
613
  updatePalette(filter);
329
614
  palette.show();
330
- // CRITICAL: Do NOT focus palette. Keep input focused.
331
615
  screen.render();
332
616
  }
333
617
  else {
@@ -335,11 +619,33 @@ async function startTUI(options) {
335
619
  screen.render();
336
620
  }
337
621
  }
622
+ function toggleFileAutocomplete(show, query = '') {
623
+ showFileAutocomplete = show;
624
+ if (show && query) {
625
+ updateFileAutocomplete(query);
626
+ fileAutocomplete.show();
627
+ screen.render();
628
+ }
629
+ else {
630
+ fileAutocomplete.hide();
631
+ screen.render();
632
+ }
633
+ }
338
634
  // Process input
339
635
  async function processInput(value) {
340
636
  if (!value.trim())
341
637
  return;
342
- output.log(`{cyan-fg}>{/cyan-fg} ${value}`);
638
+ // Add to command history
639
+ commandHistory.unshift(value);
640
+ if (commandHistory.length > 50)
641
+ commandHistory.pop();
642
+ historyIndex = -1;
643
+ // Format input display
644
+ const isCommand = value.startsWith('/');
645
+ const inputDisplay = isCommand
646
+ ? `{cyan-fg}❯{/cyan-fg} {bold}${value}{/bold}`
647
+ : `{cyan-fg}❯{/cyan-fg} ${value}`;
648
+ output.log(inputDisplay);
343
649
  screen.render();
344
650
  const trimmed = value.trim();
345
651
  if (trimmed === '/exit' || trimmed === 'exit' || trimmed === 'quit') {
@@ -370,21 +676,47 @@ async function startTUI(options) {
370
676
  screen.render();
371
677
  };
372
678
  try {
373
- output.log('{gray-fg}Processing...{/gray-fg}');
374
- screen.render();
679
+ // Determine spinner message based on input
680
+ let spinnerMsg = 'Processing';
681
+ if (trimmed.startsWith('/plan') || trimmed === '/p')
682
+ spinnerMsg = 'Planning';
683
+ else if (trimmed.startsWith('/generate') || trimmed === '/g')
684
+ spinnerMsg = 'Generating';
685
+ else if (trimmed.startsWith('/apply') || trimmed === '/a')
686
+ spinnerMsg = 'Applying';
687
+ else if (trimmed.startsWith('/do '))
688
+ spinnerMsg = 'Executing';
689
+ else if (trimmed.startsWith('/run '))
690
+ spinnerMsg = 'Running';
691
+ else if (trimmed.startsWith('/ask') || trimmed.startsWith('/commit'))
692
+ spinnerMsg = 'Thinking';
693
+ else if (!trimmed.startsWith('/'))
694
+ spinnerMsg = 'Thinking';
695
+ startSpinner(spinnerMsg);
375
696
  await (0, orchestrator_1.orchestrate)(value);
697
+ stopSpinner();
376
698
  }
377
699
  catch (e) {
378
- output.log(`{red-fg}Error: ${e?.message || e}{/red-fg}`);
700
+ stopSpinner();
701
+ output.log(`{red-fg}✗ Error: ${e?.message || e}{/red-fg}`);
379
702
  }
380
703
  console.log = originalLog;
381
704
  console.error = originalError;
382
705
  console.warn = originalWarn;
706
+ // Update all UI elements
707
+ updateContextLine();
708
+ updateTips();
383
709
  updateStatusBar();
710
+ updateModeIndicator();
711
+ updatePlaceholder();
712
+ // Rotate tip
713
+ currentTip = (currentTip + 1) % WELCOME_TIPS.length;
714
+ output.log('');
384
715
  screen.render();
385
716
  }
386
717
  // Input events
387
718
  input.on('focus', () => {
719
+ updatePlaceholder();
388
720
  screen.render();
389
721
  });
390
722
  input.on('blur', () => {
@@ -393,13 +725,15 @@ async function startTUI(options) {
393
725
  // Manual keypress handling for Palette interaction
394
726
  input.on('keypress', (ch, key) => {
395
727
  if (!key) {
728
+ updatePlaceholder();
396
729
  screen.render();
397
730
  return;
398
731
  }
399
732
  if (key.name === 'escape') {
400
- if (showPalette) {
733
+ if (showPalette)
401
734
  togglePalette(false);
402
- }
735
+ if (showFileAutocomplete)
736
+ toggleFileAutocomplete(false);
403
737
  screen.render();
404
738
  return;
405
739
  }
@@ -409,6 +743,11 @@ async function startTUI(options) {
409
743
  screen.render();
410
744
  return;
411
745
  }
746
+ if (showFileAutocomplete) {
747
+ fileAutocomplete.down(1);
748
+ screen.render();
749
+ return;
750
+ }
412
751
  }
413
752
  if (key.name === 'up') {
414
753
  if (showPalette) {
@@ -416,16 +755,62 @@ async function startTUI(options) {
416
755
  screen.render();
417
756
  return;
418
757
  }
758
+ if (showFileAutocomplete) {
759
+ fileAutocomplete.up(1);
760
+ screen.render();
761
+ return;
762
+ }
763
+ // Command history navigation
764
+ if (commandHistory.length > 0 && historyIndex < commandHistory.length - 1) {
765
+ historyIndex++;
766
+ input.setValue(commandHistory[historyIndex]);
767
+ updatePlaceholder();
768
+ screen.render();
769
+ return;
770
+ }
771
+ }
772
+ // Tab completion for files
773
+ if (key.name === 'tab') {
774
+ if (showFileAutocomplete && autocompleteFiles.length > 0) {
775
+ const selectedIndex = fileAutocomplete.selected || 0;
776
+ const selectedFile = autocompleteFiles[selectedIndex];
777
+ if (selectedFile) {
778
+ const currentVal = input.getValue();
779
+ const parts = currentVal.split(' ');
780
+ parts[parts.length - 1] = selectedFile;
781
+ input.setValue(parts.join(' '));
782
+ toggleFileAutocomplete(false);
783
+ updatePlaceholder();
784
+ screen.render();
785
+ }
786
+ return;
787
+ }
419
788
  }
420
789
  // Check input content on next tick to see if we should show palette
421
790
  setImmediate(() => {
422
791
  const val = input.getValue();
792
+ updatePlaceholder();
423
793
  if (val && val.startsWith('/')) {
794
+ toggleFileAutocomplete(false);
424
795
  togglePalette(true, val);
796
+ // Check for file commands that need autocomplete
797
+ const fileCommands = ['/open ', '/read ', '/cat ', '/close '];
798
+ for (const cmd of fileCommands) {
799
+ if (val.startsWith(cmd)) {
800
+ const query = val.substring(cmd.length);
801
+ if (query.length > 0) {
802
+ togglePalette(false);
803
+ toggleFileAutocomplete(true, query);
804
+ }
805
+ break;
806
+ }
807
+ }
425
808
  }
426
809
  else {
427
810
  if (showPalette)
428
811
  togglePalette(false);
812
+ if (showFileAutocomplete)
813
+ toggleFileAutocomplete(false);
429
814
  }
430
815
  screen.render();
431
816
  });
@@ -436,24 +821,48 @@ async function startTUI(options) {
436
821
  if (showPalette && inputValue.startsWith('/')) {
437
822
  const selectedIndex = palette.selected || 0;
438
823
  const filter = inputValue.replace(/^\//, '').toLowerCase();
439
- const filteredCommands = COMMANDS.filter(c => c.name.startsWith(filter));
824
+ const filteredCommands = COMMANDS.filter(c => c.name.startsWith(filter) || c.description.toLowerCase().includes(filter));
440
825
  if (filteredCommands[selectedIndex]) {
441
- // Check if command needs arguments
442
826
  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
827
+ const needsArgs = ['mode', 'model', 'open', 'read', 'cat', 'close', 'do', 'run', 'ask', 'fix', 'exec', 'search'].includes(cmd.name);
828
+ if (needsArgs && !inputValue.includes(' ')) {
446
829
  input.setValue('/' + cmd.name + ' ');
447
830
  togglePalette(false);
448
831
  input.focus();
832
+ updatePlaceholder();
449
833
  screen.render();
450
834
  return;
451
835
  }
452
836
  }
453
837
  }
838
+ // Handle file autocomplete selection
839
+ if (showFileAutocomplete && autocompleteFiles.length > 0) {
840
+ const selectedIndex = fileAutocomplete.selected || 0;
841
+ const selectedFile = autocompleteFiles[selectedIndex];
842
+ if (selectedFile) {
843
+ const currentVal = inputValue;
844
+ const parts = currentVal.split(' ');
845
+ parts[parts.length - 1] = selectedFile;
846
+ const newValue = parts.join(' ');
847
+ input.clearValue();
848
+ toggleFileAutocomplete(false);
849
+ togglePalette(false);
850
+ updatePlaceholder();
851
+ screen.render();
852
+ if (newValue && newValue.trim()) {
853
+ await processInput(newValue);
854
+ }
855
+ input.focus();
856
+ updatePlaceholder();
857
+ screen.render();
858
+ return;
859
+ }
860
+ }
454
861
  // Clear input and palette before processing
455
862
  input.clearValue();
456
863
  togglePalette(false);
864
+ toggleFileAutocomplete(false);
865
+ updatePlaceholder();
457
866
  screen.render();
458
867
  // Process the input
459
868
  if (inputValue && inputValue.trim()) {
@@ -461,6 +870,7 @@ async function startTUI(options) {
461
870
  }
462
871
  // Refocus input for next command
463
872
  input.focus();
873
+ updatePlaceholder();
464
874
  screen.render();
465
875
  });
466
876
  // SETTINGS MODAL
@@ -586,6 +996,74 @@ async function startTUI(options) {
586
996
  onExit?.();
587
997
  return process.exit(0);
588
998
  });
999
+ // Quick action shortcuts
1000
+ screen.key(['C-d'], async () => {
1001
+ // Quick /do - need to prompt for task
1002
+ input.setValue('/do ');
1003
+ input.focus();
1004
+ updatePlaceholder();
1005
+ screen.render();
1006
+ });
1007
+ screen.key(['C-r'], async () => {
1008
+ // Quick /run
1009
+ input.setValue('/run ');
1010
+ input.focus();
1011
+ updatePlaceholder();
1012
+ screen.render();
1013
+ });
1014
+ screen.key(['C-p'], async () => {
1015
+ // Quick /plan
1016
+ if (screen.focused === input && !input.getValue()) {
1017
+ input.clearValue();
1018
+ togglePalette(false);
1019
+ updatePlaceholder();
1020
+ screen.render();
1021
+ await processInput('/plan');
1022
+ input.focus();
1023
+ updatePlaceholder();
1024
+ screen.render();
1025
+ }
1026
+ });
1027
+ screen.key(['C-g'], async () => {
1028
+ // Quick /generate
1029
+ if (screen.focused === input && !input.getValue()) {
1030
+ input.clearValue();
1031
+ togglePalette(false);
1032
+ updatePlaceholder();
1033
+ screen.render();
1034
+ await processInput('/generate');
1035
+ input.focus();
1036
+ updatePlaceholder();
1037
+ screen.render();
1038
+ }
1039
+ });
1040
+ screen.key(['C-z'], async () => {
1041
+ // Quick /undo
1042
+ if (screen.focused === input && !input.getValue()) {
1043
+ input.clearValue();
1044
+ togglePalette(false);
1045
+ updatePlaceholder();
1046
+ screen.render();
1047
+ await processInput('/undo');
1048
+ input.focus();
1049
+ updatePlaceholder();
1050
+ screen.render();
1051
+ }
1052
+ });
1053
+ screen.key(['C-a'], async () => {
1054
+ // Quick /ask
1055
+ input.setValue('/ask ');
1056
+ input.focus();
1057
+ updatePlaceholder();
1058
+ screen.render();
1059
+ });
1060
+ screen.key(['C-f'], async () => {
1061
+ // Quick /fix
1062
+ input.setValue('/fix ');
1063
+ input.focus();
1064
+ updatePlaceholder();
1065
+ screen.render();
1066
+ });
589
1067
  screen.key(['q'], () => {
590
1068
  // Only quit if not focused on input
591
1069
  if (screen.focused !== input) {