oxe-cc 1.12.0 → 1.14.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.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,31 @@ Todas as versões seguem [Semantic Versioning](https://semver.org/). As mudança
4
4
 
5
5
  ---
6
6
 
7
+ ## [1.14.0] — 2026-05-30
8
+
9
+ ### Contratos de integração para hosts — reatividade + dashboard embutível
10
+
11
+ Aditivo — nenhum contrato existente muda. Complementa o 1.13.0 (`status --json --summary`, `agentSkills`) para hosts como o OXESpace embutirem o oxe-cc de forma reativa e visual.
12
+
13
+ - **`oxe events --tail [N] --json [--since <evt_id>] [--session <s>]`** — projeção read-only e versionada (`oxeEventsSchema: 1`) do log append-only `.oxe/OXE-EVENTS.ndjson`: `{ summary: { total, byType, lastEvent }, events: [...] }`. `--since` devolve só os eventos novos desde um `event_id` conhecido (leitura incremental). Reusa `operational.readEvents`/`summarizeEvents`. Um host normalmente observa o arquivo e chama `status --json --summary`; este comando é a forma documentada de ler o tail sem reparsear tudo.
14
+ - **`oxe dashboard --json`** — emite **uma linha** estável e versionada (`oxeDashboardSchema: 1`) com `{ url, port, readOnly, projectRoot }` assim que o servidor sobe, e **continua servindo**. Permite a um host capturar URL/porta de forma robusta e embutir o dashboard num webview, em vez de raspar o banner humano. Combine com `--no-open --port 0` para porta efêmera.
15
+ - **`dashboard --port 0`** agora seleciona uma porta efêmera de verdade (antes caía no default `4173`).
16
+ - **`docs/INTEGRATION.md`** — contrato estável para hosts: `status --json --summary`, `agentSkills`, schema do `OXE-EVENTS.ndjson`, `events --json` e `dashboard --json`; marca o que é estável vs experimental.
17
+
18
+ ---
19
+
20
+ ## [1.13.0] — 2026-05-29
21
+
22
+ ### Contratos de integração para hosts (IDEs, OXESpace)
23
+
24
+ Aditivo — nenhum contrato existente muda. Facilita hosts consumirem o oxe-cc.
25
+
26
+ - **`oxe status --json --summary`** — projeção compacta e versionada (`oxeSummarySchema: 1`) com `workspaceMode, phase, healthStatus, activeSession, nextStep, cursorCmd, reason, eventsCount, warningsCount` + um `agentSkills` compacto. Troca ~150KB do status completo por <1KB para o "glance" que um host precisa.
27
+ - **`agentSkills` no `status --json`** e **`agentSkillsReport(target)` no SDK** (`health.agentSkillsReport`) — status das skills `/oxe-*` por agente no workspace (`copilot-vscode`, `codex`, `copilot-cli`): `detected/skillsInstalled/skillsPath/status/issues`. Permite a um host detectar skills ausentes e oferecer instalação **antes** de lançar o agente (resolve o "Failed to load N skills").
28
+ - **SDK**: `health.buildStatusSummary(report)` e `health.agentSkillsReport(target)` exportados + tipados em `lib/sdk/index.d.ts`.
29
+
30
+ ---
31
+
7
32
  ## [1.12.0] — 2026-05-12
8
33
 
9
34
  ### Agent Mode, Swarm Mode, Memory Kernel & Learning Kernel
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
  [![npm](https://img.shields.io/npm/v/oxe-cc.svg?style=flat-square)](https://www.npmjs.com/package/oxe-cc)
8
8
  [![license](https://img.shields.io/npm/l/oxe-cc.svg?style=flat-square)](LICENSE)
9
9
 
10
- **Versão:** `1.12.0` · [package.json](package.json)
10
+ **Versão:** `1.14.0` · [package.json](package.json)
11
11
 
12
12
  **Framework OXE — Orchestrated eXperience Engineering**
13
13
 
@@ -2730,6 +2730,79 @@ function buildHealthReport(target) {
2730
2730
  };
2731
2731
  }
2732
2732
 
2733
+ /**
2734
+ * Compact, stable projection of `buildHealthReport` for host integrations
2735
+ * (IDEs, OXESpace). Versioned independently via `oxeSummarySchema` so a host
2736
+ * can depend on a small, cheap payload instead of parsing the full ~100KB
2737
+ * status. Pure — pass in a report from `buildHealthReport`.
2738
+ * @param {ReturnType<typeof buildHealthReport>} report
2739
+ */
2740
+ function buildStatusSummary(report) {
2741
+ const next = (report && report.next) || {};
2742
+ const gaps = Array.isArray(report && report.criticalExecutionGaps) ? report.criticalExecutionGaps.length : 0;
2743
+ const planWarn = report && report.planSelfEvaluation && Array.isArray(report.planSelfEvaluation.warnings)
2744
+ ? report.planSelfEvaluation.warnings.length
2745
+ : 0;
2746
+ return {
2747
+ oxeSummarySchema: 1,
2748
+ workspaceMode: (report && report.workspaceMode) || 'oxe_project',
2749
+ phase: (report && report.phase) || null,
2750
+ healthStatus: (report && report.healthStatus) || null,
2751
+ activeSession: (report && report.activeSession) || null,
2752
+ nextStep: next.step || null,
2753
+ cursorCmd: next.cursorCmd || null,
2754
+ reason: next.reason || null,
2755
+ eventsCount: report && report.eventsSummary && typeof report.eventsSummary.total === 'number' ? report.eventsSummary.total : 0,
2756
+ warningsCount: gaps + planWarn,
2757
+ };
2758
+ }
2759
+
2760
+ /**
2761
+ * Per-agent OXE skills/integration status for a workspace. Lets a host detect
2762
+ * when an agent lacks the `/oxe-*` skills BEFORE launching it — directly
2763
+ * addresses the "Failed to load N skills" failure seen in agent CLIs. Reuses
2764
+ * the tested copilot/codex integration reports plus a filesystem check of the
2765
+ * Copilot CLI skills home (~/.copilot/skills).
2766
+ * @param {string} target
2767
+ */
2768
+ function agentSkillsReport(target) {
2769
+ const agents = [];
2770
+
2771
+ const cp = copilotIntegrationReport(target);
2772
+ agents.push({
2773
+ agent: 'copilot-vscode',
2774
+ detected: cp.detected,
2775
+ skillsInstalled: cp.promptSource === 'workspace',
2776
+ skillsPath: cp.workspace.promptsDir,
2777
+ status: cp.status,
2778
+ issues: cp.warnings || [],
2779
+ });
2780
+
2781
+ const cx = codexIntegrationReport(target);
2782
+ agents.push({
2783
+ agent: 'codex',
2784
+ detected: cx.detected,
2785
+ skillsInstalled: Boolean(cx.skillsReady),
2786
+ skillsPath: cx.skillsRoot,
2787
+ status: cx.status,
2788
+ issues: cx.warnings || [],
2789
+ });
2790
+
2791
+ const cliSkillsRoot = path.join(copilotLegacyHome(), 'skills');
2792
+ const cliSkillDirs = listOxeSkillDirs(cliSkillsRoot);
2793
+ const cliInstalled = cliSkillDirs.length > 0;
2794
+ agents.push({
2795
+ agent: 'copilot-cli',
2796
+ detected: cliInstalled,
2797
+ skillsInstalled: cliInstalled,
2798
+ skillsPath: cliSkillsRoot,
2799
+ status: cliInstalled ? 'healthy' : 'not_installed',
2800
+ issues: cliInstalled ? [] : ['Skills OXE ausentes em ~/.copilot/skills — rode `oxe install --copilot-cli` e depois `/skills reload`'],
2801
+ });
2802
+
2803
+ return agents;
2804
+ }
2805
+
2733
2806
  module.exports = {
2734
2807
  ALLOWED_CONFIG_KEYS,
2735
2808
  EXECUTION_PROFILES,
@@ -2775,6 +2848,8 @@ module.exports = {
2775
2848
  shouldSuppressExecutionWorkspaceGates,
2776
2849
  suggestNextStep,
2777
2850
  buildHealthReport,
2851
+ buildStatusSummary,
2852
+ agentSkillsReport,
2778
2853
  buildExecutionRationality: rationality.buildExecutionRationality,
2779
2854
  oxePaths,
2780
2855
  scopedOxePaths,
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).
@@ -203,26 +203,26 @@ function buildInstallSummary(opts, fullLayout) {
203
203
  cmd: 'npx oxe-cc status',
204
204
  });
205
205
 
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) {
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) {
226
226
  nextSteps.push({
227
227
  desc: 'Para ativar integrações IDE/CLI neste repo, instale de novo sem --oxe-only:',
228
228
  cmd: 'npx oxe-cc@latest',
@@ -234,19 +234,19 @@ function buildInstallSummary(opts, fullLayout) {
234
234
  });
235
235
  }
236
236
 
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) {
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) {
250
250
  nextSteps.push({
251
251
  desc: 'No Gemini CLI: recarregar comandos personalizados (/oxe, /oxe:scan, …):',
252
252
  cmd: '/commands reload',
@@ -390,6 +390,8 @@ function parseInstallArgs(argv) {
390
390
  statusHints: false,
391
391
  /** Visão extendida CLI-first: coverage matrix + readiness gate no terminal. */
392
392
  statusFull: false,
393
+ /** Saída JSON compacta/versionada para hosts (status --json --summary). */
394
+ statusSummary: false,
393
395
  restPositional: [],
394
396
  };
395
397
  for (let i = 0; i < argv.length; i++) {
@@ -435,6 +437,7 @@ function parseInstallArgs(argv) {
435
437
  } else if (a === '--json') out.jsonOutput = true;
436
438
  else if (a === '--hints') out.statusHints = true;
437
439
  else if (a === '--full') out.statusFull = true;
440
+ else if (a === '--summary') out.statusSummary = true;
438
441
  else if (a === '--release') out.releaseDoctor = true;
439
442
  else if (a === '--write-manifest') out.writeManifest = true;
440
443
  else if (!a.startsWith('-')) out.restPositional.push(a);
@@ -1892,6 +1895,18 @@ function runStatus(target, opts = {}) {
1892
1895
  const routineHints = collectOxeRoutineHints(target, report, config);
1893
1896
  const next = report.next;
1894
1897
 
1898
+ // Lightweight, versioned summary for host integrations (IDEs, OXESpace) —
1899
+ // a small stable payload instead of the full ~100KB status. Includes a
1900
+ // compact per-agent skills view so a host can decide whether to offer
1901
+ // "install OXE skills" before launching an agent.
1902
+ if (opts.json && opts.summary) {
1903
+ const summary = oxeHealth.buildStatusSummary(report);
1904
+ summary.projectRoot = path.resolve(target);
1905
+ summary.agentSkills = oxeHealth.agentSkillsReport(target).map((a) => ({ agent: a.agent, skillsInstalled: a.skillsInstalled }));
1906
+ console.log(JSON.stringify(summary));
1907
+ return;
1908
+ }
1909
+
1895
1910
  if (opts.json) {
1896
1911
  /** @type {Record<string, unknown>} */
1897
1912
  const payload = {
@@ -1947,6 +1962,7 @@ function runStatus(target, opts = {}) {
1947
1962
  azure: report.azure,
1948
1963
  copilot: report.copilot,
1949
1964
  codex: report.codex,
1965
+ agentSkills: oxeHealth.agentSkillsReport(target),
1950
1966
  contextPacks: report.contextPacks,
1951
1967
  contextQuality: report.contextQuality,
1952
1968
  semanticsDrift: report.semanticsDrift,
@@ -2553,6 +2569,7 @@ ${cyan}oxe-cc${reset} — instala workflows OXE (núcleo .oxe/ + integrações:
2553
2569
  npx oxe-cc init-oxe [opções] [pasta-do-projeto]
2554
2570
  npx oxe-cc context <build|inspect> [opções] [pasta-do-projeto]
2555
2571
  npx oxe-cc dashboard [opções] [pasta-do-projeto]
2572
+ npx oxe-cc events [--tail N] [--since <evt_id>] [--json] [opções] [pasta-do-projeto]
2556
2573
  npx oxe-cc runtime <status|start|pause|resume|replay|compile|verify|project|ci|promote|recover|gates|agents|execute> [opções] [pasta-do-projeto]
2557
2574
  npx oxe-cc azure <status|doctor|auth|sync|find|servicebus|eventgrid|sql|operations> [opções] [pasta-do-projeto]
2558
2575
  npx oxe-cc capabilities <list|install|remove|update> [opções] [id]
@@ -2587,12 +2604,21 @@ ${green}update${reset} (executa npx oxe-cc@latest --force na pasta do projeto)
2587
2604
  ${dim}CI / sem rede:${reset} OXE_UPDATE_SKIP_REGISTRY=1 desativa consultas (--check sai 2; --if-newer sai 2 sem npx)
2588
2605
 
2589
2606
  ${green}dashboard${reset} (interface web local para revisão e aprovação do plano)
2590
- --port <número> porta local (padrão: 4173)
2607
+ --port <número> porta local (padrão: 4173; use 0 para porta efêmera)
2591
2608
  --no-open não abre o browser automaticamente
2609
+ --json emite {url,port,...} (oxeDashboardSchema:1) e segue servindo — para embutir num host
2610
+ --read-only UI visual sem persistência (embed seguro)
2592
2611
  --session <sessions/sNNN-slug> força visualização de uma sessão específica
2593
2612
  --dump-context imprime JSON consolidado e sai
2594
2613
  --dir <pasta> raiz do projeto (padrão: diretório atual)
2595
2614
 
2615
+ ${green}events${reset} (projeção read-only do log .oxe/OXE-EVENTS.ndjson — para hosts)
2616
+ --tail <N> só os últimos N eventos (padrão: 50)
2617
+ --since <evt_id> só eventos posteriores a um event_id conhecido (leitura incremental)
2618
+ --json saída estruturada (oxeEventsSchema:1) com summary + events
2619
+ --session <sessions/sNNN-slug> força sessão específica (padrão: sessão ativa do STATE.md)
2620
+ --dir <pasta> raiz do projeto (padrão: diretório atual)
2621
+
2596
2622
  ${green}context${reset} (Context Engine V2: seleção, compressão e inspeção determinística)
2597
2623
  build [--workflow <slug>] gera pack(s) em .oxe/context/packs/
2598
2624
  inspect [--workflow <slug>] lê um pack existente ou resolve sob demanda
@@ -3924,7 +3950,7 @@ function parseCapabilitiesArgs(argv) {
3924
3950
  }
3925
3951
 
3926
3952
  /**
3927
- * @typedef {{ help: boolean, dir: string, port: number, noOpen: boolean, readOnly: boolean, dumpContext: boolean, activeSession: string|null, parseError: boolean, unknownFlag: string }} DashboardOpts
3953
+ * @typedef {{ help: boolean, dir: string, port: number, noOpen: boolean, readOnly: boolean, dumpContext: boolean, json: boolean, activeSession: string|null, parseError: boolean, unknownFlag: string }} DashboardOpts
3928
3954
  */
3929
3955
 
3930
3956
  /**
@@ -3985,6 +4011,7 @@ function parseDashboardArgs(argv) {
3985
4011
  noOpen: false,
3986
4012
  readOnly: false,
3987
4013
  dumpContext: false,
4014
+ json: false,
3988
4015
  activeSession: null,
3989
4016
  parseError: false,
3990
4017
  unknownFlag: '',
@@ -3993,10 +4020,15 @@ function parseDashboardArgs(argv) {
3993
4020
  const a = argv[i];
3994
4021
  if (a === '-h' || a === '--help') out.help = true;
3995
4022
  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;
4023
+ else if (a === '--port' && argv[i + 1] !== undefined) {
4024
+ // Allow `--port 0` (OS picks an ephemeral port — used by hosts that embed
4025
+ // the dashboard and read the real port back from `--json`).
4026
+ const n = Number(argv[++i]);
4027
+ out.port = Number.isFinite(n) && n >= 0 ? n : 4173;
4028
+ } else if (a === '--no-open') out.noOpen = true;
3998
4029
  else if (a === '--read-only') out.readOnly = true;
3999
4030
  else if (a === '--dump-context') out.dumpContext = true;
4031
+ else if (a === '--json') out.json = true;
4000
4032
  else if (a === '--session' && argv[i + 1]) out.activeSession = String(argv[++i]).replace(/\\/g, '/');
4001
4033
  else if (!a.startsWith('-') && i === 0) out.dir = path.resolve(a);
4002
4034
  else {
@@ -4357,7 +4389,7 @@ async function runDashboard(opts) {
4357
4389
  return;
4358
4390
  }
4359
4391
  const c = useAnsiColors();
4360
- printSection('OXE ▸ dashboard');
4392
+ if (!opts.json) printSection('OXE ▸ dashboard');
4361
4393
  const server = oxeDashboard.createDashboardServer(target, { activeSession: opts.activeSession });
4362
4394
  await new Promise((resolve, reject) => {
4363
4395
  server.once('error', reject);
@@ -4366,6 +4398,24 @@ async function runDashboard(opts) {
4366
4398
  const address = server.address();
4367
4399
  const port = address && typeof address === 'object' ? address.port : opts.port;
4368
4400
  const url = `http://127.0.0.1:${port}/`;
4401
+ // Host-integration mode: emit one stable, versioned line with the live URL/port
4402
+ // as soon as the server is listening, then keep serving until killed. Lets a
4403
+ // host (OXESpace) embed the dashboard in a webview without scraping the
4404
+ // human-readable banner. Pair with `--no-open --port 0` for an ephemeral port.
4405
+ if (opts.json) {
4406
+ console.log(JSON.stringify({
4407
+ oxeDashboardSchema: 1,
4408
+ projectRoot: path.resolve(target),
4409
+ url,
4410
+ port,
4411
+ readOnly: Boolean(opts.readOnly),
4412
+ }));
4413
+ if (!opts.noOpen) {
4414
+ try { openUrlInBrowser(url); } catch { /* host owns presentation */ }
4415
+ }
4416
+ await new Promise(() => {});
4417
+ return;
4418
+ }
4369
4419
  console.log(` ${c ? green : ''}Projeto:${c ? reset : ''} ${c ? cyan : ''}${target}${c ? reset : ''}`);
4370
4420
  console.log(` ${c ? green : ''}URL:${c ? reset : ''} ${c ? cyan : ''}${url}${c ? reset : ''}`);
4371
4421
  if (opts.readOnly) {
@@ -4383,6 +4433,99 @@ async function runDashboard(opts) {
4383
4433
  await new Promise(() => {});
4384
4434
  }
4385
4435
 
4436
+ /**
4437
+ * @typedef {{ help: boolean, dir: string, tail: number, since: string, activeSession: string|null, json: boolean, parseError: boolean, unknownFlag: string }} EventsOpts
4438
+ */
4439
+
4440
+ /**
4441
+ * @param {string[]} argv
4442
+ * @returns {EventsOpts}
4443
+ */
4444
+ function parseEventsArgs(argv) {
4445
+ /** @type {EventsOpts} */
4446
+ const out = {
4447
+ help: false,
4448
+ dir: process.cwd(),
4449
+ tail: 50,
4450
+ since: '',
4451
+ activeSession: null,
4452
+ json: false,
4453
+ parseError: false,
4454
+ unknownFlag: '',
4455
+ };
4456
+ for (let i = 0; i < argv.length; i++) {
4457
+ const a = argv[i];
4458
+ if (a === '-h' || a === '--help') out.help = true;
4459
+ else if (a === '--dir' && argv[i + 1]) out.dir = path.resolve(argv[++i]);
4460
+ else if (a === '--tail') {
4461
+ // `--tail` alone keeps the default; `--tail N` overrides the count.
4462
+ const next = argv[i + 1];
4463
+ if (next && /^\d+$/.test(next)) out.tail = Number(argv[++i]);
4464
+ } else if (a === '--since' && argv[i + 1]) out.since = String(argv[++i]);
4465
+ else if (a === '--json') out.json = true;
4466
+ else if (a === '--session' && argv[i + 1]) out.activeSession = String(argv[++i]).replace(/\\/g, '/');
4467
+ else if (!a.startsWith('-') && i === 0) out.dir = path.resolve(a);
4468
+ else {
4469
+ out.parseError = true;
4470
+ out.unknownFlag = a;
4471
+ break;
4472
+ }
4473
+ }
4474
+ return out;
4475
+ }
4476
+
4477
+ /**
4478
+ * Read-only projection of the append-only event log (.oxe/OXE-EVENTS.ndjson)
4479
+ * for host integrations. A host typically watches that file directly and then
4480
+ * calls `status --json --summary`; this command is the documented way to read
4481
+ * the tail of the log (optionally since a known event_id) without parsing the
4482
+ * whole file. Reuses the same SDK helpers the runtime uses.
4483
+ * @param {EventsOpts} opts
4484
+ */
4485
+ function runEvents(opts) {
4486
+ const target = opts.dir;
4487
+ if (!fs.existsSync(target)) {
4488
+ if (opts.json) console.log(JSON.stringify({ oxeEventsSchema: 1, projectRoot: path.resolve(target), error: 'dir-not-found', events: [] }));
4489
+ else console.error(`${yellow}Diretório não encontrado: ${target}${reset}`);
4490
+ process.exit(1);
4491
+ }
4492
+ const stateText = fs.existsSync(oxeHealth.oxePaths(target).state)
4493
+ ? fs.readFileSync(oxeHealth.oxePaths(target).state, 'utf8')
4494
+ : '';
4495
+ const activeSession = opts.activeSession || oxeHealth.parseActiveSession(stateText) || null;
4496
+ let events = oxeOperational.readEvents(target, activeSession);
4497
+ if (opts.since) {
4498
+ const idx = events.findIndex((e) => e && e.event_id === opts.since);
4499
+ if (idx >= 0) events = events.slice(idx + 1);
4500
+ }
4501
+ const tail = opts.tail > 0 ? events.slice(-opts.tail) : events;
4502
+
4503
+ if (opts.json) {
4504
+ console.log(JSON.stringify({
4505
+ oxeEventsSchema: 1,
4506
+ projectRoot: path.resolve(target),
4507
+ activeSession,
4508
+ summary: oxeOperational.summarizeEvents(events),
4509
+ events: tail,
4510
+ }));
4511
+ return;
4512
+ }
4513
+
4514
+ const c = useAnsiColors();
4515
+ printSection('OXE ▸ events');
4516
+ const summary = oxeOperational.summarizeEvents(events);
4517
+ console.log(` ${c ? green : ''}Total:${c ? reset : ''} ${summary.total}${activeSession ? ` ${c ? dim : ''}(sessão: ${activeSession})${c ? reset : ''}` : ''}`);
4518
+ if (!tail.length) {
4519
+ console.log(` ${c ? dim : ''}Sem eventos.${c ? reset : ''}\n`);
4520
+ return;
4521
+ }
4522
+ for (const e of tail) {
4523
+ const ts = e.timestamp ? String(e.timestamp).replace('T', ' ').replace(/\..*$/, '') : '';
4524
+ console.log(` ${c ? dim : ''}${ts}${c ? reset : ''} ${c ? cyan : ''}${e.type}${c ? reset : ''}${e.run_id ? ` ${c ? dim : ''}run=${e.run_id}${c ? reset : ''}` : ''}`);
4525
+ }
4526
+ console.log('');
4527
+ }
4528
+
4386
4529
  /**
4387
4530
  * @param {RuntimeOpts} opts
4388
4531
  */
@@ -5473,6 +5616,7 @@ async function main() {
5473
5616
  argv[0] === 'init-oxe' ||
5474
5617
  argv[0] === 'context' ||
5475
5618
  argv[0] === 'dashboard' ||
5619
+ argv[0] === 'events' ||
5476
5620
  argv[0] === 'runtime' ||
5477
5621
  argv[0] === 'azure' ||
5478
5622
  argv[0] === 'uninstall' ||
@@ -5582,11 +5726,29 @@ async function main() {
5582
5726
  usage();
5583
5727
  process.exit(1);
5584
5728
  }
5585
- printBanner();
5729
+ if (!d.json) printBanner();
5586
5730
  await runDashboard(d);
5587
5731
  return;
5588
5732
  }
5589
5733
 
5734
+ if (command === 'events') {
5735
+ const e = parseEventsArgs(argv);
5736
+ if (e.help) {
5737
+ printBanner();
5738
+ usage();
5739
+ process.exit(0);
5740
+ }
5741
+ if (e.parseError) {
5742
+ printBanner();
5743
+ console.error(`${red}Opção desconhecida:${reset} ${e.unknownFlag}`);
5744
+ usage();
5745
+ process.exit(1);
5746
+ }
5747
+ if (!e.json) printBanner();
5748
+ runEvents(e);
5749
+ return;
5750
+ }
5751
+
5590
5752
  if (command === 'context') {
5591
5753
  const contextOpts = parseContextArgs(argv);
5592
5754
  if (contextOpts.help) {
@@ -5768,7 +5930,7 @@ async function main() {
5768
5930
  if (opts.statusFull) {
5769
5931
  runStatusFull(target);
5770
5932
  } else {
5771
- runStatus(target, { json: opts.jsonOutput, hints: opts.statusHints });
5933
+ runStatus(target, { json: opts.jsonOutput, hints: opts.statusHints, summary: opts.statusSummary });
5772
5934
  }
5773
5935
  return;
5774
5936
  }
@@ -0,0 +1,152 @@
1
+ # Integração com hosts (IDEs, OXESpace, …)
2
+
3
+ Este documento descreve o **contrato estável** que o `oxe-cc` expõe para um *host* — um app que embute o oxe-cc e mostra o estado de um projeto OXE (ex.: o [OXESpace](https://github.com/propagno) embute o painel OXE).
4
+
5
+ O `oxe-cc` e o host têm **releases independentes**. Para que evoluam sem se quebrar:
6
+
7
+ - Toda saída de máquina é **versionada** com um campo `oxe*Schema` próprio (independente da versão do pacote).
8
+ - Os campos são **aditivos**: campos novos podem aparecer; campos existentes não mudam de tipo nem somem dentro da mesma `*Schema`.
9
+ - O host deve **degradar graciosamente**: detectar a versão / o schema e usar o caminho antigo quando um recurso novo não existir.
10
+
11
+ Detecte a versão com `oxe --version` (imprime `oxe-cc vX.Y.Z`). Tabela de disponibilidade:
12
+
13
+ | Recurso | Desde | Schema |
14
+ |---|---|---|
15
+ | `status --json` (completo) | 1.0 | `oxeStatusSchema: 5` |
16
+ | `status --json --summary` | 1.13.0 | `oxeSummarySchema: 1` |
17
+ | `agentSkills[]` no `status --json` + `agentSkills` no summary | 1.13.0 | — |
18
+ | `events --tail --json` | 1.14.0 | `oxeEventsSchema: 1` |
19
+ | `dashboard --json` + `--port 0` efêmero | 1.14.0 | `oxeDashboardSchema: 1` |
20
+
21
+ > **Estável:** `status --json --summary`, `agentSkills`, `events --json`, `dashboard --json`.
22
+ > **Experimental (pode mudar):** o conteúdo de `payload` dentro de cada evento; o HTML servido pelo dashboard; o corpo de `status --json` completo (`oxeStatusSchema`) além dos campos listados abaixo.
23
+
24
+ ---
25
+
26
+ ## 1. Glance barato — `status --json --summary`
27
+
28
+ Em vez do `status --json` completo (~100KB), use a projeção compacta no *hot path* (atualizações frequentes):
29
+
30
+ ```bash
31
+ oxe status --json --summary --dir <projeto>
32
+ ```
33
+
34
+ ```jsonc
35
+ {
36
+ "oxeSummarySchema": 1,
37
+ "projectRoot": "/abs/path",
38
+ "workspaceMode": "oxe_project",
39
+ "phase": "execute",
40
+ "healthStatus": "warning",
41
+ "activeSession": "sessions/s003-foo",
42
+ "nextStep": "execute",
43
+ "cursorCmd": "/oxe-execute",
44
+ "reason": "…",
45
+ "eventsCount": 42,
46
+ "warningsCount": 7,
47
+ "agentSkills": [ { "agent": "copilot-cli", "skillsInstalled": false } ]
48
+ }
49
+ ```
50
+
51
+ O `status --json` completo (`oxeStatusSchema: 5`) continua disponível para a vista detalhada (diagnósticos, `criticalExecutionGaps`, `planSelfEvaluation`, artefatos, `agentSkills[]` detalhado).
52
+
53
+ ---
54
+
55
+ ## 2. Skills por-agente — `agentSkills`
56
+
57
+ O summary traz um `agentSkills` compacto (`{ agent, skillsInstalled }`). O `status --json` completo traz a forma detalhada:
58
+
59
+ ```jsonc
60
+ {
61
+ "agent": "copilot-cli", // copilot-vscode | codex | copilot-cli | cursor | …
62
+ "detected": true, // o agente está configurado neste ambiente
63
+ "skillsInstalled": false, // as skills /oxe-* estão instaladas neste workspace
64
+ "skillsPath": "/abs/.../skills",
65
+ "status": "no_skills",
66
+ "issues": ["…"] // por que falhou (caminho ausente, frontmatter, versão)
67
+ }
68
+ ```
69
+
70
+ Via SDK, sem shell: `require('oxe-cc').health.agentSkillsReport(target)`.
71
+
72
+ **Uso típico do host:** se algum agente relevante tem `skillsInstalled:false`, ofereça instalar as skills daquele agente **antes** de lançá-lo (resolve o "Failed to load N skills"). Hoje o host dispara a instalação via os comandos `install` do oxe-cc (no terminal); uma saída `install --json` silenciosa está planejada (ver §5).
73
+
74
+ ---
75
+
76
+ ## 3. Reatividade — observar eventos
77
+
78
+ O `oxe-cc` mantém um log **append-only** em:
79
+
80
+ ```
81
+ .oxe/OXE-EVENTS.ndjson # projeto sem sessão ativa
82
+ .oxe/<session>/execution/OXE-EVENTS.ndjson # quando há sessão ativa (ex.: sessions/s003-foo)
83
+ ```
84
+
85
+ Cada linha é um JSON (NDJSON). Campos estáveis por evento:
86
+
87
+ ```jsonc
88
+ {
89
+ "event_id": "evt-...", // único; use como cursor em --since
90
+ "type": "RunStarted", // RunStarted, WorkItemReady, ToolInvoked, GateRequested, RunCompleted, …
91
+ "timestamp": "2026-05-30T10:00:00.000Z",
92
+ "run_id": "run-1" | null,
93
+ "session_id": "sessions/s003-foo" | null,
94
+ "wave_id": 1 | null,
95
+ "task_id": "T1" | null,
96
+ "payload": { /* experimental — específico do tipo */ }
97
+ }
98
+ ```
99
+
100
+ **Padrão recomendado (reativo + barato):**
101
+
102
+ 1. O host faz `fs.watch` do `OXE-EVENTS.ndjson` (com debounce ~400ms).
103
+ 2. A cada mudança, re-chama `status --json --summary` para atualizar a UI.
104
+ 3. Um poll lento de segurança (~15s) cobre o caso de o watch falhar (rede/symlink).
105
+
106
+ Para **ler os eventos** sem reparsear o arquivo inteiro, use o comando read-only:
107
+
108
+ ```bash
109
+ oxe events --tail 50 --json --dir <projeto>
110
+ oxe events --since evt-abc123 --json --dir <projeto> # só os novos desde um event_id
111
+ ```
112
+
113
+ ```jsonc
114
+ {
115
+ "oxeEventsSchema": 1,
116
+ "projectRoot": "/abs/path",
117
+ "activeSession": "sessions/s003-foo" | null,
118
+ "summary": { "total": 3, "byType": { "RunStarted": 1, "…": 1 }, "lastEvent": { /* … */ } },
119
+ "events": [ /* … */ ]
120
+ }
121
+ ```
122
+
123
+ Via SDK: `oxe.operational.readEvents(root, session)` e `oxe.operational.summarizeEvents(events)`.
124
+
125
+ ---
126
+
127
+ ## 4. Dashboard embutível — `dashboard --json`
128
+
129
+ O dashboard é um servidor HTTP local (serve `/` HTML + `/api/context` + endpoints de review/runtime). Para embutir num webview/iframe, suba-o em modo host:
130
+
131
+ ```bash
132
+ oxe dashboard --no-open --port 0 --json --dir <projeto>
133
+ ```
134
+
135
+ A **primeira linha** do stdout é, assim que o servidor está ouvindo, e o processo **continua servindo** até ser morto:
136
+
137
+ ```json
138
+ {"oxeDashboardSchema":1,"projectRoot":"/abs/path","url":"http://127.0.0.1:52555/","port":52555,"readOnly":false}
139
+ ```
140
+
141
+ - `--port 0` → o SO escolhe uma porta efêmera; leia a porta real do `port`/`url`.
142
+ - `--read-only` → UI visual sem persistir mudanças (embed seguro).
143
+ - O servidor é localhost-only (`127.0.0.1`), sem autenticação — adequado a embed local.
144
+
145
+ **Ciclo de vida (host):** faça spawn do processo, leia a primeira linha JSON, carregue `url` no webview, e **mate o processo** ao fechar o workspace/app. Fallback para versões < 1.14: rode `dashboard` sem `--json` e raspe a linha `URL: …`, ou abra no navegador externo.
146
+
147
+ ---
148
+
149
+ ## 5. Roadmap de integração (ainda não estável)
150
+
151
+ - **`install … --json`** — saída idempotente `{ ok, agents:[{agent, installedPaths[]}], skipped[], errors[] }` para instalar skills sem terminal. Hoje a instalação é via os comandos `install` (no terminal) + reconferir com `agentSkills`.
152
+ - **`events --follow` / streaming (SSE)** — hoje a reatividade é via `fs.watch` + `events --tail`/`--since`.