erosolar-cli 1.7.258 → 1.7.260

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 +148 -22
  2. package/dist/core/aiFlowOptimizer.d.ts +26 -0
  3. package/dist/core/aiFlowOptimizer.d.ts.map +1 -0
  4. package/dist/core/aiFlowOptimizer.js +31 -0
  5. package/dist/core/aiFlowOptimizer.js.map +1 -0
  6. package/dist/core/aiOptimizationEngine.d.ts +158 -0
  7. package/dist/core/aiOptimizationEngine.d.ts.map +1 -0
  8. package/dist/core/aiOptimizationEngine.js +428 -0
  9. package/dist/core/aiOptimizationEngine.js.map +1 -0
  10. package/dist/core/aiOptimizationIntegration.d.ts +93 -0
  11. package/dist/core/aiOptimizationIntegration.d.ts.map +1 -0
  12. package/dist/core/aiOptimizationIntegration.js +250 -0
  13. package/dist/core/aiOptimizationIntegration.js.map +1 -0
  14. package/dist/core/customCommands.d.ts +0 -1
  15. package/dist/core/customCommands.d.ts.map +1 -1
  16. package/dist/core/customCommands.js +0 -3
  17. package/dist/core/customCommands.js.map +1 -1
  18. package/dist/core/enhancedErrorRecovery.d.ts +100 -0
  19. package/dist/core/enhancedErrorRecovery.d.ts.map +1 -0
  20. package/dist/core/enhancedErrorRecovery.js +345 -0
  21. package/dist/core/enhancedErrorRecovery.js.map +1 -0
  22. package/dist/core/toolPreconditions.d.ts.map +1 -1
  23. package/dist/core/toolPreconditions.js +14 -0
  24. package/dist/core/toolPreconditions.js.map +1 -1
  25. package/dist/core/toolRuntime.d.ts.map +1 -1
  26. package/dist/core/toolRuntime.js +5 -0
  27. package/dist/core/toolRuntime.js.map +1 -1
  28. package/dist/core/toolValidation.d.ts.map +1 -1
  29. package/dist/core/toolValidation.js +3 -14
  30. package/dist/core/toolValidation.js.map +1 -1
  31. package/dist/core/validationRunner.d.ts +3 -1
  32. package/dist/core/validationRunner.d.ts.map +1 -1
  33. package/dist/core/validationRunner.js.map +1 -1
  34. package/dist/mcp/sseClient.d.ts.map +1 -1
  35. package/dist/mcp/sseClient.js +18 -9
  36. package/dist/mcp/sseClient.js.map +1 -1
  37. package/dist/plugins/tools/build/buildPlugin.d.ts +6 -0
  38. package/dist/plugins/tools/build/buildPlugin.d.ts.map +1 -1
  39. package/dist/plugins/tools/build/buildPlugin.js +10 -4
  40. package/dist/plugins/tools/build/buildPlugin.js.map +1 -1
  41. package/dist/shell/claudeCodeStreamHandler.d.ts +145 -0
  42. package/dist/shell/claudeCodeStreamHandler.d.ts.map +1 -0
  43. package/dist/shell/claudeCodeStreamHandler.js +322 -0
  44. package/dist/shell/claudeCodeStreamHandler.js.map +1 -0
  45. package/dist/shell/inputQueueManager.d.ts +144 -0
  46. package/dist/shell/inputQueueManager.d.ts.map +1 -0
  47. package/dist/shell/inputQueueManager.js +290 -0
  48. package/dist/shell/inputQueueManager.js.map +1 -0
  49. package/dist/shell/interactiveShell.d.ts +2 -10
  50. package/dist/shell/interactiveShell.d.ts.map +1 -1
  51. package/dist/shell/interactiveShell.js +35 -190
  52. package/dist/shell/interactiveShell.js.map +1 -1
  53. package/dist/shell/streamingOutputManager.d.ts +115 -0
  54. package/dist/shell/streamingOutputManager.d.ts.map +1 -0
  55. package/dist/shell/streamingOutputManager.js +225 -0
  56. package/dist/shell/streamingOutputManager.js.map +1 -0
  57. package/dist/shell/terminalInput.d.ts +140 -66
  58. package/dist/shell/terminalInput.d.ts.map +1 -1
  59. package/dist/shell/terminalInput.js +685 -410
  60. package/dist/shell/terminalInput.js.map +1 -1
  61. package/dist/shell/terminalInputAdapter.d.ts +15 -20
  62. package/dist/shell/terminalInputAdapter.d.ts.map +1 -1
  63. package/dist/shell/terminalInputAdapter.js +22 -14
  64. package/dist/shell/terminalInputAdapter.js.map +1 -1
  65. package/dist/ui/ShellUIAdapter.d.ts.map +1 -1
  66. package/dist/ui/ShellUIAdapter.js +12 -13
  67. package/dist/ui/ShellUIAdapter.js.map +1 -1
  68. package/dist/ui/display.d.ts +0 -19
  69. package/dist/ui/display.d.ts.map +1 -1
  70. package/dist/ui/display.js +22 -135
  71. package/dist/ui/display.js.map +1 -1
  72. package/dist/ui/persistentPrompt.d.ts +50 -0
  73. package/dist/ui/persistentPrompt.d.ts.map +1 -0
  74. package/dist/ui/persistentPrompt.js +92 -0
  75. package/dist/ui/persistentPrompt.js.map +1 -0
  76. package/dist/ui/terminalUISchema.d.ts +195 -0
  77. package/dist/ui/terminalUISchema.d.ts.map +1 -0
  78. package/dist/ui/terminalUISchema.js +113 -0
  79. package/dist/ui/terminalUISchema.js.map +1 -0
  80. package/dist/ui/theme.d.ts.map +1 -1
  81. package/dist/ui/theme.js +8 -6
  82. package/dist/ui/theme.js.map +1 -1
  83. package/dist/ui/toolDisplay.d.ts +158 -0
  84. package/dist/ui/toolDisplay.d.ts.map +1 -1
  85. package/dist/ui/toolDisplay.js +348 -0
  86. package/dist/ui/toolDisplay.js.map +1 -1
  87. package/dist/ui/unified/layout.d.ts +0 -1
  88. package/dist/ui/unified/layout.d.ts.map +1 -1
  89. package/dist/ui/unified/layout.js +25 -15
  90. package/dist/ui/unified/layout.js.map +1 -1
  91. package/package.json +1 -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 } from '../ui/theme.js';
5
+ import { theme, formatUserPrompt } 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,7 +34,6 @@ const DROPDOWN_COLORS = [
34
34
  theme.success,
35
35
  theme.warning,
36
36
  ];
37
- const STREAMING_SPINNER_FRAMES = ['◐', '◓', '◑', '◒'];
38
37
  // Load MODEL_PRESETS from centralized schema
39
38
  const MODEL_PRESETS = getModels().map((model) => ({
40
39
  id: model.id,
@@ -49,13 +48,11 @@ const MODEL_PRESETS = getModels().map((model) => ({
49
48
  const BASE_SLASH_COMMANDS = getSlashCommands().map((cmd) => ({
50
49
  command: cmd.command,
51
50
  description: cmd.description,
52
- category: cmd.category,
53
51
  }));
54
52
  // Load PROVIDER_LABELS from centralized schema
55
53
  const PROVIDER_LABELS = Object.fromEntries(getProviders().map((provider) => [provider.id, provider.label]));
56
54
  // Allow enough time for paste detection to kick in before flushing buffered lines
57
55
  const CONTEXT_USAGE_THRESHOLD = 0.9;
58
- const CONTEXT_AUTOCOMPACT_PERCENT = Math.round(CONTEXT_USAGE_THRESHOLD * 100);
59
56
  const CONTEXT_RECENT_MESSAGE_COUNT = 12;
60
57
  const CONTEXT_CLEANUP_CHARS_PER_CHUNK = 6000;
61
58
  const CONTEXT_CLEANUP_MAX_OUTPUT_TOKENS = 800;
@@ -101,7 +98,6 @@ export class InteractiveShell {
101
98
  followUpQueue = [];
102
99
  isDrainingQueue = false;
103
100
  activeContextWindowTokens = null;
104
- latestTokenUsage = { used: null, limit: null };
105
101
  sessionPreferences;
106
102
  autosaveEnabled;
107
103
  autoContinueEnabled;
@@ -132,7 +128,6 @@ export class InteractiveShell {
132
128
  statusLineState = null;
133
129
  statusMessageOverride = null;
134
130
  promptRefreshTimer = null;
135
- launchPaletteShown = false;
136
131
  constructor(config) {
137
132
  this.profile = config.profile;
138
133
  this.profileLabel = config.profileLabel;
@@ -166,7 +161,6 @@ export class InteractiveShell {
166
161
  this.slashCommands.push({
167
162
  command: '/agents',
168
163
  description: 'Select the default agent profile (applies on next launch)',
169
- category: 'configuration',
170
164
  });
171
165
  }
172
166
  this.customCommands = loadCustomSlashCommands();
@@ -175,21 +169,18 @@ export class InteractiveShell {
175
169
  this.slashCommands.push({
176
170
  command: custom.command,
177
171
  description: `${custom.description} (custom)`,
178
- category: custom.category ?? 'other',
179
172
  });
180
173
  }
181
174
  if (!this.slashCommands.some((cmd) => cmd.command === '/exit')) {
182
175
  this.slashCommands.push({
183
176
  command: '/exit',
184
177
  description: 'Quit the CLI immediately',
185
- category: 'other',
186
178
  });
187
179
  }
188
180
  // Add /plugins command
189
181
  this.slashCommands.push({
190
182
  command: '/plugins',
191
183
  description: 'Show available and loaded plugins',
192
- category: 'configuration',
193
184
  });
194
185
  this.statusTracker = config.statusTracker;
195
186
  this.ui = config.ui;
@@ -224,6 +215,9 @@ export class InteractiveShell {
224
215
  });
225
216
  // Register output interceptor for cursor positioning during streaming
226
217
  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);
227
221
  // Initialize Alpha Zero 2 metrics tracking
228
222
  this.alphaZeroMetrics = new MetricsTracker(`${this.profile}-${Date.now()}`);
229
223
  this.setupStatusTracking();
@@ -281,24 +275,9 @@ export class InteractiveShell {
281
275
  await this.processInputBlock(initialPrompt);
282
276
  return;
283
277
  }
284
- this.showLaunchCommandPalette();
285
278
  // Ensure the terminal input is visible
286
279
  this.terminalInput.render();
287
280
  }
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
- }
302
281
  /**
303
282
  * TerminalInputAdapter submit handler
304
283
  */
@@ -312,8 +291,9 @@ export class InteractiveShell {
312
291
  this.handleInputChange('');
313
292
  return;
314
293
  }
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.
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);
317
297
  this.logUserPrompt(approved);
318
298
  void this.processInputBlock(approved).catch((err) => {
319
299
  display.showError(err instanceof Error ? err.message : String(err), err);
@@ -515,9 +495,10 @@ export class InteractiveShell {
515
495
  // Dispose unified UI adapter
516
496
  this.uiAdapter.dispose();
517
497
  display.newLine();
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)));
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)));
521
502
  exit(0);
522
503
  }
523
504
  /**
@@ -687,14 +668,13 @@ export class InteractiveShell {
687
668
  });
688
669
  }
689
670
  setProcessingStatus(detail) {
690
- this.latestTokenUsage = { used: null, limit: this.latestTokenUsage.limit };
691
671
  this.statusTracker.setBase('Working on your request', {
692
672
  detail: this.describeStatusDetail(detail),
693
673
  tone: 'info',
694
674
  });
695
675
  }
696
676
  describeStatusDetail(detail) {
697
- const parts = detail?.trim() ? [detail.trim()] : [];
677
+ const parts = [detail?.trim() || this.describeModelDetail()];
698
678
  const queued = this.followUpQueue.length;
699
679
  if (queued > 0) {
700
680
  parts.push(`${queued} follow-up${queued === 1 ? '' : 's'} queued`);
@@ -707,18 +687,12 @@ export class InteractiveShell {
707
687
  }
708
688
  refreshContextGauge() {
709
689
  const tokens = getContextWindowTokens(this.sessionState.model);
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
- }
690
+ this.activeContextWindowTokens =
691
+ typeof tokens === 'number' && Number.isFinite(tokens) ? tokens : null;
718
692
  }
719
693
  updateContextUsage(percentage) {
720
694
  this.uiAdapter.updateContextUsage(percentage);
721
- this.terminalInput.setContextUsage(percentage, CONTEXT_AUTOCOMPACT_PERCENT);
695
+ this.terminalInput.setContextUsage(percentage);
722
696
  }
723
697
  refreshControlBar() {
724
698
  this.terminalInput.setModeToggles({
@@ -726,8 +700,6 @@ export class InteractiveShell {
726
700
  autoContinueEnabled: this.autoContinueEnabled,
727
701
  verificationHotkey: 'alt+v',
728
702
  autoContinueHotkey: 'alt+c',
729
- thinkingModeLabel: this.thinkingMode,
730
- thinkingHotkey: '/thinking',
731
703
  });
732
704
  this.refreshStatusLine();
733
705
  this.terminalInput.render();
@@ -759,25 +731,6 @@ export class InteractiveShell {
759
731
  // Set main status (tool execution, etc.) - shown when not overridden
760
732
  const statusText = this.formatStatusLine(this.statusLineState);
761
733
  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
- });
781
734
  if (forceRender) {
782
735
  this.terminalInput.render();
783
736
  }
@@ -837,11 +790,13 @@ export class InteractiveShell {
837
790
  this.terminalInput.render();
838
791
  }
839
792
  /**
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.
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.
842
795
  */
843
- logUserPrompt(_text) {
844
- // Intentionally no-op to keep the input area persistent and uncluttered.
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`);
845
800
  }
846
801
  requestPromptRefresh(force = false) {
847
802
  if (force) {
@@ -869,29 +824,9 @@ export class InteractiveShell {
869
824
  this.uiUpdates.setMode('streaming');
870
825
  this.streamingHeartbeatStart = Date.now();
871
826
  this.streamingHeartbeatFrame = 0;
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
- });
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.
895
830
  }
896
831
  stopStreamingHeartbeat() {
897
832
  // Exit global streaming mode - allows UI to render again
@@ -907,28 +842,10 @@ export class InteractiveShell {
907
842
  // Force refresh to update the input area now that streaming has ended
908
843
  this.refreshStatusLine(true);
909
844
  }
910
- buildStreamingStatus(label, elapsedSeconds) {
845
+ buildStreamingStatus(label) {
911
846
  const detail = this.describeModelDetail();
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`;
847
+ const prefix = theme.info('');
848
+ return detail ? `${prefix} ${label} ${theme.ui.muted('·')} ${detail}` : `${prefix} ${label}`;
932
849
  }
933
850
  refreshQueueIndicators() {
934
851
  if (this.isProcessing) {
@@ -1822,75 +1739,6 @@ export class InteractiveShell {
1822
1739
  }
1823
1740
  return `${warning.label}: ${warning.reason}.`;
1824
1741
  }
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
- }
1894
1742
  buildSlashCommandList(header) {
1895
1743
  const lines = [theme.gradient.primary(header), ''];
1896
1744
  for (const command of this.slashCommands) {
@@ -2381,6 +2229,9 @@ export class InteractiveShell {
2381
2229
  this.setIdleStatus();
2382
2230
  display.newLine();
2383
2231
  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();
2384
2235
  queueMicrotask(() => this.uiUpdates.setMode('idle'));
2385
2236
  // CRITICAL: Ensure readline prompt is active for user input
2386
2237
  // Claude Code style: New prompt naturally appears at bottom
@@ -2457,7 +2308,6 @@ When truly finished with ALL tasks, explicitly state "TASK_FULLY_COMPLETE".`;
2457
2308
  try {
2458
2309
  // Send the request and capture the response (streaming disabled)
2459
2310
  display.showThinking('Responding...');
2460
- this.refreshStatusLine(true);
2461
2311
  const response = await agent.send(currentPrompt, true);
2462
2312
  await this.awaitPendingCleanup();
2463
2313
  this.captureHistorySnapshot();
@@ -2957,10 +2807,8 @@ What's the next action?`;
2957
2807
  try {
2958
2808
  // Send the error to the agent for fixing
2959
2809
  display.showThinking('Analyzing build errors');
2960
- this.refreshStatusLine(true);
2961
2810
  const response = await this.agent.send(prompt, true);
2962
2811
  display.stopThinking();
2963
- this.refreshStatusLine(true);
2964
2812
  if (response) {
2965
2813
  display.showAssistantMessage(response, { isFinal: true });
2966
2814
  }
@@ -3010,16 +2858,18 @@ What's the next action?`;
3010
2858
  display.showAssistantMessage(finalContent, enriched);
3011
2859
  }
3012
2860
  }
3013
- // Status shown in mode controls bar - no separate status line needed
2861
+ // Show status line at end (Claude Code style: "• Context X% used • Ready for prompts (2s)")
3014
2862
  display.stopThinking();
3015
- // Update context usage for mode controls display
2863
+ // Calculate context usage
2864
+ let contextInfo;
3016
2865
  if (enriched.contextWindowTokens && metadata.usage) {
3017
2866
  const total = this.totalTokens(metadata.usage);
3018
2867
  if (total && total > 0) {
3019
2868
  const percentage = Math.round((total / enriched.contextWindowTokens) * 100);
3020
- this.updateContextUsage(percentage);
2869
+ contextInfo = { percentage, tokens: total };
3021
2870
  }
3022
2871
  }
2872
+ display.showStatusLine('Ready for prompts', enriched.elapsedMs, contextInfo);
3023
2873
  // Auto-verify changes: build first (catches type errors), then tests
3024
2874
  void this.enforceAutoBuild('final-response');
3025
2875
  void this.enforceAutoTests('final-response');
@@ -3189,14 +3039,9 @@ What's the next action?`;
3189
3039
  return null;
3190
3040
  }
3191
3041
  const usageRatio = total / windowTokens;
3192
- this.latestTokenUsage = {
3193
- used: total,
3194
- limit: windowTokens,
3195
- };
3196
3042
  // Always update context usage in the UI
3197
3043
  const percentUsed = Math.round(usageRatio * 100);
3198
3044
  this.updateContextUsage(percentUsed);
3199
- this.refreshStatusLine(true);
3200
3045
  if (usageRatio < CONTEXT_USAGE_THRESHOLD) {
3201
3046
  return null;
3202
3047
  }