erosolar-cli 2.1.11 → 2.1.12

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();
@@ -420,6 +427,15 @@ export class InteractiveShell {
420
427
  return this.editGuardMode;
421
428
  }
422
429
  }
430
+ abbreviatePath(pathValue) {
431
+ if (!pathValue)
432
+ return '—';
433
+ const home = homedir();
434
+ if (home && pathValue.startsWith(home)) {
435
+ return pathValue.replace(home, '~');
436
+ }
437
+ return pathValue;
438
+ }
423
439
  async checkAndShowUpdates() {
424
440
  try {
425
441
  const { checkForUpdates, formatUpdateNotification } = await import('../core/updateChecker.js');
@@ -490,6 +506,7 @@ export class InteractiveShell {
490
506
  const statusParts = [
491
507
  theme.success('Ready'),
492
508
  `${theme.primary('Auto')}: ${this.autoContinueEnabled ? theme.success('on') : theme.ui.muted('off')}`,
509
+ `${theme.primary('Approvals')}: ${this.criticalApprovalMode === 'auto' ? theme.ui.muted('auto') : theme.warning('ask')}`,
493
510
  `${theme.primary('Verify')}: ${this.verificationEnabled ? theme.success('on') : theme.ui.muted('off')}`,
494
511
  `${theme.primary('Thinking')}: ${theme.info(thinkingLabel)}`,
495
512
  `${theme.primary('Autosave')}: ${this.autosaveEnabled ? theme.success('on') : theme.ui.muted('off')}`,
@@ -649,6 +666,7 @@ export class InteractiveShell {
649
666
  '/bug',
650
667
  '/changes', '/summary',
651
668
  '/export',
669
+ '/approvals',
652
670
  // Configuration menus
653
671
  '/model', '/models',
654
672
  '/secrets',
@@ -1230,6 +1248,78 @@ export class InteractiveShell {
1230
1248
  display.showWarning('Invalid input. Enter a step number, command (go/cancel/all/none), or your own solution.');
1231
1249
  this.syncRendererInput();
1232
1250
  }
1251
+ async runWithCriticalApproval(label, detail, action) {
1252
+ if (this.criticalApprovalMode === 'auto') {
1253
+ await action();
1254
+ return;
1255
+ }
1256
+ if (this.pendingInteraction && this.pendingInteraction.type === 'critical-approval') {
1257
+ display.showWarning('Finish the pending approval first.');
1258
+ return;
1259
+ }
1260
+ await new Promise((resolve) => {
1261
+ this.pendingInteraction = {
1262
+ type: 'critical-approval',
1263
+ label,
1264
+ detail,
1265
+ onApprove: async () => {
1266
+ this.pendingInteraction = null;
1267
+ try {
1268
+ await action();
1269
+ }
1270
+ catch (error) {
1271
+ display.showError(error instanceof Error ? error.message : String(error), error);
1272
+ }
1273
+ resolve();
1274
+ },
1275
+ onCancel: () => {
1276
+ this.pendingInteraction = null;
1277
+ display.showInfo('Action cancelled.');
1278
+ resolve();
1279
+ },
1280
+ };
1281
+ this.showCriticalApprovalPrompt(label, detail);
1282
+ });
1283
+ }
1284
+ showCriticalApprovalPrompt(label, detail) {
1285
+ const lines = [];
1286
+ lines.push(theme.gradient.primary('⚠️ Approval required'));
1287
+ lines.push('');
1288
+ lines.push(theme.bold(label));
1289
+ if (detail) {
1290
+ lines.push(theme.ui.muted(detail));
1291
+ }
1292
+ lines.push('');
1293
+ lines.push('Type "yes" to proceed or "no" to cancel.');
1294
+ display.showSystemMessage(lines.join('\n'));
1295
+ this.syncRendererInput();
1296
+ }
1297
+ async handleCriticalApprovalInput(input) {
1298
+ const pending = this.pendingInteraction;
1299
+ if (!pending || pending.type !== 'critical-approval') {
1300
+ return;
1301
+ }
1302
+ const normalized = input.trim().toLowerCase();
1303
+ if (!normalized) {
1304
+ display.showWarning('Enter "yes" to proceed or "no" to cancel.');
1305
+ this.syncRendererInput();
1306
+ return;
1307
+ }
1308
+ if (normalized === 'yes' || normalized === 'y') {
1309
+ this.pendingInteraction = null;
1310
+ await pending.onApprove();
1311
+ this.syncRendererInput();
1312
+ return;
1313
+ }
1314
+ if (normalized === 'no' || normalized === 'n' || normalized === 'cancel' || normalized === 'c') {
1315
+ this.pendingInteraction = null;
1316
+ pending.onCancel?.();
1317
+ this.syncRendererInput();
1318
+ return;
1319
+ }
1320
+ display.showWarning('Please respond with "yes" to proceed or "no" to cancel.');
1321
+ this.syncRendererInput();
1322
+ }
1233
1323
  setupHandlers() {
1234
1324
  // Handle terminal resize
1235
1325
  output.on('resize', () => {
@@ -1350,9 +1440,11 @@ export class InteractiveShell {
1350
1440
  criticalApprovalMode: this.criticalApprovalMode,
1351
1441
  criticalApprovalHotkey: 'ctrl+shift+a',
1352
1442
  });
1443
+ const workspaceDisplay = this.abbreviatePath(this.workingDir);
1353
1444
  this.terminalInput.setChromeMeta({
1354
1445
  profile: this.profileLabel,
1355
- workspace: this.workingDir,
1446
+ workspace: workspaceDisplay,
1447
+ directory: workspaceDisplay,
1356
1448
  writes: this.describeEditGuardMode(),
1357
1449
  sessionLabel: this.describeSessionLabel(),
1358
1450
  thinkingLabel: (this.thinkingMode || 'off').toString(),
@@ -1432,15 +1524,6 @@ export class InteractiveShell {
1432
1524
  * All three can be shown simultaneously (Erosolar-CLI style).
1433
1525
  */
1434
1526
  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
1527
  const elapsedSeconds = this.getAiRuntimeSeconds();
1445
1528
  const thinkingMs = display.isSpinnerActive() ? display.getThinkingElapsedMs() : null;
1446
1529
  const tokensUsed = this.latestTokenUsage.used;
@@ -1873,6 +1956,9 @@ export class InteractiveShell {
1873
1956
  case 'plan-approval':
1874
1957
  await this.handlePlanApprovalInput(input);
1875
1958
  return true;
1959
+ case 'critical-approval':
1960
+ await this.handleCriticalApprovalInput(input);
1961
+ return true;
1876
1962
  default:
1877
1963
  return false;
1878
1964
  }
@@ -1892,6 +1978,9 @@ export class InteractiveShell {
1892
1978
  case '/features':
1893
1979
  this.showFeaturesMenu(input);
1894
1980
  break;
1981
+ case '/approvals':
1982
+ this.handleApprovalsCommand(input);
1983
+ break;
1895
1984
  case '/learn':
1896
1985
  this.showLearningStatus(input);
1897
1986
  break;
@@ -2075,6 +2164,7 @@ export class InteractiveShell {
2075
2164
  ` ${theme.info('Shift+Tab')} ${theme.ui.muted('Toggle edit mode (auto/ask)')}`,
2076
2165
  ` ${theme.info('Option+V')} ${theme.ui.muted('Toggle verification')}`,
2077
2166
  ` ${theme.info('Option+C')} ${theme.ui.muted('Toggle auto-continue')}`,
2167
+ ` ${theme.info('Ctrl+Shift+A')} ${theme.ui.muted('Toggle approvals for high-impact actions')}`,
2078
2168
  ` ${theme.info('Option+T')} ${theme.ui.muted('Cycle thinking mode')}`,
2079
2169
  ` ${theme.info('Option+E')} ${theme.ui.muted('Toggle edit permission mode')}`,
2080
2170
  ` ${theme.info('Option+X')} ${theme.ui.muted('Clear/compact context')}`,
@@ -2639,40 +2729,43 @@ export class InteractiveShell {
2639
2729
  return;
2640
2730
  }
2641
2731
  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 ?? '')}`);
2732
+ const runApply = async () => {
2733
+ display.showSystemMessage(theme.gradient.primary('🚀 Running Self-Improvement Cycle...'));
2734
+ display.showSystemMessage('');
2735
+ try {
2736
+ const result = await runSelfImprovementCycle(this.workingDir, {
2737
+ maxChanges: 3,
2738
+ minConfidence: 0.7,
2739
+ runTests: true,
2740
+ autoCommit: true,
2741
+ });
2742
+ const lines = [];
2743
+ lines.push(theme.bold('Results:'));
2744
+ lines.push(result.summary);
2745
+ lines.push('');
2746
+ if (result.applied > 0) {
2747
+ lines.push(theme.success(`✅ Applied ${result.applied} improvements!`));
2748
+ for (const r of result.results.filter(r => r.success)) {
2749
+ lines.push(` - Files: ${r.filesChanged.join(', ')}`);
2750
+ if (r.commitHash) {
2751
+ lines.push(` Commit: ${theme.ui.muted(r.commitHash)}`);
2752
+ lines.push(` Rollback: ${theme.dim(r.rollbackCommand ?? '')}`);
2753
+ }
2662
2754
  }
2663
2755
  }
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}`);
2756
+ else {
2757
+ lines.push(theme.warning('No improvements were applied.'));
2758
+ for (const r of result.results.filter(r => !r.success)) {
2759
+ lines.push(` ${theme.error('✗')} ${r.error}`);
2760
+ }
2669
2761
  }
2762
+ display.showSystemMessage(lines.join('\n'));
2670
2763
  }
2671
- display.showSystemMessage(lines.join('\n'));
2672
- }
2673
- catch (error) {
2674
- display.showError(`Self-improvement failed: ${error instanceof Error ? error.message : String(error)}`);
2675
- }
2764
+ catch (error) {
2765
+ display.showError(`Self-improvement failed: ${error instanceof Error ? error.message : String(error)}`);
2766
+ }
2767
+ };
2768
+ await this.runWithCriticalApproval('Apply self-improvement changes', 'Runs automated fixes with tests and commits before continuing.', runApply);
2676
2769
  return;
2677
2770
  }
2678
2771
  if (subcommand === 'dry-run') {
@@ -2701,7 +2794,7 @@ export class InteractiveShell {
2701
2794
  return;
2702
2795
  }
2703
2796
  if (subcommand === 'auto') {
2704
- void this.runAutonomousImprovementMode();
2797
+ await this.runWithCriticalApproval('Start autonomous improvement mode', 'Allows the CLI to apply and validate changes until stopped.', () => this.runAutonomousImprovementMode());
2705
2798
  return;
2706
2799
  }
2707
2800
  if (subcommand === 'stop') {
@@ -2710,13 +2803,15 @@ export class InteractiveShell {
2710
2803
  return;
2711
2804
  }
2712
2805
  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
- }
2806
+ await this.runWithCriticalApproval('Emergency rollback of self-improvement', 'Restores the last checkpoint created by the improvement engine.', () => {
2807
+ const result = emergencyRollback(this.workingDir);
2808
+ if (result.success) {
2809
+ display.showSuccess(result.message);
2810
+ }
2811
+ else {
2812
+ display.showError(result.message);
2813
+ }
2814
+ });
2720
2815
  return;
2721
2816
  }
2722
2817
  if (subcommand === 'status') {
@@ -2919,64 +3014,67 @@ export class InteractiveShell {
2919
3014
  return;
2920
3015
  }
2921
3016
  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)}`);
3017
+ const startEvolution = async () => {
3018
+ display.showSystemMessage(theme.gradient.primary('🧬 Starting Self-Evolution Mode'));
3019
+ display.showSystemMessage('');
3020
+ display.showSystemMessage(theme.bold('Safety Features:'));
3021
+ display.showSystemMessage(' • Git checkpoint created before starting');
3022
+ display.showSystemMessage(' • Each change validated with build + tests');
3023
+ display.showSystemMessage(' • Auto-rollback on failures');
3024
+ display.showSystemMessage(' • Auto-relaunch with improved code');
3025
+ display.showSystemMessage(' • Press Ctrl+C to stop gracefully');
3026
+ display.showSystemMessage('');
3027
+ try {
3028
+ const result = await runSelfEvolution(this.workingDir, {
3029
+ maxIterations: 50,
3030
+ minConfidence: 0.8,
3031
+ runTests: true,
3032
+ autoRelaunch: true,
3033
+ }, {
3034
+ onStart: () => {
3035
+ display.showInfo('Evolution started. Creating checkpoint...');
3036
+ },
3037
+ onIteration: (iteration, issues) => {
3038
+ display.showSystemMessage(`\n[Iteration ${iteration}] Found ${issues.length} high-confidence issues`);
3039
+ },
3040
+ onFix: (issue, success) => {
3041
+ if (success) {
3042
+ display.showSuccess(`Fixed: ${issue.description.slice(0, 50)}`);
3043
+ }
3044
+ else {
3045
+ display.showWarning(`Failed: ${issue.description.slice(0, 50)}`);
3046
+ }
3047
+ },
3048
+ onRelaunch: () => {
3049
+ display.showSystemMessage('');
3050
+ display.showSystemMessage(theme.gradient.primary('🔄 Relaunching with improved code...'));
3051
+ },
3052
+ onComplete: (result) => {
3053
+ display.showSystemMessage('');
3054
+ display.showSuccess(`Evolution complete! Fixed ${result.issuesFixed} issues.`);
3055
+ },
3056
+ onError: (error) => {
3057
+ display.showError(`Evolution error: ${error}`);
3058
+ },
3059
+ });
3060
+ const lines = [];
3061
+ lines.push('');
3062
+ lines.push(theme.bold('Evolution Result:'));
3063
+ lines.push(` Success: ${result.success ? theme.success('Yes') : theme.error('No')}`);
3064
+ lines.push(` Iterations: ${result.iteration}`);
3065
+ lines.push(` Issues Found: ${result.issuesFound}`);
3066
+ lines.push(` Issues Fixed: ${result.issuesFixed}`);
3067
+ if (result.error) {
3068
+ lines.push(` Error: ${theme.error(result.error)}`);
3069
+ }
3070
+ lines.push(` Next Action: ${result.nextAction}`);
3071
+ display.showSystemMessage(lines.join('\n'));
2973
3072
  }
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
- }
3073
+ catch (error) {
3074
+ display.showError(`Evolution failed: ${error instanceof Error ? error.message : String(error)}`);
3075
+ }
3076
+ };
3077
+ await this.runWithCriticalApproval('Start self-evolution mode', 'Automates repo changes with checkpoints, tests, and relaunch.', startEvolution);
2980
3078
  return;
2981
3079
  }
2982
3080
  if (subcommand === 'stop') {
@@ -3243,42 +3341,48 @@ export class InteractiveShell {
3243
3341
  ' /offsec runs';
3244
3342
  if (sub === 'start') {
3245
3343
  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));
3344
+ const startRun = async () => {
3345
+ const scope = [];
3346
+ const objectiveParts = [];
3347
+ for (let i = 0; i < rest.length; i++) {
3348
+ if (rest[i]?.toLowerCase() === '--scope') {
3349
+ const scopeArg = rest[i + 1];
3350
+ if (scopeArg) {
3351
+ scope.push(...scopeArg.split(',').map((s) => s.trim()).filter(Boolean));
3352
+ }
3353
+ i += 1;
3354
+ continue;
3253
3355
  }
3254
- i += 1;
3255
- continue;
3356
+ objectiveParts.push(rest[i]);
3256
3357
  }
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));
3358
+ const objective = objectiveParts.join(' ').trim();
3359
+ if (!objective) {
3360
+ display.showWarning('Provide an objective. Example: /offsec start gain shell on api.example.com --scope api.example.com');
3361
+ display.showInfo(usage);
3362
+ return;
3363
+ }
3364
+ const run = startOffsecRun(objective, scope);
3365
+ this.offsecRunId = run.id;
3366
+ const next = getOffsecNextActions(run.id, 3);
3367
+ display.showSystemMessage(theme.gradient.primary('🛡️ Offsec AlphaZero run started'));
3368
+ display.showSystemMessage(formatOffsecStatus(run, next));
3369
+ };
3370
+ await this.runWithCriticalApproval('Start offensive security run', 'Begins an automated security workflow and logs results to ~/.erosolar/offsec.', startRun);
3270
3371
  return;
3271
3372
  }
3272
3373
  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)));
3374
+ const resumeRun = async () => {
3375
+ const targetRun = args[1] ?? this.offsecRunId;
3376
+ const run = resumeOffsecRun(targetRun);
3377
+ if (!run) {
3378
+ display.showWarning(`No offsec run found for id ${targetRun ?? '<unset>'}`);
3379
+ return;
3380
+ }
3381
+ this.offsecRunId = run.id;
3382
+ display.showSystemMessage(theme.gradient.primary(`Resumed offsec run ${run.id}`));
3383
+ display.showSystemMessage(formatOffsecStatus(run, getOffsecNextActions(run.id, 3)));
3384
+ };
3385
+ await this.runWithCriticalApproval('Resume offensive security run', 'Continues a previously started offensive security workflow.', resumeRun);
3282
3386
  return;
3283
3387
  }
3284
3388
  if (sub === 'runs') {
@@ -3933,6 +4037,35 @@ export class InteractiveShell {
3933
4037
  lines.push(theme.ui.muted('Shift+Enter enables multi-line input.'));
3934
4038
  display.showSystemMessage(lines.join('\n'));
3935
4039
  }
4040
+ handleApprovalsCommand(input) {
4041
+ const mode = input.split(/\s+/)[1]?.toLowerCase();
4042
+ const showStatus = () => {
4043
+ const lines = [];
4044
+ lines.push(theme.bold('High-Impact Action Approvals'));
4045
+ lines.push('');
4046
+ lines.push(`Current mode: ${this.criticalApprovalMode === 'auto' ? theme.ui.muted('auto (no prompt)') : theme.warning('ask before high-impact actions')}`);
4047
+ lines.push('');
4048
+ lines.push(theme.secondary('Switch mode:'));
4049
+ lines.push(' /approvals auto - run critical flows without prompting');
4050
+ lines.push(' /approvals ask - require approval for major actions');
4051
+ lines.push('');
4052
+ lines.push(theme.ui.muted('Keyboard: Ctrl+Shift+A toggles modes.'));
4053
+ display.showSystemMessage(lines.join('\n'));
4054
+ };
4055
+ if (!mode || mode === 'status') {
4056
+ showStatus();
4057
+ return;
4058
+ }
4059
+ if (['auto', 'default', 'off'].includes(mode)) {
4060
+ this.setCriticalApprovalMode('auto', 'command');
4061
+ return;
4062
+ }
4063
+ if (['ask', 'approval', 'require', 'confirm', 'on'].includes(mode)) {
4064
+ this.setCriticalApprovalMode('approval', 'command');
4065
+ return;
4066
+ }
4067
+ display.showWarning('Usage: /approvals [auto|ask|status]');
4068
+ }
3936
4069
  handlePermissionsCommand() {
3937
4070
  const lines = [];
3938
4071
  lines.push(theme.bold('Tool Permissions'));
@@ -3947,6 +4080,9 @@ export class InteractiveShell {
3947
4080
  lines.push(' Shift+Tab Cycle through modes');
3948
4081
  lines.push(' Option+E Toggle edit permission');
3949
4082
  lines.push('');
4083
+ lines.push(theme.secondary('High-impact approvals:'));
4084
+ lines.push(' /approvals ask|auto (Ctrl+Shift+A to toggle)');
4085
+ lines.push('');
3950
4086
  lines.push(theme.ui.muted('Use /tools to manage which tool suites are enabled.'));
3951
4087
  display.showSystemMessage(lines.join('\n'));
3952
4088
  }