erosolar-cli 2.1.11 → 2.1.13

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.
@@ -1,4 +1,5 @@
1
1
  import { stdin as input, stdout as output, exit } from 'node:process';
2
+ import { homedir } from 'node:os';
2
3
  import { exec } from 'node:child_process';
3
4
  import { promisify } from 'node:util';
4
5
  import { existsSync, readFileSync } from 'node:fs';
@@ -237,6 +238,11 @@ export class InteractiveShell {
237
238
  description: 'Show available and loaded plugins',
238
239
  category: 'configuration',
239
240
  });
241
+ this.slashCommands.push({
242
+ command: '/approvals',
243
+ description: 'Switch between auto and approval mode for high-impact actions',
244
+ category: 'configuration',
245
+ });
240
246
  this.slashCommands.push({
241
247
  command: '/offsec',
242
248
  description: 'AlphaZero offensive security run (start/status/next)',
@@ -283,6 +289,8 @@ export class InteractiveShell {
283
289
  this.alphaZeroMetrics = new MetricsTracker(`${this.profile}-${Date.now()}`);
284
290
  this.setupStatusTracking();
285
291
  this.refreshContextGauge();
292
+ // Prime renderer state before it first paints
293
+ this.refreshControlBar();
286
294
  // Start terminal input (sets up handlers)
287
295
  this.terminalInput.start();
288
296
  // Allow planning tools (e.g., ProposePlan) to open the interactive approval UI just like Codex CLI
@@ -290,7 +298,6 @@ export class InteractiveShell {
290
298
  // Set up command autocomplete with all slash commands
291
299
  this.setupCommandAutocomplete();
292
300
  // Render chat box immediately using the streaming UI lifecycle
293
- this.refreshControlBar();
294
301
  this.syncRendererInput();
295
302
  this.renderer?.render();
296
303
  this.rebuildAgent();
@@ -371,31 +378,29 @@ export class InteractiveShell {
371
378
  display.showInfo(this.sessionResumeNotice);
372
379
  this.sessionResumeNotice = null;
373
380
  }
374
- async showWelcomeBanner(force = false) {
375
- if (this.welcomeShown && !force) {
381
+ async showWelcomeBanner() {
382
+ if (this.welcomeShown)
376
383
  return;
377
- }
378
- const banner = this.buildClaudeStyleBanner();
379
- this.streamEventBlock(banner);
380
- this.streamEventBlock(this.buildQuickstartPanel());
381
384
  this.welcomeShown = true;
385
+ if (process.env['EROSOLAR_SHOW_WELCOME']) {
386
+ const pkg = this.getPackageInfo();
387
+ const version = pkg.version || 'unknown';
388
+ const model = `${this.providerLabel(this.sessionState.provider)} · ${this.sessionState.model}`;
389
+ const workspace = this.workingDir.replace(`${process.env['HOME'] ?? ''}/`, '~/');
390
+ const lines = [];
391
+ lines.push(theme.gradient.primary('╭─ Erosolar CLI ───────────────────────────────────────────────╮'));
392
+ lines.push(theme.gradient.primary('│ │'));
393
+ lines.push(theme.gradient.primary(`│ ${theme.bold('Welcome!').padEnd(60)}│`));
394
+ lines.push(theme.gradient.primary('│ │'));
395
+ lines.push(theme.gradient.primary(`│ ${theme.ui.muted('Model:').padEnd(10)}${theme.info(model).padEnd(50)}│`));
396
+ lines.push(theme.gradient.primary(`│ ${theme.ui.muted('Workspace:').padEnd(10)}${theme.ui.muted(workspace).padEnd(50)}│`));
397
+ lines.push(theme.gradient.primary(`│ ${theme.ui.muted('Version:').padEnd(10)}${theme.info(`v${version}`).padEnd(50)}│`));
398
+ lines.push(theme.gradient.primary('│ │'));
399
+ lines.push(theme.gradient.primary('╰──────────────────────────────────────────────────────────────╯'));
400
+ display.showSystemMessage(lines.join('\n'));
401
+ }
382
402
  // Check for updates asynchronously (non-blocking)
383
403
  void this.checkAndShowUpdates();
384
- this.requestPromptRefresh(true);
385
- }
386
- buildQuickstartPanel() {
387
- const commands = [
388
- { cmd: '/init', desc: 'Create AGENTS.md with working instructions' },
389
- { cmd: '/status', desc: 'Show current session configuration' },
390
- { cmd: '/model', desc: 'Choose model and reasoning effort' },
391
- { cmd: '/approvals', desc: 'Adjust what Erosolar can do without prompts' },
392
- { cmd: '/review', desc: 'Review local changes and find issues' },
393
- ];
394
- const body = commands
395
- .map(entry => `${theme.primary(entry.cmd.padEnd(9))}${theme.ui.muted('·')} ${entry.desc}`)
396
- .join('\n');
397
- const intro = `${theme.ui.muted('To get started, describe a task or try one of these commands:')}`;
398
- return `${intro}\n\n${body}`;
399
404
  }
400
405
  describeSessionLabel() {
401
406
  if (this.sessionRestoreConfig.mode === 'autosave')
@@ -420,6 +425,15 @@ export class InteractiveShell {
420
425
  return this.editGuardMode;
421
426
  }
422
427
  }
428
+ abbreviatePath(pathValue) {
429
+ if (!pathValue)
430
+ return '—';
431
+ const home = homedir();
432
+ if (home && pathValue.startsWith(home)) {
433
+ return pathValue.replace(home, '~');
434
+ }
435
+ return pathValue;
436
+ }
423
437
  async checkAndShowUpdates() {
424
438
  try {
425
439
  const { checkForUpdates, formatUpdateNotification } = await import('../core/updateChecker.js');
@@ -435,86 +449,6 @@ export class InteractiveShell {
435
449
  // Silently fail - don't interrupt user experience
436
450
  }
437
451
  }
438
- buildClaudeStyleBanner() {
439
- const pkg = this.getPackageInfo();
440
- const version = pkg.version || '2.0.0';
441
- const model = `${this.providerLabel(this.sessionState.provider)} · ${this.sessionState.model}`;
442
- const workspace = this.workingDir;
443
- const profile = this.profileLabel;
444
- const thinkingLabel = (this.thinkingMode || 'off').toString();
445
- const sessionLabel = this.describeSessionLabel();
446
- const terminalWidth = typeof process.stdout.columns === 'number' && process.stdout.columns > 0
447
- ? process.stdout.columns
448
- : 80;
449
- const maxContentWidth = Math.max(48, terminalWidth - 4);
450
- const innerWidth = Math.max(48, Math.min(maxContentWidth, 90));
451
- const gap = ' ';
452
- const stripAnsi = (value) => value.replace(/\u001B\[[0-9;]*m/g, '');
453
- const visibleLength = (value) => stripAnsi(value).length;
454
- const padLine = (value) => {
455
- const len = visibleLength(value);
456
- if (len < innerWidth) {
457
- return value + ' '.repeat(innerWidth - len);
458
- }
459
- return value;
460
- };
461
- const truncateEnd = (value, limit) => {
462
- if (limit <= 0)
463
- return '';
464
- if (value.length <= limit)
465
- return value;
466
- return `${value.slice(0, Math.max(0, limit - 1))}…`;
467
- };
468
- const truncatePath = (value, limit) => {
469
- if (limit <= 0)
470
- return '';
471
- if (value.length <= limit)
472
- return value;
473
- return `…${value.slice(-Math.max(1, limit - 1))}`;
474
- };
475
- const formatDual = (leftLabel, leftValue, rightLabel, rightValue, rightValueIsPath = false) => {
476
- const available = innerWidth - visibleLength(gap);
477
- const leftBudget = Math.min(Math.max(18, Math.floor(available * 0.52)), available - 12);
478
- const rightBudget = available - leftBudget;
479
- const leftLimit = Math.max(6, leftBudget - (leftLabel.length + 2));
480
- const rightLimit = Math.max(6, rightBudget - (rightLabel.length + 2));
481
- const leftDisplay = truncateEnd(leftValue, leftLimit);
482
- const rightDisplay = rightValueIsPath
483
- ? truncatePath(rightValue, rightLimit)
484
- : truncateEnd(rightValue, rightLimit);
485
- const leftColored = `${theme.primary(leftLabel)}: ${theme.info(leftDisplay)}`;
486
- const rightColored = `${theme.primary(rightLabel)}: ${theme.ui.muted(rightDisplay)}`;
487
- return padLine(`${leftColored}${gap}${rightColored}`);
488
- };
489
- const editGuardLabel = this.describeEditGuardMode();
490
- const statusParts = [
491
- theme.success('Ready'),
492
- `${theme.primary('Auto')}: ${this.autoContinueEnabled ? theme.success('on') : theme.ui.muted('off')}`,
493
- `${theme.primary('Verify')}: ${this.verificationEnabled ? theme.success('on') : theme.ui.muted('off')}`,
494
- `${theme.primary('Thinking')}: ${theme.info(thinkingLabel)}`,
495
- `${theme.primary('Autosave')}: ${this.autosaveEnabled ? theme.success('on') : theme.ui.muted('off')}`,
496
- ];
497
- let statusLine = statusParts.join(theme.ui.muted(' • '));
498
- while (visibleLength(statusLine) > innerWidth && statusParts.length > 2) {
499
- statusParts.pop();
500
- statusLine = statusParts.join(theme.ui.muted(' • '));
501
- }
502
- const statusContent = padLine(statusLine);
503
- const title = `${theme.primary('Erosolar CLI')} ${theme.ui.muted('·')} ${theme.info(`v${version}`)}`;
504
- const dashCount = Math.max(0, innerWidth - visibleLength(title) - 1);
505
- const top = theme.ui.muted(`╭─ ${title} ${'─'.repeat(dashCount)}╮`);
506
- const bottom = theme.ui.muted(`╰${'─'.repeat(innerWidth + 2)}╯`);
507
- const frameLine = (content) => `${theme.ui.muted('│')} ${content} ${theme.ui.muted('│')}`;
508
- const lines = [
509
- top,
510
- frameLine(formatDual('Profile', profile, 'Workspace', workspace, true)),
511
- frameLine(formatDual('Model', model, 'Writes', editGuardLabel)),
512
- frameLine(formatDual('Session', sessionLabel, 'Thinking', thinkingLabel)),
513
- frameLine(statusContent),
514
- bottom,
515
- ];
516
- return lines.join('\n');
517
- }
518
452
  getPackageInfo() {
519
453
  try {
520
454
  const pkgPath = join(process.cwd(), 'package.json');
@@ -649,6 +583,7 @@ export class InteractiveShell {
649
583
  '/bug',
650
584
  '/changes', '/summary',
651
585
  '/export',
586
+ '/approvals',
652
587
  // Configuration menus
653
588
  '/model', '/models',
654
589
  '/secrets',
@@ -1230,6 +1165,78 @@ export class InteractiveShell {
1230
1165
  display.showWarning('Invalid input. Enter a step number, command (go/cancel/all/none), or your own solution.');
1231
1166
  this.syncRendererInput();
1232
1167
  }
1168
+ async runWithCriticalApproval(label, detail, action) {
1169
+ if (this.criticalApprovalMode === 'auto') {
1170
+ await action();
1171
+ return;
1172
+ }
1173
+ if (this.pendingInteraction && this.pendingInteraction.type === 'critical-approval') {
1174
+ display.showWarning('Finish the pending approval first.');
1175
+ return;
1176
+ }
1177
+ await new Promise((resolve) => {
1178
+ this.pendingInteraction = {
1179
+ type: 'critical-approval',
1180
+ label,
1181
+ detail,
1182
+ onApprove: async () => {
1183
+ this.pendingInteraction = null;
1184
+ try {
1185
+ await action();
1186
+ }
1187
+ catch (error) {
1188
+ display.showError(error instanceof Error ? error.message : String(error), error);
1189
+ }
1190
+ resolve();
1191
+ },
1192
+ onCancel: () => {
1193
+ this.pendingInteraction = null;
1194
+ display.showInfo('Action cancelled.');
1195
+ resolve();
1196
+ },
1197
+ };
1198
+ this.showCriticalApprovalPrompt(label, detail);
1199
+ });
1200
+ }
1201
+ showCriticalApprovalPrompt(label, detail) {
1202
+ const lines = [];
1203
+ lines.push(theme.gradient.primary('⚠️ Approval required'));
1204
+ lines.push('');
1205
+ lines.push(theme.bold(label));
1206
+ if (detail) {
1207
+ lines.push(theme.ui.muted(detail));
1208
+ }
1209
+ lines.push('');
1210
+ lines.push('Type "yes" to proceed or "no" to cancel.');
1211
+ display.showSystemMessage(lines.join('\n'));
1212
+ this.syncRendererInput();
1213
+ }
1214
+ async handleCriticalApprovalInput(input) {
1215
+ const pending = this.pendingInteraction;
1216
+ if (!pending || pending.type !== 'critical-approval') {
1217
+ return;
1218
+ }
1219
+ const normalized = input.trim().toLowerCase();
1220
+ if (!normalized) {
1221
+ display.showWarning('Enter "yes" to proceed or "no" to cancel.');
1222
+ this.syncRendererInput();
1223
+ return;
1224
+ }
1225
+ if (normalized === 'yes' || normalized === 'y') {
1226
+ this.pendingInteraction = null;
1227
+ await pending.onApprove();
1228
+ this.syncRendererInput();
1229
+ return;
1230
+ }
1231
+ if (normalized === 'no' || normalized === 'n' || normalized === 'cancel' || normalized === 'c') {
1232
+ this.pendingInteraction = null;
1233
+ pending.onCancel?.();
1234
+ this.syncRendererInput();
1235
+ return;
1236
+ }
1237
+ display.showWarning('Please respond with "yes" to proceed or "no" to cancel.');
1238
+ this.syncRendererInput();
1239
+ }
1233
1240
  setupHandlers() {
1234
1241
  // Handle terminal resize
1235
1242
  output.on('resize', () => {
@@ -1350,9 +1357,11 @@ export class InteractiveShell {
1350
1357
  criticalApprovalMode: this.criticalApprovalMode,
1351
1358
  criticalApprovalHotkey: 'ctrl+shift+a',
1352
1359
  });
1360
+ const workspaceDisplay = this.abbreviatePath(this.workingDir);
1353
1361
  this.terminalInput.setChromeMeta({
1354
1362
  profile: this.profileLabel,
1355
- workspace: this.workingDir,
1363
+ workspace: workspaceDisplay,
1364
+ directory: workspaceDisplay,
1356
1365
  writes: this.describeEditGuardMode(),
1357
1366
  sessionLabel: this.describeSessionLabel(),
1358
1367
  thinkingLabel: (this.thinkingMode || 'off').toString(),
@@ -1432,15 +1441,6 @@ export class InteractiveShell {
1432
1441
  * All three can be shown simultaneously (Erosolar-CLI style).
1433
1442
  */
1434
1443
  refreshStatusLine(forceRender = false) {
1435
- const statusText = this.formatStatusLine(this.statusLineState);
1436
- // Compose streaming/override/base status into a single line so the prompt
1437
- // looks the same before and during streaming.
1438
- this.terminalInput.setStatusLine({
1439
- streaming: this.streamingStatusLabel,
1440
- override: this.statusMessageOverride,
1441
- main: statusText,
1442
- });
1443
- // Surface meta header (elapsed + context usage) above the divider
1444
1444
  const elapsedSeconds = this.getAiRuntimeSeconds();
1445
1445
  const thinkingMs = display.isSpinnerActive() ? display.getThinkingElapsedMs() : null;
1446
1446
  const tokensUsed = this.latestTokenUsage.used;
@@ -1873,6 +1873,9 @@ export class InteractiveShell {
1873
1873
  case 'plan-approval':
1874
1874
  await this.handlePlanApprovalInput(input);
1875
1875
  return true;
1876
+ case 'critical-approval':
1877
+ await this.handleCriticalApprovalInput(input);
1878
+ return true;
1876
1879
  default:
1877
1880
  return false;
1878
1881
  }
@@ -1892,6 +1895,9 @@ export class InteractiveShell {
1892
1895
  case '/features':
1893
1896
  this.showFeaturesMenu(input);
1894
1897
  break;
1898
+ case '/approvals':
1899
+ this.handleApprovalsCommand(input);
1900
+ break;
1895
1901
  case '/learn':
1896
1902
  this.showLearningStatus(input);
1897
1903
  break;
@@ -2075,6 +2081,7 @@ export class InteractiveShell {
2075
2081
  ` ${theme.info('Shift+Tab')} ${theme.ui.muted('Toggle edit mode (auto/ask)')}`,
2076
2082
  ` ${theme.info('Option+V')} ${theme.ui.muted('Toggle verification')}`,
2077
2083
  ` ${theme.info('Option+C')} ${theme.ui.muted('Toggle auto-continue')}`,
2084
+ ` ${theme.info('Ctrl+Shift+A')} ${theme.ui.muted('Toggle approvals for high-impact actions')}`,
2078
2085
  ` ${theme.info('Option+T')} ${theme.ui.muted('Cycle thinking mode')}`,
2079
2086
  ` ${theme.info('Option+E')} ${theme.ui.muted('Toggle edit permission mode')}`,
2080
2087
  ` ${theme.info('Option+X')} ${theme.ui.muted('Clear/compact context')}`,
@@ -2639,40 +2646,43 @@ export class InteractiveShell {
2639
2646
  return;
2640
2647
  }
2641
2648
  if (subcommand === 'apply') {
2642
- display.showSystemMessage(theme.gradient.primary('🚀 Running Self-Improvement Cycle...'));
2643
- display.showSystemMessage('');
2644
- try {
2645
- const result = await runSelfImprovementCycle(this.workingDir, {
2646
- maxChanges: 3,
2647
- minConfidence: 0.7,
2648
- runTests: true,
2649
- autoCommit: true,
2650
- });
2651
- const lines = [];
2652
- lines.push(theme.bold('Results:'));
2653
- lines.push(result.summary);
2654
- lines.push('');
2655
- if (result.applied > 0) {
2656
- lines.push(theme.success(`✅ Applied ${result.applied} improvements!`));
2657
- for (const r of result.results.filter(r => r.success)) {
2658
- lines.push(` - Files: ${r.filesChanged.join(', ')}`);
2659
- if (r.commitHash) {
2660
- lines.push(` Commit: ${theme.ui.muted(r.commitHash)}`);
2661
- lines.push(` Rollback: ${theme.dim(r.rollbackCommand ?? '')}`);
2649
+ const runApply = async () => {
2650
+ display.showSystemMessage(theme.gradient.primary('🚀 Running Self-Improvement Cycle...'));
2651
+ display.showSystemMessage('');
2652
+ try {
2653
+ const result = await runSelfImprovementCycle(this.workingDir, {
2654
+ maxChanges: 3,
2655
+ minConfidence: 0.7,
2656
+ runTests: true,
2657
+ autoCommit: true,
2658
+ });
2659
+ const lines = [];
2660
+ lines.push(theme.bold('Results:'));
2661
+ lines.push(result.summary);
2662
+ lines.push('');
2663
+ if (result.applied > 0) {
2664
+ lines.push(theme.success(`✅ Applied ${result.applied} improvements!`));
2665
+ for (const r of result.results.filter(r => r.success)) {
2666
+ lines.push(` - Files: ${r.filesChanged.join(', ')}`);
2667
+ if (r.commitHash) {
2668
+ lines.push(` Commit: ${theme.ui.muted(r.commitHash)}`);
2669
+ lines.push(` Rollback: ${theme.dim(r.rollbackCommand ?? '')}`);
2670
+ }
2662
2671
  }
2663
2672
  }
2664
- }
2665
- else {
2666
- lines.push(theme.warning('No improvements were applied.'));
2667
- for (const r of result.results.filter(r => !r.success)) {
2668
- lines.push(` ${theme.error('✗')} ${r.error}`);
2673
+ else {
2674
+ lines.push(theme.warning('No improvements were applied.'));
2675
+ for (const r of result.results.filter(r => !r.success)) {
2676
+ lines.push(` ${theme.error('✗')} ${r.error}`);
2677
+ }
2669
2678
  }
2679
+ display.showSystemMessage(lines.join('\n'));
2670
2680
  }
2671
- display.showSystemMessage(lines.join('\n'));
2672
- }
2673
- catch (error) {
2674
- display.showError(`Self-improvement failed: ${error instanceof Error ? error.message : String(error)}`);
2675
- }
2681
+ catch (error) {
2682
+ display.showError(`Self-improvement failed: ${error instanceof Error ? error.message : String(error)}`);
2683
+ }
2684
+ };
2685
+ await this.runWithCriticalApproval('Apply self-improvement changes', 'Runs automated fixes with tests and commits before continuing.', runApply);
2676
2686
  return;
2677
2687
  }
2678
2688
  if (subcommand === 'dry-run') {
@@ -2701,7 +2711,7 @@ export class InteractiveShell {
2701
2711
  return;
2702
2712
  }
2703
2713
  if (subcommand === 'auto') {
2704
- void this.runAutonomousImprovementMode();
2714
+ await this.runWithCriticalApproval('Start autonomous improvement mode', 'Allows the CLI to apply and validate changes until stopped.', () => this.runAutonomousImprovementMode());
2705
2715
  return;
2706
2716
  }
2707
2717
  if (subcommand === 'stop') {
@@ -2710,13 +2720,15 @@ export class InteractiveShell {
2710
2720
  return;
2711
2721
  }
2712
2722
  if (subcommand === 'rollback') {
2713
- const result = emergencyRollback(this.workingDir);
2714
- if (result.success) {
2715
- display.showSuccess(result.message);
2716
- }
2717
- else {
2718
- display.showError(result.message);
2719
- }
2723
+ await this.runWithCriticalApproval('Emergency rollback of self-improvement', 'Restores the last checkpoint created by the improvement engine.', () => {
2724
+ const result = emergencyRollback(this.workingDir);
2725
+ if (result.success) {
2726
+ display.showSuccess(result.message);
2727
+ }
2728
+ else {
2729
+ display.showError(result.message);
2730
+ }
2731
+ });
2720
2732
  return;
2721
2733
  }
2722
2734
  if (subcommand === 'status') {
@@ -2919,64 +2931,67 @@ export class InteractiveShell {
2919
2931
  return;
2920
2932
  }
2921
2933
  if (subcommand === 'start') {
2922
- display.showSystemMessage(theme.gradient.primary('🧬 Starting Self-Evolution Mode'));
2923
- display.showSystemMessage('');
2924
- display.showSystemMessage(theme.bold('Safety Features:'));
2925
- display.showSystemMessage(' Git checkpoint created before starting');
2926
- display.showSystemMessage(' • Each change validated with build + tests');
2927
- display.showSystemMessage(' • Auto-rollback on failures');
2928
- display.showSystemMessage(' • Auto-relaunch with improved code');
2929
- display.showSystemMessage(' • Press Ctrl+C to stop gracefully');
2930
- display.showSystemMessage('');
2931
- try {
2932
- const result = await runSelfEvolution(this.workingDir, {
2933
- maxIterations: 50,
2934
- minConfidence: 0.8,
2935
- runTests: true,
2936
- autoRelaunch: true,
2937
- }, {
2938
- onStart: () => {
2939
- display.showInfo('Evolution started. Creating checkpoint...');
2940
- },
2941
- onIteration: (iteration, issues) => {
2942
- display.showSystemMessage(`\n[Iteration ${iteration}] Found ${issues.length} high-confidence issues`);
2943
- },
2944
- onFix: (issue, success) => {
2945
- if (success) {
2946
- display.showSuccess(`Fixed: ${issue.description.slice(0, 50)}`);
2947
- }
2948
- else {
2949
- display.showWarning(`Failed: ${issue.description.slice(0, 50)}`);
2950
- }
2951
- },
2952
- onRelaunch: () => {
2953
- display.showSystemMessage('');
2954
- display.showSystemMessage(theme.gradient.primary('🔄 Relaunching with improved code...'));
2955
- },
2956
- onComplete: (result) => {
2957
- display.showSystemMessage('');
2958
- display.showSuccess(`Evolution complete! Fixed ${result.issuesFixed} issues.`);
2959
- },
2960
- onError: (error) => {
2961
- display.showError(`Evolution error: ${error}`);
2962
- },
2963
- });
2964
- const lines = [];
2965
- lines.push('');
2966
- lines.push(theme.bold('Evolution Result:'));
2967
- lines.push(` Success: ${result.success ? theme.success('Yes') : theme.error('No')}`);
2968
- lines.push(` Iterations: ${result.iteration}`);
2969
- lines.push(` Issues Found: ${result.issuesFound}`);
2970
- lines.push(` Issues Fixed: ${result.issuesFixed}`);
2971
- if (result.error) {
2972
- lines.push(` Error: ${theme.error(result.error)}`);
2934
+ const startEvolution = async () => {
2935
+ display.showSystemMessage(theme.gradient.primary('🧬 Starting Self-Evolution Mode'));
2936
+ display.showSystemMessage('');
2937
+ display.showSystemMessage(theme.bold('Safety Features:'));
2938
+ display.showSystemMessage(' • Git checkpoint created before starting');
2939
+ display.showSystemMessage(' • Each change validated with build + tests');
2940
+ display.showSystemMessage(' • Auto-rollback on failures');
2941
+ display.showSystemMessage(' • Auto-relaunch with improved code');
2942
+ display.showSystemMessage(' • Press Ctrl+C to stop gracefully');
2943
+ display.showSystemMessage('');
2944
+ try {
2945
+ const result = await runSelfEvolution(this.workingDir, {
2946
+ maxIterations: 50,
2947
+ minConfidence: 0.8,
2948
+ runTests: true,
2949
+ autoRelaunch: true,
2950
+ }, {
2951
+ onStart: () => {
2952
+ display.showInfo('Evolution started. Creating checkpoint...');
2953
+ },
2954
+ onIteration: (iteration, issues) => {
2955
+ display.showSystemMessage(`\n[Iteration ${iteration}] Found ${issues.length} high-confidence issues`);
2956
+ },
2957
+ onFix: (issue, success) => {
2958
+ if (success) {
2959
+ display.showSuccess(`Fixed: ${issue.description.slice(0, 50)}`);
2960
+ }
2961
+ else {
2962
+ display.showWarning(`Failed: ${issue.description.slice(0, 50)}`);
2963
+ }
2964
+ },
2965
+ onRelaunch: () => {
2966
+ display.showSystemMessage('');
2967
+ display.showSystemMessage(theme.gradient.primary('🔄 Relaunching with improved code...'));
2968
+ },
2969
+ onComplete: (result) => {
2970
+ display.showSystemMessage('');
2971
+ display.showSuccess(`Evolution complete! Fixed ${result.issuesFixed} issues.`);
2972
+ },
2973
+ onError: (error) => {
2974
+ display.showError(`Evolution error: ${error}`);
2975
+ },
2976
+ });
2977
+ const lines = [];
2978
+ lines.push('');
2979
+ lines.push(theme.bold('Evolution Result:'));
2980
+ lines.push(` Success: ${result.success ? theme.success('Yes') : theme.error('No')}`);
2981
+ lines.push(` Iterations: ${result.iteration}`);
2982
+ lines.push(` Issues Found: ${result.issuesFound}`);
2983
+ lines.push(` Issues Fixed: ${result.issuesFixed}`);
2984
+ if (result.error) {
2985
+ lines.push(` Error: ${theme.error(result.error)}`);
2986
+ }
2987
+ lines.push(` Next Action: ${result.nextAction}`);
2988
+ display.showSystemMessage(lines.join('\n'));
2973
2989
  }
2974
- lines.push(` Next Action: ${result.nextAction}`);
2975
- display.showSystemMessage(lines.join('\n'));
2976
- }
2977
- catch (error) {
2978
- display.showError(`Evolution failed: ${error instanceof Error ? error.message : String(error)}`);
2979
- }
2990
+ catch (error) {
2991
+ display.showError(`Evolution failed: ${error instanceof Error ? error.message : String(error)}`);
2992
+ }
2993
+ };
2994
+ await this.runWithCriticalApproval('Start self-evolution mode', 'Automates repo changes with checkpoints, tests, and relaunch.', startEvolution);
2980
2995
  return;
2981
2996
  }
2982
2997
  if (subcommand === 'stop') {
@@ -3243,42 +3258,48 @@ export class InteractiveShell {
3243
3258
  ' /offsec runs';
3244
3259
  if (sub === 'start') {
3245
3260
  const rest = args.slice(1);
3246
- const scope = [];
3247
- const objectiveParts = [];
3248
- for (let i = 0; i < rest.length; i++) {
3249
- if (rest[i]?.toLowerCase() === '--scope') {
3250
- const scopeArg = rest[i + 1];
3251
- if (scopeArg) {
3252
- scope.push(...scopeArg.split(',').map((s) => s.trim()).filter(Boolean));
3261
+ const startRun = async () => {
3262
+ const scope = [];
3263
+ const objectiveParts = [];
3264
+ for (let i = 0; i < rest.length; i++) {
3265
+ if (rest[i]?.toLowerCase() === '--scope') {
3266
+ const scopeArg = rest[i + 1];
3267
+ if (scopeArg) {
3268
+ scope.push(...scopeArg.split(',').map((s) => s.trim()).filter(Boolean));
3269
+ }
3270
+ i += 1;
3271
+ continue;
3253
3272
  }
3254
- i += 1;
3255
- continue;
3273
+ objectiveParts.push(rest[i]);
3256
3274
  }
3257
- objectiveParts.push(rest[i]);
3258
- }
3259
- const objective = objectiveParts.join(' ').trim();
3260
- if (!objective) {
3261
- display.showWarning('Provide an objective. Example: /offsec start gain shell on api.example.com --scope api.example.com');
3262
- display.showInfo(usage);
3263
- return;
3264
- }
3265
- const run = startOffsecRun(objective, scope);
3266
- this.offsecRunId = run.id;
3267
- const next = getOffsecNextActions(run.id, 3);
3268
- display.showSystemMessage(theme.gradient.primary('🛡️ Offsec AlphaZero run started'));
3269
- display.showSystemMessage(formatOffsecStatus(run, next));
3275
+ const objective = objectiveParts.join(' ').trim();
3276
+ if (!objective) {
3277
+ display.showWarning('Provide an objective. Example: /offsec start gain shell on api.example.com --scope api.example.com');
3278
+ display.showInfo(usage);
3279
+ return;
3280
+ }
3281
+ const run = startOffsecRun(objective, scope);
3282
+ this.offsecRunId = run.id;
3283
+ const next = getOffsecNextActions(run.id, 3);
3284
+ display.showSystemMessage(theme.gradient.primary('🛡️ Offsec AlphaZero run started'));
3285
+ display.showSystemMessage(formatOffsecStatus(run, next));
3286
+ };
3287
+ await this.runWithCriticalApproval('Start offensive security run', 'Begins an automated security workflow and logs results to ~/.erosolar/offsec.', startRun);
3270
3288
  return;
3271
3289
  }
3272
3290
  if (sub === 'resume') {
3273
- const targetRun = args[1] ?? this.offsecRunId;
3274
- const run = resumeOffsecRun(targetRun);
3275
- if (!run) {
3276
- display.showWarning(`No offsec run found for id ${targetRun ?? '<unset>'}`);
3277
- return;
3278
- }
3279
- this.offsecRunId = run.id;
3280
- display.showSystemMessage(theme.gradient.primary(`Resumed offsec run ${run.id}`));
3281
- display.showSystemMessage(formatOffsecStatus(run, getOffsecNextActions(run.id, 3)));
3291
+ const resumeRun = async () => {
3292
+ const targetRun = args[1] ?? this.offsecRunId;
3293
+ const run = resumeOffsecRun(targetRun);
3294
+ if (!run) {
3295
+ display.showWarning(`No offsec run found for id ${targetRun ?? '<unset>'}`);
3296
+ return;
3297
+ }
3298
+ this.offsecRunId = run.id;
3299
+ display.showSystemMessage(theme.gradient.primary(`Resumed offsec run ${run.id}`));
3300
+ display.showSystemMessage(formatOffsecStatus(run, getOffsecNextActions(run.id, 3)));
3301
+ };
3302
+ await this.runWithCriticalApproval('Resume offensive security run', 'Continues a previously started offensive security workflow.', resumeRun);
3282
3303
  return;
3283
3304
  }
3284
3305
  if (sub === 'runs') {
@@ -3933,6 +3954,35 @@ export class InteractiveShell {
3933
3954
  lines.push(theme.ui.muted('Shift+Enter enables multi-line input.'));
3934
3955
  display.showSystemMessage(lines.join('\n'));
3935
3956
  }
3957
+ handleApprovalsCommand(input) {
3958
+ const mode = input.split(/\s+/)[1]?.toLowerCase();
3959
+ const showStatus = () => {
3960
+ const lines = [];
3961
+ lines.push(theme.bold('High-Impact Action Approvals'));
3962
+ lines.push('');
3963
+ lines.push(`Current mode: ${this.criticalApprovalMode === 'auto' ? theme.ui.muted('auto (no prompt)') : theme.warning('ask before high-impact actions')}`);
3964
+ lines.push('');
3965
+ lines.push(theme.secondary('Switch mode:'));
3966
+ lines.push(' /approvals auto - run critical flows without prompting');
3967
+ lines.push(' /approvals ask - require approval for major actions');
3968
+ lines.push('');
3969
+ lines.push(theme.ui.muted('Keyboard: Ctrl+Shift+A toggles modes.'));
3970
+ display.showSystemMessage(lines.join('\n'));
3971
+ };
3972
+ if (!mode || mode === 'status') {
3973
+ showStatus();
3974
+ return;
3975
+ }
3976
+ if (['auto', 'default', 'off'].includes(mode)) {
3977
+ this.setCriticalApprovalMode('auto', 'command');
3978
+ return;
3979
+ }
3980
+ if (['ask', 'approval', 'require', 'confirm', 'on'].includes(mode)) {
3981
+ this.setCriticalApprovalMode('approval', 'command');
3982
+ return;
3983
+ }
3984
+ display.showWarning('Usage: /approvals [auto|ask|status]');
3985
+ }
3936
3986
  handlePermissionsCommand() {
3937
3987
  const lines = [];
3938
3988
  lines.push(theme.bold('Tool Permissions'));
@@ -3947,6 +3997,9 @@ export class InteractiveShell {
3947
3997
  lines.push(' Shift+Tab Cycle through modes');
3948
3998
  lines.push(' Option+E Toggle edit permission');
3949
3999
  lines.push('');
4000
+ lines.push(theme.secondary('High-impact approvals:'));
4001
+ lines.push(' /approvals ask|auto (Ctrl+Shift+A to toggle)');
4002
+ lines.push('');
3950
4003
  lines.push(theme.ui.muted('Use /tools to manage which tool suites are enabled.'));
3951
4004
  display.showSystemMessage(lines.join('\n'));
3952
4005
  }