oxe-cc 0.6.6 → 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 (47) hide show
  1. package/.cursor/commands/oxe-capabilities.md +11 -0
  2. package/.cursor/commands/oxe-dashboard.md +11 -0
  3. package/.github/prompts/oxe-capabilities.prompt.md +12 -0
  4. package/.github/prompts/oxe-dashboard.prompt.md +12 -0
  5. package/CHANGELOG.md +33 -0
  6. package/README.md +147 -11
  7. package/assets/oxe-framework-artifacts-paper.png +0 -0
  8. package/bin/banner.txt +1 -1
  9. package/bin/lib/oxe-azure.cjs +1445 -0
  10. package/bin/lib/oxe-dashboard.cjs +588 -0
  11. package/bin/lib/oxe-install-resolve.cjs +4 -1
  12. package/bin/lib/oxe-operational.cjs +670 -0
  13. package/bin/lib/oxe-project-health.cjs +372 -28
  14. package/bin/oxe-cc.js +1517 -312
  15. package/commands/oxe/capabilities.md +13 -0
  16. package/commands/oxe/dashboard.md +14 -0
  17. package/lib/sdk/README.md +9 -7
  18. package/lib/sdk/index.cjs +56 -0
  19. package/lib/sdk/index.d.ts +73 -0
  20. package/oxe/templates/ACTIVE-RUN.template.json +32 -0
  21. package/oxe/templates/CAPABILITIES.template.md +7 -0
  22. package/oxe/templates/CAPABILITY.template.md +45 -0
  23. package/oxe/templates/CHECKPOINTS.template.md +7 -0
  24. package/oxe/templates/EXECUTION-RUNTIME.template.md +68 -0
  25. package/oxe/templates/INVESTIGATION.template.md +38 -0
  26. package/oxe/templates/NOTES.template.md +16 -0
  27. package/oxe/templates/PLAN-REVIEW.template.md +31 -0
  28. package/oxe/templates/RESEARCH.template.md +11 -4
  29. package/oxe/templates/SPEC.template.md +6 -4
  30. package/oxe/templates/STATE.md +45 -7
  31. package/oxe/templates/config.template.json +11 -3
  32. package/oxe/workflows/ask.md +10 -1
  33. package/oxe/workflows/capabilities.md +23 -0
  34. package/oxe/workflows/dashboard.md +23 -0
  35. package/oxe/workflows/discuss.md +11 -9
  36. package/oxe/workflows/execute.md +57 -35
  37. package/oxe/workflows/help.md +256 -225
  38. package/oxe/workflows/obs.md +70 -20
  39. package/oxe/workflows/plan.md +83 -74
  40. package/oxe/workflows/quick.md +16 -11
  41. package/oxe/workflows/references/adaptive-discovery.md +27 -0
  42. package/oxe/workflows/research.md +12 -8
  43. package/oxe/workflows/retro.md +30 -5
  44. package/oxe/workflows/scan.md +1 -0
  45. package/oxe/workflows/spec.md +65 -48
  46. package/oxe/workflows/verify.md +52 -37
  47. 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 -->';
@@ -254,13 +257,13 @@ function buildUninstallFooter(u) {
254
257
  `${p}${rm} extensões multi-agente marcadas oxe-cc (OpenCode, Gemini TOML, Windsurf workflows, Codex prompts/skills, Antigravity), se existirem.`
255
258
  );
256
259
  }
257
- if (u.ideLocal) {
258
- bullets.push(
259
- `${p}${rm} integrações OXE no repositório (.cursor, .github, .claude, .copilot, .opencode, … conforme flags).`
260
- );
261
- }
262
- if (u.globalCli) bullets.push(`${p}${rm} também o pacote npm global oxe-cc do PATH.`);
263
- if (!u.noProject) {
260
+ if (u.ideLocal) {
261
+ bullets.push(
262
+ `${p}${rm} integrações OXE no repositório (.cursor, .github, .claude, .copilot, .opencode, … conforme flags).`
263
+ );
264
+ }
265
+ if (u.globalCli) bullets.push(`${p}${rm} também o pacote npm global oxe-cc do PATH.`);
266
+ if (!u.noProject) {
264
267
  bullets.push(
265
268
  `${p}${u.dryRun ? 'Seriam removidas' : 'Removidas'} no repositório: .oxe/workflows, .oxe/templates, oxe/ e commands/oxe (o que existir).`
266
269
  );
@@ -329,6 +332,8 @@ function parseInstallArgs(argv) {
329
332
  jsonOutput: false,
330
333
  /** Lembretes agregados scan/compact em `status`. */
331
334
  statusHints: false,
335
+ /** Visão extendida CLI-first: coverage matrix + readiness gate no terminal. */
336
+ statusFull: false,
332
337
  restPositional: [],
333
338
  };
334
339
  for (let i = 0; i < argv.length; i++) {
@@ -373,6 +378,7 @@ function parseInstallArgs(argv) {
373
378
  out.dir = path.resolve(argv[++i]);
374
379
  } else if (a === '--json') out.jsonOutput = true;
375
380
  else if (a === '--hints') out.statusHints = true;
381
+ else if (a === '--full') out.statusFull = true;
376
382
  else if (!a.startsWith('-')) out.restPositional.push(a);
377
383
  else {
378
384
  out.parseError = true;
@@ -1015,6 +1021,9 @@ function ensureGitignoreIgnoresOxeDir(projectRoot, opts = {}) {
1015
1021
  function bootstrapOxe(target, opts) {
1016
1022
  const oxeDir = path.join(target, '.oxe');
1017
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');
1018
1027
  const stateSrc = path.join(PKG_ROOT, 'oxe', 'templates', 'STATE.md');
1019
1028
  const stateDest = path.join(oxeDir, 'STATE.md');
1020
1029
  const configSrc = path.join(PKG_ROOT, 'oxe', 'templates', 'config.template.json');
@@ -1026,12 +1035,15 @@ function bootstrapOxe(target, opts) {
1026
1035
  }
1027
1036
 
1028
1037
  if (opts.dryRun) {
1029
- 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)`);
1030
1039
  ensureGitignoreIgnoresOxeDir(target, { dryRun: true });
1031
1040
  return;
1032
1041
  }
1033
1042
 
1034
1043
  ensureDir(codebaseDir);
1044
+ ensureDir(capabilitiesDir);
1045
+ ensureDir(investigationsDir);
1046
+ ensureDir(dashboardDir);
1035
1047
 
1036
1048
  if (!fs.existsSync(stateDest) || opts.force) {
1037
1049
  copyFile(stateSrc, stateDest, { dryRun: false });
@@ -1050,9 +1062,9 @@ function bootstrapOxe(target, opts) {
1050
1062
  }
1051
1063
 
1052
1064
  // Criar estruturas opcionais: plugins/, workstreams/, memory/
1053
- const pluginsDir = path.join(oxeDir, 'plugins');
1054
- if (!fs.existsSync(pluginsDir)) {
1055
- ensureDir(pluginsDir);
1065
+ const pluginsDir = path.join(oxeDir, 'plugins');
1066
+ if (!fs.existsSync(pluginsDir)) {
1067
+ ensureDir(pluginsDir);
1056
1068
  const pluginsReadme = path.join(PKG_ROOT, 'oxe', 'templates', 'PLUGINS.md');
1057
1069
  if (fs.existsSync(pluginsReadme)) {
1058
1070
  const destPluginsReadme = path.join(pluginsDir, 'README.md');
@@ -1063,48 +1075,112 @@ function bootstrapOxe(target, opts) {
1063
1075
  }
1064
1076
  }
1065
1077
 
1066
- const workstreamsDir = path.join(oxeDir, 'workstreams');
1067
- if (!fs.existsSync(workstreamsDir)) {
1068
- ensureDir(workstreamsDir);
1069
- }
1070
-
1071
- const sessionsDir = path.join(oxeDir, 'sessions');
1072
- if (!fs.existsSync(sessionsDir)) {
1073
- ensureDir(sessionsDir);
1074
- }
1075
-
1076
- const globalDir = path.join(oxeDir, 'global');
1077
- if (!fs.existsSync(globalDir)) {
1078
- ensureDir(globalDir);
1079
- }
1080
-
1081
- const globalMilestonesDir = path.join(globalDir, 'milestones');
1082
- if (!fs.existsSync(globalMilestonesDir)) {
1083
- ensureDir(globalMilestonesDir);
1084
- }
1085
-
1086
- const lessonsSrc = path.join(PKG_ROOT, 'oxe', 'templates', 'LESSONS.template.md');
1087
- const lessonsDest = path.join(globalDir, 'LESSONS.md');
1088
- if (fs.existsSync(lessonsSrc) && !fs.existsSync(lessonsDest)) {
1089
- copyFile(lessonsSrc, lessonsDest, { dryRun: false });
1090
- console.log(`${green}init${reset} ${lessonsDest}`);
1091
- }
1092
-
1093
- const milestonesSrc = path.join(PKG_ROOT, 'oxe', 'templates', 'MILESTONES.template.md');
1094
- const milestonesDest = path.join(globalDir, 'MILESTONES.md');
1095
- if (fs.existsSync(milestonesSrc) && !fs.existsSync(milestonesDest)) {
1096
- copyFile(milestonesSrc, milestonesDest, { dryRun: false });
1097
- console.log(`${green}init${reset} ${milestonesDest}`);
1098
- }
1099
-
1100
- const memoryDir = path.join(oxeDir, 'memory');
1101
- if (!fs.existsSync(memoryDir)) {
1102
- ensureDir(memoryDir);
1103
- }
1078
+ const workstreamsDir = path.join(oxeDir, 'workstreams');
1079
+ if (!fs.existsSync(workstreamsDir)) {
1080
+ ensureDir(workstreamsDir);
1081
+ }
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
+
1112
+ const memoryDir = path.join(oxeDir, 'memory');
1113
+ if (!fs.existsSync(memoryDir)) {
1114
+ ensureDir(memoryDir);
1115
+ }
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
+ }
1104
1165
 
1105
1166
  ensureGitignoreIgnoresOxeDir(target, { dryRun: false });
1106
1167
  }
1107
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
+
1108
1184
  /**
1109
1185
  * Lembretes de rotina (scan/compact antigos) para `status --hints` ou JSON.
1110
1186
  * @param {string} target
@@ -1144,13 +1220,13 @@ function collectOxeRoutineHints(target, r, config) {
1144
1220
  * @param {boolean} c
1145
1221
  * @param {{ skipScanCompactAgeWarnings?: boolean }} [diagOpts]
1146
1222
  */
1147
- function printOxeHealthDiagnostics(target, c, diagOpts = {}) {
1148
- const skipAge = Boolean(diagOpts.skipScanCompactAgeWarnings);
1149
- const r = oxeHealth.buildHealthReport(target);
1150
- const { config } = oxeHealth.loadOxeConfigMerged(target);
1151
-
1152
- console.log(`\n ${c ? cyan : ''}▸ Coerência .oxe/ e config${reset}`);
1153
- console.log(` ${c ? dim : ''}Saúde lógica:${c ? reset : ''} ${r.healthStatus}`);
1223
+ function printOxeHealthDiagnostics(target, c, diagOpts = {}) {
1224
+ const skipAge = Boolean(diagOpts.skipScanCompactAgeWarnings);
1225
+ const r = oxeHealth.buildHealthReport(target);
1226
+ const { config } = oxeHealth.loadOxeConfigMerged(target);
1227
+
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}`);
1154
1230
 
1155
1231
  if (r.configParseError) {
1156
1232
  console.log(` ${red}FALHA${reset} config.json: ${r.configParseError}`);
@@ -1166,18 +1242,37 @@ function printOxeHealthDiagnostics(target, c, diagOpts = {}) {
1166
1242
  );
1167
1243
  }
1168
1244
 
1169
- if (r.phase) {
1170
- console.log(` ${c ? dim : ''}Fase (STATE.md):${c ? reset : ''} ${r.phase}`);
1171
- }
1172
- if (r.activeSession) {
1173
- console.log(` ${c ? dim : ''}Sessão ativa:${c ? reset : ''} ${r.activeSession}`);
1174
- }
1175
- if (r.planSelfEvaluation && r.planSelfEvaluation.hasSection) {
1176
- const best = r.planSelfEvaluation.bestPlan || '';
1177
- const conf =
1178
- typeof r.planSelfEvaluation.confidence === 'number' ? `${r.planSelfEvaluation.confidence}%` : '—';
1179
- console.log(` ${c ? dim : ''}Plano (autoavaliação):${c ? reset : ''} melhor=${best} | confiança=${conf}`);
1180
- }
1245
+ if (r.phase) {
1246
+ console.log(` ${c ? dim : ''}Fase (STATE.md):${c ? reset : ''} ${r.phase}`);
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
+ }
1181
1276
 
1182
1277
  if (!skipAge) {
1183
1278
  if (config.scan_max_age_days > 0 && r.scanDate && r.stale.stale) {
@@ -1212,18 +1307,30 @@ function printOxeHealthDiagnostics(target, c, diagOpts = {}) {
1212
1307
  console.log(` ${c ? dim : ''}Scan (ignorar):${c ? reset : ''} ${config.scan_ignore_globs.join(', ')}`);
1213
1308
  }
1214
1309
 
1215
- for (const w of r.phaseWarn) {
1216
- console.log(` ${yellow}AVISO${reset} ${w}`);
1217
- }
1218
- for (const w of r.sessionWarn) {
1219
- console.log(` ${yellow}AVISO${reset} ${w}`);
1220
- }
1221
- for (const w of r.installWarn) {
1222
- console.log(` ${yellow}AVISO${reset} ${w}`);
1223
- }
1224
- if (r.summaryGapWarn) {
1225
- console.log(` ${yellow}AVISO${reset} ${r.summaryGapWarn}`);
1226
- }
1310
+ for (const w of r.phaseWarn) {
1311
+ console.log(` ${yellow}AVISO${reset} ${w}`);
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
+ }
1331
+ if (r.summaryGapWarn) {
1332
+ console.log(` ${yellow}AVISO${reset} ${r.summaryGapWarn}`);
1333
+ }
1227
1334
  for (const w of r.specWarn) {
1228
1335
  console.log(` ${yellow}AVISO${reset} ${w}`);
1229
1336
  }
@@ -1232,15 +1339,100 @@ function printOxeHealthDiagnostics(target, c, diagOpts = {}) {
1232
1339
  }
1233
1340
  }
1234
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
+
1235
1427
  /**
1236
1428
  * @param {string} target
1237
- * @param {{ json?: boolean, hints?: boolean }} [opts]
1429
+ * @param {{ json?: boolean, hints?: boolean, full?: boolean }} [opts]
1238
1430
  */
1239
1431
  function runStatus(target, opts = {}) {
1240
1432
  const { config } = oxeHealth.loadOxeConfigMerged(target);
1241
- const next = oxeHealth.suggestNextStep(target, { discuss_before_plan: config.discuss_before_plan });
1242
1433
  const report = oxeHealth.buildHealthReport(target);
1243
1434
  const routineHints = collectOxeRoutineHints(target, report, config);
1435
+ const next = report.next;
1244
1436
 
1245
1437
  if (opts.json) {
1246
1438
  /** @type {Record<string, unknown>} */
@@ -1250,25 +1442,35 @@ function runStatus(target, opts = {}) {
1250
1442
  nextStep: report.next.step,
1251
1443
  cursorCmd: report.next.cursorCmd,
1252
1444
  reason: report.next.reason,
1253
- artifacts: report.next.artifacts,
1254
- phase: report.phase,
1255
- healthStatus: report.healthStatus,
1256
- activeSession: report.activeSession,
1257
- scanDate: report.scanDate,
1258
- staleScan: report.stale,
1259
- compactDate: report.compactDate,
1260
- staleCompact: report.staleCompact,
1261
- planSelfEvaluation: report.planSelfEvaluation,
1262
- diagnostics: {
1263
- configParseError: report.configParseError,
1264
- typeErrors: report.typeErrors,
1265
- unknownConfigKeys: report.unknownConfigKeys,
1266
- phaseWarnings: report.phaseWarn,
1267
- sessionWarnings: report.sessionWarn,
1268
- installWarnings: report.installWarn,
1269
- summaryGapWarning: report.summaryGapWarn,
1270
- specWarnings: report.specWarn,
1271
- planWarnings: report.planWarn,
1445
+ artifacts: report.next.artifacts,
1446
+ phase: report.phase,
1447
+ healthStatus: report.healthStatus,
1448
+ activeSession: report.activeSession,
1449
+ scanDate: report.scanDate,
1450
+ staleScan: report.stale,
1451
+ compactDate: report.compactDate,
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: {
1461
+ configParseError: report.configParseError,
1462
+ typeErrors: report.typeErrors,
1463
+ unknownConfigKeys: report.unknownConfigKeys,
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,
1471
+ summaryGapWarning: report.summaryGapWarn,
1472
+ specWarnings: report.specWarn,
1473
+ planWarnings: report.planWarn,
1272
1474
  },
1273
1475
  };
1274
1476
  if (opts.hints) {
@@ -1305,12 +1507,12 @@ function runStatus(target, opts = {}) {
1305
1507
  console.log(` ${c ? dim : ''}No Cursor (referência):${c ? reset : ''} ${c ? cyan : ''}${next.cursorCmd}${reset}`);
1306
1508
  console.log(` ${c ? dim : ''}Motivo:${c ? reset : ''} ${next.reason}`);
1307
1509
 
1308
- printSummaryAndNextSteps(c, {
1309
- bullets: [
1310
- `Saúde lógica: ${report.healthStatus}`,
1311
- `Artefatos em jogo: ${next.artifacts.join(', ')}`,
1312
- ],
1313
- nextSteps: [
1510
+ printSummaryAndNextSteps(c, {
1511
+ bullets: [
1512
+ `Saúde lógica: ${report.healthStatus}`,
1513
+ `Artefatos em jogo: ${next.artifacts.join(', ')}`,
1514
+ ],
1515
+ nextSteps: [
1314
1516
  { desc: 'Diagnóstico completo (inclui pacote de workflows):', cmd: 'npx oxe-cc doctor' },
1315
1517
  { desc: 'Ação sugerida no agente:', cmd: next.cursorCmd },
1316
1518
  ],
@@ -1409,7 +1611,7 @@ function runDoctor(target) {
1409
1611
  'CONVENTIONS.md',
1410
1612
  'CONCERNS.md',
1411
1613
  ];
1412
- if (fs.existsSync(cbDir)) {
1614
+ if (fs.existsSync(cbDir)) {
1413
1615
  const missingMaps = expectedMaps.filter((f) => !fs.existsSync(path.join(cbDir, f)));
1414
1616
  if (missingMaps.length) {
1415
1617
  console.log(
@@ -1418,21 +1620,21 @@ function runDoctor(target) {
1418
1620
  } else {
1419
1621
  console.log(`${green}OK${reset} .oxe/codebase/ com os ${expectedMaps.length} mapas esperados`);
1420
1622
  }
1421
- }
1422
-
1423
- printOxeHealthDiagnostics(target, c);
1424
- const report = oxeHealth.buildHealthReport(target);
1425
- const statusColor = report.healthStatus === 'healthy' ? green : report.healthStatus === 'warning' ? yellow : red;
1426
- console.log(`\n ${statusColor}Diagnóstico ${report.healthStatus}${reset}`);
1427
- if (report.healthStatus === 'broken') {
1428
- process.exitCode = 1;
1429
- }
1430
- printSummaryAndNextSteps(c, {
1431
- bullets: [
1432
- `Projeto em ${target}`,
1433
- `Workflows conferidos em ${wfLabel}`,
1434
- `Saúde lógica: ${report.healthStatus}`,
1435
- ],
1623
+ }
1624
+
1625
+ printOxeHealthDiagnostics(target, c);
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
+ }
1632
+ printSummaryAndNextSteps(c, {
1633
+ bullets: [
1634
+ `Projeto em ${target}`,
1635
+ `Workflows conferidos em ${wfLabel}`,
1636
+ `Saúde lógica: ${report.healthStatus}`,
1637
+ ],
1436
1638
  nextSteps: [
1437
1639
  { desc: 'Mapear ou atualizar o codebase no agente:', cmd: '/oxe-scan' },
1438
1640
  { desc: 'Ver ajuda e ordem dos passos OXE:', cmd: '/oxe-help' },
@@ -1446,7 +1648,7 @@ function runDoctor(target) {
1446
1648
  * npm install -g oxe-cc@version (same version as this running CLI).
1447
1649
  * @returns {boolean}
1448
1650
  */
1449
- function installGlobalCliPackage() {
1651
+ function installGlobalCliPackage() {
1450
1652
  const name = readPkgName();
1451
1653
  const ver = readPkgVersion();
1452
1654
  const spec = `${name}@${ver}`;
@@ -1469,99 +1671,99 @@ function installGlobalCliPackage() {
1469
1671
  `\n ${c ? yellow : ''}⚠${c ? reset : ''} npm install -g falhou. Tente manualmente: ${c ? cyan : ''}npm install -g ${spec}${c ? reset : ''}\n`
1470
1672
  );
1471
1673
  return false;
1472
- }
1473
-
1474
- /**
1475
- * `npm uninstall -g oxe-cc` com a mesma semântica cross-platform do instalador.
1476
- * @returns {boolean}
1477
- */
1478
- function uninstallGlobalCliPackage() {
1479
- const name = readPkgName();
1480
- const c = useAnsiColors();
1481
- const dimOrEmpty = c ? dim : '';
1482
- const resetOrEmpty = c ? reset : '';
1483
- console.log(`\n ${dimOrEmpty}npm uninstall -g ${name}${resetOrEmpty}\n`);
1484
- const r = spawnSync('npm', ['uninstall', '-g', name], {
1485
- stdio: 'inherit',
1486
- shell: true,
1487
- env: process.env,
1488
- });
1489
- if (r.status === 0) {
1490
- console.log(
1491
- `\n ${c ? green : ''}✓${c ? reset : ''} pacote global ${c ? cyan : ''}${name}${c ? reset : ''} removido do npm global.\n`
1492
- );
1493
- return true;
1494
- }
1495
- console.log(
1496
- `\n ${c ? yellow : ''}⚠${c ? reset : ''} npm uninstall -g falhou. Remova manualmente: ${c ? cyan : ''}npm uninstall -g ${name}${c ? reset : ''}\n`
1497
- );
1498
- return false;
1499
- }
1500
-
1501
- /**
1502
- * Best-effort: detecta se esta execução vem de uma instalação global do npm.
1503
- * Usa `npm root -g` para evitar confundir execução local do repositório com pacote global.
1504
- * @returns {boolean}
1505
- */
1506
- function isRunningFromGlobalNpmInstall() {
1507
- try {
1508
- const r = spawnSync('npm', ['root', '-g'], {
1509
- encoding: 'utf8',
1510
- shell: true,
1511
- env: process.env,
1512
- });
1513
- if (r.status !== 0) return false;
1514
- const root = String(r.stdout || '').trim();
1515
- if (!root) return false;
1516
- const rel = path.relative(path.resolve(root), PKG_ROOT);
1517
- return rel && !rel.startsWith('..') && !path.isAbsolute(rel);
1518
- } catch {
1519
- return false;
1520
- }
1521
- }
1522
-
1523
- /** @returns {string[]} */
1524
- function updateForwardedInstallFlags() {
1525
- return [
1526
- '--cursor',
1527
- '--copilot',
1528
- '--copilot-cli',
1529
- '--all-agents',
1530
- '--opencode',
1531
- '--gemini',
1532
- '--codex',
1533
- '--windsurf',
1534
- '--antigravity',
1535
- '--vscode',
1536
- '--no-commands',
1537
- '--no-agents',
1538
- '--no-init-oxe',
1539
- '--oxe-only',
1540
- '--global',
1541
- '--local',
1542
- '--ide-global',
1543
- '--ide-local',
1544
- '--global-cli',
1545
- '-g',
1546
- '--no-global-cli',
1547
- '-l',
1548
- '--no-install-config',
1549
- '--force',
1550
- '-f',
1551
- '--all',
1552
- '-a',
1553
- '--config-dir',
1554
- '-c',
1555
- ];
1556
- }
1557
-
1558
- /**
1559
- * @param {string[]} rest
1560
- * @returns {boolean}
1561
- */
1562
- function updateArgsExplicitlyControlGlobalCli(rest) {
1563
- return rest.includes('--global-cli') || rest.includes('-g') || rest.includes('--no-global-cli') || rest.includes('-l');
1564
- }
1674
+ }
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
+ }
1565
1767
 
1566
1768
  /**
1567
1769
  * After copying OXE into the project: optionally install the CLI globally (pergunta interativa ou flags).
@@ -1646,27 +1848,92 @@ ${green}Uso:${reset}
1646
1848
  npx oxe-cc doctor [opções] [pasta-do-projeto]
1647
1849
  npx oxe-cc status [opções] [pasta-do-projeto]
1648
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]
1649
1855
  npx oxe-cc uninstall [opções] [pasta-do-projeto]
1650
1856
  npx oxe-cc update [opções] [argumentos extras…]
1651
1857
 
1652
- ${green}uninstall${reset} (remove OXE da pasta do usuário + pastas de workflows no repo)
1653
- --cursor / --copilot / --copilot-cli só essa integração (omissão = todas)
1654
- --all-agents também remove ficheiros multi-plataforma (com --copilot-cli implícito)
1655
- --ide-local remove integrações IDE neste repositório (.cursor, .github, .claude, .copilot, …)
1656
- --ide-only não apagar .oxe/workflows, oxe/, etc. no projeto
1657
- --global-cli, -g também executa npm uninstall -g oxe-cc
1658
- --config-dir <caminho> com exatamente uma flag IDE acima (não combina com --ide-local)
1659
- --dry-run
1660
- --dir <pasta> raiz do projeto (padrão: diretório atual)
1858
+ ${green}uninstall${reset} (remove OXE da pasta do usuário + pastas de workflows no repo)
1859
+ --cursor / --copilot / --copilot-cli só essa integração (omissão = todas)
1860
+ --all-agents também remove ficheiros multi-plataforma (com --copilot-cli implícito)
1861
+ --ide-local remove integrações IDE neste repositório (.cursor, .github, .claude, .copilot, …)
1862
+ --ide-only não apagar .oxe/workflows, oxe/, etc. no projeto
1863
+ --global-cli, -g também executa npm uninstall -g oxe-cc
1864
+ --config-dir <caminho> com exatamente uma flag IDE acima (não combina com --ide-local)
1865
+ --dry-run
1866
+ --dir <pasta> raiz do projeto (padrão: diretório atual)
1661
1867
 
1662
1868
  ${green}update${reset} (executa npx oxe-cc@latest --force na pasta do projeto)
1663
1869
  --check só consulta npm: compara versão em execução com latest (saída 0=ok, 1=há mais nova, 2=erro; incompatível com --dry-run)
1664
1870
  --if-newer só executa o npx se existir versão mais nova no npm (falha de rede/registry: saída 2, sem npx)
1665
1871
  --dir <pasta> pasta em que o npx roda (padrão: atual; ignorada com --check)
1666
1872
  --dry-run mostra o comando sem executar
1667
- [argumentos extras…] repassados ao oxe-cc (ex.: --cursor --global, --ide-local, --global-cli)
1873
+ [argumentos extras…] repassados ao oxe-cc (ex.: --cursor --global, --ide-local, --global-cli)
1668
1874
  ${dim}CI / sem rede:${reset} OXE_UPDATE_SKIP_REGISTRY=1 desativa consultas (--check sai 2; --if-newer sai 2 sem npx)
1669
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
+
1670
1937
  ${green}Opções da instalação:${reset}
1671
1938
  --cursor Copia comandos e regras para ~/.cursor (padrão com --all)
1672
1939
  --copilot Mescla instruções + prompts em ~/.copilot (global) ou .github/ (com --ide-local)
@@ -1706,6 +1973,13 @@ ${green}status${reset} (coerência .oxe/ + um próximo passo sugerido; não exig
1706
1973
  --json imprime um único objeto JSON (próximo passo + diagnósticos) em stdout; adequado a CI
1707
1974
  --hints lembretes de rotina (idade scan/compact quando configurado em config.json); com --json inclui array \`hints\`
1708
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
+
1709
1983
  ${green}Atualizar (projeto já tem OXE):${reset}
1710
1984
  /oxe-update no Cursor (outras IDEs: mesmo fluxo pelo terminal)
1711
1985
  npx oxe-cc update --check só ver se há versão nova no npm
@@ -2007,7 +2281,7 @@ function runInstall(opts) {
2007
2281
  console.log(` ${c ? green : ''}✓${c ? reset : ''} Instalação concluída com sucesso.\n`);
2008
2282
  }
2009
2283
 
2010
- /** @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 */
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 */
2011
2285
 
2012
2286
  /**
2013
2287
  * @param {UninstallOpts} u
@@ -2104,10 +2378,10 @@ function uninstallLocalIdeFromProject(u, removedPaths) {
2104
2378
  }
2105
2379
 
2106
2380
  /**
2107
- * @param {string[]} argv
2108
- * @returns {UninstallOpts}
2109
- */
2110
- function parseUninstallArgs(argv) {
2381
+ * @param {string[]} argv
2382
+ * @returns {UninstallOpts}
2383
+ */
2384
+ function parseUninstallArgs(argv) {
2111
2385
  /** @type {UninstallOpts} */
2112
2386
  const out = {
2113
2387
  help: false,
@@ -2115,11 +2389,11 @@ function parseUninstallArgs(argv) {
2115
2389
  cursor: false,
2116
2390
  copilot: false,
2117
2391
  copilotCli: false,
2118
- allAgents: false,
2119
- globalCli: false,
2120
- ideLocal: false,
2121
- ideExplicit: false,
2122
- noProject: false,
2392
+ allAgents: false,
2393
+ globalCli: false,
2394
+ ideLocal: false,
2395
+ ideExplicit: false,
2396
+ noProject: false,
2123
2397
  dir: process.cwd(),
2124
2398
  explicitConfigDir: null,
2125
2399
  parseError: false,
@@ -2142,15 +2416,15 @@ function parseUninstallArgs(argv) {
2142
2416
  } else if (a === '--copilot-cli') {
2143
2417
  out.copilotCli = true;
2144
2418
  out.ideExplicit = true;
2145
- } else if (a === '--all-agents') {
2146
- out.allAgents = true;
2147
- out.copilotCli = true;
2148
- out.ideExplicit = true;
2149
- } else if (a === '--global-cli' || a === '-g') {
2150
- out.globalCli = true;
2151
- } else if (a === '--ide-local') out.ideLocal = true;
2152
- else if (a === '--ide-only') out.noProject = true;
2153
- else if (a === '--dir' && argv[i + 1]) out.dir = path.resolve(argv[++i]);
2419
+ } else if (a === '--all-agents') {
2420
+ out.allAgents = true;
2421
+ out.copilotCli = true;
2422
+ out.ideExplicit = true;
2423
+ } else if (a === '--global-cli' || a === '-g') {
2424
+ out.globalCli = true;
2425
+ } else if (a === '--ide-local') out.ideLocal = true;
2426
+ else if (a === '--ide-only') out.noProject = true;
2427
+ else if (a === '--dir' && argv[i + 1]) out.dir = path.resolve(argv[++i]);
2154
2428
  else if (!a.startsWith('-')) rest.push(path.resolve(a));
2155
2429
  else {
2156
2430
  out.parseError = true;
@@ -2230,7 +2504,7 @@ function rmDirIfEmpty(dirPath, opts) {
2230
2504
  /**
2231
2505
  * @param {UninstallOpts} u
2232
2506
  */
2233
- function runUninstall(u) {
2507
+ function runUninstall(u) {
2234
2508
  assertNotWslWindowsNode();
2235
2509
  const c = useAnsiColors();
2236
2510
  const home = os.homedir();
@@ -2387,7 +2661,7 @@ function runUninstall(u) {
2387
2661
  }
2388
2662
  }
2389
2663
 
2390
- if (!u.dryRun && (u.cursor || u.copilot || u.copilotCli)) {
2664
+ if (!u.dryRun && (u.cursor || u.copilot || u.copilotCli)) {
2391
2665
  const prev = oxeManifest.loadFileManifest(home);
2392
2666
  const next = { ...prev };
2393
2667
  for (const p of removedPaths) delete next[p];
@@ -2403,31 +2677,31 @@ function runUninstall(u) {
2403
2677
  delete next[instPath];
2404
2678
  }
2405
2679
  }
2406
- oxeManifest.writeFileManifest(home, next, readPkgVersion());
2407
- }
2408
-
2409
- if (u.globalCli && !u.dryRun) {
2410
- uninstallGlobalCliPackage();
2411
- } else if (u.globalCli && u.dryRun) {
2412
- console.log(`${dim}npm${reset} npm uninstall -g ${readPkgName()}`);
2413
- }
2414
-
2415
- printSummaryAndNextSteps(c, buildUninstallFooter(u));
2416
- if (!u.globalCli) {
2417
- console.log(
2418
- ` ${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`
2419
- );
2420
- }
2421
- console.log(` ${c ? green : ''}✓${c ? reset : ''} Desinstalação concluída com sucesso.\n`);
2422
- }
2423
-
2424
- /** @typedef {{ help: boolean, dryRun: boolean, check: boolean, ifNewer: boolean, dir: string, rest: string[], parseError: boolean, unknownFlag: string, conflictFlags: string|null }} UpdateOpts */
2680
+ oxeManifest.writeFileManifest(home, next, readPkgVersion());
2681
+ }
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
+
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
+ }
2695
+ console.log(` ${c ? green : ''}✓${c ? reset : ''} Desinstalação concluída com sucesso.\n`);
2696
+ }
2697
+
2698
+ /** @typedef {{ help: boolean, dryRun: boolean, check: boolean, ifNewer: boolean, dir: string, rest: string[], parseError: boolean, unknownFlag: string, conflictFlags: string|null }} UpdateOpts */
2425
2699
 
2426
2700
  /**
2427
2701
  * @param {string[]} argv
2428
2702
  * @returns {UpdateOpts}
2429
2703
  */
2430
- function parseUpdateArgs(argv) {
2704
+ function parseUpdateArgs(argv) {
2431
2705
  /** @type {UpdateOpts} */
2432
2706
  const out = {
2433
2707
  help: false,
@@ -2440,31 +2714,31 @@ function parseUpdateArgs(argv) {
2440
2714
  unknownFlag: '',
2441
2715
  conflictFlags: null,
2442
2716
  };
2443
- let dirExplicit = false;
2444
- let firstPositionalConsumed = false;
2445
- const passthroughFlags = new Set(updateForwardedInstallFlags());
2446
- for (let i = 0; i < argv.length; i++) {
2447
- const a = argv[i];
2448
- if (a === '-h' || a === '--help') out.help = true;
2717
+ let dirExplicit = false;
2718
+ let firstPositionalConsumed = false;
2719
+ const passthroughFlags = new Set(updateForwardedInstallFlags());
2720
+ for (let i = 0; i < argv.length; i++) {
2721
+ const a = argv[i];
2722
+ if (a === '-h' || a === '--help') out.help = true;
2449
2723
  else if (a === '--dry-run') out.dryRun = true;
2450
2724
  else if (a === '--check') out.check = true;
2451
2725
  else if (a === '--if-newer') out.ifNewer = true;
2452
2726
  else if (a === '--dir' && argv[i + 1]) {
2453
2727
  out.dir = path.resolve(argv[++i]);
2454
2728
  dirExplicit = true;
2455
- } else if (!a.startsWith('-')) {
2456
- if (!dirExplicit && !firstPositionalConsumed) {
2457
- out.dir = path.resolve(a);
2458
- firstPositionalConsumed = true;
2459
- } else out.rest.push(a);
2460
- } else if (passthroughFlags.has(a)) {
2461
- out.rest.push(a);
2462
- if ((a === '--config-dir' || a === '-c') && argv[i + 1]) {
2463
- out.rest.push(argv[++i]);
2464
- }
2465
- } else {
2466
- out.parseError = true;
2467
- out.unknownFlag = a;
2729
+ } else if (!a.startsWith('-')) {
2730
+ if (!dirExplicit && !firstPositionalConsumed) {
2731
+ out.dir = path.resolve(a);
2732
+ firstPositionalConsumed = true;
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
+ }
2739
+ } else {
2740
+ out.parseError = true;
2741
+ out.unknownFlag = a;
2468
2742
  break;
2469
2743
  }
2470
2744
  }
@@ -2519,7 +2793,7 @@ function runUpdateVersionCheck(u) {
2519
2793
  /**
2520
2794
  * @param {UpdateOpts} u
2521
2795
  */
2522
- function runUpdate(u) {
2796
+ function runUpdate(u) {
2523
2797
  assertNotWslWindowsNode();
2524
2798
  const c = useAnsiColors();
2525
2799
  if (u.dryRun) {
@@ -2530,12 +2804,12 @@ function runUpdate(u) {
2530
2804
  );
2531
2805
  }
2532
2806
  console.log(` ${dim}Comando que seria executado (instalação):${reset}`);
2533
- const dryRunArgs = ['-y', 'oxe-cc@latest', '--force'];
2534
- if (updateArgsExplicitlyControlGlobalCli(u.rest)) dryRunArgs.push(...u.rest);
2535
- else if (isRunningFromGlobalNpmInstall()) dryRunArgs.push('--global-cli');
2536
- else dryRunArgs.push('--no-global-cli', '-l');
2537
- console.log(` ${cyan}npx ${dryRunArgs.join(' ')}${reset}`);
2538
- console.log(` ${dim}Diretório:${reset} ${u.dir}`);
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}`);
2812
+ console.log(` ${dim}Diretório:${reset} ${u.dir}`);
2539
2813
  printSummaryAndNextSteps(c, {
2540
2814
  bullets: [
2541
2815
  '[simulação] O npx baixaria o pacote oxe-cc@latest e rodaria a instalação com --force.',
@@ -2580,20 +2854,20 @@ function runUpdate(u) {
2580
2854
  console.log(
2581
2855
  ` ${dim}Há versão mais nova no npm (${res.version} > ${current}); a executar npx…${reset}\n`
2582
2856
  );
2583
- }
2584
-
2585
- printSection('OXE ▸ update');
2586
- const args = ['-y', 'oxe-cc@latest', '--force'];
2587
- if (updateArgsExplicitlyControlGlobalCli(u.rest)) {
2588
- args.push(...u.rest);
2589
- } else if (isRunningFromGlobalNpmInstall()) {
2590
- args.push('--global-cli', ...u.rest);
2591
- } else {
2592
- args.push('--no-global-cli', '-l', ...u.rest);
2593
- }
2594
- const r = spawnSync('npx', args, {
2595
- cwd: u.dir,
2596
- stdio: 'inherit',
2857
+ }
2858
+
2859
+ printSection('OXE ▸ update');
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
+ }
2868
+ const r = spawnSync('npx', args, {
2869
+ cwd: u.dir,
2870
+ stdio: 'inherit',
2597
2871
  env: { ...process.env },
2598
2872
  shell: process.platform === 'win32',
2599
2873
  });
@@ -2616,6 +2890,823 @@ function runUpdate(u) {
2616
2890
  console.log(` ${c ? green : ''}✓${c ? reset : ''} Atualização concluída com sucesso.\n`);
2617
2891
  }
2618
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
+
2619
3710
  async function main() {
2620
3711
  const argv = process.argv.slice(2);
2621
3712
  let command = 'install';
@@ -2623,8 +3714,14 @@ async function main() {
2623
3714
  argv[0] === 'doctor' ||
2624
3715
  argv[0] === 'status' ||
2625
3716
  argv[0] === 'init-oxe' ||
3717
+ argv[0] === 'dashboard' ||
3718
+ argv[0] === 'runtime' ||
3719
+ argv[0] === 'azure' ||
2626
3720
  argv[0] === 'uninstall' ||
2627
3721
  argv[0] === 'update' ||
3722
+ argv[0] === 'capabilities' ||
3723
+ argv[0] === 'plan' ||
3724
+ argv[0] === 'verify' ||
2628
3725
  argv[0] === 'install'
2629
3726
  ) {
2630
3727
  command = argv[0];
@@ -2691,6 +3788,108 @@ async function main() {
2691
3788
  return;
2692
3789
  }
2693
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
+
2694
3893
  const opts = parseInstallArgs(argv);
2695
3894
 
2696
3895
  if (opts.version) {
@@ -2737,7 +3936,11 @@ async function main() {
2737
3936
  console.error(`${yellow}Diretório não encontrado: ${target}${reset}`);
2738
3937
  process.exit(1);
2739
3938
  }
2740
- 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
+ }
2741
3944
  return;
2742
3945
  }
2743
3946
 
@@ -2753,8 +3956,8 @@ async function main() {
2753
3956
  bootstrapOxe(target, { dryRun: opts.dryRun, force: opts.force });
2754
3957
  printSummaryAndNextSteps(c0, {
2755
3958
  bullets: opts.dryRun
2756
- ? ['[simulação] Seriam criados ou atualizados .oxe/STATE.md, .oxe/config.json e .oxe/codebase/']
2757
- : ['.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)'],
2758
3961
  nextSteps: [
2759
3962
  { desc: 'Validar o projeto:', cmd: 'npx oxe-cc doctor' },
2760
3963
  { desc: 'Instalar integrações IDE/CLI (se ainda não fez):', cmd: 'npx oxe-cc@latest' },
@@ -2772,6 +3975,8 @@ async function main() {
2772
3975
  }
2773
3976
 
2774
3977
  main().catch((err) => {
2775
- 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);
2776
3981
  process.exit(1);
2777
3982
  });