oxe-cc 0.6.5 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/.cursor/commands/oxe-ask.md +11 -0
  2. package/.cursor/commands/oxe-capabilities.md +11 -0
  3. package/.cursor/commands/oxe-dashboard.md +11 -0
  4. package/.github/prompts/oxe-ask.prompt.md +12 -0
  5. package/.github/prompts/oxe-capabilities.prompt.md +12 -0
  6. package/.github/prompts/oxe-dashboard.prompt.md +12 -0
  7. package/CHANGELOG.md +33 -0
  8. package/README.md +189 -34
  9. package/assets/oxe-framework-artifacts-paper.png +0 -0
  10. package/bin/banner.txt +1 -1
  11. package/bin/lib/oxe-azure.cjs +1445 -0
  12. package/bin/lib/oxe-dashboard.cjs +588 -0
  13. package/bin/lib/oxe-install-resolve.cjs +4 -1
  14. package/bin/lib/oxe-operational.cjs +670 -0
  15. package/bin/lib/oxe-project-health.cjs +655 -118
  16. package/bin/oxe-cc.js +1404 -17
  17. package/commands/oxe/ask.md +14 -0
  18. package/commands/oxe/capabilities.md +13 -0
  19. package/commands/oxe/dashboard.md +14 -0
  20. package/lib/sdk/README.md +9 -7
  21. package/lib/sdk/index.cjs +56 -0
  22. package/lib/sdk/index.d.ts +73 -0
  23. package/oxe/templates/ACTIVE-RUN.template.json +32 -0
  24. package/oxe/templates/CAPABILITIES.template.md +7 -0
  25. package/oxe/templates/CAPABILITY.template.md +45 -0
  26. package/oxe/templates/CHECKPOINTS.template.md +7 -0
  27. package/oxe/templates/CONFIG.md +3 -2
  28. package/oxe/templates/EXECUTION-RUNTIME.template.md +68 -0
  29. package/oxe/templates/INVESTIGATION.template.md +38 -0
  30. package/oxe/templates/NOTES.template.md +16 -0
  31. package/oxe/templates/PLAN-REVIEW.template.md +31 -0
  32. package/oxe/templates/PLAN.template.md +22 -7
  33. package/oxe/templates/RESEARCH.template.md +11 -4
  34. package/oxe/templates/SPEC.template.md +6 -4
  35. package/oxe/templates/STATE.md +45 -7
  36. package/oxe/templates/config.template.json +14 -5
  37. package/oxe/workflows/ask.md +71 -0
  38. package/oxe/workflows/capabilities.md +23 -0
  39. package/oxe/workflows/dashboard.md +23 -0
  40. package/oxe/workflows/discuss.md +11 -9
  41. package/oxe/workflows/execute.md +46 -17
  42. package/oxe/workflows/help.md +273 -239
  43. package/oxe/workflows/next.md +10 -8
  44. package/oxe/workflows/obs.md +70 -20
  45. package/oxe/workflows/plan-agent.md +2 -1
  46. package/oxe/workflows/plan.md +70 -21
  47. package/oxe/workflows/quick.md +14 -6
  48. package/oxe/workflows/references/adaptive-discovery.md +27 -0
  49. package/oxe/workflows/references/flow-robustness-contract.md +80 -0
  50. package/oxe/workflows/research.md +12 -8
  51. package/oxe/workflows/retro.md +30 -5
  52. package/oxe/workflows/scan.md +1 -0
  53. package/oxe/workflows/spec.md +58 -33
  54. package/oxe/workflows/verify.md +40 -10
  55. package/package.json +2 -2
package/bin/oxe-cc.js CHANGED
@@ -18,6 +18,9 @@ const oxeAgentInstall = require(path.join(__dirname, 'lib', 'oxe-agent-install.c
18
18
  const oxeWorkflows = require(path.join(__dirname, 'lib', 'oxe-workflows.cjs'));
19
19
  const oxeInstallResolve = require(path.join(__dirname, 'lib', 'oxe-install-resolve.cjs'));
20
20
  const oxeNpmVersion = require(path.join(__dirname, 'lib', 'oxe-npm-version.cjs'));
21
+ const oxeDashboard = require(path.join(__dirname, 'lib', 'oxe-dashboard.cjs'));
22
+ const oxeOperational = require(path.join(__dirname, 'lib', 'oxe-operational.cjs'));
23
+ const oxeAzure = require(path.join(__dirname, 'lib', 'oxe-azure.cjs'));
21
24
 
22
25
  /** Merge markers for ~/.copilot/copilot-instructions.md (bloco OXE). */
23
26
  const OXE_INST_BEGIN = '<!-- oxe-cc:install-begin -->';
@@ -259,6 +262,7 @@ function buildUninstallFooter(u) {
259
262
  `${p}${rm} integrações OXE no repositório (.cursor, .github, .claude, .copilot, .opencode, … conforme flags).`
260
263
  );
261
264
  }
265
+ if (u.globalCli) bullets.push(`${p}${rm} também o pacote npm global oxe-cc do PATH.`);
262
266
  if (!u.noProject) {
263
267
  bullets.push(
264
268
  `${p}${u.dryRun ? 'Seriam removidas' : 'Removidas'} no repositório: .oxe/workflows, .oxe/templates, oxe/ e commands/oxe (o que existir).`
@@ -328,6 +332,8 @@ function parseInstallArgs(argv) {
328
332
  jsonOutput: false,
329
333
  /** Lembretes agregados scan/compact em `status`. */
330
334
  statusHints: false,
335
+ /** Visão extendida CLI-first: coverage matrix + readiness gate no terminal. */
336
+ statusFull: false,
331
337
  restPositional: [],
332
338
  };
333
339
  for (let i = 0; i < argv.length; i++) {
@@ -372,6 +378,7 @@ function parseInstallArgs(argv) {
372
378
  out.dir = path.resolve(argv[++i]);
373
379
  } else if (a === '--json') out.jsonOutput = true;
374
380
  else if (a === '--hints') out.statusHints = true;
381
+ else if (a === '--full') out.statusFull = true;
375
382
  else if (!a.startsWith('-')) out.restPositional.push(a);
376
383
  else {
377
384
  out.parseError = true;
@@ -1014,6 +1021,9 @@ function ensureGitignoreIgnoresOxeDir(projectRoot, opts = {}) {
1014
1021
  function bootstrapOxe(target, opts) {
1015
1022
  const oxeDir = path.join(target, '.oxe');
1016
1023
  const codebaseDir = path.join(oxeDir, 'codebase');
1024
+ const capabilitiesDir = path.join(oxeDir, 'capabilities');
1025
+ const investigationsDir = path.join(oxeDir, 'investigations');
1026
+ const dashboardDir = path.join(oxeDir, 'dashboard');
1017
1027
  const stateSrc = path.join(PKG_ROOT, 'oxe', 'templates', 'STATE.md');
1018
1028
  const stateDest = path.join(oxeDir, 'STATE.md');
1019
1029
  const configSrc = path.join(PKG_ROOT, 'oxe', 'templates', 'config.template.json');
@@ -1025,12 +1035,15 @@ function bootstrapOxe(target, opts) {
1025
1035
  }
1026
1036
 
1027
1037
  if (opts.dryRun) {
1028
- console.log(`${dim}init${reset} ${oxeDir}/ (STATE.md, config.json, codebase/)`);
1038
+ console.log(`${dim}init${reset} ${oxeDir}/ (STATE.md, config.json, codebase/, capabilities/, investigations/, dashboard/, runs/, OXE-EVENTS.ndjson, ACTIVE-RUN.json)`);
1029
1039
  ensureGitignoreIgnoresOxeDir(target, { dryRun: true });
1030
1040
  return;
1031
1041
  }
1032
1042
 
1033
1043
  ensureDir(codebaseDir);
1044
+ ensureDir(capabilitiesDir);
1045
+ ensureDir(investigationsDir);
1046
+ ensureDir(dashboardDir);
1034
1047
 
1035
1048
  if (!fs.existsSync(stateDest) || opts.force) {
1036
1049
  copyFile(stateSrc, stateDest, { dryRun: false });
@@ -1067,14 +1080,107 @@ function bootstrapOxe(target, opts) {
1067
1080
  ensureDir(workstreamsDir);
1068
1081
  }
1069
1082
 
1083
+ const sessionsDir = path.join(oxeDir, 'sessions');
1084
+ if (!fs.existsSync(sessionsDir)) {
1085
+ ensureDir(sessionsDir);
1086
+ }
1087
+
1088
+ const globalDir = path.join(oxeDir, 'global');
1089
+ if (!fs.existsSync(globalDir)) {
1090
+ ensureDir(globalDir);
1091
+ }
1092
+
1093
+ const globalMilestonesDir = path.join(globalDir, 'milestones');
1094
+ if (!fs.existsSync(globalMilestonesDir)) {
1095
+ ensureDir(globalMilestonesDir);
1096
+ }
1097
+
1098
+ const lessonsSrc = path.join(PKG_ROOT, 'oxe', 'templates', 'LESSONS.template.md');
1099
+ const lessonsDest = path.join(globalDir, 'LESSONS.md');
1100
+ if (fs.existsSync(lessonsSrc) && !fs.existsSync(lessonsDest)) {
1101
+ copyFile(lessonsSrc, lessonsDest, { dryRun: false });
1102
+ console.log(`${green}init${reset} ${lessonsDest}`);
1103
+ }
1104
+
1105
+ const milestonesSrc = path.join(PKG_ROOT, 'oxe', 'templates', 'MILESTONES.template.md');
1106
+ const milestonesDest = path.join(globalDir, 'MILESTONES.md');
1107
+ if (fs.existsSync(milestonesSrc) && !fs.existsSync(milestonesDest)) {
1108
+ copyFile(milestonesSrc, milestonesDest, { dryRun: false });
1109
+ console.log(`${green}init${reset} ${milestonesDest}`);
1110
+ }
1111
+
1070
1112
  const memoryDir = path.join(oxeDir, 'memory');
1071
1113
  if (!fs.existsSync(memoryDir)) {
1072
1114
  ensureDir(memoryDir);
1073
1115
  }
1074
1116
 
1117
+ const runsDir = path.join(oxeDir, 'runs');
1118
+ if (!fs.existsSync(runsDir)) {
1119
+ ensureDir(runsDir);
1120
+ }
1121
+
1122
+ const runtimeSrc = path.join(PKG_ROOT, 'oxe', 'templates', 'EXECUTION-RUNTIME.template.md');
1123
+ const runtimeDest = path.join(oxeDir, 'EXECUTION-RUNTIME.md');
1124
+ if (fs.existsSync(runtimeSrc) && !fs.existsSync(runtimeDest)) {
1125
+ copyFile(runtimeSrc, runtimeDest, { dryRun: false });
1126
+ console.log(`${green}init${reset} ${runtimeDest}`);
1127
+ }
1128
+
1129
+ const activeRunSrc = path.join(PKG_ROOT, 'oxe', 'templates', 'ACTIVE-RUN.template.json');
1130
+ const activeRunDest = path.join(oxeDir, 'ACTIVE-RUN.json');
1131
+ if (fs.existsSync(activeRunSrc) && !fs.existsSync(activeRunDest)) {
1132
+ copyFile(activeRunSrc, activeRunDest, { dryRun: false });
1133
+ console.log(`${green}init${reset} ${activeRunDest}`);
1134
+ }
1135
+
1136
+ const eventsDest = path.join(oxeDir, 'OXE-EVENTS.ndjson');
1137
+ if (!fs.existsSync(eventsDest)) {
1138
+ fs.writeFileSync(eventsDest, '', 'utf8');
1139
+ console.log(`${green}init${reset} ${eventsDest}`);
1140
+ }
1141
+
1142
+ const checkpointsSrc = path.join(PKG_ROOT, 'oxe', 'templates', 'CHECKPOINTS.template.md');
1143
+ const checkpointsDest = path.join(oxeDir, 'CHECKPOINTS.md');
1144
+ if (fs.existsSync(checkpointsSrc) && !fs.existsSync(checkpointsDest)) {
1145
+ copyFile(checkpointsSrc, checkpointsDest, { dryRun: false });
1146
+ console.log(`${green}init${reset} ${checkpointsDest}`);
1147
+ }
1148
+
1149
+ const capabilitiesSrc = path.join(PKG_ROOT, 'oxe', 'templates', 'CAPABILITIES.template.md');
1150
+ const capabilitiesDest = path.join(oxeDir, 'CAPABILITIES.md');
1151
+ if (fs.existsSync(capabilitiesSrc) && !fs.existsSync(capabilitiesDest)) {
1152
+ copyFile(capabilitiesSrc, capabilitiesDest, { dryRun: false });
1153
+ console.log(`${green}init${reset} ${capabilitiesDest}`);
1154
+ }
1155
+
1156
+ const investigationsIndexDest = path.join(oxeDir, 'INVESTIGATIONS.md');
1157
+ if (!fs.existsSync(investigationsIndexDest)) {
1158
+ fs.writeFileSync(
1159
+ investigationsIndexDest,
1160
+ '# OXE — Investigações\n\n| Data | Ficheiro | Objetivo | Modo | Estado |\n|------|----------|----------|------|--------|\n',
1161
+ 'utf8'
1162
+ );
1163
+ console.log(`${green}init${reset} ${investigationsIndexDest}`);
1164
+ }
1165
+
1075
1166
  ensureGitignoreIgnoresOxeDir(target, { dryRun: false });
1076
1167
  }
1077
1168
 
1169
+ /**
1170
+ * @param {string} url
1171
+ */
1172
+ function openUrlInBrowser(url) {
1173
+ if (process.platform === 'win32') {
1174
+ spawnSync('cmd', ['/c', 'start', '', url], { stdio: 'ignore', detached: true, shell: false });
1175
+ return;
1176
+ }
1177
+ if (process.platform === 'darwin') {
1178
+ spawnSync('open', [url], { stdio: 'ignore', detached: true, shell: false });
1179
+ return;
1180
+ }
1181
+ spawnSync('xdg-open', [url], { stdio: 'ignore', detached: true, shell: false });
1182
+ }
1183
+
1078
1184
  /**
1079
1185
  * Lembretes de rotina (scan/compact antigos) para `status --hints` ou JSON.
1080
1186
  * @param {string} target
@@ -1120,6 +1226,7 @@ function printOxeHealthDiagnostics(target, c, diagOpts = {}) {
1120
1226
  const { config } = oxeHealth.loadOxeConfigMerged(target);
1121
1227
 
1122
1228
  console.log(`\n ${c ? cyan : ''}▸ Coerência .oxe/ e config${reset}`);
1229
+ console.log(` ${c ? dim : ''}Saúde lógica:${c ? reset : ''} ${r.healthStatus}`);
1123
1230
 
1124
1231
  if (r.configParseError) {
1125
1232
  console.log(` ${red}FALHA${reset} config.json: ${r.configParseError}`);
@@ -1138,6 +1245,34 @@ function printOxeHealthDiagnostics(target, c, diagOpts = {}) {
1138
1245
  if (r.phase) {
1139
1246
  console.log(` ${c ? dim : ''}Fase (STATE.md):${c ? reset : ''} ${r.phase}`);
1140
1247
  }
1248
+ if (r.activeSession) {
1249
+ console.log(` ${c ? dim : ''}Sessão ativa:${c ? reset : ''} ${r.activeSession}`);
1250
+ }
1251
+ if (r.planReviewStatus) {
1252
+ console.log(` ${c ? dim : ''}Revisão do plano:${c ? reset : ''} ${r.planReviewStatus}`);
1253
+ }
1254
+ if (r.activeRun && r.activeRun.run_id) {
1255
+ console.log(` ${c ? dim : ''}Run ativo:${c ? reset : ''} ${r.activeRun.run_id} (${r.activeRun.status || 'planned'})`);
1256
+ }
1257
+ if (r.eventsSummary) {
1258
+ console.log(` ${c ? dim : ''}Tracing:${c ? reset : ''} ${r.eventsSummary.total} evento(s)`);
1259
+ }
1260
+ if (r.planSelfEvaluation && r.planSelfEvaluation.hasSection) {
1261
+ const best = r.planSelfEvaluation.bestPlan || '—';
1262
+ const conf =
1263
+ typeof r.planSelfEvaluation.confidence === 'number' ? `${r.planSelfEvaluation.confidence}%` : '—';
1264
+ console.log(` ${c ? dim : ''}Plano (autoavaliação):${c ? reset : ''} melhor=${best} | confiança=${conf}`);
1265
+ }
1266
+ if (r.azureActive && r.azure) {
1267
+ console.log(` ${c ? dim : ''}Azure:${c ? reset : ''} ${r.azure.authStatus && r.azure.authStatus.login_active ? 'login ativo' : 'sem login'} | subscription=${r.azure.profile && (r.azure.profile.subscription_name || r.azure.profile.subscription_id) || '—'}`);
1268
+ console.log(` ${c ? dim : ''}Azure inventory:${c ? reset : ''} total=${r.azure.inventorySummary ? r.azure.inventorySummary.total : 0} | pendências=${r.azure.pendingOperations || 0}`);
1269
+ if (r.azure.inventoryStale && r.azure.inventoryStale.stale) {
1270
+ console.log(` ${yellow}AVISO${reset} Inventário Azure stale — rode ${cyan}npx oxe-cc azure sync${reset}`);
1271
+ }
1272
+ for (const warning of r.azure.warnings || []) {
1273
+ console.log(` ${yellow}AVISO${reset} ${warning}`);
1274
+ }
1275
+ }
1141
1276
 
1142
1277
  if (!skipAge) {
1143
1278
  if (config.scan_max_age_days > 0 && r.scanDate && r.stale.stale) {
@@ -1175,6 +1310,24 @@ function printOxeHealthDiagnostics(target, c, diagOpts = {}) {
1175
1310
  for (const w of r.phaseWarn) {
1176
1311
  console.log(` ${yellow}AVISO${reset} ${w}`);
1177
1312
  }
1313
+ for (const w of r.runtimeWarn) {
1314
+ console.log(` ${yellow}AVISO${reset} ${w}`);
1315
+ }
1316
+ for (const w of r.reviewWarn) {
1317
+ console.log(` ${yellow}AVISO${reset} ${w}`);
1318
+ }
1319
+ for (const w of r.capabilityWarn) {
1320
+ console.log(` ${yellow}AVISO${reset} ${w}`);
1321
+ }
1322
+ for (const w of r.investigationWarn) {
1323
+ console.log(` ${yellow}AVISO${reset} ${w}`);
1324
+ }
1325
+ for (const w of r.sessionWarn) {
1326
+ console.log(` ${yellow}AVISO${reset} ${w}`);
1327
+ }
1328
+ for (const w of r.installWarn) {
1329
+ console.log(` ${yellow}AVISO${reset} ${w}`);
1330
+ }
1178
1331
  if (r.summaryGapWarn) {
1179
1332
  console.log(` ${yellow}AVISO${reset} ${r.summaryGapWarn}`);
1180
1333
  }
@@ -1186,15 +1339,100 @@ function printOxeHealthDiagnostics(target, c, diagOpts = {}) {
1186
1339
  }
1187
1340
  }
1188
1341
 
1342
+ /**
1343
+ * Imprime uma célula de coverage com cor ANSI.
1344
+ * @param {boolean} exists
1345
+ * @param {string} label
1346
+ */
1347
+ function coverageCell(exists, label) {
1348
+ const c = useAnsiColors();
1349
+ return exists ? `${c ? green : ''}✓ ${label}${c ? reset : ''}` : `${c ? dim : ''}✗ ${label}${c ? reset : ''}`;
1350
+ }
1351
+
1352
+ /**
1353
+ * Visão CLI-first: health + coverage matrix + readiness gate no terminal.
1354
+ * @param {string} target
1355
+ */
1356
+ function runStatusFull(target) {
1357
+ const c = useAnsiColors();
1358
+ const report = oxeHealth.buildHealthReport(target);
1359
+ const p = oxeHealth.oxePaths(target);
1360
+ const activeSession = report.activeSession || null;
1361
+ let sp = p;
1362
+ if (activeSession) {
1363
+ sp = oxeHealth.scopedOxePaths(target, activeSession);
1364
+ }
1365
+
1366
+ printSection('OXE ▸ status --full');
1367
+ console.log(` ${c ? green : ''}Projeto:${c ? reset : ''} ${c ? cyan : ''}${target}${c ? reset : ''}`);
1368
+ console.log(` ${c ? green : ''}Sessão:${c ? reset : ''} ${c ? cyan : ''}${activeSession || 'modo legado'}${c ? reset : ''}`);
1369
+ console.log(` ${c ? green : ''}Fase:${c ? reset : ''} ${report.phase || '—'}`);
1370
+
1371
+ const healthColor = report.healthStatus === 'healthy' ? green : report.healthStatus === 'warning' ? yellow : red;
1372
+ console.log(` ${c ? green : ''}Saúde:${c ? reset : ''} ${c ? healthColor : ''}${report.healthStatus}${c ? reset : ''}`);
1373
+
1374
+ // Coverage matrix
1375
+ const specPath = activeSession && sp.spec ? sp.spec : p.spec;
1376
+ const planPath = activeSession && sp.plan ? sp.plan : p.plan;
1377
+ const verifyPath = activeSession && sp.verify ? sp.verify : p.verify;
1378
+ const specExists = fs.existsSync(specPath);
1379
+ const planExists = fs.existsSync(planPath);
1380
+ const verifyExists = fs.existsSync(verifyPath);
1381
+ const lessonsExists = fs.existsSync(p.globalLessons || p.lessons);
1382
+ const codebaseExists = fs.existsSync(p.codebase);
1383
+
1384
+ console.log(`\n ${c ? yellow : ''}Coverage matrix${c ? reset : ''}`);
1385
+ console.log(` ${coverageCell(codebaseExists, 'codebase scan')} ${coverageCell(specExists, 'SPEC.md')} ${coverageCell(planExists, 'PLAN.md')} ${coverageCell(verifyExists, 'VERIFY.md')} ${coverageCell(lessonsExists, 'LESSONS.md')}`);
1386
+
1387
+ // Readiness gate
1388
+ const ready = specExists && planExists && !report.planWarn.length && !report.runtimeWarn.length;
1389
+ const gateColor = ready ? green : yellow;
1390
+ console.log(`\n ${c ? yellow : ''}Readiness gate${c ? reset : ''}`);
1391
+ console.log(` ${c ? gateColor : ''}${ready ? '✓ Pronto para executar' : '✗ Não pronto para executar'}${c ? reset : ''}`);
1392
+ if (!specExists) console.log(` ${c ? dim : ''} • SPEC.md ausente — rode /oxe-spec${c ? reset : ''}`);
1393
+ if (!planExists) console.log(` ${c ? dim : ''} • PLAN.md ausente — rode /oxe-plan${c ? reset : ''}`);
1394
+ if (report.planWarn.length) {
1395
+ for (const w of report.planWarn) {
1396
+ console.log(` ${c ? yellow : ''} • ${w}${c ? reset : ''}`);
1397
+ }
1398
+ }
1399
+
1400
+ // Active run summary
1401
+ if (report.activeRun) {
1402
+ const ar = report.activeRun;
1403
+ console.log(`\n ${c ? yellow : ''}Active run${c ? reset : ''}`);
1404
+ console.log(` ${c ? dim : ''}Run:${c ? reset : ''} ${ar.run_id || '—'} ${c ? dim : ''}Estado:${c ? reset : ''} ${ar.status || '—'} ${c ? dim : ''}Onda:${c ? reset : ''} ${ar.current_wave != null ? ar.current_wave : '—'}`);
1405
+ }
1406
+
1407
+ // Plan self-evaluation
1408
+ if (report.planSelfEvaluation) {
1409
+ const pse = report.planSelfEvaluation;
1410
+ console.log(`\n ${c ? yellow : ''}Autoavaliação do plano${c ? reset : ''}`);
1411
+ if (pse.best_plan_current != null) {
1412
+ const bestColor = pse.best_plan_current ? green : red;
1413
+ console.log(` ${c ? dim : ''}Melhor plano atual:${c ? reset : ''} ${c ? bestColor : ''}${pse.best_plan_current ? 'sim' : 'não'}${c ? reset : ''}`);
1414
+ }
1415
+ if (pse.confidence != null) {
1416
+ const confColor = Number(pse.confidence) >= 70 ? green : Number(pse.confidence) >= 50 ? yellow : red;
1417
+ console.log(` ${c ? dim : ''}Confiança:${c ? reset : ''} ${c ? confColor : ''}${pse.confidence}%${c ? reset : ''}`);
1418
+ }
1419
+ }
1420
+
1421
+ console.log(`\n ${c ? dim : ''}Próximo passo:${c ? reset : ''} ${c ? cyan : ''}${report.next && report.next.cursorCmd ? report.next.cursorCmd : '—'}${c ? reset : ''}`);
1422
+ console.log(` ${c ? dim : ''}Motivo:${c ? reset : ''} ${report.next && report.next.reason ? report.next.reason : '—'}`);
1423
+ console.log(`\n ${c ? dim : ''}Para visão operacional completa (web): ${cyan}oxe-cc dashboard${c ? reset : ''}`);
1424
+ console.log(` ${c ? green : ''}✓${c ? reset : ''} status --full concluído.\n`);
1425
+ }
1426
+
1189
1427
  /**
1190
1428
  * @param {string} target
1191
- * @param {{ json?: boolean, hints?: boolean }} [opts]
1429
+ * @param {{ json?: boolean, hints?: boolean, full?: boolean }} [opts]
1192
1430
  */
1193
1431
  function runStatus(target, opts = {}) {
1194
1432
  const { config } = oxeHealth.loadOxeConfigMerged(target);
1195
- const next = oxeHealth.suggestNextStep(target, { discuss_before_plan: config.discuss_before_plan });
1196
1433
  const report = oxeHealth.buildHealthReport(target);
1197
1434
  const routineHints = collectOxeRoutineHints(target, report, config);
1435
+ const next = report.next;
1198
1436
 
1199
1437
  if (opts.json) {
1200
1438
  /** @type {Record<string, unknown>} */
@@ -1206,15 +1444,30 @@ function runStatus(target, opts = {}) {
1206
1444
  reason: report.next.reason,
1207
1445
  artifacts: report.next.artifacts,
1208
1446
  phase: report.phase,
1447
+ healthStatus: report.healthStatus,
1448
+ activeSession: report.activeSession,
1209
1449
  scanDate: report.scanDate,
1210
1450
  staleScan: report.stale,
1211
1451
  compactDate: report.compactDate,
1212
- staleCompact: report.staleCompact,
1213
- diagnostics: {
1452
+ staleCompact: report.staleCompact,
1453
+ planSelfEvaluation: report.planSelfEvaluation,
1454
+ planReviewStatus: report.planReviewStatus,
1455
+ activeRun: report.activeRun,
1456
+ eventsSummary: report.eventsSummary,
1457
+ memoryLayers: report.memoryLayers,
1458
+ azureActive: report.azureActive,
1459
+ azure: report.azure,
1460
+ diagnostics: {
1214
1461
  configParseError: report.configParseError,
1215
1462
  typeErrors: report.typeErrors,
1216
1463
  unknownConfigKeys: report.unknownConfigKeys,
1217
1464
  phaseWarnings: report.phaseWarn,
1465
+ runtimeWarnings: report.runtimeWarn,
1466
+ reviewWarnings: report.reviewWarn,
1467
+ capabilityWarnings: report.capabilityWarn,
1468
+ investigationWarnings: report.investigationWarn,
1469
+ sessionWarnings: report.sessionWarn,
1470
+ installWarnings: report.installWarn,
1218
1471
  summaryGapWarning: report.summaryGapWarn,
1219
1472
  specWarnings: report.specWarn,
1220
1473
  planWarnings: report.planWarn,
@@ -1255,7 +1508,10 @@ function runStatus(target, opts = {}) {
1255
1508
  console.log(` ${c ? dim : ''}Motivo:${c ? reset : ''} ${next.reason}`);
1256
1509
 
1257
1510
  printSummaryAndNextSteps(c, {
1258
- bullets: [`Artefatos em jogo: ${next.artifacts.join(', ')}`],
1511
+ bullets: [
1512
+ `Saúde lógica: ${report.healthStatus}`,
1513
+ `Artefatos em jogo: ${next.artifacts.join(', ')}`,
1514
+ ],
1259
1515
  nextSteps: [
1260
1516
  { desc: 'Diagnóstico completo (inclui pacote de workflows):', cmd: 'npx oxe-cc doctor' },
1261
1517
  { desc: 'Ação sugerida no agente:', cmd: next.cursorCmd },
@@ -1367,13 +1623,17 @@ function runDoctor(target) {
1367
1623
  }
1368
1624
 
1369
1625
  printOxeHealthDiagnostics(target, c);
1370
-
1371
- console.log(`\n ${green}Diagnóstico OK nenhum bloqueio crítico encontrado.${reset}`);
1626
+ const report = oxeHealth.buildHealthReport(target);
1627
+ const statusColor = report.healthStatus === 'healthy' ? green : report.healthStatus === 'warning' ? yellow : red;
1628
+ console.log(`\n ${statusColor}Diagnóstico ${report.healthStatus}${reset}`);
1629
+ if (report.healthStatus === 'broken') {
1630
+ process.exitCode = 1;
1631
+ }
1372
1632
  printSummaryAndNextSteps(c, {
1373
1633
  bullets: [
1374
1634
  `Projeto em ${target}`,
1375
1635
  `Workflows conferidos em ${wfLabel}`,
1376
- 'Node.js e (quando existir) config.json validados',
1636
+ `Saúde lógica: ${report.healthStatus}`,
1377
1637
  ],
1378
1638
  nextSteps: [
1379
1639
  { desc: 'Mapear ou atualizar o codebase no agente:', cmd: '/oxe-scan' },
@@ -1413,6 +1673,98 @@ function installGlobalCliPackage() {
1413
1673
  return false;
1414
1674
  }
1415
1675
 
1676
+ /**
1677
+ * `npm uninstall -g oxe-cc` com a mesma semântica cross-platform do instalador.
1678
+ * @returns {boolean}
1679
+ */
1680
+ function uninstallGlobalCliPackage() {
1681
+ const name = readPkgName();
1682
+ const c = useAnsiColors();
1683
+ const dimOrEmpty = c ? dim : '';
1684
+ const resetOrEmpty = c ? reset : '';
1685
+ console.log(`\n ${dimOrEmpty}npm uninstall -g ${name}${resetOrEmpty}\n`);
1686
+ const r = spawnSync('npm', ['uninstall', '-g', name], {
1687
+ stdio: 'inherit',
1688
+ shell: true,
1689
+ env: process.env,
1690
+ });
1691
+ if (r.status === 0) {
1692
+ console.log(
1693
+ `\n ${c ? green : ''}✓${c ? reset : ''} pacote global ${c ? cyan : ''}${name}${c ? reset : ''} removido do npm global.\n`
1694
+ );
1695
+ return true;
1696
+ }
1697
+ console.log(
1698
+ `\n ${c ? yellow : ''}⚠${c ? reset : ''} npm uninstall -g falhou. Remova manualmente: ${c ? cyan : ''}npm uninstall -g ${name}${c ? reset : ''}\n`
1699
+ );
1700
+ return false;
1701
+ }
1702
+
1703
+ /**
1704
+ * Best-effort: detecta se esta execução vem de uma instalação global do npm.
1705
+ * Usa `npm root -g` para evitar confundir execução local do repositório com pacote global.
1706
+ * @returns {boolean}
1707
+ */
1708
+ function isRunningFromGlobalNpmInstall() {
1709
+ try {
1710
+ const r = spawnSync('npm', ['root', '-g'], {
1711
+ encoding: 'utf8',
1712
+ shell: true,
1713
+ env: process.env,
1714
+ });
1715
+ if (r.status !== 0) return false;
1716
+ const root = String(r.stdout || '').trim();
1717
+ if (!root) return false;
1718
+ const rel = path.relative(path.resolve(root), PKG_ROOT);
1719
+ return rel && !rel.startsWith('..') && !path.isAbsolute(rel);
1720
+ } catch {
1721
+ return false;
1722
+ }
1723
+ }
1724
+
1725
+ /** @returns {string[]} */
1726
+ function updateForwardedInstallFlags() {
1727
+ return [
1728
+ '--cursor',
1729
+ '--copilot',
1730
+ '--copilot-cli',
1731
+ '--all-agents',
1732
+ '--opencode',
1733
+ '--gemini',
1734
+ '--codex',
1735
+ '--windsurf',
1736
+ '--antigravity',
1737
+ '--vscode',
1738
+ '--no-commands',
1739
+ '--no-agents',
1740
+ '--no-init-oxe',
1741
+ '--oxe-only',
1742
+ '--global',
1743
+ '--local',
1744
+ '--ide-global',
1745
+ '--ide-local',
1746
+ '--global-cli',
1747
+ '-g',
1748
+ '--no-global-cli',
1749
+ '-l',
1750
+ '--no-install-config',
1751
+ '--force',
1752
+ '-f',
1753
+ '--all',
1754
+ '-a',
1755
+ '--config-dir',
1756
+ '-c',
1757
+ ];
1758
+ }
1759
+
1760
+ /**
1761
+ * @param {string[]} rest
1762
+ * @returns {boolean}
1763
+ */
1764
+ function updateArgsExplicitlyControlGlobalCli(rest) {
1765
+ return rest.includes('--global-cli') || rest.includes('-g') || rest.includes('--no-global-cli') || rest.includes('-l');
1766
+ }
1767
+
1416
1768
  /**
1417
1769
  * After copying OXE into the project: optionally install the CLI globally (pergunta interativa ou flags).
1418
1770
  * @param {InstallOpts} opts
@@ -1496,6 +1848,10 @@ ${green}Uso:${reset}
1496
1848
  npx oxe-cc doctor [opções] [pasta-do-projeto]
1497
1849
  npx oxe-cc status [opções] [pasta-do-projeto]
1498
1850
  npx oxe-cc init-oxe [opções] [pasta-do-projeto]
1851
+ npx oxe-cc dashboard [opções] [pasta-do-projeto]
1852
+ npx oxe-cc runtime <status|start|pause|resume|replay> [opções] [pasta-do-projeto]
1853
+ npx oxe-cc azure <status|doctor|auth|sync|find|servicebus|eventgrid|sql|operations> [opções] [pasta-do-projeto]
1854
+ npx oxe-cc capabilities <list|install|remove|update> [opções] [id]
1499
1855
  npx oxe-cc uninstall [opções] [pasta-do-projeto]
1500
1856
  npx oxe-cc update [opções] [argumentos extras…]
1501
1857
 
@@ -1504,6 +1860,7 @@ ${green}uninstall${reset} (remove OXE da pasta do usuário + pastas de workflows
1504
1860
  --all-agents também remove ficheiros multi-plataforma (com --copilot-cli implícito)
1505
1861
  --ide-local remove integrações IDE neste repositório (.cursor, .github, .claude, .copilot, …)
1506
1862
  --ide-only não apagar .oxe/workflows, oxe/, etc. no projeto
1863
+ --global-cli, -g também executa npm uninstall -g oxe-cc
1507
1864
  --config-dir <caminho> com exatamente uma flag IDE acima (não combina com --ide-local)
1508
1865
  --dry-run
1509
1866
  --dir <pasta> raiz do projeto (padrão: diretório atual)
@@ -1513,9 +1870,70 @@ ${green}update${reset} (executa npx oxe-cc@latest --force na pasta do projeto)
1513
1870
  --if-newer só executa o npx se existir versão mais nova no npm (falha de rede/registry: saída 2, sem npx)
1514
1871
  --dir <pasta> pasta em que o npx roda (padrão: atual; ignorada com --check)
1515
1872
  --dry-run mostra o comando sem executar
1516
- [argumentos extras…] repassados ao oxe-cc (ex.: --cursor --global)
1873
+ [argumentos extras…] repassados ao oxe-cc (ex.: --cursor --global, --ide-local, --global-cli)
1517
1874
  ${dim}CI / sem rede:${reset} OXE_UPDATE_SKIP_REGISTRY=1 desativa consultas (--check sai 2; --if-newer sai 2 sem npx)
1518
1875
 
1876
+ ${green}dashboard${reset} (interface web local para revisão e aprovação do plano)
1877
+ --port <número> porta local (padrão: 4173)
1878
+ --no-open não abre o browser automaticamente
1879
+ --session <sessions/sNNN-slug> força visualização de uma sessão específica
1880
+ --dump-context imprime JSON consolidado e sai
1881
+ --dir <pasta> raiz do projeto (padrão: diretório atual)
1882
+
1883
+ ${green}runtime${reset} (controle operacional explícito do ACTIVE-RUN)
1884
+ status mostra o run ativo resolvido para a sessão atual
1885
+ start cria um novo run com tracing inicial
1886
+ pause pausa o run ativo e preserva o cursor
1887
+ resume retoma o run ativo
1888
+ replay marca replay parcial por onda ou tarefa
1889
+ --session <sessions/sNNN-slug> força sessão específica
1890
+ --wave <número> fixa onda atual/cursor
1891
+ --task <Tn> fixa tarefa atual/cursor
1892
+ --mode <complete|wave|task> modo operacional do cursor
1893
+ --reason <texto> motivo explícito da transição
1894
+ --dir <pasta> raiz do projeto (padrão: diretório atual)
1895
+
1896
+ ${green}azure${reset} (provider Azure nativo via Azure CLI no Windows)
1897
+ status estado compacto: CLI, login, subscription, inventário, pendências
1898
+ doctor valida Azure CLI, login, subscription e inventário
1899
+ auth login [--tenant <id>] login interativo via Azure CLI (Entra ID: use --tenant <tenant-id>)
1900
+ auth whoami mostra identidade, tenant, subscription e cloud
1901
+ auth set-subscription --subscription <id|nome>
1902
+ fixa a subscription operacional do projeto
1903
+ Fluxo corporativo: auth login --tenant <tenant-id> → auth set-subscription --subscription <dev-sub-id>
1904
+ sync sincroniza inventário via Azure Resource Graph
1905
+ sync --diff sincroniza e mostra recursos adicionados/removidos
1906
+ find <texto> busca recursos no inventário local materializado
1907
+ find <texto> --type <tipo> filtra por tipo de serviço (ex.: servicebus, eventgrid, sql)
1908
+ find <texto> --filter-rg <rg> filtra por resource group
1909
+ servicebus <list|show|plan|apply> namespace, queue, topic, subscription
1910
+ eventgrid <list|show|plan|apply> topic, system-topic, event-subscription
1911
+ sql <list|show|plan|apply> server, database, firewall-rule
1912
+ operations list histórico de operações planejadas/aplicadas/pendentes
1913
+ --kind <tipo> ex.: namespace, queue, topic, system-topic, database
1914
+ --resource-group <rg> resource group alvo
1915
+ --name <nome> nome principal do recurso
1916
+ --namespace <nome> namespace Service Bus
1917
+ --topic-name <nome> topic Service Bus
1918
+ --subscription-name <nome> subscription de topic Service Bus
1919
+ --source-resource-id <id> origem de event subscription
1920
+ --endpoint <url|id> endpoint de Event Grid
1921
+ --server <nome> / --database <nome> recursos Azure SQL
1922
+ --location <região> localização alvo
1923
+ --admin-user <user> admin do SQL server (plan/apply)
1924
+ --admin-password-env <ENV> variável de ambiente com password do SQL admin
1925
+ --start-ip-address / --end-ip-address faixa de firewall rule para Azure SQL
1926
+ --approve aplica mutação já planejada após checkpoint formal
1927
+ --dry-run pré-visualiza comando sem executar nem criar artefatos
1928
+ --diff (sync) exibe diff de recursos adicionados/removidos
1929
+ --type <tipo> (find) filtra por família de serviço
1930
+ --filter-rg <rg> (find) filtra por resource group
1931
+ --tenant <id> (auth login) tenant Entra ID para contas corporativas
1932
+ --vpn-confirmed confirma conexão VPN quando vpn_required está configurado
1933
+ --override-policy override explícito para policy deny_unless_overridden
1934
+ --session <sessions/sNNN-slug> associa a operação ao runtime da sessão ativa
1935
+ --dir <pasta> raiz do projeto (padrão: diretório atual)
1936
+
1519
1937
  ${green}Opções da instalação:${reset}
1520
1938
  --cursor Copia comandos e regras para ~/.cursor (padrão com --all)
1521
1939
  --copilot Mescla instruções + prompts em ~/.copilot (global) ou .github/ (com --ide-local)
@@ -1555,6 +1973,13 @@ ${green}status${reset} (coerência .oxe/ + um próximo passo sugerido; não exig
1555
1973
  --json imprime um único objeto JSON (próximo passo + diagnósticos) em stdout; adequado a CI
1556
1974
  --hints lembretes de rotina (idade scan/compact quando configurado em config.json); com --json inclui array \`hints\`
1557
1975
 
1976
+ ${green}capabilities${reset} (catálogo nativo de extensões do projeto)
1977
+ list lista capabilities instaladas em .oxe/capabilities/
1978
+ install <id> cria capability local a partir do template nativo
1979
+ remove <id> remove capability do catálogo local
1980
+ update regera .oxe/CAPABILITIES.md a partir dos manifestos locais
1981
+ --dir <pasta> raiz do projeto (padrão: diretório atual)
1982
+
1558
1983
  ${green}Atualizar (projeto já tem OXE):${reset}
1559
1984
  /oxe-update no Cursor (outras IDEs: mesmo fluxo pelo terminal)
1560
1985
  npx oxe-cc update --check só ver se há versão nova no npm
@@ -1856,7 +2281,7 @@ function runInstall(opts) {
1856
2281
  console.log(` ${c ? green : ''}✓${c ? reset : ''} Instalação concluída com sucesso.\n`);
1857
2282
  }
1858
2283
 
1859
- /** @typedef {{ help: boolean, dryRun: boolean, cursor: boolean, copilot: boolean, copilotCli: boolean, allAgents: boolean, ideLocal: boolean, ideExplicit: boolean, noProject: boolean, dir: string, explicitConfigDir: string | null, parseError: boolean, unknownFlag: string, conflictFlags: string }} UninstallOpts */
2284
+ /** @typedef {{ help: boolean, dryRun: boolean, cursor: boolean, copilot: boolean, copilotCli: boolean, allAgents: boolean, globalCli: boolean, ideLocal: boolean, ideExplicit: boolean, noProject: boolean, dir: string, explicitConfigDir: string | null, parseError: boolean, unknownFlag: string, conflictFlags: string }} UninstallOpts */
1860
2285
 
1861
2286
  /**
1862
2287
  * @param {UninstallOpts} u
@@ -1965,6 +2390,7 @@ function parseUninstallArgs(argv) {
1965
2390
  copilot: false,
1966
2391
  copilotCli: false,
1967
2392
  allAgents: false,
2393
+ globalCli: false,
1968
2394
  ideLocal: false,
1969
2395
  ideExplicit: false,
1970
2396
  noProject: false,
@@ -1994,6 +2420,8 @@ function parseUninstallArgs(argv) {
1994
2420
  out.allAgents = true;
1995
2421
  out.copilotCli = true;
1996
2422
  out.ideExplicit = true;
2423
+ } else if (a === '--global-cli' || a === '-g') {
2424
+ out.globalCli = true;
1997
2425
  } else if (a === '--ide-local') out.ideLocal = true;
1998
2426
  else if (a === '--ide-only') out.noProject = true;
1999
2427
  else if (a === '--dir' && argv[i + 1]) out.dir = path.resolve(argv[++i]);
@@ -2252,7 +2680,18 @@ function runUninstall(u) {
2252
2680
  oxeManifest.writeFileManifest(home, next, readPkgVersion());
2253
2681
  }
2254
2682
 
2683
+ if (u.globalCli && !u.dryRun) {
2684
+ uninstallGlobalCliPackage();
2685
+ } else if (u.globalCli && u.dryRun) {
2686
+ console.log(`${dim}npm${reset} npm uninstall -g ${readPkgName()}`);
2687
+ }
2688
+
2255
2689
  printSummaryAndNextSteps(c, buildUninstallFooter(u));
2690
+ if (!u.globalCli) {
2691
+ console.log(
2692
+ ` ${c ? yellow : ''}Nota:${c ? reset : ''} o pacote npm global ${c ? cyan : ''}${readPkgName()}${c ? reset : ''} não é removido por padrão. Use ${c ? cyan : ''}oxe-cc uninstall --global-cli${c ? reset : ''} ou ${c ? cyan : ''}npm uninstall -g ${readPkgName()}${c ? reset : ''}.\n`
2693
+ );
2694
+ }
2256
2695
  console.log(` ${c ? green : ''}✓${c ? reset : ''} Desinstalação concluída com sucesso.\n`);
2257
2696
  }
2258
2697
 
@@ -2277,6 +2716,7 @@ function parseUpdateArgs(argv) {
2277
2716
  };
2278
2717
  let dirExplicit = false;
2279
2718
  let firstPositionalConsumed = false;
2719
+ const passthroughFlags = new Set(updateForwardedInstallFlags());
2280
2720
  for (let i = 0; i < argv.length; i++) {
2281
2721
  const a = argv[i];
2282
2722
  if (a === '-h' || a === '--help') out.help = true;
@@ -2291,6 +2731,11 @@ function parseUpdateArgs(argv) {
2291
2731
  out.dir = path.resolve(a);
2292
2732
  firstPositionalConsumed = true;
2293
2733
  } else out.rest.push(a);
2734
+ } else if (passthroughFlags.has(a)) {
2735
+ out.rest.push(a);
2736
+ if ((a === '--config-dir' || a === '-c') && argv[i + 1]) {
2737
+ out.rest.push(argv[++i]);
2738
+ }
2294
2739
  } else {
2295
2740
  out.parseError = true;
2296
2741
  out.unknownFlag = a;
@@ -2359,7 +2804,11 @@ function runUpdate(u) {
2359
2804
  );
2360
2805
  }
2361
2806
  console.log(` ${dim}Comando que seria executado (instalação):${reset}`);
2362
- console.log(` ${cyan}npx -y oxe-cc@latest --force --no-global-cli -l${reset} ${u.rest.join(' ')}`);
2807
+ const dryRunArgs = ['-y', 'oxe-cc@latest', '--force'];
2808
+ if (updateArgsExplicitlyControlGlobalCli(u.rest)) dryRunArgs.push(...u.rest);
2809
+ else if (isRunningFromGlobalNpmInstall()) dryRunArgs.push('--global-cli');
2810
+ else dryRunArgs.push('--no-global-cli', '-l');
2811
+ console.log(` ${cyan}npx ${dryRunArgs.join(' ')}${reset}`);
2363
2812
  console.log(` ${dim}Diretório:${reset} ${u.dir}`);
2364
2813
  printSummaryAndNextSteps(c, {
2365
2814
  bullets: [
@@ -2408,7 +2857,14 @@ function runUpdate(u) {
2408
2857
  }
2409
2858
 
2410
2859
  printSection('OXE ▸ update');
2411
- const args = ['-y', 'oxe-cc@latest', '--force', '--no-global-cli', '-l', ...u.rest];
2860
+ const args = ['-y', 'oxe-cc@latest', '--force'];
2861
+ if (updateArgsExplicitlyControlGlobalCli(u.rest)) {
2862
+ args.push(...u.rest);
2863
+ } else if (isRunningFromGlobalNpmInstall()) {
2864
+ args.push('--global-cli', ...u.rest);
2865
+ } else {
2866
+ args.push('--no-global-cli', '-l', ...u.rest);
2867
+ }
2412
2868
  const r = spawnSync('npx', args, {
2413
2869
  cwd: u.dir,
2414
2870
  stdio: 'inherit',
@@ -2434,6 +2890,823 @@ function runUpdate(u) {
2434
2890
  console.log(` ${c ? green : ''}✓${c ? reset : ''} Atualização concluída com sucesso.\n`);
2435
2891
  }
2436
2892
 
2893
+ /**
2894
+ * @typedef {{ help: boolean, dir: string, action: string, id: string, parseError: boolean, unknownFlag: string }} CapabilityOpts
2895
+ */
2896
+
2897
+ /**
2898
+ * @param {string[]} argv
2899
+ * @returns {CapabilityOpts}
2900
+ */
2901
+ function parseCapabilitiesArgs(argv) {
2902
+ /** @type {CapabilityOpts} */
2903
+ const out = {
2904
+ help: false,
2905
+ dir: process.cwd(),
2906
+ action: 'list',
2907
+ id: '',
2908
+ parseError: false,
2909
+ unknownFlag: '',
2910
+ };
2911
+ const positionals = [];
2912
+ for (let i = 0; i < argv.length; i++) {
2913
+ const a = argv[i];
2914
+ if (a === '-h' || a === '--help') out.help = true;
2915
+ else if (a === '--dir' && argv[i + 1]) out.dir = path.resolve(argv[++i]);
2916
+ else if (!a.startsWith('-')) positionals.push(a);
2917
+ else {
2918
+ out.parseError = true;
2919
+ out.unknownFlag = a;
2920
+ break;
2921
+ }
2922
+ }
2923
+ if (positionals.length) {
2924
+ out.action = positionals[0];
2925
+ out.id = positionals[1] || '';
2926
+ }
2927
+ return out;
2928
+ }
2929
+
2930
+ /**
2931
+ * @typedef {{ help: boolean, dir: string, port: number, noOpen: boolean, readOnly: boolean, dumpContext: boolean, activeSession: string|null, parseError: boolean, unknownFlag: string }} DashboardOpts
2932
+ */
2933
+
2934
+ /**
2935
+ * @typedef {{ help: boolean, dir: string, action: string, activeSession: string|null, wave: number|null, task: string, mode: string, reason: string, parseError: boolean, unknownFlag: string }} RuntimeOpts
2936
+ */
2937
+
2938
+ /**
2939
+ * @typedef {{
2940
+ * help: boolean,
2941
+ * dir: string,
2942
+ * scope: string,
2943
+ * action: string,
2944
+ * query: string,
2945
+ * activeSession: string|null,
2946
+ * subscription: string,
2947
+ * kind: string,
2948
+ * resourceGroup: string,
2949
+ * name: string,
2950
+ * namespace: string,
2951
+ * topicName: string,
2952
+ * subscriptionName: string,
2953
+ * sourceResourceId: string,
2954
+ * endpoint: string,
2955
+ * location: string,
2956
+ * server: string,
2957
+ * database: string,
2958
+ * adminUser: string,
2959
+ * adminPasswordEnv: string,
2960
+ * startIpAddress: string,
2961
+ * endIpAddress: string,
2962
+ * approve: boolean,
2963
+ * overridePolicy: boolean,
2964
+ * dryRun: boolean,
2965
+ * diff: boolean,
2966
+ * filterType: string,
2967
+ * filterRg: string,
2968
+ * vpnConfirmed: boolean,
2969
+ * tenant: string,
2970
+ * parseError: boolean,
2971
+ * unknownFlag: string
2972
+ * }} AzureOpts
2973
+ */
2974
+
2975
+ /**
2976
+ * @param {string[]} argv
2977
+ * @returns {DashboardOpts}
2978
+ */
2979
+ function parseDashboardArgs(argv) {
2980
+ /** @type {DashboardOpts} */
2981
+ const out = {
2982
+ help: false,
2983
+ dir: process.cwd(),
2984
+ port: 4173,
2985
+ noOpen: false,
2986
+ readOnly: false,
2987
+ dumpContext: false,
2988
+ activeSession: null,
2989
+ parseError: false,
2990
+ unknownFlag: '',
2991
+ };
2992
+ for (let i = 0; i < argv.length; i++) {
2993
+ const a = argv[i];
2994
+ if (a === '-h' || a === '--help') out.help = true;
2995
+ else if (a === '--dir' && argv[i + 1]) out.dir = path.resolve(argv[++i]);
2996
+ else if (a === '--port' && argv[i + 1]) out.port = Number(argv[++i]) || 4173;
2997
+ else if (a === '--no-open') out.noOpen = true;
2998
+ else if (a === '--read-only') out.readOnly = true;
2999
+ else if (a === '--dump-context') out.dumpContext = true;
3000
+ else if (a === '--session' && argv[i + 1]) out.activeSession = String(argv[++i]).replace(/\\/g, '/');
3001
+ else if (!a.startsWith('-') && i === 0) out.dir = path.resolve(a);
3002
+ else {
3003
+ out.parseError = true;
3004
+ out.unknownFlag = a;
3005
+ break;
3006
+ }
3007
+ }
3008
+ return out;
3009
+ }
3010
+
3011
+ /**
3012
+ * @param {string[]} argv
3013
+ * @returns {RuntimeOpts}
3014
+ */
3015
+ function parseRuntimeArgs(argv) {
3016
+ /** @type {RuntimeOpts} */
3017
+ const out = {
3018
+ help: false,
3019
+ dir: process.cwd(),
3020
+ action: 'status',
3021
+ activeSession: null,
3022
+ wave: null,
3023
+ task: '',
3024
+ mode: '',
3025
+ reason: '',
3026
+ parseError: false,
3027
+ unknownFlag: '',
3028
+ };
3029
+ const positionals = [];
3030
+ for (let i = 0; i < argv.length; i += 1) {
3031
+ const a = argv[i];
3032
+ if (a === '-h' || a === '--help') out.help = true;
3033
+ else if (a === '--dir' && argv[i + 1]) out.dir = path.resolve(argv[++i]);
3034
+ else if (a === '--session' && argv[i + 1]) out.activeSession = String(argv[++i]).replace(/\\/g, '/');
3035
+ else if (a === '--wave' && argv[i + 1]) out.wave = Number(argv[++i]);
3036
+ else if (a === '--task' && argv[i + 1]) out.task = String(argv[++i]);
3037
+ else if (a === '--mode' && argv[i + 1]) out.mode = String(argv[++i]);
3038
+ else if (a === '--reason' && argv[i + 1]) out.reason = String(argv[++i]);
3039
+ else if (!a.startsWith('-')) positionals.push(a);
3040
+ else {
3041
+ out.parseError = true;
3042
+ out.unknownFlag = a;
3043
+ break;
3044
+ }
3045
+ }
3046
+ if (positionals[0]) out.action = positionals[0];
3047
+ if (positionals[1]) out.dir = path.resolve(positionals[1]);
3048
+ if (Number.isNaN(out.wave)) out.wave = null;
3049
+ return out;
3050
+ }
3051
+
3052
+ /**
3053
+ * @param {string[]} argv
3054
+ * @returns {AzureOpts}
3055
+ */
3056
+ function parseAzureArgs(argv) {
3057
+ /** @type {AzureOpts} */
3058
+ const out = {
3059
+ help: false,
3060
+ dir: process.cwd(),
3061
+ scope: 'doctor',
3062
+ action: '',
3063
+ query: '',
3064
+ activeSession: null,
3065
+ subscription: '',
3066
+ kind: '',
3067
+ resourceGroup: '',
3068
+ name: '',
3069
+ namespace: '',
3070
+ topicName: '',
3071
+ subscriptionName: '',
3072
+ sourceResourceId: '',
3073
+ endpoint: '',
3074
+ location: '',
3075
+ server: '',
3076
+ database: '',
3077
+ adminUser: '',
3078
+ adminPasswordEnv: '',
3079
+ startIpAddress: '',
3080
+ endIpAddress: '',
3081
+ approve: false,
3082
+ overridePolicy: false,
3083
+ dryRun: false,
3084
+ diff: false,
3085
+ filterType: '',
3086
+ filterRg: '',
3087
+ vpnConfirmed: false,
3088
+ tenant: '',
3089
+ parseError: false,
3090
+ unknownFlag: '',
3091
+ };
3092
+ const positionals = [];
3093
+ for (let i = 0; i < argv.length; i += 1) {
3094
+ const a = argv[i];
3095
+ if (a === '-h' || a === '--help') out.help = true;
3096
+ else if (a === '--dir' && argv[i + 1]) out.dir = path.resolve(argv[++i]);
3097
+ else if (a === '--session' && argv[i + 1]) out.activeSession = String(argv[++i]).replace(/\\/g, '/');
3098
+ else if (a === '--subscription' && argv[i + 1]) out.subscription = String(argv[++i]);
3099
+ else if (a === '--kind' && argv[i + 1]) out.kind = String(argv[++i]);
3100
+ else if (a === '--resource-group' && argv[i + 1]) out.resourceGroup = String(argv[++i]);
3101
+ else if (a === '--name' && argv[i + 1]) out.name = String(argv[++i]);
3102
+ else if (a === '--namespace' && argv[i + 1]) out.namespace = String(argv[++i]);
3103
+ else if (a === '--topic-name' && argv[i + 1]) out.topicName = String(argv[++i]);
3104
+ else if (a === '--subscription-name' && argv[i + 1]) out.subscriptionName = String(argv[++i]);
3105
+ else if (a === '--source-resource-id' && argv[i + 1]) out.sourceResourceId = String(argv[++i]);
3106
+ else if (a === '--endpoint' && argv[i + 1]) out.endpoint = String(argv[++i]);
3107
+ else if (a === '--location' && argv[i + 1]) out.location = String(argv[++i]);
3108
+ else if (a === '--server' && argv[i + 1]) out.server = String(argv[++i]);
3109
+ else if (a === '--database' && argv[i + 1]) out.database = String(argv[++i]);
3110
+ else if (a === '--admin-user' && argv[i + 1]) out.adminUser = String(argv[++i]);
3111
+ else if (a === '--admin-password-env' && argv[i + 1]) out.adminPasswordEnv = String(argv[++i]);
3112
+ else if (a === '--start-ip-address' && argv[i + 1]) out.startIpAddress = String(argv[++i]);
3113
+ else if (a === '--end-ip-address' && argv[i + 1]) out.endIpAddress = String(argv[++i]);
3114
+ else if (a === '--approve') out.approve = true;
3115
+ else if (a === '--override-policy') out.overridePolicy = true;
3116
+ else if (a === '--dry-run') out.dryRun = true;
3117
+ else if (a === '--diff') out.diff = true;
3118
+ else if (a === '--type' && argv[i + 1]) out.filterType = String(argv[++i]);
3119
+ else if (a === '--filter-rg' && argv[i + 1]) out.filterRg = String(argv[++i]);
3120
+ else if (a === '--vpn-confirmed') out.vpnConfirmed = true;
3121
+ else if (a === '--tenant' && argv[i + 1]) out.tenant = String(argv[++i]);
3122
+ else if (!a.startsWith('-')) positionals.push(a);
3123
+ else {
3124
+ out.parseError = true;
3125
+ out.unknownFlag = a;
3126
+ break;
3127
+ }
3128
+ }
3129
+ if (positionals[0]) out.scope = String(positionals[0]);
3130
+ if (positionals[1]) out.action = String(positionals[1]);
3131
+ if (out.scope === 'find') {
3132
+ out.query = positionals.slice(1).join(' ').trim();
3133
+ } else if (!out.action && positionals[2]) {
3134
+ out.query = positionals.slice(2).join(' ').trim();
3135
+ } else if (positionals[2]) {
3136
+ out.query = positionals.slice(2).join(' ').trim();
3137
+ }
3138
+ return out;
3139
+ }
3140
+
3141
+ /**
3142
+ * @param {DashboardOpts} opts
3143
+ */
3144
+ async function runDashboard(opts) {
3145
+ assertNotWslWindowsNode();
3146
+ if (!fs.existsSync(opts.dir)) {
3147
+ console.error(`${yellow}Diretório não encontrado: ${opts.dir}${reset}`);
3148
+ process.exit(1);
3149
+ }
3150
+ const target = opts.dir;
3151
+ if (opts.dumpContext) {
3152
+ console.log(JSON.stringify(oxeDashboard.loadDashboardContext(target, { activeSession: opts.activeSession }), null, 2));
3153
+ return;
3154
+ }
3155
+ const c = useAnsiColors();
3156
+ printSection('OXE ▸ dashboard');
3157
+ const server = oxeDashboard.createDashboardServer(target, { activeSession: opts.activeSession });
3158
+ await new Promise((resolve, reject) => {
3159
+ server.once('error', reject);
3160
+ server.listen(opts.port, '127.0.0.1', () => resolve());
3161
+ });
3162
+ const address = server.address();
3163
+ const port = address && typeof address === 'object' ? address.port : opts.port;
3164
+ const url = `http://127.0.0.1:${port}/`;
3165
+ console.log(` ${c ? green : ''}Projeto:${c ? reset : ''} ${c ? cyan : ''}${target}${c ? reset : ''}`);
3166
+ console.log(` ${c ? green : ''}URL:${c ? reset : ''} ${c ? cyan : ''}${url}${c ? reset : ''}`);
3167
+ if (opts.readOnly) {
3168
+ console.log(` ${c ? yellow : ''}Modo:${c ? reset : ''} read-only (UI visual; persistência deve ser evitada no uso desta flag)`);
3169
+ }
3170
+ if (!opts.noOpen) {
3171
+ try {
3172
+ openUrlInBrowser(url);
3173
+ console.log(` ${c ? green : ''}✓${c ? reset : ''} Browser aberto.`);
3174
+ } catch {
3175
+ console.log(` ${yellow}Não foi possível abrir o browser automaticamente.${reset}`);
3176
+ }
3177
+ }
3178
+ console.log(` ${dim}Pressione Ctrl+C para encerrar o servidor local.${reset}\n`);
3179
+ await new Promise(() => {});
3180
+ }
3181
+
3182
+ /**
3183
+ * @param {RuntimeOpts} opts
3184
+ */
3185
+ function runRuntime(opts) {
3186
+ const c = useAnsiColors();
3187
+ printSection('OXE ▸ runtime');
3188
+ if (!fs.existsSync(opts.dir)) {
3189
+ console.error(`${yellow}Diretório não encontrado: ${opts.dir}${reset}`);
3190
+ process.exit(1);
3191
+ }
3192
+ bootstrapOxe(opts.dir, { dryRun: false, force: false });
3193
+ const statePath = oxeHealth.oxePaths(opts.dir).state;
3194
+ const stateText = fs.existsSync(statePath) ? fs.readFileSync(statePath, 'utf8') : '';
3195
+ const activeSession = opts.activeSession || oxeHealth.parseActiveSession(stateText) || null;
3196
+ const p = oxeOperational.operationalPaths(opts.dir, activeSession);
3197
+ console.log(` ${c ? green : ''}Projeto:${c ? reset : ''} ${c ? cyan : ''}${opts.dir}${c ? reset : ''}`);
3198
+ console.log(` ${c ? green : ''}Sessão:${c ? reset : ''} ${c ? cyan : ''}${activeSession || 'modo legado'}${c ? reset : ''}`);
3199
+
3200
+ if (opts.action === 'status') {
3201
+ const current = oxeOperational.readRunState(opts.dir, activeSession);
3202
+ if (!current) {
3203
+ console.log(` ${yellow}Nenhum ACTIVE-RUN encontrado.${reset}`);
3204
+ return;
3205
+ }
3206
+ console.log(` ${c ? green : ''}Run:${c ? reset : ''} ${current.run_id}`);
3207
+ console.log(` ${c ? green : ''}Estado:${c ? reset : ''} ${current.status}`);
3208
+ console.log(` ${c ? green : ''}Cursor:${c ? reset : ''} onda=${current.cursor && current.cursor.wave != null ? current.cursor.wave : '—'} tarefa=${current.cursor && current.cursor.task ? current.cursor.task : '—'} modo=${current.cursor && current.cursor.mode ? current.cursor.mode : '—'}`);
3209
+ console.log(` ${c ? green : ''}Arquivo:${c ? reset : ''} ${path.join(p.runsDir, `${current.run_id}.json`)}`);
3210
+ return;
3211
+ }
3212
+
3213
+ try {
3214
+ const next = oxeOperational.applyRuntimeAction(opts.dir, activeSession, {
3215
+ action: opts.action,
3216
+ wave: opts.wave,
3217
+ task: opts.task || null,
3218
+ mode: opts.mode || null,
3219
+ reason: opts.reason || '',
3220
+ });
3221
+ console.log(` ${c ? green : ''}✓${c ? reset : ''} Runtime atualizado.`);
3222
+ console.log(` ${c ? green : ''}Run:${c ? reset : ''} ${next.run_id}`);
3223
+ console.log(` ${c ? green : ''}Estado:${c ? reset : ''} ${next.status}`);
3224
+ console.log(` ${c ? green : ''}Cursor:${c ? reset : ''} onda=${next.cursor && next.cursor.wave != null ? next.cursor.wave : '—'} tarefa=${next.cursor && next.cursor.task ? next.cursor.task : '—'} modo=${next.cursor && next.cursor.mode ? next.cursor.mode : '—'}`);
3225
+ console.log(` ${c ? green : ''}Trace:${c ? reset : ''} ${p.events}`);
3226
+ } catch (err) {
3227
+ console.error(`${red}${err && err.message ? err.message : 'Falha ao atualizar runtime.'}${reset}`);
3228
+ process.exit(1);
3229
+ }
3230
+ }
3231
+
3232
+ /**
3233
+ * @param {AzureOpts} opts
3234
+ */
3235
+ function runAzure(opts) {
3236
+ const c = useAnsiColors();
3237
+ printSection('OXE ▸ azure');
3238
+ if (!fs.existsSync(opts.dir)) {
3239
+ console.error(`${yellow}Diretório não encontrado: ${opts.dir}${reset}`);
3240
+ process.exit(1);
3241
+ }
3242
+ bootstrapOxe(opts.dir, { dryRun: false, force: false });
3243
+ oxeAzure.ensureAzureArtifacts(opts.dir);
3244
+ oxeAzure.ensureAzureCapabilities(opts.dir);
3245
+ writeCapabilitiesIndex(opts.dir);
3246
+ const { config } = oxeHealth.loadOxeConfigMerged(opts.dir);
3247
+ const statePath = oxeHealth.oxePaths(opts.dir).state;
3248
+ const stateText = fs.existsSync(statePath) ? fs.readFileSync(statePath, 'utf8') : '';
3249
+ const activeSession = opts.activeSession || oxeHealth.parseActiveSession(stateText) || null;
3250
+ const azureCfg = config.azure && typeof config.azure === 'object' ? config.azure : {};
3251
+ const preferredLocation = Array.isArray(azureCfg.preferred_locations) && azureCfg.preferred_locations.length
3252
+ ? String(azureCfg.preferred_locations[0])
3253
+ : '';
3254
+ const input = {
3255
+ kind: opts.kind,
3256
+ resourceGroup: opts.resourceGroup || String(azureCfg.default_resource_group || ''),
3257
+ name: opts.name || opts.server,
3258
+ namespace: opts.namespace,
3259
+ topicName: opts.topicName,
3260
+ subscriptionName: opts.subscriptionName,
3261
+ sourceResourceId: opts.sourceResourceId,
3262
+ endpoint: opts.endpoint,
3263
+ location: opts.location || preferredLocation,
3264
+ server: opts.server,
3265
+ database: opts.database,
3266
+ adminUser: opts.adminUser,
3267
+ adminPasswordEnv: opts.adminPasswordEnv || 'AZURE_SQL_ADMIN_PASSWORD',
3268
+ startIpAddress: opts.startIpAddress,
3269
+ endIpAddress: opts.endIpAddress,
3270
+ env: process.env,
3271
+ };
3272
+
3273
+ console.log(` ${c ? green : ''}Projeto:${c ? reset : ''} ${c ? cyan : ''}${opts.dir}${c ? reset : ''}`);
3274
+ console.log(` ${c ? green : ''}Sessão:${c ? reset : ''} ${c ? cyan : ''}${activeSession || 'modo legado'}${c ? reset : ''}`);
3275
+
3276
+ try {
3277
+ if (opts.scope === 'status') {
3278
+ const st = oxeAzure.statusAzure(opts.dir, config);
3279
+ const loginColor = st.loginActive ? green : red;
3280
+ const invColor = st.inventoryPresent && !st.inventoryStale ? green : yellow;
3281
+ console.log(` ${c ? (st.cliInstalled ? green : red) : ''}CLI:${c ? reset : ''} ${st.cliInstalled ? `Azure CLI ${st.cliVersion || 'detectada'}` : 'não instalada'}`);
3282
+ console.log(` ${c ? loginColor : ''}Login:${c ? reset : ''} ${st.loginActive ? 'ativo' : 'ausente'}`);
3283
+ console.log(` ${c ? green : ''}Sub:${c ? reset : ''} ${st.subscription || '—'}`);
3284
+ console.log(` ${c ? invColor : ''}Inventário:${c ? reset : ''} ${st.inventoryPresent ? `${st.inventoryAgeHours !== null ? `${st.inventoryAgeHours}h atrás` : 'presente'}${st.inventoryStale ? ' (stale)' : ''}` : 'ausente'}`);
3285
+ if (st.inventorySummary) {
3286
+ console.log(` ${c ? green : ''}Recursos:${c ? reset : ''} total=${st.inventorySummary.total} sb=${st.inventorySummary.servicebus} eg=${st.inventorySummary.eventgrid} sql=${st.inventorySummary.sql}`);
3287
+ }
3288
+ if (st.pendingOperations > 0) {
3289
+ console.log(` ${yellow}Ops pendentes:${reset} ${st.pendingOperations} (${st.pendingOperationIds.join(', ')})`);
3290
+ }
3291
+ if (st.vpnRequired) {
3292
+ console.log(` ${yellow}VPN:${reset} requerida pela configuração do projeto`);
3293
+ }
3294
+ return;
3295
+ }
3296
+
3297
+ if (opts.scope === 'operations') {
3298
+ const ops = oxeAzure.listAzureOperations(opts.dir);
3299
+ if (!ops.length) {
3300
+ console.log(` ${c ? dim : ''}Nenhuma operação registrada.${c ? reset : ''}`);
3301
+ return;
3302
+ }
3303
+ for (const op of ops) {
3304
+ const phaseColor = op.phase === 'applied' ? green : op.phase === 'waiting_approval' ? yellow : op.phase === 'failed' ? red : dim;
3305
+ console.log(` ${c ? phaseColor : ''}${op.phase}${c ? reset : ''} · ${op.operation_id} · ${op.domain}/${op.kind} · ${op.summary || '—'}`);
3306
+ }
3307
+ return;
3308
+ }
3309
+
3310
+ if (opts.scope === 'doctor') {
3311
+ const report = oxeAzure.azureDoctor(opts.dir, config, {
3312
+ autoInstall: Boolean(azureCfg.resource_graph_auto_install !== false),
3313
+ });
3314
+ console.log(` ${c ? green : ''}CLI:${c ? reset : ''} ${report.authStatus.installed ? `Azure CLI ${report.authStatus.version || 'detectada'}` : 'não instalada'}`);
3315
+ console.log(` ${c ? green : ''}Login:${c ? reset : ''} ${report.authStatus.login_active ? 'ativo' : 'ausente'}`);
3316
+ console.log(` ${c ? green : ''}Subscription:${c ? reset : ''} ${report.profile.subscription_name || report.profile.subscription_id || '—'}`);
3317
+ console.log(` ${c ? green : ''}Inventário:${c ? reset : ''} ${report.inventory && report.inventory.synced_at ? `sync em ${report.inventory.synced_at}` : 'ausente'}`);
3318
+ if (report.warnings.length) {
3319
+ for (const warning of report.warnings) {
3320
+ console.log(` ${yellow}AVISO${reset} ${warning}`);
3321
+ }
3322
+ } else {
3323
+ console.log(` ${c ? green : ''}✓${c ? reset : ''} Contexto Azure saudável.`);
3324
+ }
3325
+ if (!report.authStatus.installed || !report.authStatus.login_active || !report.profile.subscription_id) {
3326
+ process.exit(1);
3327
+ }
3328
+ return;
3329
+ }
3330
+
3331
+ if (opts.scope === 'auth') {
3332
+ if (opts.action === 'login') {
3333
+ const context = oxeAzure.loginAzure(opts.dir, { inherit: true, tenant: opts.tenant || undefined });
3334
+ console.log(` ${c ? green : ''}✓${c ? reset : ''} Login Azure concluído.`);
3335
+ console.log(` ${c ? green : ''}Conta:${c ? reset : ''} ${context.authStatus.user || '—'}`);
3336
+ console.log(` ${c ? green : ''}Subscription:${c ? reset : ''} ${context.profile.subscription_name || context.profile.subscription_id || '—'}`);
3337
+ return;
3338
+ }
3339
+ if (opts.action === 'whoami' || !opts.action) {
3340
+ const context = oxeAzure.getAzureContext(opts.dir);
3341
+ console.log(` ${c ? green : ''}Cloud:${c ? reset : ''} ${context.profile.cloud || '—'}`);
3342
+ console.log(` ${c ? green : ''}Conta:${c ? reset : ''} ${context.authStatus.user || '—'}`);
3343
+ console.log(` ${c ? green : ''}Tipo:${c ? reset : ''} ${context.authStatus.user_type || context.profile.auth_mode || '—'}`);
3344
+ console.log(` ${c ? green : ''}Tenant:${c ? reset : ''} ${context.profile.tenant_id || '—'}`);
3345
+ console.log(` ${c ? green : ''}Subscription:${c ? reset : ''} ${context.profile.subscription_name || context.profile.subscription_id || '—'}`);
3346
+ console.log(` ${c ? green : ''}Resource Graph:${c ? reset : ''} ${context.authStatus.resource_graph_enabled ? 'habilitado' : 'ausente'}`);
3347
+ return;
3348
+ }
3349
+ if (opts.action === 'set-subscription') {
3350
+ if (!opts.subscription) {
3351
+ console.error(`${red}Informe --subscription <id|nome>.${reset}`);
3352
+ process.exit(1);
3353
+ }
3354
+ const context = oxeAzure.setAzureSubscription(opts.dir, opts.subscription);
3355
+ console.log(` ${c ? green : ''}✓${c ? reset : ''} Subscription selecionada.`);
3356
+ console.log(` ${c ? green : ''}Subscription:${c ? reset : ''} ${context.profile.subscription_name || context.profile.subscription_id || '—'}`);
3357
+ return;
3358
+ }
3359
+ throw new Error(`Subcomando Azure auth desconhecido: ${opts.action || '—'}`);
3360
+ }
3361
+
3362
+ if (opts.scope === 'sync') {
3363
+ const synced = oxeAzure.syncAzureInventory(opts.dir, {
3364
+ autoInstall: Boolean(azureCfg.resource_graph_auto_install !== false),
3365
+ diff: opts.diff,
3366
+ });
3367
+ console.log(` ${c ? green : ''}✓${c ? reset : ''} Inventário Azure sincronizado.`);
3368
+ console.log(` ${c ? green : ''}Subscription:${c ? reset : ''} ${synced.profile.subscription_name || synced.profile.subscription_id || '—'}`);
3369
+ console.log(` ${c ? green : ''}Resumo:${c ? reset : ''} total=${synced.inventory.summary.total} servicebus=${synced.inventory.summary.servicebus} eventgrid=${synced.inventory.summary.eventgrid} sql=${synced.inventory.summary.sql}`);
3370
+ console.log(` ${c ? green : ''}Artefato:${c ? reset : ''} ${synced.paths.inventory}`);
3371
+ if (synced.diff) {
3372
+ const d = synced.diff;
3373
+ console.log(` ${c ? green : ''}Diff:${c ? reset : ''} +${d.added.length} adicionados · -${d.removed.length} removidos · ${d.unchanged} inalterados`);
3374
+ for (const item of d.added) console.log(` ${c ? green : ''}+${c ? reset : ''} ${item.name} · ${item.type} · ${item.resourceGroup || '—'}`);
3375
+ for (const item of d.removed) console.log(` ${c ? red : ''}-${c ? reset : ''} ${item.name} · ${item.type} · ${item.resourceGroup || '—'}`);
3376
+ }
3377
+ return;
3378
+ }
3379
+
3380
+ if (opts.scope === 'find') {
3381
+ const filters = {};
3382
+ if (opts.filterType) filters.type = opts.filterType;
3383
+ if (opts.filterRg) filters.resourceGroup = opts.filterRg;
3384
+ const matches = oxeAzure.searchAzureInventory(opts.dir, opts.query, filters);
3385
+ if (!matches.length) {
3386
+ console.log(` ${yellow}Nenhum recurso encontrado no inventário local.${reset}`);
3387
+ return;
3388
+ }
3389
+ for (const item of matches) {
3390
+ console.log(` ${c ? dim : ''}•${c ? reset : ''} ${item.name} · ${item.type} · ${item.resourceGroup || '—'} · ${item.location || '—'}`);
3391
+ }
3392
+ return;
3393
+ }
3394
+
3395
+ if (!['servicebus', 'eventgrid', 'sql'].includes(opts.scope)) {
3396
+ throw new Error(`Escopo Azure desconhecido: ${opts.scope}. Use: doctor | status | auth | sync | find | operations | servicebus | eventgrid | sql`);
3397
+ }
3398
+ if (!opts.action) {
3399
+ throw new Error(`Informe a ação para ${opts.scope}: list | show | plan | apply`);
3400
+ }
3401
+
3402
+ if (opts.action === 'list' || opts.action === 'show') {
3403
+ const result = oxeAzure.executeAzureRead(opts.dir, activeSession, opts.scope, opts.action, input);
3404
+ console.log(JSON.stringify(result, null, 2));
3405
+ return;
3406
+ }
3407
+ if (opts.action === 'plan') {
3408
+ const planned = oxeAzure.planAzureOperation(opts.dir, activeSession, opts.scope, input);
3409
+ console.log(` ${c ? green : ''}✓${c ? reset : ''} Operação Azure planejada.`);
3410
+ console.log(` ${c ? green : ''}Operation ID:${c ? reset : ''} ${planned.operation.operation_id}`);
3411
+ console.log(` ${c ? green : ''}Checkpoint:${c ? reset : ''} ${planned.operation.checkpoint_id}`);
3412
+ console.log(` ${c ? green : ''}Comando:${c ? reset : ''} ${planned.operation.command_display_redacted}`);
3413
+ console.log(` ${c ? green : ''}Resumo:${c ? reset : ''} ${planned.operation.summary}`);
3414
+ console.log(` ${c ? green : ''}Artefatos:${c ? reset : ''} ${planned.files.jsonPath} · ${planned.files.mdPath}`);
3415
+ return;
3416
+ }
3417
+ if (opts.action === 'apply') {
3418
+ const applied = oxeAzure.applyAzureOperation(opts.dir, activeSession, opts.scope, input, {
3419
+ approve: opts.approve,
3420
+ overridePolicy: opts.overridePolicy,
3421
+ dryRun: opts.dryRun,
3422
+ vpnRequired: Boolean(azureCfg.vpn_required),
3423
+ vpnConfirmed: opts.vpnConfirmed,
3424
+ });
3425
+ if (applied.dryRun) {
3426
+ console.log(` ${yellow}[dry-run]${reset} Validação OK — nenhuma alteração foi feita.`);
3427
+ console.log(` ${c ? green : ''}Comando:${c ? reset : ''} ${applied.commandPreview}`);
3428
+ console.log(` ${c ? green : ''}Resumo:${c ? reset : ''} ${applied.operation.summary}`);
3429
+ return;
3430
+ }
3431
+ if (!applied.approved) {
3432
+ console.log(` ${yellow}Checkpoint aberto antes da mutação.${reset}`);
3433
+ console.log(` ${c ? green : ''}Operation ID:${c ? reset : ''} ${applied.operation.operation_id}`);
3434
+ console.log(` ${c ? green : ''}Checkpoint:${c ? reset : ''} ${applied.checkpoint_id}`);
3435
+ console.log(` ${c ? green : ''}Resumo:${c ? reset : ''} ${applied.operation.summary}`);
3436
+ console.log(` ${c ? green : ''}Reexecute:${c ? reset : ''} npx oxe-cc azure ${opts.scope} apply --approve --kind ${applied.operation.kind} --resource-group ${applied.operation.resource_group}`);
3437
+ return;
3438
+ }
3439
+ console.log(` ${c ? green : ''}✓${c ? reset : ''} Operação Azure aplicada.`);
3440
+ console.log(` ${c ? green : ''}Operation ID:${c ? reset : ''} ${applied.operation.operation_id}`);
3441
+ console.log(` ${c ? green : ''}Artefatos:${c ? reset : ''} ${applied.files.jsonPath} · ${applied.files.mdPath}`);
3442
+ return;
3443
+ }
3444
+
3445
+ throw new Error(`Ação Azure desconhecida: ${opts.action}`);
3446
+ } catch (err) {
3447
+ console.error(`${red}${err && err.message ? err.message : 'Falha no provider Azure.'}${reset}`);
3448
+ process.exit(1);
3449
+ }
3450
+ }
3451
+
3452
+ function capabilityManifestPath(target, id) {
3453
+ return path.join(target, '.oxe', 'capabilities', id, 'CAPABILITY.md');
3454
+ }
3455
+
3456
+ function listLocalCapabilities(target) {
3457
+ const dir = path.join(target, '.oxe', 'capabilities');
3458
+ if (!fs.existsSync(dir)) return [];
3459
+ return fs
3460
+ .readdirSync(dir, { withFileTypes: true })
3461
+ .filter((e) => e.isDirectory() && fs.existsSync(path.join(dir, e.name, 'CAPABILITY.md')))
3462
+ .map((e) => e.name)
3463
+ .sort();
3464
+ }
3465
+
3466
+ function renderCapabilitiesIndex(target) {
3467
+ const template = ['# OXE — Capabilities Instaladas', '', '> Catálogo local de capabilities do projeto. Cada capability vive em `.oxe/capabilities/<id>/`.', '', '| ID | Tipo | Status | Escopo | Política | Side effects | Requer env | Evidência | Resumo |', '|----|------|--------|--------|-----------|--------------|------------|-----------|--------|'];
3468
+ const catalog = oxeOperational.readCapabilityCatalog(target);
3469
+ if (!catalog.length) {
3470
+ template.push('| (vazio) | — | — | — | — | — | — | — | Nenhuma capability instalada |');
3471
+ return template.join('\n') + '\n';
3472
+ }
3473
+ for (const cap of catalog) {
3474
+ template.push(`| ${cap.id} | ${cap.type} | ${cap.status} | ${cap.scope} | ${cap.approvalPolicy || '—'} | ${(cap.sideEffects || []).join(', ') || '—'} | ${(cap.requiresEnv || []).join(', ') || '—'} | ${(cap.evidenceOutputs || []).join(', ') || '—'} | ${cap.description} |`);
3475
+ }
3476
+ return template.join('\n') + '\n';
3477
+ }
3478
+
3479
+ function writeCapabilitiesIndex(target) {
3480
+ const dest = path.join(target, '.oxe', 'CAPABILITIES.md');
3481
+ fs.writeFileSync(dest, renderCapabilitiesIndex(target), 'utf8');
3482
+ }
3483
+
3484
+ /**
3485
+ * @param {CapabilityOpts} opts
3486
+ */
3487
+ function runCapabilities(opts) {
3488
+ const c = useAnsiColors();
3489
+ printSection('OXE ▸ capabilities');
3490
+ if (!fs.existsSync(opts.dir)) {
3491
+ console.error(`${yellow}Diretório não encontrado: ${opts.dir}${reset}`);
3492
+ process.exit(1);
3493
+ }
3494
+ bootstrapOxe(opts.dir, { dryRun: false, force: false });
3495
+ const capsDir = path.join(opts.dir, '.oxe', 'capabilities');
3496
+ ensureDir(capsDir);
3497
+ if (opts.action === 'list') {
3498
+ const ids = listLocalCapabilities(opts.dir);
3499
+ console.log(` ${c ? green : ''}Projeto:${c ? reset : ''} ${c ? cyan : ''}${opts.dir}${c ? reset : ''}`);
3500
+ if (!ids.length) {
3501
+ console.log(` ${yellow}Nenhuma capability instalada.${reset}`);
3502
+ } else {
3503
+ for (const id of ids) console.log(` ${c ? dim : ''}•${c ? reset : ''} ${id}`);
3504
+ }
3505
+ return;
3506
+ }
3507
+ if (!opts.id && (opts.action === 'install' || opts.action === 'remove')) {
3508
+ console.error(`${red}Informe o ID da capability.${reset}`);
3509
+ process.exit(1);
3510
+ }
3511
+ if (opts.action === 'install') {
3512
+ const safeId = opts.id.trim().toLowerCase();
3513
+ if (!/^[a-z0-9][a-z0-9-]*$/.test(safeId)) {
3514
+ console.error(`${red}ID inválido para capability:${reset} ${opts.id}`);
3515
+ process.exit(1);
3516
+ }
3517
+ const dir = path.join(capsDir, safeId);
3518
+ ensureDir(dir);
3519
+ const manifest = capabilityManifestPath(opts.dir, safeId);
3520
+ if (!fs.existsSync(manifest)) {
3521
+ const src = path.join(PKG_ROOT, 'oxe', 'templates', 'CAPABILITY.template.md');
3522
+ let raw = fs.readFileSync(src, 'utf8');
3523
+ raw = raw.replace(/^id:\s*sample-capability$/m, `id: ${safeId}`);
3524
+ fs.writeFileSync(manifest, raw, 'utf8');
3525
+ }
3526
+ writeCapabilitiesIndex(opts.dir);
3527
+ oxeOperational.appendEvent(opts.dir, null, { type: 'capability_installed', payload: { capability_id: safeId } });
3528
+ console.log(` ${c ? green : ''}✓${c ? reset : ''} Capability instalada: ${safeId}`);
3529
+ return;
3530
+ }
3531
+ if (opts.action === 'remove') {
3532
+ fs.rmSync(path.join(capsDir, opts.id), { recursive: true, force: true });
3533
+ writeCapabilitiesIndex(opts.dir);
3534
+ oxeOperational.appendEvent(opts.dir, null, { type: 'capability_removed', payload: { capability_id: opts.id } });
3535
+ console.log(` ${c ? green : ''}✓${c ? reset : ''} Capability removida: ${opts.id}`);
3536
+ return;
3537
+ }
3538
+ if (opts.action === 'update') {
3539
+ writeCapabilitiesIndex(opts.dir);
3540
+ oxeOperational.appendEvent(opts.dir, null, { type: 'capability_index_refreshed' });
3541
+ console.log(` ${c ? green : ''}✓${c ? reset : ''} Índice de capabilities atualizado.`);
3542
+ return;
3543
+ }
3544
+ console.error(`${red}Ação desconhecida:${reset} ${opts.action}`);
3545
+ process.exit(1);
3546
+ }
3547
+
3548
+ /**
3549
+ * @param {string[]} argv
3550
+ * @returns {{ help: boolean, dir: string, visual: boolean, activeSession: string|null, parseError: boolean, unknownFlag: string }}
3551
+ */
3552
+ function parsePlanArgs(argv) {
3553
+ const out = { help: false, dir: process.cwd(), visual: false, activeSession: null, parseError: false, unknownFlag: '' };
3554
+ for (let i = 0; i < argv.length; i++) {
3555
+ const a = argv[i];
3556
+ if (a === '-h' || a === '--help') out.help = true;
3557
+ else if (a === '--visual') out.visual = true;
3558
+ else if (a === '--dir' && argv[i + 1]) out.dir = path.resolve(argv[++i]);
3559
+ else if (a === '--session' && argv[i + 1]) out.activeSession = String(argv[++i]).replace(/\\/g, '/');
3560
+ else if (!a.startsWith('-') && i === 0) out.dir = path.resolve(a);
3561
+ else { out.parseError = true; out.unknownFlag = a; break; }
3562
+ }
3563
+ return out;
3564
+ }
3565
+
3566
+ /**
3567
+ * @param {string[]} argv
3568
+ * @returns {{ help: boolean, dir: string, matrix: boolean, activeSession: string|null, parseError: boolean, unknownFlag: string }}
3569
+ */
3570
+ function parseVerifyArgs(argv) {
3571
+ const out = { help: false, dir: process.cwd(), matrix: false, activeSession: null, parseError: false, unknownFlag: '' };
3572
+ for (let i = 0; i < argv.length; i++) {
3573
+ const a = argv[i];
3574
+ if (a === '-h' || a === '--help') out.help = true;
3575
+ else if (a === '--matrix') out.matrix = true;
3576
+ else if (a === '--dir' && argv[i + 1]) out.dir = path.resolve(argv[++i]);
3577
+ else if (a === '--session' && argv[i + 1]) out.activeSession = String(argv[++i]).replace(/\\/g, '/');
3578
+ else if (!a.startsWith('-') && i === 0) out.dir = path.resolve(a);
3579
+ else { out.parseError = true; out.unknownFlag = a; break; }
3580
+ }
3581
+ return out;
3582
+ }
3583
+
3584
+ /**
3585
+ * Imprime grafo ASCII de ondas e tarefas lido do PLAN.md.
3586
+ * @param {{ dir: string, activeSession: string|null }} opts
3587
+ */
3588
+ function runPlanVisual(opts) {
3589
+ const c = useAnsiColors();
3590
+ printSection('OXE ▸ plan --visual');
3591
+ const statePath = oxeHealth.oxePaths(opts.dir).state;
3592
+ const stateText = fs.existsSync(statePath) ? fs.readFileSync(statePath, 'utf8') : '';
3593
+ const activeSession = opts.activeSession || oxeHealth.parseActiveSession(stateText) || null;
3594
+ const sp = oxeHealth.scopedOxePaths(opts.dir, activeSession);
3595
+ const planPath = sp.plan || oxeHealth.oxePaths(opts.dir).plan;
3596
+
3597
+ console.log(` ${c ? green : ''}Projeto:${c ? reset : ''} ${c ? cyan : ''}${opts.dir}${c ? reset : ''}`);
3598
+ console.log(` ${c ? green : ''}Sessão:${c ? reset : ''} ${c ? cyan : ''}${activeSession || 'modo legado'}${c ? reset : ''}`);
3599
+
3600
+ if (!fs.existsSync(planPath)) {
3601
+ console.log(`\n ${yellow}PLAN.md não encontrado em ${planPath}${reset}`);
3602
+ console.log(` ${dim}Rode /oxe-plan para criar o plano.${reset}\n`);
3603
+ return;
3604
+ }
3605
+
3606
+ const planMd = fs.readFileSync(planPath, 'utf8');
3607
+ const plan = oxeDashboard.parsePlan(planMd);
3608
+
3609
+ if (!plan.waves.length) {
3610
+ console.log(`\n ${yellow}Nenhuma onda encontrada no PLAN.md.${reset}\n`);
3611
+ return;
3612
+ }
3613
+
3614
+ console.log('');
3615
+ for (const wave of plan.waves) {
3616
+ const waveLabel = ` ${c ? yellow : ''}Onda ${wave.wave}${c ? reset : ''}`;
3617
+ for (let i = 0; i < wave.tasks.length; i++) {
3618
+ const task = wave.tasks[i];
3619
+ const deps = task.dependsOn.length ? ` ${c ? dim : ''}← ${task.dependsOn.join(', ')}${c ? reset : ''}` : '';
3620
+ const complexity = task.complexity ? ` ${c ? dim : ''}[${task.complexity}]${c ? reset : ''}` : '';
3621
+ if (i === 0) {
3622
+ console.log(`${waveLabel} ${c ? cyan : ''}${task.id}${c ? reset : ''} — ${task.title}${complexity}${deps}`);
3623
+ } else {
3624
+ console.log(`${''.padEnd(waveLabel.replace(/\x1b\[[0-9;]*m/g, '').length + 3)}${c ? cyan : ''}${task.id}${c ? reset : ''} — ${task.title}${complexity}${deps}`);
3625
+ }
3626
+ }
3627
+ console.log('');
3628
+ }
3629
+
3630
+ const total = plan.totalTasks;
3631
+ const waveCount = plan.waves.length;
3632
+ console.log(` ${c ? dim : ''}${total} tarefa${total !== 1 ? 's' : ''} · ${waveCount} onda${waveCount !== 1 ? 's' : ''}${c ? reset : ''}`);
3633
+ console.log(` ${c ? green : ''}✓${c ? reset : ''} plan --visual concluído.\n`);
3634
+ }
3635
+
3636
+ /**
3637
+ * Imprime tabela ANSI spec → plan → verify (coverage matrix).
3638
+ * @param {{ dir: string, activeSession: string|null }} opts
3639
+ */
3640
+ function runVerifyMatrix(opts) {
3641
+ const c = useAnsiColors();
3642
+ printSection('OXE ▸ verify --matrix');
3643
+ const statePath = oxeHealth.oxePaths(opts.dir).state;
3644
+ const stateText = fs.existsSync(statePath) ? fs.readFileSync(statePath, 'utf8') : '';
3645
+ const activeSession = opts.activeSession || oxeHealth.parseActiveSession(stateText) || null;
3646
+ const sp = oxeHealth.scopedOxePaths(opts.dir, activeSession);
3647
+ const specPath = sp.spec || oxeHealth.oxePaths(opts.dir).spec;
3648
+ const planPath = sp.plan || oxeHealth.oxePaths(opts.dir).plan;
3649
+ const verifyPath = sp.verify || oxeHealth.oxePaths(opts.dir).verify;
3650
+
3651
+ console.log(` ${c ? green : ''}Projeto:${c ? reset : ''} ${c ? cyan : ''}${opts.dir}${c ? reset : ''}`);
3652
+ console.log(` ${c ? green : ''}Sessão:${c ? reset : ''} ${c ? cyan : ''}${activeSession || 'modo legado'}${c ? reset : ''}`);
3653
+
3654
+ const specMd = fs.existsSync(specPath) ? fs.readFileSync(specPath, 'utf8') : '';
3655
+ const planMd = fs.existsSync(planPath) ? fs.readFileSync(planPath, 'utf8') : '';
3656
+ const verifyMd = fs.existsSync(verifyPath) ? fs.readFileSync(verifyPath, 'utf8') : '';
3657
+
3658
+ const spec = oxeDashboard.parseSpec(specMd);
3659
+ const plan = oxeDashboard.parsePlan(planMd);
3660
+ const verify = oxeDashboard.parseVerify(verifyMd);
3661
+ const matrix = oxeDashboard.buildCoverageMatrix(spec, plan, verify);
3662
+
3663
+ if (!matrix.length) {
3664
+ if (!spec.criteria.length) {
3665
+ console.log(`\n ${yellow}Nenhum critério A* encontrado no SPEC.md.${reset}`);
3666
+ console.log(` ${dim}Rode /oxe-spec para criar a especificação.${reset}\n`);
3667
+ } else {
3668
+ console.log(`\n ${yellow}Nenhum dado disponível para a matriz.${reset}\n`);
3669
+ }
3670
+ return;
3671
+ }
3672
+
3673
+ const statusSymbol = (s) => {
3674
+ if (s === 'passed') return c ? `${green}✓${reset}` : '✓';
3675
+ if (s === 'failed') return c ? `${red}✗${reset}` : '✗';
3676
+ return c ? `${dim}—${reset}` : '—';
3677
+ };
3678
+ const planSymbol = (covered) => covered ? (c ? `${green}✓${reset}` : '✓') : (c ? `${dim}✗${reset}` : '✗');
3679
+
3680
+ // Compute column widths
3681
+ const colId = Math.max(9, ...matrix.map((r) => r.id.length)) + 2;
3682
+ const colTasks = Math.max(12, ...matrix.map((r) => (r.tasks.join(', ') || '—').length)) + 2;
3683
+
3684
+ const sep = ` ${'─'.repeat(colId)}┼${'─'.repeat(colTasks)}┼──────────┼──────────`;
3685
+ const header = ` ${' Critério'.padEnd(colId)}│${' Tarefas'.padEnd(colTasks)}│ PLAN │ VERIFY`;
3686
+
3687
+ console.log('');
3688
+ console.log(c ? `${dim}${header}${reset}` : header);
3689
+ console.log(sep);
3690
+
3691
+ let covered = 0;
3692
+ let verified = 0;
3693
+ for (const row of matrix) {
3694
+ const tasks = row.tasks.length ? row.tasks.join(', ') : '—';
3695
+ const planCell = planSymbol(row.planCovered);
3696
+ const verifyCell = statusSymbol(row.verifyStatus);
3697
+ const label = ` ${row.id}`.padEnd(colId);
3698
+ const taskCell = ` ${tasks}`.padEnd(colTasks);
3699
+ console.log(`${label}│${taskCell}│ ${planCell} │ ${verifyCell}`);
3700
+ if (row.planCovered) covered++;
3701
+ if (row.verifyStatus === 'passed') verified++;
3702
+ }
3703
+
3704
+ console.log(sep);
3705
+ const total = matrix.length;
3706
+ console.log(`\n ${c ? dim : ''}${covered}/${total} critérios cobertos pelo plano · ${verified}/${total} aprovados no verify${c ? reset : ''}`);
3707
+ console.log(` ${c ? green : ''}✓${c ? reset : ''} verify --matrix concluído.\n`);
3708
+ }
3709
+
2437
3710
  async function main() {
2438
3711
  const argv = process.argv.slice(2);
2439
3712
  let command = 'install';
@@ -2441,8 +3714,14 @@ async function main() {
2441
3714
  argv[0] === 'doctor' ||
2442
3715
  argv[0] === 'status' ||
2443
3716
  argv[0] === 'init-oxe' ||
3717
+ argv[0] === 'dashboard' ||
3718
+ argv[0] === 'runtime' ||
3719
+ argv[0] === 'azure' ||
2444
3720
  argv[0] === 'uninstall' ||
2445
3721
  argv[0] === 'update' ||
3722
+ argv[0] === 'capabilities' ||
3723
+ argv[0] === 'plan' ||
3724
+ argv[0] === 'verify' ||
2446
3725
  argv[0] === 'install'
2447
3726
  ) {
2448
3727
  command = argv[0];
@@ -2509,6 +3788,108 @@ async function main() {
2509
3788
  return;
2510
3789
  }
2511
3790
 
3791
+ if (command === 'capabilities') {
3792
+ const cap = parseCapabilitiesArgs(argv);
3793
+ if (cap.help) {
3794
+ printBanner();
3795
+ usage();
3796
+ process.exit(0);
3797
+ }
3798
+ if (cap.parseError) {
3799
+ printBanner();
3800
+ console.error(`${red}Opção desconhecida:${reset} ${cap.unknownFlag}`);
3801
+ usage();
3802
+ process.exit(1);
3803
+ }
3804
+ printBanner();
3805
+ if (!fs.existsSync(cap.dir)) {
3806
+ console.error(`${yellow}Diretório não encontrado: ${cap.dir}${reset}`);
3807
+ process.exit(1);
3808
+ }
3809
+ runCapabilities(cap);
3810
+ return;
3811
+ }
3812
+
3813
+ if (command === 'dashboard') {
3814
+ const d = parseDashboardArgs(argv);
3815
+ if (d.help) {
3816
+ printBanner();
3817
+ usage();
3818
+ process.exit(0);
3819
+ }
3820
+ if (d.parseError) {
3821
+ printBanner();
3822
+ console.error(`${red}Opção desconhecida:${reset} ${d.unknownFlag}`);
3823
+ usage();
3824
+ process.exit(1);
3825
+ }
3826
+ printBanner();
3827
+ await runDashboard(d);
3828
+ return;
3829
+ }
3830
+
3831
+ if (command === 'runtime') {
3832
+ const runtime = parseRuntimeArgs(argv);
3833
+ if (runtime.help) {
3834
+ printBanner();
3835
+ usage();
3836
+ process.exit(0);
3837
+ }
3838
+ if (runtime.parseError) {
3839
+ printBanner();
3840
+ console.error(`${red}Opção desconhecida:${reset} ${runtime.unknownFlag}`);
3841
+ usage();
3842
+ process.exit(1);
3843
+ }
3844
+ printBanner();
3845
+ if (!fs.existsSync(runtime.dir)) {
3846
+ console.error(`${yellow}Diretório não encontrado: ${runtime.dir}${reset}`);
3847
+ process.exit(1);
3848
+ }
3849
+ runRuntime(runtime);
3850
+ return;
3851
+ }
3852
+
3853
+ if (command === 'azure') {
3854
+ const azure = parseAzureArgs(argv);
3855
+ if (azure.help) {
3856
+ printBanner();
3857
+ usage();
3858
+ process.exit(0);
3859
+ }
3860
+ if (azure.parseError) {
3861
+ printBanner();
3862
+ console.error(`${red}Opção desconhecida:${reset} ${azure.unknownFlag}`);
3863
+ usage();
3864
+ process.exit(1);
3865
+ }
3866
+ printBanner();
3867
+ runAzure(azure);
3868
+ return;
3869
+ }
3870
+
3871
+ if (command === 'plan') {
3872
+ const planOpts = parsePlanArgs(argv);
3873
+ if (planOpts.help) { printBanner(); usage(); process.exit(0); }
3874
+ if (planOpts.parseError) { printBanner(); console.error(`${red}Opção desconhecida:${reset} ${planOpts.unknownFlag}`); usage(); process.exit(1); }
3875
+ if (!planOpts.visual) { printBanner(); console.error(`${yellow}Use oxe-cc plan --visual${reset}`); process.exit(1); }
3876
+ printBanner();
3877
+ if (!fs.existsSync(planOpts.dir)) { console.error(`${yellow}Diretório não encontrado: ${planOpts.dir}${reset}`); process.exit(1); }
3878
+ runPlanVisual(planOpts);
3879
+ return;
3880
+ }
3881
+
3882
+ if (command === 'verify') {
3883
+ const verifyOpts = parseVerifyArgs(argv);
3884
+ if (verifyOpts.help) { printBanner(); usage(); process.exit(0); }
3885
+ if (verifyOpts.parseError) { printBanner(); console.error(`${red}Opção desconhecida:${reset} ${verifyOpts.unknownFlag}`); usage(); process.exit(1); }
3886
+ if (!verifyOpts.matrix) { printBanner(); console.error(`${yellow}Use oxe-cc verify --matrix${reset}`); process.exit(1); }
3887
+ printBanner();
3888
+ if (!fs.existsSync(verifyOpts.dir)) { console.error(`${yellow}Diretório não encontrado: ${verifyOpts.dir}${reset}`); process.exit(1); }
3889
+ runVerifyMatrix(verifyOpts);
3890
+ return;
3891
+ }
3892
+
2512
3893
  const opts = parseInstallArgs(argv);
2513
3894
 
2514
3895
  if (opts.version) {
@@ -2555,7 +3936,11 @@ async function main() {
2555
3936
  console.error(`${yellow}Diretório não encontrado: ${target}${reset}`);
2556
3937
  process.exit(1);
2557
3938
  }
2558
- runStatus(target, { json: opts.jsonOutput, hints: opts.statusHints });
3939
+ if (opts.statusFull) {
3940
+ runStatusFull(target);
3941
+ } else {
3942
+ runStatus(target, { json: opts.jsonOutput, hints: opts.statusHints });
3943
+ }
2559
3944
  return;
2560
3945
  }
2561
3946
 
@@ -2571,8 +3956,8 @@ async function main() {
2571
3956
  bootstrapOxe(target, { dryRun: opts.dryRun, force: opts.force });
2572
3957
  printSummaryAndNextSteps(c0, {
2573
3958
  bullets: opts.dryRun
2574
- ? ['[simulação] Seriam criados ou atualizados .oxe/STATE.md, .oxe/config.json e .oxe/codebase/']
2575
- : ['.oxe/STATE.md, .oxe/config.json e pasta .oxe/codebase/ (criados ou atualizados conforme --force)'],
3959
+ ? ['[simulação] Seriam criados ou atualizados .oxe/STATE.md, .oxe/config.json, .oxe/codebase/, .oxe/ACTIVE-RUN.json e .oxe/OXE-EVENTS.ndjson']
3960
+ : ['.oxe/STATE.md, .oxe/config.json, .oxe/codebase/, .oxe/ACTIVE-RUN.json e .oxe/OXE-EVENTS.ndjson (criados ou atualizados conforme --force)'],
2576
3961
  nextSteps: [
2577
3962
  { desc: 'Validar o projeto:', cmd: 'npx oxe-cc doctor' },
2578
3963
  { desc: 'Instalar integrações IDE/CLI (se ainda não fez):', cmd: 'npx oxe-cc@latest' },
@@ -2590,6 +3975,8 @@ async function main() {
2590
3975
  }
2591
3976
 
2592
3977
  main().catch((err) => {
2593
- console.error(err);
3978
+ const msg = err && err.message ? err.message : String(err);
3979
+ console.error(`${red}Erro:${reset} ${msg}`);
3980
+ if (process.env.OXE_DEBUG === '1') console.error(err);
2594
3981
  process.exit(1);
2595
3982
  });