oxe-cc 0.3.5 → 0.3.6

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/bin/oxe-cc.js CHANGED
@@ -17,6 +17,7 @@ const oxeHealth = require(path.join(__dirname, 'lib', 'oxe-project-health.cjs'))
17
17
  const oxeAgentInstall = require(path.join(__dirname, 'lib', 'oxe-agent-install.cjs'));
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
+ const oxeNpmVersion = require(path.join(__dirname, 'lib', 'oxe-npm-version.cjs'));
20
21
 
21
22
  /** Merge markers for ~/.copilot/copilot-instructions.md (bloco OXE). */
22
23
  const OXE_INST_BEGIN = '<!-- oxe-cc:install-begin -->';
@@ -36,7 +37,7 @@ const RULE = '━━━━━━━━━━━━━━━━━━━━━━
36
37
  /** Plain banner if banner.txt is missing (keep in sync with bin/banner.txt style). */
37
38
  const DEFAULT_BANNER = ` .============================================.
38
39
  | OXE · spec-driven workflow CLI |
39
- | Cursor · GitHub Copilot |
40
+ | multi-IDE · Cursor · Copilot · +CLIs |
40
41
  '============================================'
41
42
  v{version}
42
43
  `;
@@ -127,13 +128,14 @@ function printSummaryAndNextSteps(c, { bullets, nextSteps, dryRun = false }) {
127
128
  /**
128
129
  * @param {InstallOpts} opts
129
130
  * @param {boolean} fullLayout
130
- * @param {string} cursorBase
131
- * @param {string} copilotRoot
132
- * @param {string} claudeBase
133
131
  */
134
- function buildInstallSummary(opts, fullLayout, cursorBase, copilotRoot, claudeBase) {
132
+ function buildInstallSummary(opts, fullLayout) {
135
133
  const bullets = [];
136
134
  const prefix = opts.dryRun ? '[simulação] ' : '';
135
+ const cursorBase = installCursorBase(opts);
136
+ const copilotCliHome = installCopilotCliHome(opts);
137
+ const claudeBase = installClaudeBase(opts);
138
+ const agentPaths = oxeAgentInstall.buildAgentInstallPaths(!opts.ideLocal, opts.dir);
137
139
 
138
140
  if (opts.oxeOnly) {
139
141
  bullets.push(`${prefix}Repositório: .oxe/workflows/ e .oxe/templates/`);
@@ -157,18 +159,31 @@ function buildInstallSummary(opts, fullLayout, cursorBase, copilotRoot, claudeBa
157
159
  }
158
160
  if (opts.copilot) {
159
161
  bullets.push(
160
- `${prefix}Copilot (VS Code): trecho OXE em ${displayPathForUser(path.join(copilotRoot, 'copilot-instructions.md'))} e prompts em ${displayPathForUser(path.join(copilotRoot, 'prompts'))}`
162
+ `${prefix}Copilot (VS Code): trecho OXE em ${displayPathForUser(copilotInstructionsPath(opts))} e prompts em ${displayPathForUser(copilotPromptsDirPath(opts))}`
161
163
  );
162
164
  }
163
165
  if (opts.copilotCli && !opts.allAgents) {
164
166
  bullets.push(
165
- `${prefix}CLI: skills Copilot em ${displayPathForUser(path.join(copilotRoot, 'skills'))} (/oxe, /oxe-scan, …); cópia legado em ${displayPathForUser(path.join(claudeBase, 'commands'))} e ${displayPathForUser(path.join(copilotRoot, 'commands'))}`
167
+ `${prefix}CLI: skills Copilot em ${displayPathForUser(path.join(copilotCliHome, 'skills'))} (/oxe, /oxe-scan, …); cópia legado em ${displayPathForUser(path.join(claudeBase, 'commands'))} e ${displayPathForUser(path.join(copilotCliHome, 'commands'))}`
166
168
  );
167
169
  }
168
170
  if (opts.allAgents) {
171
+ const oc = agentPaths.opencodeCommandDirs.map((d) => displayPathForUser(d)).join(' + ');
169
172
  bullets.push(
170
- `${prefix}Multi-agente (vários homes): OpenCode ${displayPathForUser(path.join(process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config'), 'opencode', 'commands'))} + ${displayPathForUser(path.join(os.homedir(), '.opencode', 'commands'))}; Gemini ${displayPathForUser(path.join(os.homedir(), '.gemini', 'commands'))} (/oxe, /oxe:scan); Codex ${displayPathForUser(path.join(os.homedir(), '.agents', 'skills'))} + ${displayPathForUser(path.join(os.homedir(), '.codex', 'prompts'))}; Windsurf ${displayPathForUser(path.join(os.homedir(), '.codeium', 'windsurf', 'global_workflows'))}; Antigravity ${displayPathForUser(path.join(os.homedir(), '.gemini', 'antigravity', 'skills'))}; + Claude/Copilot/Copilot skills como em --copilot-cli`
173
+ `${prefix}Multi-agente: OpenCode ${oc}; Gemini ${displayPathForUser(agentPaths.geminiCommandsBase)}; Codex ${displayPathForUser(agentPaths.codexAgentsSkillsRoot)} + ${displayPathForUser(agentPaths.codexPromptsDir)}; Windsurf ${displayPathForUser(agentPaths.windsurfWorkflowsDir)}; Antigravity ${displayPathForUser(agentPaths.antigravitySkillsRoot)}; + Claude/Copilot CLI como em --copilot-cli`
171
174
  );
175
+ } else if (anyGranularAgent(opts)) {
176
+ const parts = [];
177
+ if (opts.agentOpenCode) parts.push(`OpenCode ${agentPaths.opencodeCommandDirs.map((d) => displayPathForUser(d)).join(' + ')}`);
178
+ if (opts.agentGemini) parts.push(`Gemini ${displayPathForUser(agentPaths.geminiCommandsBase)}`);
179
+ if (opts.agentCodex) {
180
+ parts.push(
181
+ `Codex ${displayPathForUser(agentPaths.codexAgentsSkillsRoot)} + ${displayPathForUser(agentPaths.codexPromptsDir)}`
182
+ );
183
+ }
184
+ if (opts.agentWindsurf) parts.push(`Windsurf ${displayPathForUser(agentPaths.windsurfWorkflowsDir)}`);
185
+ if (opts.agentAntigravity) parts.push(`Antigravity ${displayPathForUser(agentPaths.antigravitySkillsRoot)}`);
186
+ if (parts.length) bullets.push(`${prefix}Agentes (seleção): ${parts.join('; ')}`);
172
187
  }
173
188
 
174
189
  const nextSteps = [];
@@ -184,7 +199,7 @@ function buildInstallSummary(opts, fullLayout, cursorBase, copilotRoot, claudeBa
184
199
  const agentHint = [];
185
200
  if (opts.cursor) agentHint.push('Cursor');
186
201
  if (opts.copilot) agentHint.push('Copilot no VS Code');
187
- if (opts.copilotCli || opts.allAgents) agentHint.push('CLIs / multi-agente');
202
+ if (opts.copilotCli || opts.allAgents || anyGranularAgent(opts)) agentHint.push('CLIs / multi-agente');
188
203
  if (agentHint.length) {
189
204
  nextSteps.push({
190
205
  desc: `Mapear o código no agente (${agentHint.join(', ')}):`,
@@ -192,7 +207,7 @@ function buildInstallSummary(opts, fullLayout, cursorBase, copilotRoot, claudeBa
192
207
  });
193
208
  } else if (opts.oxeOnly) {
194
209
  nextSteps.push({
195
- desc: 'Para ativar Cursor ou Copilot neste repo, instale de novo sem --oxe-only:',
210
+ desc: 'Para ativar integrações IDE/CLI neste repo, instale de novo sem --oxe-only:',
196
211
  cmd: 'npx oxe-cc@latest',
197
212
  });
198
213
  } else {
@@ -208,7 +223,7 @@ function buildInstallSummary(opts, fullLayout, cursorBase, copilotRoot, claudeBa
208
223
  cmd: '/skills list',
209
224
  });
210
225
  }
211
- if (opts.allAgents) {
226
+ if (opts.allAgents || opts.agentGemini) {
212
227
  nextSteps.push({
213
228
  desc: 'No Gemini CLI: recarregar comandos personalizados (/oxe, /oxe:scan, …):',
214
229
  cmd: '/commands reload',
@@ -239,6 +254,11 @@ function buildUninstallFooter(u) {
239
254
  `${p}${rm} extensões multi-agente marcadas oxe-cc (OpenCode, Gemini TOML, Windsurf workflows, Codex prompts/skills, Antigravity), se existirem.`
240
255
  );
241
256
  }
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
+ }
242
262
  if (!u.noProject) {
243
263
  bullets.push(
244
264
  `${p}${u.dryRun ? 'Seriam removidas' : 'Removidas'} no repositório: .oxe/workflows, .oxe/templates, oxe/ e commands/oxe (o que existir).`
@@ -253,7 +273,17 @@ function buildUninstallFooter(u) {
253
273
  return { bullets, nextSteps, dryRun: u.dryRun };
254
274
  }
255
275
 
256
- /** @typedef {{ help: boolean, version: boolean, cursor: boolean, copilot: boolean, copilotCli: boolean, allAgents: boolean, vscode: boolean, commands: boolean, agents: boolean, force: boolean, dryRun: boolean, dir: string, all: boolean, noInitOxe: boolean, oxeOnly: boolean, globalCli: boolean, noGlobalCli: boolean, installAssetsGlobal: boolean, explicitScope: boolean, integrationsUnset: boolean, explicitConfigDir: string | null, parseError: boolean, unknownFlag: string, conflictFlags: string, ignoreInstallConfig: boolean }} InstallOpts */
276
+ /** @typedef {{ help: boolean, version: boolean, cursor: boolean, copilot: boolean, copilotCli: boolean, allAgents: boolean, agentOpenCode: boolean, agentGemini: boolean, agentCodex: boolean, agentWindsurf: boolean, agentAntigravity: boolean, vscode: boolean, commands: boolean, agents: boolean, force: boolean, dryRun: boolean, dir: string, all: boolean, noInitOxe: boolean, oxeOnly: boolean, globalCli: boolean, noGlobalCli: boolean, installAssetsGlobal: boolean, explicitScope: boolean, integrationsUnset: boolean, ideLocal: boolean, explicitIdeScope: boolean, explicitConfigDir: string | null, parseError: boolean, unknownFlag: string, conflictFlags: string, ignoreInstallConfig: boolean }} InstallOpts */
277
+
278
+ /** @param {InstallOpts} o */
279
+ function anyGranularAgent(o) {
280
+ return !!(o.agentOpenCode || o.agentGemini || o.agentCodex || o.agentWindsurf || o.agentAntigravity);
281
+ }
282
+
283
+ /** @param {InstallOpts} o */
284
+ function anyIdeIntegration(o) {
285
+ return !!(o.cursor || o.copilot || o.copilotCli || o.allAgents || anyGranularAgent(o));
286
+ }
257
287
 
258
288
  /**
259
289
  * @param {string[]} argv
@@ -268,6 +298,11 @@ function parseInstallArgs(argv) {
268
298
  copilot: false,
269
299
  copilotCli: false,
270
300
  allAgents: false,
301
+ agentOpenCode: false,
302
+ agentGemini: false,
303
+ agentCodex: false,
304
+ agentWindsurf: false,
305
+ agentAntigravity: false,
271
306
  vscode: false,
272
307
  commands: true,
273
308
  agents: true,
@@ -282,11 +317,15 @@ function parseInstallArgs(argv) {
282
317
  installAssetsGlobal: false,
283
318
  explicitScope: false,
284
319
  integrationsUnset: false,
320
+ ideLocal: false,
321
+ explicitIdeScope: false,
285
322
  explicitConfigDir: null,
286
323
  parseError: false,
287
324
  unknownFlag: '',
288
325
  conflictFlags: '',
289
326
  ignoreInstallConfig: false,
327
+ /** Saída JSON em `status` (CI / agentes). */
328
+ jsonOutput: false,
290
329
  restPositional: [],
291
330
  };
292
331
  for (let i = 0; i < argv.length; i++) {
@@ -302,10 +341,21 @@ function parseInstallArgs(argv) {
302
341
  } else if (a === '--local') {
303
342
  out.installAssetsGlobal = false;
304
343
  out.explicitScope = true;
344
+ } else if (a === '--ide-global') {
345
+ out.ideLocal = false;
346
+ out.explicitIdeScope = true;
347
+ } else if (a === '--ide-local') {
348
+ out.ideLocal = true;
349
+ out.explicitIdeScope = true;
305
350
  } else if (a === '--cursor') out.cursor = true;
306
351
  else if (a === '--copilot') out.copilot = true;
307
352
  else if (a === '--copilot-cli') out.copilotCli = true;
308
353
  else if (a === '--all-agents') out.allAgents = true;
354
+ else if (a === '--opencode') out.agentOpenCode = true;
355
+ else if (a === '--gemini') out.agentGemini = true;
356
+ else if (a === '--codex') out.agentCodex = true;
357
+ else if (a === '--windsurf') out.agentWindsurf = true;
358
+ else if (a === '--antigravity') out.agentAntigravity = true;
309
359
  else if (a === '--vscode') out.vscode = true;
310
360
  else if (a === '--no-commands') out.commands = false;
311
361
  else if (a === '--no-agents') out.agents = false;
@@ -318,7 +368,8 @@ function parseInstallArgs(argv) {
318
368
  else if (a === '--no-global-cli' || a === '-l') out.noGlobalCli = true;
319
369
  else if (a === '--dir' && argv[i + 1]) {
320
370
  out.dir = path.resolve(argv[++i]);
321
- } else if (!a.startsWith('-')) out.restPositional.push(a);
371
+ } else if (a === '--json') out.jsonOutput = true;
372
+ else if (!a.startsWith('-')) out.restPositional.push(a);
322
373
  else {
323
374
  out.parseError = true;
324
375
  out.unknownFlag = a;
@@ -329,7 +380,13 @@ function parseInstallArgs(argv) {
329
380
  out.conflictFlags = 'Não use --global-cli (-g) e --no-global-cli (-l) ao mesmo tempo';
330
381
  }
331
382
  if (!out.conflictFlags && argv.includes('--global') && argv.includes('--local')) {
332
- out.conflictFlags = 'Não use --global e --local ao mesmo tempo';
383
+ out.conflictFlags = 'Não use --global e --local ao mesmo tempo (são o layout do repositório: oxe/ vs só .oxe/)';
384
+ }
385
+ if (!out.conflictFlags && argv.includes('--ide-global') && argv.includes('--ide-local')) {
386
+ out.conflictFlags = 'Não use --ide-global e --ide-local ao mesmo tempo';
387
+ }
388
+ if (!out.conflictFlags && out.ideLocal && out.explicitConfigDir) {
389
+ out.conflictFlags = '--ide-local não combina com --config-dir';
333
390
  }
334
391
  if (!out.conflictFlags && out.explicitConfigDir) {
335
392
  if (out.oxeOnly || out.allAgents) {
@@ -345,12 +402,22 @@ function parseInstallArgs(argv) {
345
402
  if (out.allAgents && !out.oxeOnly) {
346
403
  out.cursor = true;
347
404
  out.copilot = true;
405
+ out.agentOpenCode = true;
406
+ out.agentGemini = true;
407
+ out.agentCodex = true;
408
+ out.agentWindsurf = true;
409
+ out.agentAntigravity = true;
348
410
  }
349
411
  if (out.oxeOnly) {
350
412
  out.cursor = false;
351
413
  out.copilot = false;
352
414
  out.copilotCli = false;
353
415
  out.allAgents = false;
416
+ out.agentOpenCode = false;
417
+ out.agentGemini = false;
418
+ out.agentCodex = false;
419
+ out.agentWindsurf = false;
420
+ out.agentAntigravity = false;
354
421
  out.vscode = false;
355
422
  out.commands = false;
356
423
  out.agents = false;
@@ -359,7 +426,7 @@ function parseInstallArgs(argv) {
359
426
  out.cursor = true;
360
427
  out.copilot = true;
361
428
  out.integrationsUnset = false;
362
- } else if (!out.cursor && !out.copilot && !out.copilotCli && !out.vscode) {
429
+ } else if (!out.cursor && !out.copilot && !out.copilotCli && !out.vscode && !anyGranularAgent(out)) {
363
430
  out.integrationsUnset = true;
364
431
  } else {
365
432
  out.integrationsUnset = false;
@@ -453,6 +520,51 @@ function claudeUserDir(opts) {
453
520
  return path.join(os.homedir(), '.claude');
454
521
  }
455
522
 
523
+ /** Base Cursor: ~/.cursor ou <projeto>/.cursor (com --ide-local). */
524
+ function installCursorBase(opts) {
525
+ const target = path.resolve(opts.dir);
526
+ if (opts.ideLocal && opts.cursor) {
527
+ return path.join(target, '.cursor');
528
+ }
529
+ return cursorUserDir(opts);
530
+ }
531
+
532
+ /** Home Copilot CLI (skills, commands): ~/.copilot ou ./.copilot. */
533
+ function installCopilotCliHome(opts) {
534
+ const target = path.resolve(opts.dir);
535
+ if (opts.ideLocal && (opts.copilotCli || opts.allAgents)) {
536
+ return path.join(target, '.copilot');
537
+ }
538
+ return copilotUserDir(opts);
539
+ }
540
+
541
+ /** Pasta .claude (comandos) global ou no projeto. */
542
+ function installClaudeBase(opts) {
543
+ const target = path.resolve(opts.dir);
544
+ if (opts.ideLocal && (opts.copilotCli || opts.allAgents)) {
545
+ return path.join(target, '.claude');
546
+ }
547
+ return claudeUserDir(opts);
548
+ }
549
+
550
+ /** Ficheiro copilot-instructions (VS Code): ~/.copilot ou .github no repo. */
551
+ function copilotInstructionsPath(opts) {
552
+ const target = path.resolve(opts.dir);
553
+ if (opts.ideLocal && opts.copilot) {
554
+ return path.join(target, '.github', 'copilot-instructions.md');
555
+ }
556
+ return path.join(copilotUserDir(opts), 'copilot-instructions.md');
557
+ }
558
+
559
+ /** Pasta de prompts Copilot VS Code. */
560
+ function copilotPromptsDirPath(opts) {
561
+ const target = path.resolve(opts.dir);
562
+ if (opts.ideLocal && opts.copilot) {
563
+ return path.join(target, '.github', 'prompts');
564
+ }
565
+ return path.join(copilotUserDir(opts), 'prompts');
566
+ }
567
+
456
568
  /** Layout “clássico”: pasta `oxe/` na raiz do repo. Caso contrário: só `.oxe/` (workflows em `.oxe/workflows`). */
457
569
  function useFullRepoLayout(opts) {
458
570
  return opts.installAssetsGlobal === true;
@@ -556,37 +668,118 @@ function applyInstallFromOxeConfig(opts, targetDir) {
556
668
  }
557
669
  }
558
670
 
559
- /** @returns {Promise<{ cursor: boolean, copilot: boolean, copilotCli: boolean, allAgents: boolean, vscode: boolean, commands: boolean, agents: boolean }>} */
560
- async function promptIntegrationProfile() {
671
+ /**
672
+ * Mapa número da lista → chaves de runtime (estilo GSD).
673
+ * @param {string} input
674
+ * @returns {string[]}
675
+ */
676
+ function parseRuntimeMultiselect(input) {
677
+ const runtimeMap = {
678
+ '1': 'claude',
679
+ '2': 'opencode',
680
+ '3': 'gemini',
681
+ '4': 'codex',
682
+ '5': 'copilot',
683
+ '6': 'antigravity',
684
+ '7': 'cursor',
685
+ '8': 'windsurf',
686
+ };
687
+ const trimmed = (input || '7').trim();
688
+ if (trimmed === '9') return ['all'];
689
+ const choices = trimmed.split(/[\s,]+/).filter(Boolean);
690
+ const selected = [];
691
+ for (const c of choices) {
692
+ const k = runtimeMap[c];
693
+ if (k && !selected.includes(k)) selected.push(k);
694
+ }
695
+ return selected.length > 0 ? selected : ['cursor', 'copilot'];
696
+ }
697
+
698
+ /**
699
+ * @param {InstallOpts} opts
700
+ * @param {string[]} keys
701
+ */
702
+ function applyRuntimeKeysToOpts(opts, keys) {
703
+ if (keys.includes('all')) {
704
+ opts.cursor = true;
705
+ opts.copilot = true;
706
+ opts.copilotCli = true;
707
+ opts.allAgents = true;
708
+ opts.agentOpenCode = true;
709
+ opts.agentGemini = true;
710
+ opts.agentCodex = true;
711
+ opts.agentWindsurf = true;
712
+ opts.agentAntigravity = true;
713
+ opts.commands = true;
714
+ opts.agents = true;
715
+ return;
716
+ }
717
+ opts.cursor = keys.includes('cursor');
718
+ opts.copilot = keys.includes('copilot');
719
+ opts.copilotCli = keys.includes('claude');
720
+ opts.agentOpenCode = keys.includes('opencode');
721
+ opts.agentGemini = keys.includes('gemini');
722
+ opts.agentCodex = keys.includes('codex');
723
+ opts.agentWindsurf = keys.includes('windsurf');
724
+ opts.agentAntigravity = keys.includes('antigravity');
725
+ opts.allAgents =
726
+ opts.agentOpenCode &&
727
+ opts.agentGemini &&
728
+ opts.agentCodex &&
729
+ opts.agentWindsurf &&
730
+ opts.agentAntigravity;
731
+ const coreOnly = keys.length === 0;
732
+ if (coreOnly) {
733
+ opts.commands = true;
734
+ opts.agents = true;
735
+ return;
736
+ }
737
+ opts.commands = true;
738
+ opts.agents = true;
739
+ }
740
+
741
+ /** Multiselect de ambientes (1–8, 9=todos), em português. */
742
+ async function promptRuntimeSelection() {
743
+ const rl = readlinePromises.createInterface({ input: process.stdin, output: process.stdout });
744
+ const c = useAnsiColors();
745
+ try {
746
+ const home = displayPathForUser(os.homedir());
747
+ console.log(` ${c ? yellow : ''}Para quais ambientes deseja instalar o OXE?${c ? reset : ''}
748
+
749
+ ${c ? cyan : ''}1${c ? reset : ''}) Claude Code ${c ? dim : ''}(${home}/.claude, CLI Copilot)${c ? reset : ''}
750
+ ${c ? cyan : ''}2${c ? reset : ''}) OpenCode ${c ? dim : ''}(${home}/.config/opencode ou ${home}/.opencode)${c ? reset : ''}
751
+ ${c ? cyan : ''}3${c ? reset : ''}) Gemini ${c ? dim : ''}(${home}/.gemini)${c ? reset : ''}
752
+ ${c ? cyan : ''}4${c ? reset : ''}) Codex ${c ? dim : ''}(${home}/.codex, ${home}/.agents)${c ? reset : ''}
753
+ ${c ? cyan : ''}5${c ? reset : ''}) Copilot ${c ? dim : ''}(${home}/.copilot — VS Code / instruções)${c ? reset : ''}
754
+ ${c ? cyan : ''}6${c ? reset : ''}) Antigravity ${c ? dim : ''}(${home}/.gemini/antigravity)${c ? reset : ''}
755
+ ${c ? cyan : ''}7${c ? reset : ''}) Cursor ${c ? dim : ''}(${home}/.cursor)${c ? reset : ''}
756
+ ${c ? cyan : ''}8${c ? reset : ''}) Windsurf ${c ? dim : ''}(${home}/.codeium/windsurf)${c ? reset : ''}
757
+ ${c ? cyan : ''}9${c ? reset : ''}) Todos os ambientes acima
758
+
759
+ ${c ? dim : ''}Vários: 1,4,7 ou 1 4 7 (Enter = 7 Cursor + 5 Copilot, recomendado)${c ? reset : ''}
760
+ `);
761
+ const answer = await rl.question(` ${c ? dim : ''}Escolha${c ? reset : ''} ${c ? dim : ''}[7 5]${c ? reset : ''}: `);
762
+ return parseRuntimeMultiselect(answer || '7 5');
763
+ } finally {
764
+ rl.close();
765
+ }
766
+ }
767
+
768
+ /** Global vs local (pastas do projeto), estilo GSD. */
769
+ async function promptIdeLocation(opts) {
561
770
  const rl = readlinePromises.createInterface({ input: process.stdin, output: process.stdout });
562
771
  const c = useAnsiColors();
772
+ const target = path.resolve(opts.dir);
563
773
  try {
564
- console.log(` ${c ? yellow : ''}Onde você quer integrar o OXE?${c ? reset : ''}
565
- ${c ? cyan : ''}1${c ? reset : ''}) ${c ? dim : ''}Cursor + GitHub Copilot${c ? reset : ''} ${c ? dim : ''}(recomendado)${c ? reset : ''}
566
- ${c ? cyan : ''}2${c ? reset : ''}) ${c ? dim : ''} Cursor${c ? reset : ''}
567
- ${c ? cyan : ''}3${c ? reset : ''}) ${c ? dim : ''}Só Copilot${c ? reset : ''} ${c ? dim : ''}(VS Code)${c ? reset : ''}
568
- ${c ? cyan : ''}4${c ? reset : ''}) ${c ? dim : ''}Cursor + Copilot + CLIs${c ? reset : ''} ${c ? dim : ''}(~/.claude, ~/.copilot, skills Copilot)${c ? reset : ''}
569
- ${c ? cyan : ''}5${c ? reset : ''}) ${c ? dim : ''}Só o núcleo${c ? reset : ''} ${c ? dim : ''}(apenas .oxe/ com workflows, sem IDE)${c ? reset : ''}
570
- ${c ? cyan : ''}6${c ? reset : ''}) ${c ? dim : ''}Tudo + multi-agente${c ? reset : ''} ${c ? dim : ''}— opção 4 + OpenCode, Gemini, Codex, Windsurf, Antigravity${c ? reset : ''}
774
+ console.log(` ${c ? yellow : ''}Onde instalar as integrações de IDE?${c ? reset : ''}
775
+
776
+ ${c ? cyan : ''}1${c ? reset : ''}) Global ${c ? dim : ''}(~/.cursor, ~/.copilot, ~/.claude, … — disponível em todos os projetos)${c ? reset : ''}
777
+ ${c ? cyan : ''}2${c ? reset : ''}) Local ${c ? dim : ''}(${displayPathForUser(path.join(target, '.cursor'))}, .github, .claude, .copilot, só este repositório)${c ? reset : ''}
571
778
  `);
572
- const answer = await rl.question(` ${c ? cyan : ''}Escolha${c ? reset : ''} ${c ? dim : ''}[1]${c ? reset : ''}: `);
779
+ const answer = await rl.question(` ${c ? dim : ''}Escolha${c ? reset : ''} ${c ? dim : ''}[1]${c ? reset : ''}: `);
573
780
  const choice = (answer || '1').trim();
574
- if (choice === '5') {
575
- return { cursor: false, copilot: false, copilotCli: false, allAgents: false, vscode: false, commands: false, agents: false };
576
- }
577
- if (choice === '2') {
578
- return { cursor: true, copilot: false, copilotCli: false, allAgents: false, vscode: false, commands: true, agents: true };
579
- }
580
- if (choice === '3') {
581
- return { cursor: false, copilot: true, copilotCli: false, allAgents: false, vscode: false, commands: true, agents: true };
582
- }
583
- if (choice === '4') {
584
- return { cursor: true, copilot: true, copilotCli: true, allAgents: false, vscode: false, commands: true, agents: true };
585
- }
586
- if (choice === '6') {
587
- return { cursor: true, copilot: true, copilotCli: true, allAgents: true, vscode: false, commands: true, agents: true };
588
- }
589
- return { cursor: true, copilot: true, copilotCli: false, allAgents: false, vscode: false, commands: true, agents: true };
781
+ opts.ideLocal = choice === '2';
782
+ opts.explicitIdeScope = true;
590
783
  } finally {
591
784
  rl.close();
592
785
  }
@@ -594,18 +787,21 @@ async function promptIntegrationProfile() {
594
787
 
595
788
  /** @param {InstallOpts} opts */
596
789
  async function promptInstallScope(opts) {
597
- const hasIde = opts.cursor || opts.copilot || opts.copilotCli || opts.allAgents;
790
+ const hasIde = anyIdeIntegration(opts);
598
791
  if (!hasIde) return;
599
792
  const rl = readlinePromises.createInterface({ input: process.stdin, output: process.stdout });
600
793
  const c = useAnsiColors();
601
794
  try {
602
- console.log(` ${c ? yellow : ''}Como organizar o OXE no repositório?${c ? reset : ''}
603
- ${c ? dim : ''}Cursor, Copilot e CLI usam sempre a pasta do seu usuário (${c ? cyan : ''}~/.cursor${c ? dim : ''}, ${c ? cyan : ''}~/.copilot${c ? dim : ''}, ${c ? cyan : ''}~/.claude${c ? dim : ''}).${c ? reset : ''}
795
+ const ideHint = opts.ideLocal
796
+ ? `${c ? dim : ''}Integrações IDE ficam em pastas dentro deste repo (${c ? cyan : ''}.cursor${c ? dim : ''}, ${c ? cyan : ''}.github${c ? dim : ''}, ).${c ? reset : ''}`
797
+ : `${c ? dim : ''}Comandos Cursor/Copilot/CLI ficam no seu utilizador (${c ? cyan : ''}~/.cursor${c ? dim : ''}, …).${c ? reset : ''}`;
798
+ console.log(` ${c ? yellow : ''}Como organizar os ficheiros OXE na raiz do repositório?${c ? reset : ''}
799
+ ${ideHint}
604
800
 
605
801
  ${c ? cyan : ''}1${c ? reset : ''}) ${c ? dim : ''}Clássico${c ? reset : ''} — ${c ? dim : ''}pasta ${c ? cyan : ''}oxe/${c ? dim : ''} na raiz + ${c ? cyan : ''}.oxe/${c ? dim : ''} (e, se aplicável, ${c ? cyan : ''}commands/oxe${c ? dim : ''}, ${c ? cyan : ''}AGENTS.md${c ? dim : ''})${c ? reset : ''}
606
802
  ${c ? cyan : ''}2${c ? reset : ''}) ${c ? dim : ''}Só ${c ? cyan : ''}.oxe/${c ? reset : ''} ${c ? dim : ''}— workflows em ${c ? cyan : ''}.oxe/workflows/${c ? dim : ''}; sem ${c ? cyan : ''}oxe/${c ? dim : ''} na raiz${c ? reset : ''}
607
803
  `);
608
- const answer = await rl.question(` ${c ? cyan : ''}Escolha${c ? reset : ''} ${c ? dim : ''}[1]${c ? reset : ''}: `);
804
+ const answer = await rl.question(` ${c ? dim : ''}Escolha${c ? reset : ''} ${c ? dim : ''}[1]${c ? reset : ''}: `);
609
805
  const choice = (answer || '1').trim();
610
806
  opts.installAssetsGlobal = choice !== '2';
611
807
  } finally {
@@ -624,9 +820,14 @@ async function resolveInteractiveInstall(opts) {
624
820
  opts.cursor = true;
625
821
  opts.copilot = true;
626
822
  opts.allAgents = false;
823
+ opts.agentOpenCode = false;
824
+ opts.agentGemini = false;
825
+ opts.agentCodex = false;
826
+ opts.agentWindsurf = false;
827
+ opts.agentAntigravity = false;
627
828
  opts.integrationsUnset = false;
628
829
  }
629
- if (!opts.explicitScope && (opts.cursor || opts.copilot || opts.copilotCli || opts.allAgents)) {
830
+ if (!opts.explicitScope && anyIdeIntegration(opts)) {
630
831
  opts.installAssetsGlobal = false;
631
832
  }
632
833
  return;
@@ -636,8 +837,22 @@ async function resolveInteractiveInstall(opts) {
636
837
 
637
838
  if (opts.integrationsUnset) {
638
839
  if (can) {
639
- const p = await promptIntegrationProfile();
640
- Object.assign(opts, p);
840
+ const keys = await promptRuntimeSelection();
841
+ if (keys.length === 0) {
842
+ opts.cursor = false;
843
+ opts.copilot = false;
844
+ opts.copilotCli = false;
845
+ opts.allAgents = false;
846
+ opts.agentOpenCode = false;
847
+ opts.agentGemini = false;
848
+ opts.agentCodex = false;
849
+ opts.agentWindsurf = false;
850
+ opts.agentAntigravity = false;
851
+ opts.commands = false;
852
+ opts.agents = false;
853
+ } else {
854
+ applyRuntimeKeysToOpts(opts, keys);
855
+ }
641
856
  opts.integrationsUnset = false;
642
857
  } else {
643
858
  opts.cursor = true;
@@ -646,13 +861,23 @@ async function resolveInteractiveInstall(opts) {
646
861
  opts.integrationsUnset = false;
647
862
  const c = useAnsiColors();
648
863
  console.log(
649
- `\n ${c ? yellow : ''}Terminal não interativo${c ? reset : ''} — layout mínimo: só ${c ? cyan : ''}.oxe/${c ? reset : ''}; integrações em ~/.cursor e ~/.copilot. Para ${c ? cyan : ''}oxe/${c ? reset : ''} na raiz use ${c ? cyan : ''}--global${c ? reset : ''}. Outras flags: ${c ? cyan : ''}--cursor${c ? reset : ''}, ${c ? cyan : ''}--copilot${c ? reset : ''}, ${c ? cyan : ''}--all-agents${c ? reset : ''}, ${c ? cyan : ''}--oxe-only${c ? reset : ''}, ${c ? cyan : ''}OXE_NO_PROMPT=1${c ? reset : ''}.\n`
864
+ `\n ${c ? yellow : ''}Terminal não interativo${c ? reset : ''} — layout mínimo: só ${c ? cyan : ''}.oxe/${c ? reset : ''}; integrações em ~/.cursor e ~/.copilot. Para ${c ? cyan : ''}oxe/${c ? reset : ''} na raiz use ${c ? cyan : ''}--global${c ? reset : ''} (layout do repo). IDE no projeto: ${c ? cyan : ''}--ide-local${c ? reset : ''}. Flags: ${c ? cyan : ''}--cursor${c ? reset : ''}, ${c ? cyan : ''}--copilot${c ? reset : ''}, ${c ? cyan : ''}--all-agents${c ? reset : ''}, ${c ? cyan : ''}--opencode${c ? reset : ''}, … ${c ? cyan : ''}--oxe-only${c ? reset : ''}, ${c ? cyan : ''}OXE_NO_PROMPT=1${c ? reset : ''}.\n`
865
+ );
866
+ }
867
+ }
868
+
869
+ if (anyIdeIntegration(opts) && !opts.explicitIdeScope) {
870
+ if (can) await promptIdeLocation(opts);
871
+ else {
872
+ opts.ideLocal = false;
873
+ const c = useAnsiColors();
874
+ console.log(
875
+ `\n ${c ? yellow : ''}Terminal não interativo${c ? reset : ''} — integrações IDE em pastas globais (~/.cursor, …). Para instalar só neste repo: ${c ? cyan : ''}--ide-local${c ? reset : ''}.\n`
650
876
  );
651
877
  }
652
878
  }
653
879
 
654
- const hasIde = opts.cursor || opts.copilot || opts.copilotCli || opts.allAgents;
655
- if (hasIde && !opts.explicitScope) {
880
+ if (anyIdeIntegration(opts) && !opts.explicitScope) {
656
881
  if (can) await promptInstallScope(opts);
657
882
  else {
658
883
  opts.installAssetsGlobal = false;
@@ -735,6 +960,49 @@ function copyDir(srcDir, destDir, opts, pathRewriteNested = false) {
735
960
  }
736
961
  }
737
962
 
963
+ /**
964
+ * @param {string} content
965
+ * @returns {boolean}
966
+ */
967
+ function gitignoreAlreadyIgnoresOxe(content) {
968
+ for (const line of content.split(/\r?\n/)) {
969
+ const t = line.replace(/#.*$/, '').trim();
970
+ if (!t || t.startsWith('!')) continue;
971
+ if (t === '.oxe' || t === '.oxe/') return true;
972
+ if (t === '.oxe/*' || t === '.oxe/**') return true;
973
+ }
974
+ return false;
975
+ }
976
+
977
+ /**
978
+ * Garante que o `.gitignore` na raiz do projeto inclui `.oxe/` (pasta de estado OXE, não versionar).
979
+ * @param {string} projectRoot
980
+ * @param {{ dryRun?: boolean }} opts
981
+ */
982
+ function ensureGitignoreIgnoresOxeDir(projectRoot, opts = {}) {
983
+ const dryRun = opts.dryRun === true;
984
+ const giPath = path.join(projectRoot, '.gitignore');
985
+ const rel = path.relative(projectRoot, giPath) || '.gitignore';
986
+ const block =
987
+ '\n# OXE (oxe-cc) — pasta de estado local; não versionar\n' + '.oxe/\n';
988
+
989
+ if (dryRun) {
990
+ console.log(`${dim}gitignore${reset} garantir .oxe/ em ${rel}`);
991
+ return;
992
+ }
993
+
994
+ if (fs.existsSync(giPath)) {
995
+ const raw = fs.readFileSync(giPath, 'utf8');
996
+ if (gitignoreAlreadyIgnoresOxe(raw)) return;
997
+ let out = raw;
998
+ if (out.length > 0 && !/\n$/.test(out)) out += '\n';
999
+ fs.writeFileSync(giPath, out + block, 'utf8');
1000
+ } else {
1001
+ fs.writeFileSync(giPath, block.replace(/^\n/, ''), 'utf8');
1002
+ }
1003
+ console.log(`${green}gitignore${reset} ${rel} (+ .oxe/)`);
1004
+ }
1005
+
738
1006
  /**
739
1007
  * Create `.oxe/STATE.md` from template and ensure `.oxe/codebase/` exists.
740
1008
  * @param {string} target
@@ -755,6 +1023,7 @@ function bootstrapOxe(target, opts) {
755
1023
 
756
1024
  if (opts.dryRun) {
757
1025
  console.log(`${dim}init${reset} ${oxeDir}/ (STATE.md, config.json, codebase/)`);
1026
+ ensureGitignoreIgnoresOxeDir(target, { dryRun: true });
758
1027
  return;
759
1028
  }
760
1029
 
@@ -775,6 +1044,8 @@ function bootstrapOxe(target, opts) {
775
1044
  console.log(`${dim}omitido${reset} ${configDest} (já existe — use --force para substituir)`);
776
1045
  }
777
1046
  }
1047
+
1048
+ ensureGitignoreIgnoresOxeDir(target, { dryRun: false });
778
1049
  }
779
1050
 
780
1051
  /**
@@ -839,8 +1110,39 @@ function printOxeHealthDiagnostics(target, c) {
839
1110
 
840
1111
  /**
841
1112
  * @param {string} target
1113
+ * @param {{ json?: boolean }} [opts]
842
1114
  */
843
- function runStatus(target) {
1115
+ function runStatus(target, opts = {}) {
1116
+ const { config } = oxeHealth.loadOxeConfigMerged(target);
1117
+ const next = oxeHealth.suggestNextStep(target, { discuss_before_plan: config.discuss_before_plan });
1118
+ const report = oxeHealth.buildHealthReport(target);
1119
+
1120
+ if (opts.json) {
1121
+ /** @type {Record<string, unknown>} */
1122
+ const payload = {
1123
+ oxeStatusSchema: 1,
1124
+ projectRoot: path.resolve(target),
1125
+ nextStep: report.next.step,
1126
+ cursorCmd: report.next.cursorCmd,
1127
+ reason: report.next.reason,
1128
+ artifacts: report.next.artifacts,
1129
+ phase: report.phase,
1130
+ scanDate: report.scanDate,
1131
+ staleScan: report.stale,
1132
+ diagnostics: {
1133
+ configParseError: report.configParseError,
1134
+ typeErrors: report.typeErrors,
1135
+ unknownConfigKeys: report.unknownConfigKeys,
1136
+ phaseWarnings: report.phaseWarn,
1137
+ summaryGapWarning: report.summaryGapWarn,
1138
+ specWarnings: report.specWarn,
1139
+ planWarnings: report.planWarn,
1140
+ },
1141
+ };
1142
+ console.log(JSON.stringify(payload));
1143
+ return;
1144
+ }
1145
+
844
1146
  printSection('OXE ▸ status');
845
1147
  const c = useAnsiColors();
846
1148
  console.log(` ${c ? green : ''}Projeto:${c ? reset : ''} ${c ? cyan : ''}${target}${c ? reset : ''}`);
@@ -852,12 +1154,9 @@ function runStatus(target) {
852
1154
 
853
1155
  printOxeHealthDiagnostics(target, c);
854
1156
 
855
- const { config } = oxeHealth.loadOxeConfigMerged(target);
856
- const next = oxeHealth.suggestNextStep(target, { discuss_before_plan: config.discuss_before_plan });
857
-
858
1157
  console.log(`\n ${c ? yellow : ''}Próximo passo sugerido (único)${reset}`);
859
1158
  console.log(` ${c ? dim : ''}Passo:${c ? reset : ''} ${c ? green : ''}${next.step}${reset}`);
860
- console.log(` ${c ? dim : ''}No Cursor:${c ? reset : ''} ${c ? cyan : ''}${next.cursorCmd}${reset}`);
1159
+ console.log(` ${c ? dim : ''}No Cursor (referência):${c ? reset : ''} ${c ? cyan : ''}${next.cursorCmd}${reset}`);
861
1160
  console.log(` ${c ? dim : ''}Motivo:${c ? reset : ''} ${next.reason}`);
862
1161
 
863
1162
  printSummaryAndNextSteps(c, {
@@ -1053,17 +1352,31 @@ function maybePromptGlobalCli(opts) {
1053
1352
  const c = useAnsiColors();
1054
1353
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
1055
1354
 
1355
+ const colW = 42;
1356
+ const row = (cmd, desc) => {
1357
+ const pad = ' '.repeat(Math.max(1, colW - cmd.length));
1358
+ console.log(` ${c ? dim : ''}${cmd}${c ? reset : ''}${pad}${desc}`);
1359
+ };
1360
+
1056
1361
  console.log(
1057
1362
  ` ${c ? yellow : ''}Instalar o comando oxe-cc globalmente?${c ? reset : ''}
1058
- (Os arquivos OXE já foram copiados para o projeto.)
1363
+ (Os ficheiros OXE já foram copiados para o projeto.)
1364
+
1365
+ O ${c ? cyan : ''}oxe-cc${c ? reset : ''} na linha de comandos permite validar e atualizar sem depender só do npx:
1059
1366
 
1060
- ${c ? cyan : ''}1${c ? reset : ''}) ${c ? dim : ''}Não — uso ${c ? reset : ''}${c ? cyan : ''}npx oxe-cc@latest${c ? reset : ''}${c ? dim : ''} para atualizar (recomendado em CI)${c ? reset : ''}
1367
+ `
1368
+ );
1369
+ row('npx oxe-cc doctor', 'Validar workflows, config e mapa de scan');
1370
+ row('npx oxe-cc status', 'Estado .oxe/ e um próximo passo sugerido');
1371
+ row(`npx oxe-cc@latest --force`, 'Reinstalar/atualizar workflows no projeto');
1372
+ console.log(`
1373
+ ${c ? cyan : ''}1${c ? reset : ''}) ${c ? dim : ''}Não — uso ${c ? reset : ''}${c ? cyan : ''}npx oxe-cc@latest${c ? reset : ''}${c ? dim : ''} (recomendado em CI)${c ? reset : ''}
1061
1374
  ${c ? cyan : ''}2${c ? reset : ''}) ${c ? dim : ''}Sim — ${c ? reset : ''}${c ? cyan : ''}npm install -g ${readPkgName()}@${readPkgVersion()}${c ? reset : ''}${c ? dim : ''} (${c ? reset : ''}${c ? cyan : ''}oxe-cc${c ? reset : ''}${c ? dim : ''} no PATH)${c ? reset : ''}
1062
1375
  `
1063
1376
  );
1064
1377
 
1065
1378
  return new Promise((resolve) => {
1066
- rl.question(` ${c ? cyan : ''}Escolha${c ? reset : ''} ${c ? dim : ''}[1]${c ? reset : ''}: `, (answer) => {
1379
+ rl.question(` ${c ? dim : ''}Escolha${c ? reset : ''} ${c ? dim : ''}[1]${c ? reset : ''}: `, (answer) => {
1067
1380
  rl.close();
1068
1381
  const choice = (answer || '1').trim();
1069
1382
  if (choice === '2') installGlobalCliPackage();
@@ -1079,7 +1392,7 @@ function maybePromptGlobalCli(opts) {
1079
1392
 
1080
1393
  function usage() {
1081
1394
  console.log(`
1082
- ${cyan}oxe-cc${reset} — instala workflows OXE (Cursor + GitHub Copilot) no projeto
1395
+ ${cyan}oxe-cc${reset} — instala workflows OXE (núcleo .oxe/ + integrações: Cursor, Copilot, Claude, OpenCode, Gemini, Codex, Windsurf, Antigravity, …)
1083
1396
 
1084
1397
  ${green}Uso:${reset}
1085
1398
  npx oxe-cc@latest [opções] [pasta-do-projeto]
@@ -1094,30 +1407,37 @@ ${green}Uso:${reset}
1094
1407
  ${green}uninstall${reset} (remove OXE da pasta do usuário + pastas de workflows no repo)
1095
1408
  --cursor / --copilot / --copilot-cli só essa integração (omissão = todas)
1096
1409
  --all-agents também remove ficheiros multi-plataforma (com --copilot-cli implícito)
1410
+ --ide-local remove integrações IDE neste repositório (.cursor, .github, .claude, .copilot, …)
1097
1411
  --ide-only não apagar .oxe/workflows, oxe/, etc. no projeto
1098
- --config-dir <caminho> com exatamente uma flag IDE acima
1412
+ --config-dir <caminho> com exatamente uma flag IDE acima (não combina com --ide-local)
1099
1413
  --dry-run
1100
1414
  --dir <pasta> raiz do projeto (padrão: diretório atual)
1101
1415
 
1102
1416
  ${green}update${reset} (executa npx oxe-cc@latest --force na pasta do projeto)
1103
- --dir <pasta> pasta em que o npx roda (padrão: atual)
1417
+ --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)
1418
+ --if-newer só executa o npx se existir versão mais nova no npm (falha de rede/registry: saída 2, sem npx)
1419
+ --dir <pasta> pasta em que o npx roda (padrão: atual; ignorada com --check)
1104
1420
  --dry-run mostra o comando sem executar
1105
1421
  [argumentos extras…] repassados ao oxe-cc (ex.: --cursor --global)
1422
+ ${dim}CI / sem rede:${reset} OXE_UPDATE_SKIP_REGISTRY=1 desativa consultas (--check sai 2; --if-newer sai 2 sem npx)
1106
1423
 
1107
1424
  ${green}Opções da instalação:${reset}
1108
1425
  --cursor Copia comandos e regras para ~/.cursor (padrão com --all)
1109
- --copilot Mescla instruções + prompts em ~/.copilot (não fica .github/ no repo)
1426
+ --copilot Mescla instruções + prompts em ~/.copilot (global) ou .github/ (com --ide-local)
1110
1427
  --copilot-cli Skills em ~/.copilot/skills (/oxe, /oxe-scan, …) + cópia legado em ~/.claude/commands e ~/.copilot/commands
1111
1428
  (subconjunto de --all-agents)
1112
- --all-agents Opção 6 no menu: Cursor+Copilot + CLIs + OpenCode, Gemini (TOML), Codex, Windsurf, Antigravity
1429
+ --all-agents Cursor+Copilot + CLIs + OpenCode, Gemini (TOML), Codex, Windsurf, Antigravity
1430
+ --opencode / --gemini / --codex / --windsurf / --antigravity só esse agente (sem os outros)
1431
+ --ide-global Instalar integrações IDE nas pastas do utilizador (~/.cursor, …) — predefinido
1432
+ --ide-local Instalar integrações IDE só neste repositório (.cursor, .github, .copilot, …)
1113
1433
  --vscode Também copia .vscode/settings.json (chat.promptFiles)
1114
1434
  --all, -a Cursor + Copilot (padrão se não passar --cursor nem --copilot)
1115
1435
  --no-commands Não copia commands/oxe
1116
1436
  --no-agents Não copia AGENTS.md
1117
1437
  --no-init-oxe Não cria .oxe/STATE.md + .oxe/codebase/ após copiar workflows
1118
- --oxe-only Só .oxe/workflows e templates (sem Cursor, Copilot, commands, AGENTS.md)
1119
- --global Layout clássico: oxe/ na raiz + .oxe/; IDE em ~/.cursor, ~/.copilot, ~/.claude
1120
- --local Layout mínimo (padrão): só .oxe/ com .oxe/workflows; IDE nas pastas do usuário
1438
+ --oxe-only Só .oxe/workflows e templates (sem integrações IDE/CLI, commands, AGENTS.md)
1439
+ --global Layout do ${dim}repositório${reset}: pasta oxe/ na raiz + .oxe/ ${dim}(não confundir com --ide-global)${reset}
1440
+ --local Layout do ${dim}repositório${reset}: só .oxe/workflows ${dim}(não confundir com --ide-local)${reset}
1121
1441
  --global-cli, -g Depois da cópia: npm install -g oxe-cc@<versão> (sem pergunta)
1122
1442
  --no-global-cli, -l Não pergunta pelo CLI global (recomendado em CI)
1123
1443
  --force, -f Sobrescreve arquivos existentes
@@ -1130,8 +1450,11 @@ ${green}Opções da instalação:${reset}
1130
1450
 
1131
1451
  ${green}status${reset} (coerência .oxe/ + um próximo passo sugerido; não exige pacote de workflows completo)
1132
1452
  --dir <pasta> raiz do projeto (padrão: diretório atual)
1453
+ --json imprime um único objeto JSON (próximo passo + diagnósticos) em stdout; adequado a CI
1133
1454
 
1134
1455
  ${green}Atualizar (projeto já tem OXE):${reset}
1456
+ /oxe-update no Cursor (outras IDEs: mesmo fluxo pelo terminal)
1457
+ npx oxe-cc update --check só ver se há versão nova no npm
1135
1458
  npx oxe-cc update
1136
1459
  npx oxe-cc@latest --force
1137
1460
  npm install -g oxe-cc@latest && oxe-cc --force
@@ -1143,6 +1466,7 @@ ${green}Exemplos:${reset}
1143
1466
  npx oxe-cc@latest --cursor --dry-run
1144
1467
  npx oxe-cc@latest --copilot --copilot-cli
1145
1468
  npx oxe-cc@latest --all-agents
1469
+ npx oxe-cc@latest --cursor --ide-local
1146
1470
  npx oxe-cc doctor
1147
1471
  npx oxe-cc init-oxe --dir ./meu-app
1148
1472
  npx oxe-cc uninstall --dir .
@@ -1178,14 +1502,21 @@ function runInstall(opts) {
1178
1502
  ` ${c ? dim : ''}Layout repo:${c ? reset : ''} ${c ? yellow : ''}só .oxe/${c ? reset : ''} ${c ? dim : ''}(${c ? cyan : ''}.oxe/workflows${c ? dim : ''})${c ? reset : ''}`
1179
1503
  );
1180
1504
  }
1181
- const ideAny = opts.cursor || opts.copilot || opts.copilotCli || opts.allAgents;
1505
+ const ideAny = anyIdeIntegration(opts);
1182
1506
  if (ideAny) {
1183
- console.log(
1184
- ` ${c ? dim : ''}Integrações IDE:${c ? reset : ''} ${c ? yellow : ''}~/.cursor${c ? reset : ''}, ${c ? yellow : ''}~/.copilot${c ? reset : ''}, ${c ? yellow : ''}~/.claude${c ? reset : ''}${opts.allAgents ? `${c ? dim : ''}, OpenCode, Gemini, Codex, Windsurf, Antigravity${c ? reset : ''}` : ''} ${c ? dim : ''}(conforme opções)${c ? reset : ''}`
1185
- );
1507
+ const scopeHint = opts.ideLocal
1508
+ ? `${c ? dim : ''}pastas neste repositório (.cursor, .github, )${c ? reset : ''}`
1509
+ : `${c ? dim : ''}pastas do utilizador (${c ? yellow : ''}~/.cursor${c ? dim : ''}, ${c ? yellow : ''}~/.copilot${c ? dim : ''}, …)${c ? reset : ''}`;
1510
+ const extra = opts.allAgents
1511
+ ? `${c ? dim : ''} + OpenCode, Gemini, Codex, Windsurf, Antigravity${c ? reset : ''}`
1512
+ : anyGranularAgent(opts)
1513
+ ? `${c ? dim : ''} + agentes selecionados${c ? reset : ''}`
1514
+ : '';
1515
+ console.log(` ${c ? dim : ''}Integrações IDE:${c ? reset : ''} ${scopeHint}${extra}`);
1186
1516
  }
1187
1517
 
1188
1518
  const copyOpts = { dryRun: opts.dryRun, force: opts.force };
1519
+ const agentPaths = oxeAgentInstall.buildAgentInstallPaths(!opts.ideLocal, target);
1189
1520
 
1190
1521
  if (fullLayout) {
1191
1522
  copyDir(path.join(PKG_ROOT, 'oxe'), path.join(target, 'oxe'), copyOpts, false);
@@ -1195,7 +1526,7 @@ function runInstall(opts) {
1195
1526
  copyDir(path.join(PKG_ROOT, 'oxe', 'templates'), path.join(nested, 'templates'), copyOpts, true);
1196
1527
  }
1197
1528
 
1198
- const cursorBase = cursorUserDir(opts);
1529
+ const cursorBase = installCursorBase(opts);
1199
1530
  if (opts.cursor) {
1200
1531
  const cCmd = path.join(PKG_ROOT, '.cursor', 'commands');
1201
1532
  const cRules = path.join(PKG_ROOT, '.cursor', 'rules');
@@ -1206,8 +1537,9 @@ function runInstall(opts) {
1206
1537
  const doAgentClis = opts.copilotCli || opts.allAgents;
1207
1538
  if (doAgentClis) {
1208
1539
  const cCmd = path.join(PKG_ROOT, '.cursor', 'commands');
1209
- const clDest = path.join(claudeUserDir(opts), 'commands');
1210
- const cpHome = copilotUserDir(opts);
1540
+ const clBase = installClaudeBase(opts);
1541
+ const cpHome = installCopilotCliHome(opts);
1542
+ const clDest = path.join(clBase, 'commands');
1211
1543
  const cpCmdDest = path.join(cpHome, 'commands');
1212
1544
  const cpSkills = path.join(cpHome, 'skills');
1213
1545
  if (fs.existsSync(cCmd)) {
@@ -1222,29 +1554,39 @@ function runInstall(opts) {
1222
1554
  }
1223
1555
  }
1224
1556
 
1225
- if (opts.allAgents) {
1226
- const cCmd = path.join(PKG_ROOT, '.cursor', 'commands');
1227
- if (fs.existsSync(cCmd)) {
1228
- const logO = (d) => console.log(`${dim}omitido${reset} ${d} (já existe — use --force)`);
1229
- const logW = (msg) => console.log(`${dim}agents${reset} ${msg}`);
1230
- console.log(
1231
- ` ${c ? green : ''}agents${c ? reset : ''} ${c ? dim : ''}OpenCode, Gemini (TOML), Windsurf, Codex (prompts + skills), Antigravity${c ? reset : ''}`
1232
- );
1233
- oxeAgentInstall.installOpenCodeCommands(cCmd, copyOpts, idePathRewrite, logO, logW);
1234
- oxeAgentInstall.installGeminiTomlCommands(cCmd, copyOpts, idePathRewrite, logO, logW);
1235
- oxeAgentInstall.installWindsurfGlobalWorkflows(cCmd, copyOpts, idePathRewrite, logO, logW);
1236
- oxeAgentInstall.installCodexPrompts(cCmd, copyOpts, idePathRewrite, logO, logW);
1557
+ const cCmdAgents = path.join(PKG_ROOT, '.cursor', 'commands');
1558
+ if (fs.existsSync(cCmdAgents) && (opts.allAgents || anyGranularAgent(opts))) {
1559
+ const logO = (d) => console.log(`${dim}omitido${reset} ${d} (já existe — use --force)`);
1560
+ const logW = (msg) => console.log(`${dim}agents${reset} ${msg}`);
1561
+ console.log(
1562
+ ` ${c ? green : ''}agents${c ? reset : ''} ${c ? dim : ''}OpenCode, Gemini (TOML), Windsurf, Codex (prompts + skills), Antigravity (conforme seleção)${c ? reset : ''}`
1563
+ );
1564
+ if (opts.agentOpenCode || opts.allAgents) {
1565
+ oxeAgentInstall.installOpenCodeCommands(cCmdAgents, agentPaths, copyOpts, idePathRewrite, logO, logW);
1566
+ }
1567
+ if (opts.agentGemini || opts.allAgents) {
1568
+ oxeAgentInstall.installGeminiTomlCommands(cCmdAgents, agentPaths, copyOpts, idePathRewrite, logO, logW);
1569
+ }
1570
+ if (opts.agentWindsurf || opts.allAgents) {
1571
+ oxeAgentInstall.installWindsurfGlobalWorkflows(cCmdAgents, agentPaths, copyOpts, idePathRewrite, logO, logW);
1572
+ }
1573
+ if (opts.agentCodex || opts.allAgents) {
1574
+ oxeAgentInstall.installCodexPrompts(cCmdAgents, agentPaths, copyOpts, idePathRewrite, logO, logW);
1575
+ }
1576
+ if (opts.agentAntigravity || opts.allAgents) {
1237
1577
  oxeAgentInstall.installSkillTreeFromCursorCommands(
1238
- cCmd,
1239
- oxeAgentInstall.antigravitySkillsRoot(),
1578
+ cCmdAgents,
1579
+ agentPaths.antigravitySkillsRoot,
1240
1580
  copyOpts,
1241
1581
  idePathRewrite,
1242
1582
  logO,
1243
1583
  logW
1244
1584
  );
1585
+ }
1586
+ if (opts.agentCodex || opts.allAgents) {
1245
1587
  oxeAgentInstall.installSkillTreeFromCursorCommands(
1246
- cCmd,
1247
- oxeAgentInstall.codexAgentsSkillsRoot(),
1588
+ cCmdAgents,
1589
+ agentPaths.codexAgentsSkillsRoot,
1248
1590
  copyOpts,
1249
1591
  idePathRewrite,
1250
1592
  logO,
@@ -1253,17 +1595,15 @@ function runInstall(opts) {
1253
1595
  }
1254
1596
  }
1255
1597
 
1256
- const copilotRoot = copilotUserDir(opts);
1257
1598
  if (opts.copilot) {
1258
1599
  const gh = path.join(PKG_ROOT, '.github');
1259
1600
  const inst = path.join(gh, 'copilot-instructions.md');
1260
1601
  const prompts = path.join(gh, 'prompts');
1261
1602
  if (fs.existsSync(inst)) {
1262
- const dest = path.join(copilotRoot, 'copilot-instructions.md');
1263
- installMergedCopilotInstructions(inst, dest, copyOpts, idePathRewrite);
1603
+ installMergedCopilotInstructions(inst, copilotInstructionsPath(opts), copyOpts, idePathRewrite);
1264
1604
  }
1265
1605
  if (fs.existsSync(prompts)) {
1266
- copyDir(prompts, path.join(copilotRoot, 'prompts'), copyOpts, idePathRewrite);
1606
+ copyDir(prompts, copilotPromptsDirPath(opts), copyOpts, idePathRewrite);
1267
1607
  }
1268
1608
  }
1269
1609
 
@@ -1296,8 +1636,9 @@ function runInstall(opts) {
1296
1636
  }
1297
1637
 
1298
1638
  if (!opts.noInitOxe) bootstrapOxe(target, { dryRun: opts.dryRun, force: opts.force });
1639
+ else ensureGitignoreIgnoresOxeDir(target, { dryRun: opts.dryRun });
1299
1640
 
1300
- if (!opts.dryRun && (opts.cursor || opts.copilot || opts.copilotCli || opts.allAgents)) {
1641
+ if (!opts.dryRun && (opts.cursor || opts.copilot || opts.copilotCli || opts.allAgents || anyGranularAgent(opts))) {
1301
1642
  const nextFiles = {};
1302
1643
  const addTracked = (root, nameFilter) => {
1303
1644
  if (!fs.existsSync(root)) return;
@@ -1318,12 +1659,13 @@ function runInstall(opts) {
1318
1659
  /* skip */
1319
1660
  }
1320
1661
  };
1662
+ const cpCliHome = installCopilotCliHome(opts);
1321
1663
  if (opts.cursor) {
1322
1664
  addTracked(path.join(cursorBase, 'commands'), (n) => n.startsWith('oxe-') && n.endsWith('.md'));
1323
1665
  addTracked(path.join(cursorBase, 'rules'), (n) => n.includes('oxe') && (n.endsWith('.mdc') || n.endsWith('.md')));
1324
1666
  }
1325
1667
  if (opts.copilot) {
1326
- const instP = path.join(copilotRoot, 'copilot-instructions.md');
1668
+ const instP = copilotInstructionsPath(opts);
1327
1669
  if (fs.existsSync(instP)) {
1328
1670
  try {
1329
1671
  nextFiles[instP] = oxeManifest.sha256File(instP);
@@ -1331,12 +1673,12 @@ function runInstall(opts) {
1331
1673
  /* skip */
1332
1674
  }
1333
1675
  }
1334
- addTracked(path.join(copilotRoot, 'prompts'), (n) => n.startsWith('oxe-'));
1676
+ addTracked(copilotPromptsDirPath(opts), (n) => n.startsWith('oxe-'));
1335
1677
  }
1336
1678
  if (opts.copilotCli || opts.allAgents) {
1337
- addTracked(path.join(claudeUserDir(opts), 'commands'), (n) => n.startsWith('oxe-') && n.endsWith('.md'));
1338
- addTracked(path.join(copilotRoot, 'commands'), (n) => n.startsWith('oxe-') && n.endsWith('.md'));
1339
- const skRoot = path.join(copilotRoot, 'skills');
1679
+ addTracked(path.join(installClaudeBase(opts), 'commands'), (n) => n.startsWith('oxe-') && n.endsWith('.md'));
1680
+ addTracked(path.join(cpCliHome, 'commands'), (n) => n.startsWith('oxe-') && n.endsWith('.md'));
1681
+ const skRoot = path.join(cpCliHome, 'skills');
1340
1682
  if (fs.existsSync(skRoot)) {
1341
1683
  for (const sub of fs.readdirSync(skRoot, { withFileTypes: true })) {
1342
1684
  if (!sub.isDirectory() || !/^oxe($|-)/.test(sub.name)) continue;
@@ -1351,11 +1693,13 @@ function runInstall(opts) {
1351
1693
  }
1352
1694
  }
1353
1695
  }
1354
- if (opts.allAgents) {
1355
- for (const d of oxeAgentInstall.opencodeCommandDirs()) {
1696
+ if (opts.agentOpenCode || opts.allAgents) {
1697
+ for (const d of agentPaths.opencodeCommandDirs) {
1356
1698
  addTracked(d, (n) => n.startsWith('oxe-') && n.endsWith('.md'));
1357
1699
  }
1358
- const gCmd = path.join(oxeAgentInstall.geminiUserDir(), 'commands');
1700
+ }
1701
+ if (opts.agentGemini || opts.allAgents) {
1702
+ const gCmd = agentPaths.geminiCommandsBase;
1359
1703
  trackFile(path.join(gCmd, 'oxe.toml'));
1360
1704
  const oxeGem = path.join(gCmd, 'oxe');
1361
1705
  if (fs.existsSync(oxeGem)) {
@@ -1363,14 +1707,28 @@ function runInstall(opts) {
1363
1707
  if (n.endsWith('.toml')) trackFile(path.join(oxeGem, n));
1364
1708
  }
1365
1709
  }
1366
- addTracked(oxeAgentInstall.windsurfGlobalWorkflowsDir(), (n) => (n === 'oxe.md' || (n.startsWith('oxe-') && n.endsWith('.md'))));
1367
- const cxPrompts = oxeAgentInstall.codexPromptsDir();
1710
+ }
1711
+ if (opts.agentWindsurf || opts.allAgents) {
1712
+ addTracked(agentPaths.windsurfWorkflowsDir, (n) => n === 'oxe.md' || (n.startsWith('oxe-') && n.endsWith('.md')));
1713
+ }
1714
+ if (opts.agentCodex || opts.allAgents) {
1715
+ const cxPrompts = agentPaths.codexPromptsDir;
1368
1716
  if (fs.existsSync(cxPrompts)) {
1369
1717
  addTracked(cxPrompts, (n) => n.startsWith('oxe-') && n.endsWith('.md'));
1370
1718
  }
1371
- for (const rootFn of [oxeAgentInstall.antigravitySkillsRoot, oxeAgentInstall.codexAgentsSkillsRoot]) {
1372
- const root = rootFn();
1373
- if (!fs.existsSync(root)) continue;
1719
+ }
1720
+ if (opts.agentAntigravity || opts.allAgents) {
1721
+ const root = agentPaths.antigravitySkillsRoot;
1722
+ if (fs.existsSync(root)) {
1723
+ for (const sub of fs.readdirSync(root, { withFileTypes: true })) {
1724
+ if (!sub.isDirectory() || !/^oxe($|-)/.test(sub.name)) continue;
1725
+ trackFile(path.join(root, sub.name, 'SKILL.md'));
1726
+ }
1727
+ }
1728
+ }
1729
+ if (opts.agentCodex || opts.allAgents) {
1730
+ const root = agentPaths.codexAgentsSkillsRoot;
1731
+ if (fs.existsSync(root)) {
1374
1732
  for (const sub of fs.readdirSync(root, { withFileTypes: true })) {
1375
1733
  if (!sub.isDirectory() || !/^oxe($|-)/.test(sub.name)) continue;
1376
1734
  trackFile(path.join(root, sub.name, 'SKILL.md'));
@@ -1381,14 +1739,105 @@ function runInstall(opts) {
1381
1739
  oxeManifest.writeFileManifest(home, mergedManifest, readPkgVersion());
1382
1740
  }
1383
1741
 
1384
- printSummaryAndNextSteps(
1385
- c,
1386
- buildInstallSummary(opts, fullLayout, cursorBase, copilotRoot, claudeUserDir(opts))
1387
- );
1742
+ printSummaryAndNextSteps(c, buildInstallSummary(opts, fullLayout));
1388
1743
  console.log(` ${c ? green : ''}✓${c ? reset : ''} Instalação concluída com sucesso.\n`);
1389
1744
  }
1390
1745
 
1391
- /** @typedef {{ help: boolean, dryRun: boolean, cursor: boolean, copilot: boolean, copilotCli: boolean, allAgents: boolean, ideExplicit: boolean, noProject: boolean, dir: string, explicitConfigDir: string | null, parseError: boolean, unknownFlag: string, conflictFlags: string }} UninstallOpts */
1746
+ /** @typedef {{ help: boolean, dryRun: boolean, cursor: boolean, copilot: boolean, copilotCli: boolean, allAgents: boolean, ideLocal: boolean, ideExplicit: boolean, noProject: boolean, dir: string, explicitConfigDir: string | null, parseError: boolean, unknownFlag: string, conflictFlags: string }} UninstallOpts */
1747
+
1748
+ /**
1749
+ * @param {UninstallOpts} u
1750
+ * @param {string[]} removedPaths
1751
+ */
1752
+ function uninstallLocalIdeFromProject(u, removedPaths) {
1753
+ const proj = path.resolve(u.dir);
1754
+ const track = (p) => {
1755
+ if (removedPaths.indexOf(p) === -1) removedPaths.push(p);
1756
+ };
1757
+
1758
+ if (u.cursor) {
1759
+ const cmdDir = path.join(proj, '.cursor', 'commands');
1760
+ if (fs.existsSync(cmdDir)) {
1761
+ for (const name of fs.readdirSync(cmdDir)) {
1762
+ if (name.startsWith('oxe-') && name.endsWith('.md')) {
1763
+ const p = path.join(cmdDir, name);
1764
+ unlinkQuiet(p, u);
1765
+ track(p);
1766
+ }
1767
+ }
1768
+ }
1769
+ const ruleDir = path.join(proj, '.cursor', 'rules');
1770
+ if (fs.existsSync(ruleDir)) {
1771
+ for (const name of fs.readdirSync(ruleDir)) {
1772
+ if (name.includes('oxe') && (name.endsWith('.mdc') || name.endsWith('.md'))) {
1773
+ const p = path.join(ruleDir, name);
1774
+ unlinkQuiet(p, u);
1775
+ track(p);
1776
+ }
1777
+ }
1778
+ }
1779
+ }
1780
+
1781
+ if (u.copilot) {
1782
+ const inst = path.join(proj, '.github', 'copilot-instructions.md');
1783
+ stripOxeFromCopilotInstructions(inst, u);
1784
+ const pr = path.join(proj, '.github', 'prompts');
1785
+ if (fs.existsSync(pr)) {
1786
+ for (const name of fs.readdirSync(pr)) {
1787
+ if (name.startsWith('oxe-')) {
1788
+ const p = path.join(pr, name);
1789
+ unlinkQuiet(p, u);
1790
+ track(p);
1791
+ }
1792
+ }
1793
+ }
1794
+ }
1795
+
1796
+ if (u.copilotCli || u.allAgents) {
1797
+ for (const base of [path.join(proj, '.claude'), path.join(proj, '.copilot')]) {
1798
+ const cmdDir = path.join(base, 'commands');
1799
+ if (!fs.existsSync(cmdDir)) continue;
1800
+ for (const name of fs.readdirSync(cmdDir)) {
1801
+ if (name.startsWith('oxe-') && name.endsWith('.md')) {
1802
+ const p = path.join(cmdDir, name);
1803
+ unlinkQuiet(p, u);
1804
+ track(p);
1805
+ }
1806
+ }
1807
+ }
1808
+ const skillsRoot = path.join(proj, '.copilot', 'skills');
1809
+ if (fs.existsSync(skillsRoot)) {
1810
+ for (const ent of fs.readdirSync(skillsRoot, { withFileTypes: true })) {
1811
+ if (!ent.isDirectory() || !/^oxe($|-)/.test(ent.name)) continue;
1812
+ const skillFile = path.join(skillsRoot, ent.name, 'SKILL.md');
1813
+ if (!fs.existsSync(skillFile)) continue;
1814
+ let txt = '';
1815
+ try {
1816
+ txt = fs.readFileSync(skillFile, 'utf8');
1817
+ } catch {
1818
+ continue;
1819
+ }
1820
+ if (!txt.includes('<!-- oxe-cc managed -->')) continue;
1821
+ const dir = path.join(skillsRoot, ent.name);
1822
+ if (u.dryRun) {
1823
+ console.log(`${dim}rm -r${reset} ${dir}`);
1824
+ } else {
1825
+ fs.rmSync(dir, { recursive: true, force: true });
1826
+ }
1827
+ track(skillFile);
1828
+ }
1829
+ }
1830
+ }
1831
+
1832
+ if (u.copilotCli || u.allAgents) {
1833
+ const localPaths = oxeAgentInstall.buildAgentInstallPaths(false, proj);
1834
+ if (!u.dryRun) {
1835
+ oxeAgentInstall.cleanupMarkedUnifiedArtifacts(u, localPaths);
1836
+ } else {
1837
+ console.log(`${dim}agents${reset} (dry-run) limparia marcadores oxe-cc em pastas locais do projeto (OpenCode, Gemini, …)`);
1838
+ }
1839
+ }
1840
+ }
1392
1841
 
1393
1842
  /**
1394
1843
  * @param {string[]} argv
@@ -1403,6 +1852,7 @@ function parseUninstallArgs(argv) {
1403
1852
  copilot: false,
1404
1853
  copilotCli: false,
1405
1854
  allAgents: false,
1855
+ ideLocal: false,
1406
1856
  ideExplicit: false,
1407
1857
  noProject: false,
1408
1858
  dir: process.cwd(),
@@ -1431,7 +1881,8 @@ function parseUninstallArgs(argv) {
1431
1881
  out.allAgents = true;
1432
1882
  out.copilotCli = true;
1433
1883
  out.ideExplicit = true;
1434
- } else if (a === '--ide-only') out.noProject = true;
1884
+ } else if (a === '--ide-local') out.ideLocal = true;
1885
+ else if (a === '--ide-only') out.noProject = true;
1435
1886
  else if (a === '--dir' && argv[i + 1]) out.dir = path.resolve(argv[++i]);
1436
1887
  else if (!a.startsWith('-')) rest.push(path.resolve(a));
1437
1888
  else {
@@ -1447,7 +1898,9 @@ function parseUninstallArgs(argv) {
1447
1898
  out.copilotCli = true;
1448
1899
  }
1449
1900
  if (!out.conflictFlags && out.explicitConfigDir) {
1450
- if (out.allAgents) {
1901
+ if (out.ideLocal) {
1902
+ out.conflictFlags = '--config-dir não combina com --ide-local';
1903
+ } else if (out.allAgents) {
1451
1904
  out.conflictFlags = '--config-dir não combina com --all-agents';
1452
1905
  } else {
1453
1906
  const n = [out.cursor, out.copilot, out.copilotCli].filter(Boolean).length;
@@ -1539,6 +1992,14 @@ function runUninstall(u) {
1539
1992
  parseError: false,
1540
1993
  unknownFlag: '',
1541
1994
  conflictFlags: '',
1995
+ ignoreInstallConfig: false,
1996
+ ideLocal: false,
1997
+ explicitIdeScope: true,
1998
+ agentOpenCode: false,
1999
+ agentGemini: false,
2000
+ agentCodex: false,
2001
+ agentWindsurf: false,
2002
+ agentAntigravity: false,
1542
2003
  });
1543
2004
 
1544
2005
  printSection('OXE ▸ uninstall');
@@ -1628,6 +2089,10 @@ function runUninstall(u) {
1628
2089
  }
1629
2090
  }
1630
2091
 
2092
+ if (u.ideLocal) {
2093
+ uninstallLocalIdeFromProject(u, removedPaths);
2094
+ }
2095
+
1631
2096
  if (!u.noProject) {
1632
2097
  const target = u.dir;
1633
2098
  const nestedWf = path.join(target, '.oxe', 'workflows');
@@ -1678,7 +2143,7 @@ function runUninstall(u) {
1678
2143
  console.log(` ${c ? green : ''}✓${c ? reset : ''} Desinstalação concluída com sucesso.\n`);
1679
2144
  }
1680
2145
 
1681
- /** @typedef {{ help: boolean, dryRun: boolean, dir: string, rest: string[], parseError: boolean, unknownFlag: string }} UpdateOpts */
2146
+ /** @typedef {{ help: boolean, dryRun: boolean, check: boolean, ifNewer: boolean, dir: string, rest: string[], parseError: boolean, unknownFlag: string, conflictFlags: string|null }} UpdateOpts */
1682
2147
 
1683
2148
  /**
1684
2149
  * @param {string[]} argv
@@ -1689,10 +2154,13 @@ function parseUpdateArgs(argv) {
1689
2154
  const out = {
1690
2155
  help: false,
1691
2156
  dryRun: false,
2157
+ check: false,
2158
+ ifNewer: false,
1692
2159
  dir: process.cwd(),
1693
2160
  rest: [],
1694
2161
  parseError: false,
1695
2162
  unknownFlag: '',
2163
+ conflictFlags: null,
1696
2164
  };
1697
2165
  let dirExplicit = false;
1698
2166
  let firstPositionalConsumed = false;
@@ -1700,6 +2168,8 @@ function parseUpdateArgs(argv) {
1700
2168
  const a = argv[i];
1701
2169
  if (a === '-h' || a === '--help') out.help = true;
1702
2170
  else if (a === '--dry-run') out.dryRun = true;
2171
+ else if (a === '--check') out.check = true;
2172
+ else if (a === '--if-newer') out.ifNewer = true;
1703
2173
  else if (a === '--dir' && argv[i + 1]) {
1704
2174
  out.dir = path.resolve(argv[++i]);
1705
2175
  dirExplicit = true;
@@ -1714,9 +2184,54 @@ function parseUpdateArgs(argv) {
1714
2184
  break;
1715
2185
  }
1716
2186
  }
2187
+ if (!out.parseError) {
2188
+ if (out.check && out.dryRun) {
2189
+ out.conflictFlags = 'Combinação inválida: --check não pode ser usada com --dry-run.';
2190
+ } else if (out.check && out.ifNewer) {
2191
+ out.conflictFlags = 'Combinação inválida: use só --check ou só --if-newer.';
2192
+ }
2193
+ }
1717
2194
  return out;
1718
2195
  }
1719
2196
 
2197
+ /**
2198
+ * Compara versão em execução com `npm view`; termina o processo com 0 / 1 / 2.
2199
+ * @param {UpdateOpts} u
2200
+ */
2201
+ function runUpdateVersionCheck(u) {
2202
+ assertNotWslWindowsNode();
2203
+ const c = useAnsiColors();
2204
+ const skip =
2205
+ process.env.OXE_UPDATE_SKIP_REGISTRY === '1' || process.env.OXE_UPDATE_SKIP_REGISTRY === 'true';
2206
+ printSection('OXE ▸ update — verificação no npm');
2207
+ if (skip) {
2208
+ console.error(
2209
+ `${yellow}Consulta ao registro desativada (OXE_UPDATE_SKIP_REGISTRY). Não foi possível verificar a versão.${reset}\n`
2210
+ );
2211
+ process.exit(2);
2212
+ }
2213
+ const pkgName = readPkgName();
2214
+ const current = readPkgVersion();
2215
+ const res = oxeNpmVersion.syncNpmViewVersion(pkgName);
2216
+ if (!res.ok) {
2217
+ console.error(`${red}Não foi possível obter a versão no npm:${reset} ${res.error}\n`);
2218
+ process.exit(2);
2219
+ }
2220
+ const latest = res.version;
2221
+ const newer = oxeNpmVersion.isNewerThan(latest, current);
2222
+ console.log(` ${dim}Pacote:${reset} ${pkgName}`);
2223
+ console.log(` ${dim}Versão em execução:${reset} ${current}`);
2224
+ console.log(` ${dim}Última no npm (tag latest):${reset} ${latest}`);
2225
+ if (newer) {
2226
+ console.log(
2227
+ `\n ${yellow}Existe uma versão mais recente no npm.${reset} Atualize com ${cyan}/oxe-update${reset} no Cursor ou, na raiz do projeto (qualquer IDE):\n ${cyan}npx oxe-cc update${reset} (ou ${cyan}npx oxe-cc update --if-newer${reset})\n`
2228
+ );
2229
+ process.exit(1);
2230
+ }
2231
+ console.log(`\n ${c ? green : ''}✓${c ? reset : ''} Já está na mesma ou numa versão mais recente que a publicada como latest.\n`);
2232
+ process.exit(0);
2233
+ }
2234
+
1720
2235
  /**
1721
2236
  * @param {UpdateOpts} u
1722
2237
  */
@@ -1725,12 +2240,21 @@ function runUpdate(u) {
1725
2240
  const c = useAnsiColors();
1726
2241
  if (u.dryRun) {
1727
2242
  printSection('OXE ▸ update (simulação)');
1728
- console.log(` ${dim}Comando que seria executado:${reset}`);
2243
+ if (u.ifNewer) {
2244
+ console.log(
2245
+ ` ${dim}Com --if-newer, primeiro correria${reset} ${cyan}npm view ${readPkgName()} version${reset} ${dim}(salvo OXE_UPDATE_SKIP_REGISTRY). Só depois, se houver versão mais nova que a em execução, o npx abaixo.${reset}\n`
2246
+ );
2247
+ }
2248
+ console.log(` ${dim}Comando que seria executado (instalação):${reset}`);
1729
2249
  console.log(` ${cyan}npx -y oxe-cc@latest --force --no-global-cli -l${reset} ${u.rest.join(' ')}`);
1730
2250
  console.log(` ${dim}Diretório:${reset} ${u.dir}`);
1731
2251
  printSummaryAndNextSteps(c, {
1732
- bullets: ['[simulação] O npx baixaria o pacote oxe-cc@latest e rodaria a instalação com --force.'],
2252
+ bullets: [
2253
+ '[simulação] O npx baixaria o pacote oxe-cc@latest e rodaria a instalação com --force.',
2254
+ u.ifNewer ? '[simulação] Com --if-newer, sem versão nova no npm não executaria o npx.' : '',
2255
+ ].filter(Boolean),
1733
2256
  nextSteps: [
2257
+ { desc: 'Só ver versão no npm (sem instalar):', cmd: 'npx oxe-cc update --check' },
1734
2258
  { desc: 'Rodar de verdade (sem --dry-run), na pasta do projeto:', cmd: 'npx oxe-cc update' },
1735
2259
  { desc: 'Depois, validar:', cmd: 'npx oxe-cc doctor' },
1736
2260
  ],
@@ -1738,6 +2262,38 @@ function runUpdate(u) {
1738
2262
  });
1739
2263
  return;
1740
2264
  }
2265
+
2266
+ if (u.ifNewer) {
2267
+ const skip =
2268
+ process.env.OXE_UPDATE_SKIP_REGISTRY === '1' || process.env.OXE_UPDATE_SKIP_REGISTRY === 'true';
2269
+ if (skip) {
2270
+ console.error(
2271
+ `${yellow}OXE_UPDATE_SKIP_REGISTRY está ativo: não é possível comparar versões; o update não foi executado.${reset}\n`
2272
+ );
2273
+ process.exit(2);
2274
+ }
2275
+ const res = oxeNpmVersion.syncNpmViewVersion(readPkgName());
2276
+ if (!res.ok) {
2277
+ console.error(`${red}Não foi possível obter a versão no npm:${reset} ${res.error}`);
2278
+ console.error(`${dim}O update não foi executado (use sem --if-newer para forçar).${reset}\n`);
2279
+ process.exit(2);
2280
+ }
2281
+ const current = readPkgVersion();
2282
+ if (!oxeNpmVersion.isNewerThan(res.version, current)) {
2283
+ printSection('OXE ▸ update');
2284
+ console.log(
2285
+ ` ${dim}Versão em execução:${reset} ${current} ${dim}| npm latest:${reset} ${res.version}`
2286
+ );
2287
+ console.log(
2288
+ `\n ${c ? green : ''}✓${c ? reset : ''} Nenhuma versão mais nova no npm; nada a instalar. Use ${cyan}npx oxe-cc update --check${reset} para só consultar.\n`
2289
+ );
2290
+ return;
2291
+ }
2292
+ console.log(
2293
+ ` ${dim}Há versão mais nova no npm (${res.version} > ${current}); a executar npx…${reset}\n`
2294
+ );
2295
+ }
2296
+
1741
2297
  printSection('OXE ▸ update');
1742
2298
  const args = ['-y', 'oxe-cc@latest', '--force', '--no-global-cli', '-l', ...u.rest];
1743
2299
  const r = spawnSync('npx', args, {
@@ -1815,6 +2371,12 @@ async function main() {
1815
2371
  usage();
1816
2372
  process.exit(0);
1817
2373
  }
2374
+ if (u.conflictFlags) {
2375
+ printBanner();
2376
+ console.error(`${red}${u.conflictFlags}${reset}`);
2377
+ usage();
2378
+ process.exit(1);
2379
+ }
1818
2380
  if (u.parseError) {
1819
2381
  printBanner();
1820
2382
  console.error(`${red}Opção desconhecida:${reset} ${u.unknownFlag}`);
@@ -1822,6 +2384,10 @@ async function main() {
1822
2384
  process.exit(1);
1823
2385
  }
1824
2386
  printBanner();
2387
+ if (u.check) {
2388
+ runUpdateVersionCheck(u);
2389
+ return;
2390
+ }
1825
2391
  if (!u.dryRun && !fs.existsSync(u.dir)) {
1826
2392
  console.error(`${yellow}Diretório não encontrado: ${u.dir}${reset}`);
1827
2393
  process.exit(1);
@@ -1857,7 +2423,9 @@ async function main() {
1857
2423
  process.exit(0);
1858
2424
  }
1859
2425
 
1860
- printBanner();
2426
+ if (!(command === 'status' && opts.jsonOutput)) {
2427
+ printBanner();
2428
+ }
1861
2429
 
1862
2430
  const target = opts.dir;
1863
2431
  if (command === 'doctor') {
@@ -1874,7 +2442,7 @@ async function main() {
1874
2442
  console.error(`${yellow}Diretório não encontrado: ${target}${reset}`);
1875
2443
  process.exit(1);
1876
2444
  }
1877
- runStatus(target);
2445
+ runStatus(target, { json: opts.jsonOutput });
1878
2446
  return;
1879
2447
  }
1880
2448
 
@@ -1894,7 +2462,7 @@ async function main() {
1894
2462
  : ['.oxe/STATE.md, .oxe/config.json e pasta .oxe/codebase/ (criados ou atualizados conforme --force)'],
1895
2463
  nextSteps: [
1896
2464
  { desc: 'Validar o projeto:', cmd: 'npx oxe-cc doctor' },
1897
- { desc: 'Instalar integrações Cursor/Copilot (se ainda não fez):', cmd: 'npx oxe-cc@latest' },
2465
+ { desc: 'Instalar integrações IDE/CLI (se ainda não fez):', cmd: 'npx oxe-cc@latest' },
1898
2466
  { desc: 'Começar o fluxo no agente:', cmd: '/oxe-scan' },
1899
2467
  ],
1900
2468
  dryRun: opts.dryRun,