erosolar-cli 1.7.257 → 1.7.258

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 (91) hide show
  1. package/README.md +22 -148
  2. package/dist/core/customCommands.d.ts +1 -0
  3. package/dist/core/customCommands.d.ts.map +1 -1
  4. package/dist/core/customCommands.js +3 -0
  5. package/dist/core/customCommands.js.map +1 -1
  6. package/dist/core/toolPreconditions.d.ts.map +1 -1
  7. package/dist/core/toolPreconditions.js +0 -14
  8. package/dist/core/toolPreconditions.js.map +1 -1
  9. package/dist/core/toolRuntime.d.ts.map +1 -1
  10. package/dist/core/toolRuntime.js +0 -5
  11. package/dist/core/toolRuntime.js.map +1 -1
  12. package/dist/core/toolValidation.d.ts.map +1 -1
  13. package/dist/core/toolValidation.js +14 -3
  14. package/dist/core/toolValidation.js.map +1 -1
  15. package/dist/core/validationRunner.d.ts +1 -3
  16. package/dist/core/validationRunner.d.ts.map +1 -1
  17. package/dist/core/validationRunner.js.map +1 -1
  18. package/dist/mcp/sseClient.d.ts.map +1 -1
  19. package/dist/mcp/sseClient.js +9 -18
  20. package/dist/mcp/sseClient.js.map +1 -1
  21. package/dist/plugins/tools/build/buildPlugin.d.ts +0 -6
  22. package/dist/plugins/tools/build/buildPlugin.d.ts.map +1 -1
  23. package/dist/plugins/tools/build/buildPlugin.js +4 -10
  24. package/dist/plugins/tools/build/buildPlugin.js.map +1 -1
  25. package/dist/shell/interactiveShell.d.ts +10 -2
  26. package/dist/shell/interactiveShell.d.ts.map +1 -1
  27. package/dist/shell/interactiveShell.js +190 -35
  28. package/dist/shell/interactiveShell.js.map +1 -1
  29. package/dist/shell/terminalInput.d.ts +66 -130
  30. package/dist/shell/terminalInput.d.ts.map +1 -1
  31. package/dist/shell/terminalInput.js +409 -606
  32. package/dist/shell/terminalInput.js.map +1 -1
  33. package/dist/shell/terminalInputAdapter.d.ts +20 -15
  34. package/dist/shell/terminalInputAdapter.d.ts.map +1 -1
  35. package/dist/shell/terminalInputAdapter.js +14 -22
  36. package/dist/shell/terminalInputAdapter.js.map +1 -1
  37. package/dist/ui/ShellUIAdapter.d.ts.map +1 -1
  38. package/dist/ui/ShellUIAdapter.js +13 -12
  39. package/dist/ui/ShellUIAdapter.js.map +1 -1
  40. package/dist/ui/display.d.ts +19 -0
  41. package/dist/ui/display.d.ts.map +1 -1
  42. package/dist/ui/display.js +135 -22
  43. package/dist/ui/display.js.map +1 -1
  44. package/dist/ui/theme.d.ts.map +1 -1
  45. package/dist/ui/theme.js +6 -8
  46. package/dist/ui/theme.js.map +1 -1
  47. package/dist/ui/toolDisplay.d.ts +0 -158
  48. package/dist/ui/toolDisplay.d.ts.map +1 -1
  49. package/dist/ui/toolDisplay.js +0 -348
  50. package/dist/ui/toolDisplay.js.map +1 -1
  51. package/dist/ui/unified/layout.d.ts +1 -0
  52. package/dist/ui/unified/layout.d.ts.map +1 -1
  53. package/dist/ui/unified/layout.js +15 -25
  54. package/dist/ui/unified/layout.js.map +1 -1
  55. package/package.json +1 -1
  56. package/dist/core/aiFlowOptimizer.d.ts +0 -26
  57. package/dist/core/aiFlowOptimizer.d.ts.map +0 -1
  58. package/dist/core/aiFlowOptimizer.js +0 -31
  59. package/dist/core/aiFlowOptimizer.js.map +0 -1
  60. package/dist/core/aiOptimizationEngine.d.ts +0 -158
  61. package/dist/core/aiOptimizationEngine.d.ts.map +0 -1
  62. package/dist/core/aiOptimizationEngine.js +0 -428
  63. package/dist/core/aiOptimizationEngine.js.map +0 -1
  64. package/dist/core/aiOptimizationIntegration.d.ts +0 -93
  65. package/dist/core/aiOptimizationIntegration.d.ts.map +0 -1
  66. package/dist/core/aiOptimizationIntegration.js +0 -250
  67. package/dist/core/aiOptimizationIntegration.js.map +0 -1
  68. package/dist/core/enhancedErrorRecovery.d.ts +0 -100
  69. package/dist/core/enhancedErrorRecovery.d.ts.map +0 -1
  70. package/dist/core/enhancedErrorRecovery.js +0 -345
  71. package/dist/core/enhancedErrorRecovery.js.map +0 -1
  72. package/dist/shell/claudeCodeStreamHandler.d.ts +0 -145
  73. package/dist/shell/claudeCodeStreamHandler.d.ts.map +0 -1
  74. package/dist/shell/claudeCodeStreamHandler.js +0 -322
  75. package/dist/shell/claudeCodeStreamHandler.js.map +0 -1
  76. package/dist/shell/inputQueueManager.d.ts +0 -144
  77. package/dist/shell/inputQueueManager.d.ts.map +0 -1
  78. package/dist/shell/inputQueueManager.js +0 -290
  79. package/dist/shell/inputQueueManager.js.map +0 -1
  80. package/dist/shell/streamingOutputManager.d.ts +0 -115
  81. package/dist/shell/streamingOutputManager.d.ts.map +0 -1
  82. package/dist/shell/streamingOutputManager.js +0 -225
  83. package/dist/shell/streamingOutputManager.js.map +0 -1
  84. package/dist/ui/persistentPrompt.d.ts +0 -50
  85. package/dist/ui/persistentPrompt.d.ts.map +0 -1
  86. package/dist/ui/persistentPrompt.js +0 -92
  87. package/dist/ui/persistentPrompt.js.map +0 -1
  88. package/dist/ui/terminalUISchema.d.ts +0 -195
  89. package/dist/ui/terminalUISchema.d.ts.map +0 -1
  90. package/dist/ui/terminalUISchema.js +0 -113
  91. package/dist/ui/terminalUISchema.js.map +0 -1
@@ -2,7 +2,7 @@ import { stdin as input, stdout as output, exit } from 'node:process';
2
2
  import { exec } from 'node:child_process';
3
3
  import { promisify } from 'node:util';
4
4
  import { display } from '../ui/display.js';
5
- import { theme, formatUserPrompt } from '../ui/theme.js';
5
+ import { theme } from '../ui/theme.js';
6
6
  import { getContextWindowTokens } from '../core/contextWindow.js';
7
7
  import { ensureSecretForProvider, getSecretDefinitionForProvider, getSecretValue, listSecretDefinitions, maskSecret, setSecretValue, } from '../core/secretStore.js';
8
8
  import { saveActiveProfilePreference, saveModelPreference, loadToolSettings, saveToolSettings, clearToolSettings, clearActiveProfilePreference, loadSessionPreferences, saveSessionPreferences, } from '../core/preferences.js';
@@ -34,6 +34,7 @@ const DROPDOWN_COLORS = [
34
34
  theme.success,
35
35
  theme.warning,
36
36
  ];
37
+ const STREAMING_SPINNER_FRAMES = ['◐', '◓', '◑', '◒'];
37
38
  // Load MODEL_PRESETS from centralized schema
38
39
  const MODEL_PRESETS = getModels().map((model) => ({
39
40
  id: model.id,
@@ -48,11 +49,13 @@ const MODEL_PRESETS = getModels().map((model) => ({
48
49
  const BASE_SLASH_COMMANDS = getSlashCommands().map((cmd) => ({
49
50
  command: cmd.command,
50
51
  description: cmd.description,
52
+ category: cmd.category,
51
53
  }));
52
54
  // Load PROVIDER_LABELS from centralized schema
53
55
  const PROVIDER_LABELS = Object.fromEntries(getProviders().map((provider) => [provider.id, provider.label]));
54
56
  // Allow enough time for paste detection to kick in before flushing buffered lines
55
57
  const CONTEXT_USAGE_THRESHOLD = 0.9;
58
+ const CONTEXT_AUTOCOMPACT_PERCENT = Math.round(CONTEXT_USAGE_THRESHOLD * 100);
56
59
  const CONTEXT_RECENT_MESSAGE_COUNT = 12;
57
60
  const CONTEXT_CLEANUP_CHARS_PER_CHUNK = 6000;
58
61
  const CONTEXT_CLEANUP_MAX_OUTPUT_TOKENS = 800;
@@ -98,6 +101,7 @@ export class InteractiveShell {
98
101
  followUpQueue = [];
99
102
  isDrainingQueue = false;
100
103
  activeContextWindowTokens = null;
104
+ latestTokenUsage = { used: null, limit: null };
101
105
  sessionPreferences;
102
106
  autosaveEnabled;
103
107
  autoContinueEnabled;
@@ -128,6 +132,7 @@ export class InteractiveShell {
128
132
  statusLineState = null;
129
133
  statusMessageOverride = null;
130
134
  promptRefreshTimer = null;
135
+ launchPaletteShown = false;
131
136
  constructor(config) {
132
137
  this.profile = config.profile;
133
138
  this.profileLabel = config.profileLabel;
@@ -161,6 +166,7 @@ export class InteractiveShell {
161
166
  this.slashCommands.push({
162
167
  command: '/agents',
163
168
  description: 'Select the default agent profile (applies on next launch)',
169
+ category: 'configuration',
164
170
  });
165
171
  }
166
172
  this.customCommands = loadCustomSlashCommands();
@@ -169,18 +175,21 @@ export class InteractiveShell {
169
175
  this.slashCommands.push({
170
176
  command: custom.command,
171
177
  description: `${custom.description} (custom)`,
178
+ category: custom.category ?? 'other',
172
179
  });
173
180
  }
174
181
  if (!this.slashCommands.some((cmd) => cmd.command === '/exit')) {
175
182
  this.slashCommands.push({
176
183
  command: '/exit',
177
184
  description: 'Quit the CLI immediately',
185
+ category: 'other',
178
186
  });
179
187
  }
180
188
  // Add /plugins command
181
189
  this.slashCommands.push({
182
190
  command: '/plugins',
183
191
  description: 'Show available and loaded plugins',
192
+ category: 'configuration',
184
193
  });
185
194
  this.statusTracker = config.statusTracker;
186
195
  this.ui = config.ui;
@@ -215,9 +224,6 @@ export class InteractiveShell {
215
224
  });
216
225
  // Register output interceptor for cursor positioning during streaming
217
226
  this.terminalInput.registerOutputInterceptor(display);
218
- // Use flow mode: input renders inline after content for a unified layout
219
- // This eliminates the blank space between banner and input area
220
- this.terminalInput.setFlowMode(true);
221
227
  // Initialize Alpha Zero 2 metrics tracking
222
228
  this.alphaZeroMetrics = new MetricsTracker(`${this.profile}-${Date.now()}`);
223
229
  this.setupStatusTracking();
@@ -275,9 +281,24 @@ export class InteractiveShell {
275
281
  await this.processInputBlock(initialPrompt);
276
282
  return;
277
283
  }
284
+ this.showLaunchCommandPalette();
278
285
  // Ensure the terminal input is visible
279
286
  this.terminalInput.render();
280
287
  }
288
+ showLaunchCommandPalette() {
289
+ if (this.launchPaletteShown) {
290
+ return;
291
+ }
292
+ const palette = this.buildLaunchCommandPalette();
293
+ if (!palette.length) {
294
+ return;
295
+ }
296
+ display.showCommandPalette(palette, {
297
+ title: 'Quick commands',
298
+ intro: 'Describe a task or run a slash command to get started:',
299
+ });
300
+ this.launchPaletteShown = true;
301
+ }
281
302
  /**
282
303
  * TerminalInputAdapter submit handler
283
304
  */
@@ -291,9 +312,8 @@ export class InteractiveShell {
291
312
  this.handleInputChange('');
292
313
  return;
293
314
  }
294
- // Enter streaming mode BEFORE logging prompt - this positions cursor correctly
295
- // so content appears right after the banner, not at bottom with blank space above
296
- this.terminalInput.setStreaming(true);
315
+ // DON'T clear the input here - keep it visible while streaming.
316
+ // The input will be cleared after streaming completes in the finally block.
297
317
  this.logUserPrompt(approved);
298
318
  void this.processInputBlock(approved).catch((err) => {
299
319
  display.showError(err instanceof Error ? err.message : String(err), err);
@@ -495,10 +515,9 @@ export class InteractiveShell {
495
515
  // Dispose unified UI adapter
496
516
  this.uiAdapter.dispose();
497
517
  display.newLine();
498
- console.log(theme.gradient.warm('━'.repeat(50)));
499
- console.log(` ${theme.gradient.cool('Goodbye!')} ${theme.ui.muted('·')} ${theme.info('support@ero.solar')}`);
500
- console.log(` ${theme.ui.muted('Read:')} ${theme.accent('anthropic.com/news/disrupting-AI-espionage')}`);
501
- console.log(theme.gradient.warm('━'.repeat(50)));
518
+ console.log(theme.ui.muted('━'.repeat(44)));
519
+ console.log(theme.ui.muted(' Goodbye! · support@ero.solar'));
520
+ console.log(theme.ui.muted(''.repeat(44)));
502
521
  exit(0);
503
522
  }
504
523
  /**
@@ -668,13 +687,14 @@ export class InteractiveShell {
668
687
  });
669
688
  }
670
689
  setProcessingStatus(detail) {
690
+ this.latestTokenUsage = { used: null, limit: this.latestTokenUsage.limit };
671
691
  this.statusTracker.setBase('Working on your request', {
672
692
  detail: this.describeStatusDetail(detail),
673
693
  tone: 'info',
674
694
  });
675
695
  }
676
696
  describeStatusDetail(detail) {
677
- const parts = [detail?.trim() || this.describeModelDetail()];
697
+ const parts = detail?.trim() ? [detail.trim()] : [];
678
698
  const queued = this.followUpQueue.length;
679
699
  if (queued > 0) {
680
700
  parts.push(`${queued} follow-up${queued === 1 ? '' : 's'} queued`);
@@ -687,12 +707,18 @@ export class InteractiveShell {
687
707
  }
688
708
  refreshContextGauge() {
689
709
  const tokens = getContextWindowTokens(this.sessionState.model);
690
- this.activeContextWindowTokens =
691
- typeof tokens === 'number' && Number.isFinite(tokens) ? tokens : null;
710
+ const normalizedTokens = typeof tokens === 'number' && Number.isFinite(tokens) ? tokens : null;
711
+ this.activeContextWindowTokens = normalizedTokens;
712
+ if (normalizedTokens !== null) {
713
+ this.latestTokenUsage = {
714
+ used: this.latestTokenUsage.used,
715
+ limit: normalizedTokens,
716
+ };
717
+ }
692
718
  }
693
719
  updateContextUsage(percentage) {
694
720
  this.uiAdapter.updateContextUsage(percentage);
695
- this.terminalInput.setContextUsage(percentage);
721
+ this.terminalInput.setContextUsage(percentage, CONTEXT_AUTOCOMPACT_PERCENT);
696
722
  }
697
723
  refreshControlBar() {
698
724
  this.terminalInput.setModeToggles({
@@ -700,6 +726,8 @@ export class InteractiveShell {
700
726
  autoContinueEnabled: this.autoContinueEnabled,
701
727
  verificationHotkey: 'alt+v',
702
728
  autoContinueHotkey: 'alt+c',
729
+ thinkingModeLabel: this.thinkingMode,
730
+ thinkingHotkey: '/thinking',
703
731
  });
704
732
  this.refreshStatusLine();
705
733
  this.terminalInput.render();
@@ -731,6 +759,25 @@ export class InteractiveShell {
731
759
  // Set main status (tool execution, etc.) - shown when not overridden
732
760
  const statusText = this.formatStatusLine(this.statusLineState);
733
761
  this.terminalInput.setStatusMessage(statusText);
762
+ // Surface meta header (elapsed + context usage) above the divider
763
+ const elapsedSeconds = this.statusLineState
764
+ ? Math.max(0, Math.floor((Date.now() - this.statusLineState.startedAt) / 1000))
765
+ : null;
766
+ const thinkingMs = display.isSpinnerActive() ? display.getThinkingElapsedMs() : null;
767
+ const tokensUsed = this.latestTokenUsage.used;
768
+ const tokenLimit = this.latestTokenUsage.limit ?? this.activeContextWindowTokens;
769
+ this.terminalInput.setMetaStatus({
770
+ elapsedSeconds,
771
+ tokensUsed,
772
+ tokenLimit,
773
+ thinkingMs,
774
+ thinkingHasContent: display.isSpinnerActive(),
775
+ });
776
+ // Keep model/provider visible in the controls bar
777
+ this.terminalInput.setModelContext({
778
+ model: this.sessionState.model,
779
+ provider: this.providerLabel(this.sessionState.provider),
780
+ });
734
781
  if (forceRender) {
735
782
  this.terminalInput.render();
736
783
  }
@@ -790,13 +837,11 @@ export class InteractiveShell {
790
837
  this.terminalInput.render();
791
838
  }
792
839
  /**
793
- * Log the user's prompt as a visible message in the conversation.
794
- * This creates a persistent log entry that remains visible during and after streaming.
840
+ * Keep submissions out of the transcript to preserve the persistent input area.
841
+ * The chat box already holds the user's prompt, so avoid echoing it into output.
795
842
  */
796
- logUserPrompt(text) {
797
- // Display the user's prompt with the standard prefix
798
- const prefix = formatUserPrompt();
799
- display.stream(`\n${prefix}${text}\n\n`);
843
+ logUserPrompt(_text) {
844
+ // Intentionally no-op to keep the input area persistent and uncluttered.
800
845
  }
801
846
  requestPromptRefresh(force = false) {
802
847
  if (force) {
@@ -824,9 +869,29 @@ export class InteractiveShell {
824
869
  this.uiUpdates.setMode('streaming');
825
870
  this.streamingHeartbeatStart = Date.now();
826
871
  this.streamingHeartbeatFrame = 0;
827
- // Note: We don't start a heartbeat during streaming anymore
828
- // because the UI shouldn't be rendering during streaming.
829
- // The streaming status is shown in the streaming header instead.
872
+ const initialFrame = STREAMING_SPINNER_FRAMES[this.streamingHeartbeatFrame];
873
+ this.streamingStatusLabel = this.buildStreamingStatus(`${initialFrame} ${label}`, 0);
874
+ display.updateStreamingStatus(this.streamingStatusLabel);
875
+ this.refreshStatusLine(true);
876
+ // Periodically refresh the pinned input/status region while streaming so
877
+ // elapsed time remains visible without interrupting the scroll region.
878
+ this.uiUpdates.startHeartbeat('streaming', {
879
+ intervalMs: 1000,
880
+ lane: 'heartbeat',
881
+ mode: ['streaming', 'processing'],
882
+ coalesceKey: 'streaming:heartbeat',
883
+ run: () => {
884
+ const elapsedSeconds = this.streamingHeartbeatStart
885
+ ? Math.max(0, Math.floor((Date.now() - this.streamingHeartbeatStart) / 1000))
886
+ : 0;
887
+ this.streamingHeartbeatFrame =
888
+ (this.streamingHeartbeatFrame + 1) % STREAMING_SPINNER_FRAMES.length;
889
+ const frame = STREAMING_SPINNER_FRAMES[this.streamingHeartbeatFrame];
890
+ this.streamingStatusLabel = this.buildStreamingStatus(`${frame} ${label}`, elapsedSeconds);
891
+ display.updateStreamingStatus(this.streamingStatusLabel);
892
+ this.refreshStatusLine(true);
893
+ },
894
+ });
830
895
  }
831
896
  stopStreamingHeartbeat() {
832
897
  // Exit global streaming mode - allows UI to render again
@@ -842,10 +907,28 @@ export class InteractiveShell {
842
907
  // Force refresh to update the input area now that streaming has ended
843
908
  this.refreshStatusLine(true);
844
909
  }
845
- buildStreamingStatus(label) {
910
+ buildStreamingStatus(label, elapsedSeconds) {
846
911
  const detail = this.describeModelDetail();
847
- const prefix = theme.info('');
848
- return detail ? `${prefix} ${label} ${theme.ui.muted('·')} ${detail}` : `${prefix} ${label}`;
912
+ const elapsedLabel = typeof elapsedSeconds === 'number' && elapsedSeconds >= 0
913
+ ? theme.ui.muted(this.formatElapsedShort(elapsedSeconds))
914
+ : null;
915
+ const prefix = theme.info('⏺');
916
+ const parts = [label];
917
+ if (detail) {
918
+ parts.push(theme.ui.muted('·'), detail);
919
+ }
920
+ if (elapsedLabel) {
921
+ parts.push(theme.ui.muted('·'), elapsedLabel);
922
+ }
923
+ return `${prefix} ${parts.join(' ')}`.trim();
924
+ }
925
+ formatElapsedShort(seconds) {
926
+ if (seconds < 60) {
927
+ return `${seconds}s`;
928
+ }
929
+ const minutes = Math.floor(seconds / 60);
930
+ const remaining = seconds % 60;
931
+ return remaining > 0 ? `${minutes}m ${remaining}s` : `${minutes}m`;
849
932
  }
850
933
  refreshQueueIndicators() {
851
934
  if (this.isProcessing) {
@@ -1739,6 +1822,75 @@ export class InteractiveShell {
1739
1822
  }
1740
1823
  return `${warning.label}: ${warning.reason}.`;
1741
1824
  }
1825
+ buildLaunchCommandPalette() {
1826
+ const entries = [];
1827
+ const secretsSummary = this.summarizeSecretsForPalette();
1828
+ const toolSummary = this.getToolSelectionSummary();
1829
+ const autosaveLabel = this.autosaveEnabled ? 'on' : 'off';
1830
+ for (const command of this.slashCommands) {
1831
+ const entry = {
1832
+ command: command.command,
1833
+ description: command.description,
1834
+ category: command.category ?? 'other',
1835
+ };
1836
+ switch (command.command) {
1837
+ case '/secrets':
1838
+ if (secretsSummary.text) {
1839
+ entry.description = `${command.description} (${secretsSummary.text})`;
1840
+ entry.tone = secretsSummary.tone;
1841
+ }
1842
+ break;
1843
+ case '/tools':
1844
+ if (toolSummary) {
1845
+ entry.description = `${command.description} (${toolSummary})`;
1846
+ }
1847
+ break;
1848
+ case '/sessions':
1849
+ entry.description = `${command.description} (autosave ${autosaveLabel})`;
1850
+ break;
1851
+ case '/model':
1852
+ entry.description = `${command.description} (current: ${this.sessionState.model})`;
1853
+ break;
1854
+ case '/provider':
1855
+ entry.description = `${command.description} (current: ${this.providerLabel(this.sessionState.provider)})`;
1856
+ break;
1857
+ default:
1858
+ break;
1859
+ }
1860
+ entries.push(entry);
1861
+ }
1862
+ return entries;
1863
+ }
1864
+ summarizeSecretsForPalette() {
1865
+ const definitions = listSecretDefinitions();
1866
+ if (!definitions.length) {
1867
+ return { text: null };
1868
+ }
1869
+ const missing = definitions.filter((definition) => !getSecretValue(definition.id));
1870
+ if (missing.length === 0) {
1871
+ return { text: 'all configured', tone: 'success' };
1872
+ }
1873
+ const labels = missing.map((definition) => definition.label ?? definition.id);
1874
+ return { text: `missing ${this.formatList(labels)}`, tone: 'warn' };
1875
+ }
1876
+ getToolSelectionSummary() {
1877
+ const toolSettings = loadToolSettings();
1878
+ const selection = buildEnabledToolSet(toolSettings);
1879
+ const options = getToolToggleOptions();
1880
+ if (!options.length) {
1881
+ return null;
1882
+ }
1883
+ const enabledCount = options.filter((option) => selection.has(option.id)).length;
1884
+ return `${enabledCount}/${options.length} enabled`;
1885
+ }
1886
+ formatList(values, maxItems = 3) {
1887
+ if (!values.length) {
1888
+ return '';
1889
+ }
1890
+ const shown = values.slice(0, maxItems);
1891
+ const suffix = values.length > maxItems ? ', …' : '';
1892
+ return `${shown.join(', ')}${suffix}`;
1893
+ }
1742
1894
  buildSlashCommandList(header) {
1743
1895
  const lines = [theme.gradient.primary(header), ''];
1744
1896
  for (const command of this.slashCommands) {
@@ -2229,9 +2381,6 @@ export class InteractiveShell {
2229
2381
  this.setIdleStatus();
2230
2382
  display.newLine();
2231
2383
  this.updateStatusMessage(null);
2232
- // Claude Code style: Show unified status bar before prompt
2233
- // This creates consistent UI between startup and post-streaming
2234
- this.showUnifiedStatusBar();
2235
2384
  queueMicrotask(() => this.uiUpdates.setMode('idle'));
2236
2385
  // CRITICAL: Ensure readline prompt is active for user input
2237
2386
  // Claude Code style: New prompt naturally appears at bottom
@@ -2308,6 +2457,7 @@ When truly finished with ALL tasks, explicitly state "TASK_FULLY_COMPLETE".`;
2308
2457
  try {
2309
2458
  // Send the request and capture the response (streaming disabled)
2310
2459
  display.showThinking('Responding...');
2460
+ this.refreshStatusLine(true);
2311
2461
  const response = await agent.send(currentPrompt, true);
2312
2462
  await this.awaitPendingCleanup();
2313
2463
  this.captureHistorySnapshot();
@@ -2807,8 +2957,10 @@ What's the next action?`;
2807
2957
  try {
2808
2958
  // Send the error to the agent for fixing
2809
2959
  display.showThinking('Analyzing build errors');
2960
+ this.refreshStatusLine(true);
2810
2961
  const response = await this.agent.send(prompt, true);
2811
2962
  display.stopThinking();
2963
+ this.refreshStatusLine(true);
2812
2964
  if (response) {
2813
2965
  display.showAssistantMessage(response, { isFinal: true });
2814
2966
  }
@@ -2858,18 +3010,16 @@ What's the next action?`;
2858
3010
  display.showAssistantMessage(finalContent, enriched);
2859
3011
  }
2860
3012
  }
2861
- // Show status line at end (Claude Code style: "• Context X% used • Ready for prompts (2s)")
3013
+ // Status shown in mode controls bar - no separate status line needed
2862
3014
  display.stopThinking();
2863
- // Calculate context usage
2864
- let contextInfo;
3015
+ // Update context usage for mode controls display
2865
3016
  if (enriched.contextWindowTokens && metadata.usage) {
2866
3017
  const total = this.totalTokens(metadata.usage);
2867
3018
  if (total && total > 0) {
2868
3019
  const percentage = Math.round((total / enriched.contextWindowTokens) * 100);
2869
- contextInfo = { percentage, tokens: total };
3020
+ this.updateContextUsage(percentage);
2870
3021
  }
2871
3022
  }
2872
- display.showStatusLine('Ready for prompts', enriched.elapsedMs, contextInfo);
2873
3023
  // Auto-verify changes: build first (catches type errors), then tests
2874
3024
  void this.enforceAutoBuild('final-response');
2875
3025
  void this.enforceAutoTests('final-response');
@@ -3039,9 +3189,14 @@ What's the next action?`;
3039
3189
  return null;
3040
3190
  }
3041
3191
  const usageRatio = total / windowTokens;
3192
+ this.latestTokenUsage = {
3193
+ used: total,
3194
+ limit: windowTokens,
3195
+ };
3042
3196
  // Always update context usage in the UI
3043
3197
  const percentUsed = Math.round(usageRatio * 100);
3044
3198
  this.updateContextUsage(percentUsed);
3199
+ this.refreshStatusLine(true);
3045
3200
  if (usageRatio < CONTEXT_USAGE_THRESHOLD) {
3046
3201
  return null;
3047
3202
  }