oxe-cc 1.12.0 → 1.16.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 (40) hide show
  1. package/.github/dependabot.yml +31 -0
  2. package/.github/workflows/ci.yml +141 -56
  3. package/.github/workflows/release.yml +114 -89
  4. package/CHANGELOG.md +866 -754
  5. package/README.md +600 -736
  6. package/bin/lib/oxe-agent-install.cjs +299 -284
  7. package/bin/lib/oxe-artifact-catalog.cjs +376 -0
  8. package/bin/lib/oxe-command-registry.cjs +31 -0
  9. package/bin/lib/oxe-context-engine.cjs +11 -11
  10. package/bin/lib/oxe-core-command-handlers.cjs +82 -0
  11. package/bin/lib/oxe-dashboard.cjs +140 -140
  12. package/bin/lib/oxe-manifest.cjs +20 -20
  13. package/bin/lib/oxe-npm-version.cjs +6 -4
  14. package/bin/lib/oxe-plugin-cli.cjs +95 -0
  15. package/bin/lib/oxe-plugins.cjs +94 -3
  16. package/bin/lib/oxe-process.cjs +67 -0
  17. package/bin/lib/oxe-project-health.cjs +2846 -2781
  18. package/bin/lib/oxe-runtime-semantics.cjs +68 -69
  19. package/bin/oxe-cc.js +369 -353
  20. package/docs/INTEGRATION.md +182 -0
  21. package/docs/QUALITY-GATES.md +46 -0
  22. package/docs/RELEASE-READINESS.md +86 -61
  23. package/docs/RUNTIME-SMOKE-MATRIX.md +137 -135
  24. package/docs/oxe-artifact-map.html +1172 -0
  25. package/lib/sdk/index.cjs +20 -0
  26. package/lib/sdk/index.d.ts +971 -876
  27. package/lib/sdk/index.types.ts +933 -0
  28. package/oxe/templates/PLUGINS.md +8 -1
  29. package/oxe/templates/STATE-REFERENCE.md +125 -0
  30. package/oxe/templates/STATE.md +11 -121
  31. package/oxe/workflows/help.md +2 -0
  32. package/package.json +129 -108
  33. package/packages/runtime/package.json +18 -18
  34. package/packages/runtime/src/evidence/evidence-store.ts +2 -2
  35. package/packages/runtime/src/scheduler/multi-agent-coordinator.ts +728 -728
  36. package/packages/runtime/src/workspace/strategies/git-worktree.ts +24 -24
  37. package/packages/runtime/tsconfig.json +8 -2
  38. package/vscode-extension/.vscodeignore +2 -0
  39. package/vscode-extension/package.json +193 -185
  40. package/vscode-extension/src/extension.js +11 -1
package/bin/oxe-cc.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
  /**
3
3
  * OXE — CLI em pt-BR: instala workflows no projeto, bootstrap `.oxe/`, doctor, uninstall, update.
4
4
  * Uso: npx oxe-cc, doctor, status, init-oxe, uninstall, update (ver --help).
@@ -21,10 +21,13 @@ const oxeNpmVersion = require(path.join(__dirname, 'lib', 'oxe-npm-version.cjs')
21
21
  const oxeDashboard = require(path.join(__dirname, 'lib', 'oxe-dashboard.cjs'));
22
22
  const oxeOperational = require(path.join(__dirname, 'lib', 'oxe-operational.cjs'));
23
23
  const oxeAzure = require(path.join(__dirname, 'lib', 'oxe-azure.cjs'));
24
- const oxePlugins = require(path.join(__dirname, 'lib', 'oxe-plugins.cjs'));
24
+ const oxePluginCli = require(path.join(__dirname, 'lib', 'oxe-plugin-cli.cjs'));
25
25
  const oxeContext = require(path.join(__dirname, 'lib', 'oxe-context-engine.cjs'));
26
26
  const oxeRuntimeSemantics = require(path.join(__dirname, 'lib', 'oxe-runtime-semantics.cjs'));
27
27
  const oxeRelease = require(path.join(__dirname, 'lib', 'oxe-release.cjs'));
28
+ const oxeArtifactCatalog = require(path.join(__dirname, 'lib', 'oxe-artifact-catalog.cjs'));
29
+ const { runPackageManagerSync } = require(path.join(__dirname, 'lib', 'oxe-process.cjs'));
30
+ const { createCoreCommandRegistry } = require(path.join(__dirname, 'lib', 'oxe-core-command-handlers.cjs'));
28
31
 
29
32
  /** Merge markers for ~/.copilot/copilot-instructions.md (bloco OXE). */
30
33
  const OXE_INST_BEGIN = '<!-- oxe-cc:install-begin -->';
@@ -203,26 +206,26 @@ function buildInstallSummary(opts, fullLayout) {
203
206
  cmd: 'npx oxe-cc status',
204
207
  });
205
208
 
206
- const agentHint = [];
207
- if (opts.cursor) agentHint.push('Cursor');
208
- if (opts.copilot) agentHint.push('Copilot no VS Code');
209
- if (opts.copilotCli || opts.allAgents || anyGranularAgent(opts)) agentHint.push('CLIs / multi-agente');
210
- const onlyCodexAgent =
211
- opts.agentCodex &&
212
- !opts.allAgents &&
213
- !opts.copilotCli &&
214
- !opts.cursor &&
215
- !opts.copilot &&
216
- !opts.agentOpenCode &&
217
- !opts.agentGemini &&
218
- !opts.agentWindsurf &&
219
- !opts.agentAntigravity;
220
- if (agentHint.length) {
221
- nextSteps.push({
222
- desc: `Entrar no fluxo OXE no agente (${agentHint.join(', ')}) e deixar o router indicar o primeiro passo:`,
223
- cmd: onlyCodexAgent ? '/prompts:oxe' : '/oxe',
224
- });
225
- } else if (opts.oxeOnly) {
209
+ const agentHint = [];
210
+ if (opts.cursor) agentHint.push('Cursor');
211
+ if (opts.copilot) agentHint.push('Copilot no VS Code');
212
+ if (opts.copilotCli || opts.allAgents || anyGranularAgent(opts)) agentHint.push('CLIs / multi-agente');
213
+ const onlyCodexAgent =
214
+ opts.agentCodex &&
215
+ !opts.allAgents &&
216
+ !opts.copilotCli &&
217
+ !opts.cursor &&
218
+ !opts.copilot &&
219
+ !opts.agentOpenCode &&
220
+ !opts.agentGemini &&
221
+ !opts.agentWindsurf &&
222
+ !opts.agentAntigravity;
223
+ if (agentHint.length) {
224
+ nextSteps.push({
225
+ desc: `Entrar no fluxo OXE no agente (${agentHint.join(', ')}) e deixar o router indicar o primeiro passo:`,
226
+ cmd: onlyCodexAgent ? '/prompts:oxe' : '/oxe',
227
+ });
228
+ } else if (opts.oxeOnly) {
226
229
  nextSteps.push({
227
230
  desc: 'Para ativar integrações IDE/CLI neste repo, instale de novo sem --oxe-only:',
228
231
  cmd: 'npx oxe-cc@latest',
@@ -234,19 +237,19 @@ function buildInstallSummary(opts, fullLayout) {
234
237
  });
235
238
  }
236
239
 
237
- if (opts.copilotCli || opts.allAgents) {
238
- nextSteps.push({
239
- desc: 'No Copilot CLI: após instalar, rode /skills reload (ou reinicie o copilot) e use /oxe ou /oxe-scan:',
240
- cmd: '/skills list',
241
- });
242
- }
243
- if (opts.agentCodex || opts.allAgents) {
244
- nextSteps.push({
245
- desc: 'No Codex Desktop: reinicie o app ou abra uma nova conversa para recarregar prompts/skills; use a superfície de prompts do Codex:',
246
- cmd: '/prompts:oxe ou /prompts:oxe-spec',
247
- });
248
- }
249
- if (opts.allAgents || opts.agentGemini) {
240
+ if (opts.copilotCli || opts.allAgents) {
241
+ nextSteps.push({
242
+ desc: 'No Copilot CLI: após instalar, rode /skills reload (ou reinicie o copilot) e use /oxe ou /oxe-scan:',
243
+ cmd: '/skills list',
244
+ });
245
+ }
246
+ if (opts.agentCodex || opts.allAgents) {
247
+ nextSteps.push({
248
+ desc: 'No Codex Desktop: reinicie o app ou abra uma nova conversa para recarregar prompts/skills; use a superfície de prompts do Codex:',
249
+ cmd: '/prompts:oxe ou /prompts:oxe-spec',
250
+ });
251
+ }
252
+ if (opts.allAgents || opts.agentGemini) {
250
253
  nextSteps.push({
251
254
  desc: 'No Gemini CLI: recarregar comandos personalizados (/oxe, /oxe:scan, …):',
252
255
  cmd: '/commands reload',
@@ -390,6 +393,8 @@ function parseInstallArgs(argv) {
390
393
  statusHints: false,
391
394
  /** Visão extendida CLI-first: coverage matrix + readiness gate no terminal. */
392
395
  statusFull: false,
396
+ /** Saída JSON compacta/versionada para hosts (status --json --summary). */
397
+ statusSummary: false,
393
398
  restPositional: [],
394
399
  };
395
400
  for (let i = 0; i < argv.length; i++) {
@@ -435,6 +440,7 @@ function parseInstallArgs(argv) {
435
440
  } else if (a === '--json') out.jsonOutput = true;
436
441
  else if (a === '--hints') out.statusHints = true;
437
442
  else if (a === '--full') out.statusFull = true;
443
+ else if (a === '--summary') out.statusSummary = true;
438
444
  else if (a === '--release') out.releaseDoctor = true;
439
445
  else if (a === '--write-manifest') out.writeManifest = true;
440
446
  else if (!a.startsWith('-')) out.restPositional.push(a);
@@ -932,7 +938,7 @@ function applyInstallFromOxeConfig(opts, targetDir) {
932
938
  }
933
939
 
934
940
  /**
935
- * Mapa número da lista → chaves de runtime
941
+ * Mapa número da lista → chaves de runtime
936
942
  * @param {string} input
937
943
  * @returns {string[]}
938
944
  */
@@ -1288,20 +1294,48 @@ function ensureGitignoreIgnoresOxeDir(projectRoot, opts = {}) {
1288
1294
  }
1289
1295
 
1290
1296
  /**
1291
- * Create `.oxe/STATE.md` from template and ensure `.oxe/codebase/` exists.
1297
+ * Ensure a subdirectory under `.oxe/` exists, creating it (and parents) on
1298
+ * demand. This is the lazy-scaffolding primitive: writers call it right before
1299
+ * persisting so the `.oxe/` tree only grows as features are actually used,
1300
+ * instead of being scaffolded eagerly at install time.
1301
+ * @param {string} target project root
1302
+ * @param {string} sub path relative to `.oxe/` (e.g. 'runs', 'global/milestones')
1303
+ * @returns {string} absolute path of the ensured directory
1304
+ */
1305
+ function ensureOxeSubdir(target, sub) {
1306
+ const dir = path.join(target, '.oxe', ...String(sub).split(/[\\/]/).filter(Boolean));
1307
+ ensureDir(dir);
1308
+ return dir;
1309
+ }
1310
+
1311
+ /**
1312
+ * (Re)generate the `.oxe/README.md` legend from the single artifact catalog.
1313
+ * Written at init and refreshed by `doctor`, so the local documentation always
1314
+ * reflects the canonical structure without the empty folders cluttering disk.
1315
+ * @param {string} target project root
1316
+ * @param {{ quiet?: boolean }} [opts]
1317
+ */
1318
+ function writeOxeLegend(target, opts = {}) {
1319
+ const oxeDir = path.join(target, '.oxe');
1320
+ const dest = path.join(oxeDir, 'README.md');
1321
+ try {
1322
+ ensureDir(oxeDir);
1323
+ fs.writeFileSync(dest, oxeArtifactCatalog.renderLegend(), 'utf8');
1324
+ if (!opts.quiet) console.log(`${green}init${reset} ${dest}`);
1325
+ } catch {
1326
+ /* best-effort: a missing legend is never fatal */
1327
+ }
1328
+ }
1329
+
1330
+ /**
1331
+ * Create the lean `.oxe/` core: STATE.md, config.json and the generated
1332
+ * README.md legend. Every other artifact/folder is created lazily, on first
1333
+ * use, by the workflow that owns it (see `bin/lib/oxe-artifact-catalog.cjs`).
1292
1334
  * @param {string} target
1293
1335
  * @param {{ dryRun: boolean, force: boolean }} opts
1294
1336
  */
1295
1337
  function bootstrapOxe(target, opts) {
1296
1338
  const oxeDir = path.join(target, '.oxe');
1297
- const codebaseDir = path.join(oxeDir, 'codebase');
1298
- const capabilitiesDir = path.join(oxeDir, 'capabilities');
1299
- const investigationsDir = path.join(oxeDir, 'investigations');
1300
- const dashboardDir = path.join(oxeDir, 'dashboard');
1301
- const contextDir = path.join(oxeDir, 'context');
1302
- const contextPacksDir = path.join(contextDir, 'packs');
1303
- const contextSummariesDir = path.join(contextDir, 'summaries');
1304
- const installDir = path.join(oxeDir, 'install');
1305
1339
  const stateSrc = path.join(PKG_ROOT, 'oxe', 'templates', 'STATE.md');
1306
1340
  const stateDest = path.join(oxeDir, 'STATE.md');
1307
1341
  const configSrc = path.join(PKG_ROOT, 'oxe', 'templates', 'config.template.json');
@@ -1313,18 +1347,12 @@ function bootstrapOxe(target, opts) {
1313
1347
  }
1314
1348
 
1315
1349
  if (opts.dryRun) {
1316
- console.log(`${dim}init${reset} ${oxeDir}/ (STATE.md, config.json, codebase/, capabilities/, investigations/, dashboard/, context/, install/, runs/, OXE-EVENTS.ndjson, ACTIVE-RUN.json)`);
1350
+ console.log(`${dim}init${reset} ${oxeDir}/ (STATE.md, config.json, README.md demais artefatos sob demanda)`);
1317
1351
  ensureGitignoreIgnoresOxeDir(target, { dryRun: true });
1318
1352
  return;
1319
1353
  }
1320
1354
 
1321
- ensureDir(codebaseDir);
1322
- ensureDir(capabilitiesDir);
1323
- ensureDir(investigationsDir);
1324
- ensureDir(dashboardDir);
1325
- ensureDir(contextPacksDir);
1326
- ensureDir(contextSummariesDir);
1327
- ensureDir(installDir);
1355
+ ensureDir(oxeDir);
1328
1356
 
1329
1357
  if (!fs.existsSync(stateDest) || opts.force) {
1330
1358
  copyFile(stateSrc, stateDest, { dryRun: false });
@@ -1348,107 +1376,8 @@ function bootstrapOxe(target, opts) {
1348
1376
  }
1349
1377
  }
1350
1378
 
1351
- // Criar estruturas opcionais: plugins/, workstreams/, memory/
1352
- const pluginsDir = path.join(oxeDir, 'plugins');
1353
- if (!fs.existsSync(pluginsDir)) {
1354
- ensureDir(pluginsDir);
1355
- const pluginsReadme = path.join(PKG_ROOT, 'oxe', 'templates', 'PLUGINS.md');
1356
- if (fs.existsSync(pluginsReadme)) {
1357
- const destPluginsReadme = path.join(pluginsDir, 'README.md');
1358
- if (!fs.existsSync(destPluginsReadme)) {
1359
- copyFile(pluginsReadme, destPluginsReadme, { dryRun: false });
1360
- console.log(`${green}init${reset} ${destPluginsReadme}`);
1361
- }
1362
- }
1363
- }
1364
-
1365
- const workstreamsDir = path.join(oxeDir, 'workstreams');
1366
- if (!fs.existsSync(workstreamsDir)) {
1367
- ensureDir(workstreamsDir);
1368
- }
1369
-
1370
- const sessionsDir = path.join(oxeDir, 'sessions');
1371
- if (!fs.existsSync(sessionsDir)) {
1372
- ensureDir(sessionsDir);
1373
- }
1374
-
1375
- const globalDir = path.join(oxeDir, 'global');
1376
- if (!fs.existsSync(globalDir)) {
1377
- ensureDir(globalDir);
1378
- }
1379
-
1380
- const globalMilestonesDir = path.join(globalDir, 'milestones');
1381
- if (!fs.existsSync(globalMilestonesDir)) {
1382
- ensureDir(globalMilestonesDir);
1383
- }
1384
-
1385
- const lessonsSrc = path.join(PKG_ROOT, 'oxe', 'templates', 'LESSONS.template.md');
1386
- const lessonsDest = path.join(globalDir, 'LESSONS.md');
1387
- if (fs.existsSync(lessonsSrc) && !fs.existsSync(lessonsDest)) {
1388
- copyFile(lessonsSrc, lessonsDest, { dryRun: false });
1389
- console.log(`${green}init${reset} ${lessonsDest}`);
1390
- }
1391
-
1392
- const milestonesSrc = path.join(PKG_ROOT, 'oxe', 'templates', 'MILESTONES.template.md');
1393
- const milestonesDest = path.join(globalDir, 'MILESTONES.md');
1394
- if (fs.existsSync(milestonesSrc) && !fs.existsSync(milestonesDest)) {
1395
- copyFile(milestonesSrc, milestonesDest, { dryRun: false });
1396
- console.log(`${green}init${reset} ${milestonesDest}`);
1397
- }
1398
-
1399
- const memoryDir = path.join(oxeDir, 'memory');
1400
- if (!fs.existsSync(memoryDir)) {
1401
- ensureDir(memoryDir);
1402
- }
1403
-
1404
- const runsDir = path.join(oxeDir, 'runs');
1405
- if (!fs.existsSync(runsDir)) {
1406
- ensureDir(runsDir);
1407
- }
1408
-
1409
- const runtimeSrc = path.join(PKG_ROOT, 'oxe', 'templates', 'EXECUTION-RUNTIME.template.md');
1410
- const runtimeDest = path.join(oxeDir, 'EXECUTION-RUNTIME.md');
1411
- if (fs.existsSync(runtimeSrc) && !fs.existsSync(runtimeDest)) {
1412
- copyFile(runtimeSrc, runtimeDest, { dryRun: false });
1413
- console.log(`${green}init${reset} ${runtimeDest}`);
1414
- }
1415
-
1416
- const activeRunSrc = path.join(PKG_ROOT, 'oxe', 'templates', 'ACTIVE-RUN.template.json');
1417
- const activeRunDest = path.join(oxeDir, 'ACTIVE-RUN.json');
1418
- if (fs.existsSync(activeRunSrc) && !fs.existsSync(activeRunDest)) {
1419
- copyFile(activeRunSrc, activeRunDest, { dryRun: false });
1420
- console.log(`${green}init${reset} ${activeRunDest}`);
1421
- }
1422
-
1423
- const eventsDest = path.join(oxeDir, 'OXE-EVENTS.ndjson');
1424
- if (!fs.existsSync(eventsDest)) {
1425
- fs.writeFileSync(eventsDest, '', 'utf8');
1426
- console.log(`${green}init${reset} ${eventsDest}`);
1427
- }
1428
-
1429
- const checkpointsSrc = path.join(PKG_ROOT, 'oxe', 'templates', 'CHECKPOINTS.template.md');
1430
- const checkpointsDest = path.join(oxeDir, 'CHECKPOINTS.md');
1431
- if (fs.existsSync(checkpointsSrc) && !fs.existsSync(checkpointsDest)) {
1432
- copyFile(checkpointsSrc, checkpointsDest, { dryRun: false });
1433
- console.log(`${green}init${reset} ${checkpointsDest}`);
1434
- }
1435
-
1436
- const capabilitiesSrc = path.join(PKG_ROOT, 'oxe', 'templates', 'CAPABILITIES.template.md');
1437
- const capabilitiesDest = path.join(oxeDir, 'CAPABILITIES.md');
1438
- if (fs.existsSync(capabilitiesSrc) && !fs.existsSync(capabilitiesDest)) {
1439
- copyFile(capabilitiesSrc, capabilitiesDest, { dryRun: false });
1440
- console.log(`${green}init${reset} ${capabilitiesDest}`);
1441
- }
1442
-
1443
- const investigationsIndexDest = path.join(oxeDir, 'INVESTIGATIONS.md');
1444
- if (!fs.existsSync(investigationsIndexDest)) {
1445
- fs.writeFileSync(
1446
- investigationsIndexDest,
1447
- '# OXE — Investigações\n\n| Data | Ficheiro | Objetivo | Modo | Estado |\n|------|----------|----------|------|--------|\n',
1448
- 'utf8'
1449
- );
1450
- console.log(`${green}init${reset} ${investigationsIndexDest}`);
1451
- }
1379
+ // Generated legend always (re)written so it tracks the current catalog.
1380
+ writeOxeLegend(target);
1452
1381
 
1453
1382
  ensureGitignoreIgnoresOxeDir(target, { dryRun: false });
1454
1383
  }
@@ -1892,6 +1821,18 @@ function runStatus(target, opts = {}) {
1892
1821
  const routineHints = collectOxeRoutineHints(target, report, config);
1893
1822
  const next = report.next;
1894
1823
 
1824
+ // Lightweight, versioned summary for host integrations (IDEs, OXESpace) —
1825
+ // a small stable payload instead of the full ~100KB status. Includes a
1826
+ // compact per-agent skills view so a host can decide whether to offer
1827
+ // "install OXE skills" before launching an agent.
1828
+ if (opts.json && opts.summary) {
1829
+ const summary = oxeHealth.buildStatusSummary(report);
1830
+ summary.projectRoot = path.resolve(target);
1831
+ summary.agentSkills = oxeHealth.agentSkillsReport(target).map((a) => ({ agent: a.agent, skillsInstalled: a.skillsInstalled }));
1832
+ console.log(JSON.stringify(summary));
1833
+ return;
1834
+ }
1835
+
1895
1836
  if (opts.json) {
1896
1837
  /** @type {Record<string, unknown>} */
1897
1838
  const payload = {
@@ -1947,6 +1888,7 @@ function runStatus(target, opts = {}) {
1947
1888
  azure: report.azure,
1948
1889
  copilot: report.copilot,
1949
1890
  codex: report.codex,
1891
+ agentSkills: oxeHealth.agentSkillsReport(target),
1950
1892
  contextPacks: report.contextPacks,
1951
1893
  contextQuality: report.contextQuality,
1952
1894
  semanticsDrift: report.semanticsDrift,
@@ -2119,6 +2061,11 @@ function runDoctor(target, options = {}) {
2119
2061
  }
2120
2062
  console.log(`${green}OK${reset} Node.js`);
2121
2063
 
2064
+ // Keep the generated `.oxe/README.md` legend fresh against the current catalog.
2065
+ if (fs.existsSync(path.join(target, '.oxe'))) {
2066
+ writeOxeLegend(target, { quiet: true });
2067
+ }
2068
+
2122
2069
  const wfPkg = path.join(PKG_ROOT, 'oxe', 'workflows');
2123
2070
  const wfTgt = oxeWorkflows.resolveWorkflowsDir(target);
2124
2071
  if (!fs.existsSync(wfPkg)) {
@@ -2352,9 +2299,8 @@ function installGlobalCliPackage() {
2352
2299
  const dimOrEmpty = c ? dim : '';
2353
2300
  const resetOrEmpty = c ? reset : '';
2354
2301
  console.log(`\n ${dimOrEmpty}npm install -g ${spec}${resetOrEmpty}\n`);
2355
- const r = spawnSync('npm', ['install', '-g', spec], {
2302
+ const r = runPackageManagerSync('npm', ['install', '-g', spec], {
2356
2303
  stdio: 'inherit',
2357
- shell: true,
2358
2304
  env: process.env,
2359
2305
  });
2360
2306
  if (r.error) {
@@ -2385,9 +2331,8 @@ function uninstallGlobalCliPackage() {
2385
2331
  const dimOrEmpty = c ? dim : '';
2386
2332
  const resetOrEmpty = c ? reset : '';
2387
2333
  console.log(`\n ${dimOrEmpty}npm uninstall -g ${name}${resetOrEmpty}\n`);
2388
- const r = spawnSync('npm', ['uninstall', '-g', name], {
2334
+ const r = runPackageManagerSync('npm', ['uninstall', '-g', name], {
2389
2335
  stdio: 'inherit',
2390
- shell: true,
2391
2336
  env: process.env,
2392
2337
  });
2393
2338
  if (r.status === 0) {
@@ -2409,9 +2354,8 @@ function uninstallGlobalCliPackage() {
2409
2354
  */
2410
2355
  function isRunningFromGlobalNpmInstall() {
2411
2356
  try {
2412
- const r = spawnSync('npm', ['root', '-g'], {
2357
+ const r = runPackageManagerSync('npm', ['root', '-g'], {
2413
2358
  encoding: 'utf8',
2414
- shell: true,
2415
2359
  env: process.env,
2416
2360
  });
2417
2361
  if (r.status !== 0) return false;
@@ -2553,12 +2497,15 @@ ${cyan}oxe-cc${reset} — instala workflows OXE (núcleo .oxe/ + integrações:
2553
2497
  npx oxe-cc init-oxe [opções] [pasta-do-projeto]
2554
2498
  npx oxe-cc context <build|inspect> [opções] [pasta-do-projeto]
2555
2499
  npx oxe-cc dashboard [opções] [pasta-do-projeto]
2500
+ npx oxe-cc events [--tail N] [--since <evt_id>] [--json] [opções] [pasta-do-projeto]
2501
+ npx oxe-cc map [--json] [opções] [pasta-do-projeto]
2556
2502
  npx oxe-cc runtime <status|start|pause|resume|replay|compile|verify|project|ci|promote|recover|gates|agents|execute> [opções] [pasta-do-projeto]
2557
2503
  npx oxe-cc azure <status|doctor|auth|sync|find|servicebus|eventgrid|sql|operations> [opções] [pasta-do-projeto]
2558
2504
  npx oxe-cc capabilities <list|install|remove|update> [opções] [id]
2505
+ npx oxe-cc plugins <list|install|remove> [opções]
2559
2506
  npx oxe-cc uninstall [opções] [pasta-do-projeto]
2560
2507
  npx oxe-cc update [opções] [argumentos extras…]
2561
-
2508
+
2562
2509
  ${green}doctor${reset}
2563
2510
  --release valida release readiness: versões, changelog, wrappers, runtime build e relatórios obrigatórios
2564
2511
  --write-manifest persiste .oxe/release/release-manifest.json durante doctor --release
@@ -2587,12 +2534,21 @@ ${green}update${reset} (executa npx oxe-cc@latest --force na pasta do projeto)
2587
2534
  ${dim}CI / sem rede:${reset} OXE_UPDATE_SKIP_REGISTRY=1 desativa consultas (--check sai 2; --if-newer sai 2 sem npx)
2588
2535
 
2589
2536
  ${green}dashboard${reset} (interface web local para revisão e aprovação do plano)
2590
- --port <número> porta local (padrão: 4173)
2537
+ --port <número> porta local (padrão: 4173; use 0 para porta efêmera)
2591
2538
  --no-open não abre o browser automaticamente
2539
+ --json emite {url,port,...} (oxeDashboardSchema:1) e segue servindo — para embutir num host
2540
+ --read-only UI visual sem persistência (embed seguro)
2592
2541
  --session <sessions/sNNN-slug> força visualização de uma sessão específica
2593
2542
  --dump-context imprime JSON consolidado e sai
2594
2543
  --dir <pasta> raiz do projeto (padrão: diretório atual)
2595
2544
 
2545
+ ${green}events${reset} (projeção read-only do log .oxe/OXE-EVENTS.ndjson — para hosts)
2546
+ --tail <N> só os últimos N eventos (padrão: 50)
2547
+ --since <evt_id> só eventos posteriores a um event_id conhecido (leitura incremental)
2548
+ --json saída estruturada (oxeEventsSchema:1) com summary + events
2549
+ --session <sessions/sNNN-slug> força sessão específica (padrão: sessão ativa do STATE.md)
2550
+ --dir <pasta> raiz do projeto (padrão: diretório atual)
2551
+
2596
2552
  ${green}context${reset} (Context Engine V2: seleção, compressão e inspeção determinística)
2597
2553
  build [--workflow <slug>] gera pack(s) em .oxe/context/packs/
2598
2554
  inspect [--workflow <slug>] lê um pack existente ou resolve sob demanda
@@ -2602,6 +2558,13 @@ ${green}context${reset} (Context Engine V2: seleção, compressão e inspeção
2602
2558
  --json saída estruturada em JSON
2603
2559
  --dir <pasta> raiz do projeto (padrão: diretório atual)
2604
2560
 
2561
+ ${green}plugins${reset} (plugins de lifecycle locais ou publicados no npm)
2562
+ list lista plugins carregados e erros de resolução
2563
+ install <npm-package> [version] instala somente pacote npm; prefixo npm: é opcional
2564
+ remove <id> mostra como remover a referência e os arquivos npm
2565
+ --dir <pasta> raiz do projeto (padrão: diretório atual)
2566
+ ${dim}Plugins por path:${reset} crie o .cjs localmente e referencie-o em .oxe/config.json; o comando install não aceita paths
2567
+
2605
2568
  ${green}runtime${reset} (controle operacional explícito do ACTIVE-RUN)
2606
2569
  status mostra o run ativo resolvido para a sessão atual
2607
2570
  start cria um novo run com tracing inicial
@@ -3458,11 +3421,6 @@ function runUninstall(u) {
3458
3421
  ignoreInstallConfig: false,
3459
3422
  ideLocal: false,
3460
3423
  explicitIdeScope: true,
3461
- agentOpenCode: false,
3462
- agentGemini: false,
3463
- agentCodex: false,
3464
- agentWindsurf: false,
3465
- agentAntigravity: false,
3466
3424
  });
3467
3425
 
3468
3426
  printSection('OXE ▸ uninstall');
@@ -3861,11 +3819,10 @@ function runUpdate(u) {
3861
3819
  } else {
3862
3820
  args.push('--no-global-cli', '-l', ...u.rest);
3863
3821
  }
3864
- const r = spawnSync('npx', args, {
3822
+ const r = runPackageManagerSync('npx', args, {
3865
3823
  cwd: u.dir,
3866
3824
  stdio: 'inherit',
3867
3825
  env: { ...process.env, OXE_NO_PROMPT: '1' },
3868
- shell: process.platform === 'win32',
3869
3826
  });
3870
3827
  if (r.error) {
3871
3828
  console.error(`${red}Falha ao executar npx:${reset}`, r.error.message);
@@ -3924,7 +3881,7 @@ function parseCapabilitiesArgs(argv) {
3924
3881
  }
3925
3882
 
3926
3883
  /**
3927
- * @typedef {{ help: boolean, dir: string, port: number, noOpen: boolean, readOnly: boolean, dumpContext: boolean, activeSession: string|null, parseError: boolean, unknownFlag: string }} DashboardOpts
3884
+ * @typedef {{ help: boolean, dir: string, port: number, noOpen: boolean, readOnly: boolean, dumpContext: boolean, json: boolean, activeSession: string|null, parseError: boolean, unknownFlag: string }} DashboardOpts
3928
3885
  */
3929
3886
 
3930
3887
  /**
@@ -3985,6 +3942,7 @@ function parseDashboardArgs(argv) {
3985
3942
  noOpen: false,
3986
3943
  readOnly: false,
3987
3944
  dumpContext: false,
3945
+ json: false,
3988
3946
  activeSession: null,
3989
3947
  parseError: false,
3990
3948
  unknownFlag: '',
@@ -3993,10 +3951,15 @@ function parseDashboardArgs(argv) {
3993
3951
  const a = argv[i];
3994
3952
  if (a === '-h' || a === '--help') out.help = true;
3995
3953
  else if (a === '--dir' && argv[i + 1]) out.dir = path.resolve(argv[++i]);
3996
- else if (a === '--port' && argv[i + 1]) out.port = Number(argv[++i]) || 4173;
3997
- else if (a === '--no-open') out.noOpen = true;
3954
+ else if (a === '--port' && argv[i + 1] !== undefined) {
3955
+ // Allow `--port 0` (OS picks an ephemeral port — used by hosts that embed
3956
+ // the dashboard and read the real port back from `--json`).
3957
+ const n = Number(argv[++i]);
3958
+ out.port = Number.isFinite(n) && n >= 0 ? n : 4173;
3959
+ } else if (a === '--no-open') out.noOpen = true;
3998
3960
  else if (a === '--read-only') out.readOnly = true;
3999
3961
  else if (a === '--dump-context') out.dumpContext = true;
3962
+ else if (a === '--json') out.json = true;
4000
3963
  else if (a === '--session' && argv[i + 1]) out.activeSession = String(argv[++i]).replace(/\\/g, '/');
4001
3964
  else if (!a.startsWith('-') && i === 0) out.dir = path.resolve(a);
4002
3965
  else {
@@ -4357,7 +4320,7 @@ async function runDashboard(opts) {
4357
4320
  return;
4358
4321
  }
4359
4322
  const c = useAnsiColors();
4360
- printSection('OXE ▸ dashboard');
4323
+ if (!opts.json) printSection('OXE ▸ dashboard');
4361
4324
  const server = oxeDashboard.createDashboardServer(target, { activeSession: opts.activeSession });
4362
4325
  await new Promise((resolve, reject) => {
4363
4326
  server.once('error', reject);
@@ -4366,6 +4329,24 @@ async function runDashboard(opts) {
4366
4329
  const address = server.address();
4367
4330
  const port = address && typeof address === 'object' ? address.port : opts.port;
4368
4331
  const url = `http://127.0.0.1:${port}/`;
4332
+ // Host-integration mode: emit one stable, versioned line with the live URL/port
4333
+ // as soon as the server is listening, then keep serving until killed. Lets a
4334
+ // host (OXESpace) embed the dashboard in a webview without scraping the
4335
+ // human-readable banner. Pair with `--no-open --port 0` for an ephemeral port.
4336
+ if (opts.json) {
4337
+ console.log(JSON.stringify({
4338
+ oxeDashboardSchema: 1,
4339
+ projectRoot: path.resolve(target),
4340
+ url,
4341
+ port,
4342
+ readOnly: Boolean(opts.readOnly),
4343
+ }));
4344
+ if (!opts.noOpen) {
4345
+ try { openUrlInBrowser(url); } catch { /* host owns presentation */ }
4346
+ }
4347
+ await new Promise(() => {});
4348
+ return;
4349
+ }
4369
4350
  console.log(` ${c ? green : ''}Projeto:${c ? reset : ''} ${c ? cyan : ''}${target}${c ? reset : ''}`);
4370
4351
  console.log(` ${c ? green : ''}URL:${c ? reset : ''} ${c ? cyan : ''}${url}${c ? reset : ''}`);
4371
4352
  if (opts.readOnly) {
@@ -4383,6 +4364,163 @@ async function runDashboard(opts) {
4383
4364
  await new Promise(() => {});
4384
4365
  }
4385
4366
 
4367
+ /**
4368
+ * @typedef {{ help: boolean, dir: string, tail: number, since: string, activeSession: string|null, json: boolean, parseError: boolean, unknownFlag: string }} EventsOpts
4369
+ */
4370
+
4371
+ /**
4372
+ * @param {string[]} argv
4373
+ * @returns {EventsOpts}
4374
+ */
4375
+ function parseEventsArgs(argv) {
4376
+ /** @type {EventsOpts} */
4377
+ const out = {
4378
+ help: false,
4379
+ dir: process.cwd(),
4380
+ tail: 50,
4381
+ since: '',
4382
+ activeSession: null,
4383
+ json: false,
4384
+ parseError: false,
4385
+ unknownFlag: '',
4386
+ };
4387
+ for (let i = 0; i < argv.length; i++) {
4388
+ const a = argv[i];
4389
+ if (a === '-h' || a === '--help') out.help = true;
4390
+ else if (a === '--dir' && argv[i + 1]) out.dir = path.resolve(argv[++i]);
4391
+ else if (a === '--tail') {
4392
+ // `--tail` alone keeps the default; `--tail N` overrides the count.
4393
+ const next = argv[i + 1];
4394
+ if (next && /^\d+$/.test(next)) out.tail = Number(argv[++i]);
4395
+ } else if (a === '--since' && argv[i + 1]) out.since = String(argv[++i]);
4396
+ else if (a === '--json') out.json = true;
4397
+ else if (a === '--session' && argv[i + 1]) out.activeSession = String(argv[++i]).replace(/\\/g, '/');
4398
+ else if (!a.startsWith('-') && i === 0) out.dir = path.resolve(a);
4399
+ else {
4400
+ out.parseError = true;
4401
+ out.unknownFlag = a;
4402
+ break;
4403
+ }
4404
+ }
4405
+ return out;
4406
+ }
4407
+
4408
+ /**
4409
+ * Read-only projection of the append-only event log (.oxe/OXE-EVENTS.ndjson)
4410
+ * for host integrations. A host typically watches that file directly and then
4411
+ * calls `status --json --summary`; this command is the documented way to read
4412
+ * the tail of the log (optionally since a known event_id) without parsing the
4413
+ * whole file. Reuses the same SDK helpers the runtime uses.
4414
+ * @param {EventsOpts} opts
4415
+ */
4416
+ function runEvents(opts) {
4417
+ const target = opts.dir;
4418
+ if (!fs.existsSync(target)) {
4419
+ if (opts.json) console.log(JSON.stringify({ oxeEventsSchema: 1, projectRoot: path.resolve(target), error: 'dir-not-found', events: [] }));
4420
+ else console.error(`${yellow}Diretório não encontrado: ${target}${reset}`);
4421
+ process.exit(1);
4422
+ }
4423
+ const stateText = fs.existsSync(oxeHealth.oxePaths(target).state)
4424
+ ? fs.readFileSync(oxeHealth.oxePaths(target).state, 'utf8')
4425
+ : '';
4426
+ const activeSession = opts.activeSession || oxeHealth.parseActiveSession(stateText) || null;
4427
+ let events = oxeOperational.readEvents(target, activeSession);
4428
+ if (opts.since) {
4429
+ const idx = events.findIndex((e) => e && e.event_id === opts.since);
4430
+ if (idx >= 0) events = events.slice(idx + 1);
4431
+ }
4432
+ const tail = opts.tail > 0 ? events.slice(-opts.tail) : events;
4433
+
4434
+ if (opts.json) {
4435
+ console.log(JSON.stringify({
4436
+ oxeEventsSchema: 1,
4437
+ projectRoot: path.resolve(target),
4438
+ activeSession,
4439
+ summary: oxeOperational.summarizeEvents(events),
4440
+ events: tail,
4441
+ }));
4442
+ return;
4443
+ }
4444
+
4445
+ const c = useAnsiColors();
4446
+ printSection('OXE ▸ events');
4447
+ const summary = oxeOperational.summarizeEvents(events);
4448
+ console.log(` ${c ? green : ''}Total:${c ? reset : ''} ${summary.total}${activeSession ? ` ${c ? dim : ''}(sessão: ${activeSession})${c ? reset : ''}` : ''}`);
4449
+ if (!tail.length) {
4450
+ console.log(` ${c ? dim : ''}Sem eventos.${c ? reset : ''}\n`);
4451
+ return;
4452
+ }
4453
+ for (const e of tail) {
4454
+ const ts = e.timestamp ? String(e.timestamp).replace('T', ' ').replace(/\..*$/, '') : '';
4455
+ console.log(` ${c ? dim : ''}${ts}${c ? reset : ''} ${c ? cyan : ''}${e.type}${c ? reset : ''}${e.run_id ? ` ${c ? dim : ''}run=${e.run_id}${c ? reset : ''}` : ''}`);
4456
+ }
4457
+ console.log('');
4458
+ }
4459
+
4460
+ /**
4461
+ * @typedef {{ help: boolean, dir: string, json: boolean, parseError: boolean, unknownFlag: string }} MapOpts
4462
+ * @param {string[]} argv
4463
+ * @returns {MapOpts}
4464
+ */
4465
+ function parseMapArgs(argv) {
4466
+ /** @type {MapOpts} */
4467
+ const out = { help: false, dir: process.cwd(), json: false, parseError: false, unknownFlag: '' };
4468
+ for (let i = 0; i < argv.length; i++) {
4469
+ const a = argv[i];
4470
+ if (a === '-h' || a === '--help') out.help = true;
4471
+ else if (a === '--dir' && argv[i + 1]) out.dir = path.resolve(argv[++i]);
4472
+ else if (a === '--json') out.json = true;
4473
+ else if (!a.startsWith('-') && i === 0) out.dir = path.resolve(a);
4474
+ else {
4475
+ out.parseError = true;
4476
+ out.unknownFlag = a;
4477
+ break;
4478
+ }
4479
+ }
4480
+ return out;
4481
+ }
4482
+
4483
+ /**
4484
+ * Live, annotated map of the `.oxe/` artifacts: what already exists, what is
4485
+ * available on demand, and the state of each (active/empty/stale). Built from
4486
+ * the single artifact catalog so it never drifts from the legend or docs.
4487
+ * @param {MapOpts} opts
4488
+ */
4489
+ function runMap(opts) {
4490
+ const target = opts.dir;
4491
+ if (!fs.existsSync(target)) {
4492
+ if (opts.json) console.log(JSON.stringify({ oxeMapSchema: 1, projectRoot: path.resolve(target), error: 'dir-not-found', groups: [] }));
4493
+ else console.error(`${yellow}Diretório não encontrado: ${target}${reset}`);
4494
+ process.exit(1);
4495
+ }
4496
+ // Stale-scan signal reuses the same health helpers `status`/`doctor` rely on.
4497
+ let staleScan = false;
4498
+ try {
4499
+ const statePath = oxeHealth.oxePaths(target).state;
4500
+ const stateText = fs.existsSync(statePath) ? fs.readFileSync(statePath, 'utf8') : '';
4501
+ const cfg = oxeHealth.loadOxeConfigMerged(target);
4502
+ const scanDate = oxeHealth.parseLastScanDate(stateText);
4503
+ staleScan = oxeHealth.isStaleScan(scanDate, cfg.scan_max_age_days || 0);
4504
+ } catch {
4505
+ staleScan = false;
4506
+ }
4507
+ const model = oxeArtifactCatalog.buildMapModel(target, { staleScan });
4508
+
4509
+ if (opts.json) {
4510
+ console.log(JSON.stringify({ oxeMapSchema: 1, ...model }));
4511
+ return;
4512
+ }
4513
+
4514
+ const c = useAnsiColors();
4515
+ const palette = { active: green, empty: yellow, stale: yellow, absent: dim, group: cyan, dim, root: green };
4516
+ const paint = (s, kind) => (c && palette[kind] ? `${palette[kind]}${s}${reset}` : s);
4517
+ printSection('OXE ▸ map');
4518
+ console.log(` ${paint('Projeto:', 'group')} ${paint(model.projectRoot, 'group')}`);
4519
+ console.log('');
4520
+ console.log(oxeArtifactCatalog.renderMap(model, { paint }));
4521
+ console.log('');
4522
+ }
4523
+
4386
4524
  /**
4387
4525
  * @param {RuntimeOpts} opts
4388
4526
  */
@@ -5473,6 +5611,8 @@ async function main() {
5473
5611
  argv[0] === 'init-oxe' ||
5474
5612
  argv[0] === 'context' ||
5475
5613
  argv[0] === 'dashboard' ||
5614
+ argv[0] === 'events' ||
5615
+ argv[0] === 'map' ||
5476
5616
  argv[0] === 'runtime' ||
5477
5617
  argv[0] === 'azure' ||
5478
5618
  argv[0] === 'uninstall' ||
@@ -5487,103 +5627,85 @@ async function main() {
5487
5627
  argv.shift();
5488
5628
  }
5489
5629
 
5490
- if (command === 'uninstall') {
5491
- const u = parseUninstallArgs(argv);
5492
- if (u.help) {
5493
- printBanner();
5494
- usage();
5495
- process.exit(0);
5496
- }
5497
- if (u.conflictFlags) {
5498
- printBanner();
5499
- console.error(`${red}${u.conflictFlags}${reset}`);
5500
- usage();
5501
- process.exit(1);
5502
- }
5503
- if (u.parseError) {
5504
- printBanner();
5505
- console.error(`${red}Opção desconhecida:${reset} ${u.unknownFlag}`);
5506
- usage();
5507
- process.exit(1);
5508
- }
5509
- printBanner();
5510
- if (!u.dryRun && !fs.existsSync(u.dir)) {
5511
- console.error(`${yellow}Diretório não encontrado: ${u.dir}${reset}`);
5512
- process.exit(1);
5513
- }
5514
- runUninstall(u);
5515
- return;
5516
- }
5630
+ const coreRegistry = createCoreCommandRegistry({
5631
+ existsSync: fs.existsSync,
5632
+ printBanner,
5633
+ usage,
5634
+ log: console.log,
5635
+ error: console.error,
5636
+ exit: process.exit,
5637
+ colors: { red, yellow, reset },
5638
+ readPkgVersion,
5639
+ parseUninstallArgs,
5640
+ parseUpdateArgs,
5641
+ parseCapabilitiesArgs,
5642
+ parseRuntimeArgs,
5643
+ parseAzureArgs,
5644
+ parseInstallArgs,
5645
+ runUninstall,
5646
+ runUpdateVersionCheck,
5647
+ runUpdate,
5648
+ runCapabilities,
5649
+ runRuntime,
5650
+ runAzure,
5651
+ runDoctor,
5652
+ runStatus,
5653
+ runStatusFull,
5654
+ });
5655
+ const coreDispatch = await coreRegistry.dispatch(command, argv);
5656
+ if (coreDispatch.handled) return;
5517
5657
 
5518
- if (command === 'update') {
5519
- const u = parseUpdateArgs(argv);
5520
- if (u.help) {
5658
+ if (command === 'dashboard') {
5659
+ const d = parseDashboardArgs(argv);
5660
+ if (d.help) {
5521
5661
  printBanner();
5522
5662
  usage();
5523
5663
  process.exit(0);
5524
5664
  }
5525
- if (u.conflictFlags) {
5526
- printBanner();
5527
- console.error(`${red}${u.conflictFlags}${reset}`);
5528
- usage();
5529
- process.exit(1);
5530
- }
5531
- if (u.parseError) {
5665
+ if (d.parseError) {
5532
5666
  printBanner();
5533
- console.error(`${red}Opção desconhecida:${reset} ${u.unknownFlag}`);
5667
+ console.error(`${red}Opção desconhecida:${reset} ${d.unknownFlag}`);
5534
5668
  usage();
5535
5669
  process.exit(1);
5536
5670
  }
5537
- printBanner();
5538
- if (u.check) {
5539
- runUpdateVersionCheck(u);
5540
- return;
5541
- }
5542
- if (!u.dryRun && !fs.existsSync(u.dir)) {
5543
- console.error(`${yellow}Diretório não encontrado: ${u.dir}${reset}`);
5544
- process.exit(1);
5545
- }
5546
- runUpdate(u);
5671
+ if (!d.json) printBanner();
5672
+ await runDashboard(d);
5547
5673
  return;
5548
5674
  }
5549
5675
 
5550
- if (command === 'capabilities') {
5551
- const cap = parseCapabilitiesArgs(argv);
5552
- if (cap.help) {
5676
+ if (command === 'map') {
5677
+ const m = parseMapArgs(argv);
5678
+ if (m.help) {
5553
5679
  printBanner();
5554
5680
  usage();
5555
5681
  process.exit(0);
5556
5682
  }
5557
- if (cap.parseError) {
5683
+ if (m.parseError) {
5558
5684
  printBanner();
5559
- console.error(`${red}Opção desconhecida:${reset} ${cap.unknownFlag}`);
5685
+ console.error(`${red}Opção desconhecida:${reset} ${m.unknownFlag}`);
5560
5686
  usage();
5561
5687
  process.exit(1);
5562
5688
  }
5563
- printBanner();
5564
- if (!fs.existsSync(cap.dir)) {
5565
- console.error(`${yellow}Diretório não encontrado: ${cap.dir}${reset}`);
5566
- process.exit(1);
5567
- }
5568
- runCapabilities(cap);
5689
+ if (!m.json) printBanner();
5690
+ runMap(m);
5569
5691
  return;
5570
5692
  }
5571
5693
 
5572
- if (command === 'dashboard') {
5573
- const d = parseDashboardArgs(argv);
5574
- if (d.help) {
5694
+ if (command === 'events') {
5695
+ const e = parseEventsArgs(argv);
5696
+ if (e.help) {
5575
5697
  printBanner();
5576
5698
  usage();
5577
5699
  process.exit(0);
5578
5700
  }
5579
- if (d.parseError) {
5701
+ if (e.parseError) {
5580
5702
  printBanner();
5581
- console.error(`${red}Opção desconhecida:${reset} ${d.unknownFlag}`);
5703
+ console.error(`${red}Opção desconhecida:${reset} ${e.unknownFlag}`);
5582
5704
  usage();
5583
5705
  process.exit(1);
5584
5706
  }
5585
- printBanner();
5586
- await runDashboard(d);
5707
+ if (!e.json) printBanner();
5708
+ runEvents(e);
5587
5709
  return;
5588
5710
  }
5589
5711
 
@@ -5605,95 +5727,13 @@ async function main() {
5605
5727
  return;
5606
5728
  }
5607
5729
 
5608
- if (command === 'runtime') {
5609
- const runtime = parseRuntimeArgs(argv);
5610
- if (runtime.help) {
5611
- printBanner();
5612
- usage();
5613
- process.exit(0);
5614
- }
5615
- if (runtime.parseError) {
5616
- printBanner();
5617
- console.error(`${red}Opção desconhecida:${reset} ${runtime.unknownFlag}`);
5618
- usage();
5619
- process.exit(1);
5620
- }
5621
- printBanner();
5622
- if (!fs.existsSync(runtime.dir)) {
5623
- console.error(`${yellow}Diretório não encontrado: ${runtime.dir}${reset}`);
5624
- process.exit(1);
5625
- }
5626
- runRuntime(runtime).catch((err) => {
5627
- console.error(`${red}${err && err.message ? err.message : 'Falha ao executar runtime.'}${reset}`);
5628
- process.exit(1);
5629
- });
5630
- return;
5631
- }
5632
-
5633
5730
  if (command === 'plugins') {
5634
5731
  printBanner();
5635
- // Parse --dir flag
5636
- let pluginsDir = process.cwd();
5637
- const pluginsArgv = argv.slice();
5638
- for (let i = 0; i < pluginsArgv.length; i++) {
5639
- if (pluginsArgv[i] === '--dir' && pluginsArgv[i + 1]) {
5640
- pluginsDir = path.resolve(pluginsArgv[++i]);
5641
- pluginsArgv.splice(i - 1, 2);
5642
- i -= 2;
5643
- }
5644
- }
5645
- const c = useAnsiColors();
5646
- const subCmd = pluginsArgv[0] || 'list';
5647
- const pluginTarget = pluginsArgv[1] || '';
5648
-
5649
- if (subCmd === 'list') {
5650
- const result = oxePlugins.loadPlugins(pluginsDir);
5651
- console.log(`\n ${c ? green : ''}Plugins carregados:${c ? reset : ''} ${result.plugins.length}`);
5652
- for (const p of result.plugins) {
5653
- console.log(` • ${p.name}${p.version ? ` (${p.version})` : ''} — hooks: ${Object.keys(p.hooks).join(', ')}`);
5654
- }
5655
- if (result.errors.length) {
5656
- console.log(`\n ${c ? yellow : ''}Erros:${c ? reset : ''}`);
5657
- for (const e of result.errors) {
5658
- console.log(` ✗ ${e.file}: ${e.error}`);
5659
- }
5660
- }
5661
- } else if (subCmd === 'install' && pluginTarget) {
5662
- const src = pluginTarget.startsWith('npm:') ? pluginTarget.slice(4) : pluginTarget;
5663
- const ver = pluginsArgv[2] || '';
5664
- console.log(` Instalando plugin: ${src}${ver ? `@${ver}` : ''}...`);
5665
- const result = oxePlugins.installNpmPlugin(pluginsDir, src, ver || undefined);
5666
- if (result.ok) {
5667
- console.log(` ${c ? green : ''}✓${c ? reset : ''} Instalado em: ${result.path}`);
5668
- console.log(` ${c ? dim : ''}Adicione ao .oxe/config.json: "plugins": [{ "source": "npm:${src}" }]${c ? reset : ''}`);
5669
- } else {
5670
- console.error(` ${c ? red : ''}✗ Falha:${c ? reset : ''} ${result.error}`);
5671
- process.exit(1);
5672
- }
5673
- } else if (subCmd === 'remove' && pluginTarget) {
5674
- console.log(` ${c ? yellow : ''}Remove "${pluginTarget}" de .oxe/config.json → plugins[] manualmente.${c ? reset : ''}`);
5675
- console.log(` ${c ? dim : ''}Arquivos npm: rm -rf .oxe/plugins/_npm/node_modules/${pluginTarget}${c ? reset : ''}`);
5676
- } else {
5677
- console.log(` ${c ? yellow : ''}Uso: oxe-cc plugins list | install <npm:pkg|path> | remove <id>${c ? reset : ''}`);
5678
- }
5679
- return;
5680
- }
5681
-
5682
- if (command === 'azure') {
5683
- const azure = parseAzureArgs(argv);
5684
- if (azure.help) {
5685
- printBanner();
5686
- usage();
5687
- process.exit(0);
5688
- }
5689
- if (azure.parseError) {
5690
- printBanner();
5691
- console.error(`${red}Opção desconhecida:${reset} ${azure.unknownFlag}`);
5692
- usage();
5693
- process.exit(1);
5694
- }
5695
- printBanner();
5696
- runAzure(azure);
5732
+ const exitCode = oxePluginCli.runPluginCommand(argv, {
5733
+ useAnsiColors,
5734
+ colors: { green, yellow, dim, red, reset },
5735
+ });
5736
+ if (exitCode !== 0) process.exit(exitCode);
5697
5737
  return;
5698
5738
  }
5699
5739
 
@@ -5746,33 +5786,9 @@ async function main() {
5746
5786
  process.exit(0);
5747
5787
  }
5748
5788
 
5749
- if (!((command === 'status' && opts.jsonOutput) || (command === 'doctor' && opts.jsonOutput))) {
5750
- printBanner();
5751
- }
5789
+ printBanner();
5752
5790
 
5753
5791
  const target = opts.dir;
5754
- if (command === 'doctor') {
5755
- if (!fs.existsSync(target)) {
5756
- console.error(`${yellow}Diretório não encontrado: ${target}${reset}`);
5757
- process.exit(1);
5758
- }
5759
- runDoctor(target, { release: opts.releaseDoctor, json: opts.jsonOutput, writeManifest: opts.writeManifest });
5760
- return;
5761
- }
5762
-
5763
- if (command === 'status') {
5764
- if (!fs.existsSync(target)) {
5765
- console.error(`${yellow}Diretório não encontrado: ${target}${reset}`);
5766
- process.exit(1);
5767
- }
5768
- if (opts.statusFull) {
5769
- runStatusFull(target);
5770
- } else {
5771
- runStatus(target, { json: opts.jsonOutput, hints: opts.statusHints });
5772
- }
5773
- return;
5774
- }
5775
-
5776
5792
  if (command === 'init-oxe') {
5777
5793
  if (!opts.dryRun && !fs.existsSync(target)) {
5778
5794
  console.error(`${yellow}Diretório não encontrado: ${target}${reset}`);
@@ -5785,8 +5801,8 @@ async function main() {
5785
5801
  bootstrapOxe(target, { dryRun: opts.dryRun, force: opts.force });
5786
5802
  printSummaryAndNextSteps(c0, {
5787
5803
  bullets: opts.dryRun
5788
- ? ['[simulação] Seriam criados ou atualizados .oxe/STATE.md, .oxe/config.json, .oxe/codebase/, .oxe/ACTIVE-RUN.json e .oxe/OXE-EVENTS.ndjson']
5789
- : ['.oxe/STATE.md, .oxe/config.json, .oxe/codebase/, .oxe/context/, .oxe/ACTIVE-RUN.json e .oxe/OXE-EVENTS.ndjson (criados ou atualizados conforme --force)'],
5804
+ ? ['[simulação] Seriam criados .oxe/STATE.md, .oxe/config.json e .oxe/README.md (legenda) — demais artefatos nascem sob demanda; veja com oxe-cc map']
5805
+ : ['.oxe/STATE.md, .oxe/config.json e .oxe/README.md (legenda) — núcleo enxuto; demais pastas/artefatos são criados sob demanda pelo workflow correspondente (veja oxe-cc map)'],
5790
5806
  nextSteps: [
5791
5807
  { desc: 'Validar o projeto:', cmd: 'npx oxe-cc doctor' },
5792
5808
  { desc: 'Instalar integrações IDE/CLI (se ainda não fez):', cmd: 'npx oxe-cc@latest' },