oxe-cc 0.3.2 → 0.3.4
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/.github/copilot-instructions.md +18 -12
- package/AGENTS.md +2 -1
- package/README.md +60 -38
- package/assets/readme-banner.svg +10 -9
- package/bin/banner.txt +1 -1
- package/bin/lib/oxe-manifest.cjs +117 -0
- package/bin/lib/oxe-project-health.cjs +412 -0
- package/bin/oxe-cc.js +1324 -93
- package/oxe/templates/CONFIG.md +10 -5
- package/oxe/templates/PLAN.template.md +1 -1
- package/oxe/templates/SPEC.template.md +9 -5
- package/oxe/templates/STATE.md +9 -1
- package/oxe/templates/config.template.json +6 -1
- package/oxe/workflows/discuss.md +4 -4
- package/oxe/workflows/execute.md +14 -6
- package/oxe/workflows/help.md +23 -20
- package/oxe/workflows/next.md +20 -13
- package/oxe/workflows/plan.md +14 -13
- package/oxe/workflows/quick.md +23 -10
- package/oxe/workflows/review-pr.md +11 -11
- package/oxe/workflows/scan.md +13 -11
- package/oxe/workflows/spec.md +15 -14
- package/oxe/workflows/verify.md +19 -16
- package/package.json +2 -2
package/bin/oxe-cc.js
CHANGED
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* OXE —
|
|
4
|
-
*
|
|
5
|
-
* npx oxe-cc [options] [target-dir]
|
|
6
|
-
* npx oxe-cc doctor [options] [target-dir]
|
|
7
|
-
* npx oxe-cc init-oxe [options] [target-dir]
|
|
3
|
+
* OXE — CLI em pt-BR: instala workflows no projeto, bootstrap `.oxe/`, doctor, uninstall, update.
|
|
4
|
+
* Uso: npx oxe-cc, doctor, status, init-oxe, uninstall, update (ver --help).
|
|
8
5
|
*/
|
|
9
6
|
|
|
10
7
|
const fs = require('fs');
|
|
11
8
|
const path = require('path');
|
|
9
|
+
const os = require('os');
|
|
10
|
+
const readline = require('readline');
|
|
11
|
+
const readlinePromises = require('readline/promises');
|
|
12
|
+
const { spawnSync } = require('child_process');
|
|
12
13
|
|
|
13
14
|
const PKG_ROOT = path.join(__dirname, '..');
|
|
15
|
+
const oxeManifest = require(path.join(__dirname, 'lib', 'oxe-manifest.cjs'));
|
|
16
|
+
const oxeHealth = require(path.join(__dirname, 'lib', 'oxe-project-health.cjs'));
|
|
17
|
+
|
|
18
|
+
/** GSD-style merge markers for ~/.copilot/copilot-instructions.md */
|
|
19
|
+
const OXE_INST_BEGIN = '<!-- oxe-cc:install-begin -->';
|
|
20
|
+
const OXE_INST_END = '<!-- oxe-cc:install-end -->';
|
|
14
21
|
|
|
15
22
|
const cyan = '\x1b[36m';
|
|
16
23
|
const green = '\x1b[32m';
|
|
@@ -20,6 +27,9 @@ const red = '\x1b[31m';
|
|
|
20
27
|
const bold = '\x1b[1m';
|
|
21
28
|
const reset = '\x1b[0m';
|
|
22
29
|
|
|
30
|
+
/** @type {string} */
|
|
31
|
+
const RULE = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━';
|
|
32
|
+
|
|
23
33
|
/** Plain banner if banner.txt is missing (keep in sync with bin/banner.txt style). */
|
|
24
34
|
const DEFAULT_BANNER = ` .============================================.
|
|
25
35
|
| OXE · spec-driven workflow CLI |
|
|
@@ -34,6 +44,18 @@ function useAnsiColors() {
|
|
|
34
44
|
return process.stdout.isTTY === true;
|
|
35
45
|
}
|
|
36
46
|
|
|
47
|
+
/** Section header (GSD-inspired). */
|
|
48
|
+
function printSection(title) {
|
|
49
|
+
const c = useAnsiColors();
|
|
50
|
+
if (!c) {
|
|
51
|
+
console.log(`\n${title}\n${'─'.repeat(50)}\n`);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
console.log(`\n${dim}${RULE}${reset}`);
|
|
55
|
+
console.log(` ${cyan}${bold}${title}${reset}`);
|
|
56
|
+
console.log(`${dim}${RULE}${reset}\n`);
|
|
57
|
+
}
|
|
58
|
+
|
|
37
59
|
/** Print branded header; skip with OXE_NO_BANNER=1. Not used for --version (scripts). */
|
|
38
60
|
function printBanner() {
|
|
39
61
|
if (process.env.OXE_NO_BANNER === '1' || process.env.OXE_NO_BANNER === 'true') return;
|
|
@@ -49,6 +71,7 @@ function printBanner() {
|
|
|
49
71
|
}
|
|
50
72
|
}
|
|
51
73
|
const text = raw.replace(/\{version\}/g, ver).replace(/\r\n/g, '\n').trimEnd();
|
|
74
|
+
if (color) console.log(`${dim}${RULE}${reset}\n`);
|
|
52
75
|
if (!color) {
|
|
53
76
|
console.log(text + '\n');
|
|
54
77
|
return;
|
|
@@ -58,10 +81,152 @@ function printBanner() {
|
|
|
58
81
|
if (line.includes(`v${ver}`)) console.log(`${dim}${line}${reset}`);
|
|
59
82
|
else console.log(`${cyan}${bold}${line}${reset}`);
|
|
60
83
|
}
|
|
61
|
-
console.log(
|
|
84
|
+
if (color) console.log(`\n${dim}${RULE}${reset}\n`);
|
|
85
|
+
else console.log('');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Caminho amigável: prefere ~/ quando estiver sob o HOME.
|
|
90
|
+
* @param {string} absPath
|
|
91
|
+
*/
|
|
92
|
+
function displayPathForUser(absPath) {
|
|
93
|
+
const home = os.homedir();
|
|
94
|
+
const rel = path.relative(home, absPath);
|
|
95
|
+
if (rel.startsWith('..') || path.isAbsolute(rel)) return absPath;
|
|
96
|
+
const normalized = rel.split(path.sep).join('/');
|
|
97
|
+
return '~/' + normalized;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Rodapé estilo GSD: o que foi afetado + próximos comandos (pt-BR).
|
|
102
|
+
* @param {boolean} c
|
|
103
|
+
* @param {{ bullets: string[], nextSteps: { desc: string, cmd: string }[], dryRun?: boolean }} block
|
|
104
|
+
*/
|
|
105
|
+
function printSummaryAndNextSteps(c, { bullets, nextSteps, dryRun = false }) {
|
|
106
|
+
const title = dryRun ? 'Simulação (dry-run)' : 'Resumo do que foi feito';
|
|
107
|
+
console.log(`\n ${c ? dim : ''}${RULE}${reset}`);
|
|
108
|
+
console.log(` ${c ? cyan : ''}${title}${reset}`);
|
|
109
|
+
for (const b of bullets) {
|
|
110
|
+
console.log(` ${c ? green : ''}•${c ? reset : ''} ${b}`);
|
|
111
|
+
}
|
|
112
|
+
if (nextSteps.length) {
|
|
113
|
+
console.log(`\n ${c ? yellow : ''}Próximos passos sugeridos${c ? reset : ''}`);
|
|
114
|
+
let n = 1;
|
|
115
|
+
for (const s of nextSteps) {
|
|
116
|
+
console.log(` ${c ? dim : ''}${n}.${c ? reset : ''} ${s.desc}`);
|
|
117
|
+
console.log(` ${c ? cyan : ''}${s.cmd}${reset}`);
|
|
118
|
+
n += 1;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
console.log(` ${c ? dim : ''}${RULE}${reset}\n`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* @param {InstallOpts} opts
|
|
126
|
+
* @param {boolean} fullLayout
|
|
127
|
+
* @param {string} cursorBase
|
|
128
|
+
* @param {string} copilotRoot
|
|
129
|
+
* @param {string} claudeBase
|
|
130
|
+
*/
|
|
131
|
+
function buildInstallSummary(opts, fullLayout, cursorBase, copilotRoot, claudeBase) {
|
|
132
|
+
const bullets = [];
|
|
133
|
+
const prefix = opts.dryRun ? '[simulação] ' : '';
|
|
134
|
+
|
|
135
|
+
if (opts.oxeOnly) {
|
|
136
|
+
bullets.push(`${prefix}Repositório: .oxe/workflows/ e .oxe/templates/`);
|
|
137
|
+
} else if (fullLayout) {
|
|
138
|
+
bullets.push(`${prefix}Repositório: pastas oxe/ e .oxe/ (workflows e templates)`);
|
|
139
|
+
if (opts.commands) bullets.push(`${prefix}Repositório: commands/oxe/ (comandos estilo Claude)`);
|
|
140
|
+
if (opts.agents) bullets.push(`${prefix}Repositório: AGENTS.md na raiz`);
|
|
141
|
+
if (opts.vscode) bullets.push(`${prefix}Repositório: .vscode/settings.json (chat.promptFiles)`);
|
|
142
|
+
} else {
|
|
143
|
+
bullets.push(`${prefix}Repositório: .oxe/workflows/ e .oxe/templates/ (layout mínimo, sem oxe/ na raiz)`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (!opts.noInitOxe) {
|
|
147
|
+
bullets.push(`${prefix}Bootstrap .oxe/: STATE.md, config.json e pasta codebase/`);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (opts.cursor) {
|
|
151
|
+
bullets.push(
|
|
152
|
+
`${prefix}Cursor: comandos em ${displayPathForUser(path.join(cursorBase, 'commands'))} e regras em ${displayPathForUser(path.join(cursorBase, 'rules'))}`
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
if (opts.copilot) {
|
|
156
|
+
bullets.push(
|
|
157
|
+
`${prefix}Copilot (VS Code): trecho OXE em ${displayPathForUser(path.join(copilotRoot, 'copilot-instructions.md'))} e prompts em ${displayPathForUser(path.join(copilotRoot, 'prompts'))}`
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
if (opts.copilotCli) {
|
|
161
|
+
bullets.push(
|
|
162
|
+
`${prefix}CLI: mesmos comandos em ${displayPathForUser(path.join(claudeBase, 'commands'))} e em ${displayPathForUser(path.join(copilotRoot, 'commands'))}`
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const nextSteps = [];
|
|
167
|
+
nextSteps.push({
|
|
168
|
+
desc: 'Validar workflows e pasta .oxe (rode na raiz do projeto):',
|
|
169
|
+
cmd: 'npx oxe-cc doctor',
|
|
170
|
+
});
|
|
171
|
+
nextSteps.push({
|
|
172
|
+
desc: 'Resumo rápido: coerência .oxe/ e um único próximo passo:',
|
|
173
|
+
cmd: 'npx oxe-cc status',
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
const agentHint = [];
|
|
177
|
+
if (opts.cursor) agentHint.push('Cursor');
|
|
178
|
+
if (opts.copilot) agentHint.push('Copilot no VS Code');
|
|
179
|
+
if (opts.copilotCli) agentHint.push('Copilot CLI / Claude');
|
|
180
|
+
if (agentHint.length) {
|
|
181
|
+
nextSteps.push({
|
|
182
|
+
desc: `Mapear o código no agente (${agentHint.join(', ')}):`,
|
|
183
|
+
cmd: '/oxe-scan',
|
|
184
|
+
});
|
|
185
|
+
} else if (opts.oxeOnly) {
|
|
186
|
+
nextSteps.push({
|
|
187
|
+
desc: 'Para ativar Cursor ou Copilot neste repo, instale de novo sem --oxe-only:',
|
|
188
|
+
cmd: 'npx oxe-cc@latest',
|
|
189
|
+
});
|
|
190
|
+
} else {
|
|
191
|
+
nextSteps.push({
|
|
192
|
+
desc: 'Primeiro passo do fluxo no seu editor:',
|
|
193
|
+
cmd: '/oxe-scan',
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
nextSteps.push({
|
|
198
|
+
desc: 'Atualizar OXE depois (mesmo repositório):',
|
|
199
|
+
cmd: 'npx oxe-cc@latest --force ou npx oxe-cc update',
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
return { bullets, nextSteps, dryRun: opts.dryRun };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* @param {UninstallOpts} u
|
|
207
|
+
*/
|
|
208
|
+
function buildUninstallFooter(u) {
|
|
209
|
+
const bullets = [];
|
|
210
|
+
const p = u.dryRun ? '[simulação] ' : '';
|
|
211
|
+
const rm = u.dryRun ? 'Seriam removidos' : 'Removidos';
|
|
212
|
+
if (u.cursor) bullets.push(`${p}${rm} artefatos OXE em ~/.cursor (comandos e regras).`);
|
|
213
|
+
if (u.copilot) bullets.push(`${p}${rm} prompts oxe-* e bloco OXE em copilot-instructions (se existissem).`);
|
|
214
|
+
if (u.copilotCli) bullets.push(`${p}${rm} comandos oxe-* em ~/.claude/commands e ~/.copilot/commands.`);
|
|
215
|
+
if (!u.noProject) {
|
|
216
|
+
bullets.push(
|
|
217
|
+
`${p}${u.dryRun ? 'Seriam removidas' : 'Removidas'} no repositório: .oxe/workflows, .oxe/templates, oxe/ e commands/oxe (o que existir).`
|
|
218
|
+
);
|
|
219
|
+
} else {
|
|
220
|
+
bullets.push(`${p}Pastas do repositório não ${u.dryRun ? 'seriam alteradas' : 'foram alteradas'} (--ide-only).`);
|
|
221
|
+
}
|
|
222
|
+
const nextSteps = [
|
|
223
|
+
{ desc: 'Instalar OXE de novo neste projeto:', cmd: 'npx oxe-cc@latest' },
|
|
224
|
+
{ desc: 'Conferir o estado após reinstalar:', cmd: 'npx oxe-cc doctor' },
|
|
225
|
+
];
|
|
226
|
+
return { bullets, nextSteps, dryRun: u.dryRun };
|
|
62
227
|
}
|
|
63
228
|
|
|
64
|
-
/** @typedef {{ help: boolean, version: boolean, cursor: boolean, copilot: boolean, vscode: boolean, commands: boolean, agents: boolean, force: boolean, dryRun: boolean, dir: string, all: boolean, noInitOxe: boolean, oxeOnly: boolean, parseError: boolean, unknownFlag: string }} InstallOpts */
|
|
229
|
+
/** @typedef {{ help: boolean, version: boolean, cursor: boolean, copilot: boolean, copilotCli: 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 }} InstallOpts */
|
|
65
230
|
|
|
66
231
|
/**
|
|
67
232
|
* @param {string[]} argv
|
|
@@ -74,6 +239,7 @@ function parseInstallArgs(argv) {
|
|
|
74
239
|
version: false,
|
|
75
240
|
cursor: false,
|
|
76
241
|
copilot: false,
|
|
242
|
+
copilotCli: false,
|
|
77
243
|
vscode: false,
|
|
78
244
|
commands: true,
|
|
79
245
|
agents: true,
|
|
@@ -83,16 +249,32 @@ function parseInstallArgs(argv) {
|
|
|
83
249
|
all: false,
|
|
84
250
|
noInitOxe: false,
|
|
85
251
|
oxeOnly: false,
|
|
252
|
+
globalCli: false,
|
|
253
|
+
noGlobalCli: false,
|
|
254
|
+
installAssetsGlobal: false,
|
|
255
|
+
explicitScope: false,
|
|
256
|
+
integrationsUnset: false,
|
|
257
|
+
explicitConfigDir: null,
|
|
86
258
|
parseError: false,
|
|
87
259
|
unknownFlag: '',
|
|
260
|
+
conflictFlags: '',
|
|
88
261
|
restPositional: [],
|
|
89
262
|
};
|
|
90
263
|
for (let i = 0; i < argv.length; i++) {
|
|
91
264
|
const a = argv[i];
|
|
92
265
|
if (a === '-h' || a === '--help') out.help = true;
|
|
93
266
|
else if (a === '-v' || a === '--version') out.version = true;
|
|
94
|
-
else if (a === '--
|
|
267
|
+
else if ((a === '--config-dir' || a === '-c') && argv[i + 1]) {
|
|
268
|
+
out.explicitConfigDir = path.resolve(expandTilde(argv[++i]));
|
|
269
|
+
} else if (a === '--global') {
|
|
270
|
+
out.installAssetsGlobal = true;
|
|
271
|
+
out.explicitScope = true;
|
|
272
|
+
} else if (a === '--local') {
|
|
273
|
+
out.installAssetsGlobal = false;
|
|
274
|
+
out.explicitScope = true;
|
|
275
|
+
} else if (a === '--cursor') out.cursor = true;
|
|
95
276
|
else if (a === '--copilot') out.copilot = true;
|
|
277
|
+
else if (a === '--copilot-cli') out.copilotCli = true;
|
|
96
278
|
else if (a === '--vscode') out.vscode = true;
|
|
97
279
|
else if (a === '--no-commands') out.commands = false;
|
|
98
280
|
else if (a === '--no-agents') out.agents = false;
|
|
@@ -101,6 +283,8 @@ function parseInstallArgs(argv) {
|
|
|
101
283
|
else if (a === '--all' || a === '-a') out.all = true;
|
|
102
284
|
else if (a === '--no-init-oxe') out.noInitOxe = true;
|
|
103
285
|
else if (a === '--oxe-only') out.oxeOnly = true;
|
|
286
|
+
else if (a === '--global-cli' || a === '-g') out.globalCli = true;
|
|
287
|
+
else if (a === '--no-global-cli' || a === '-l') out.noGlobalCli = true;
|
|
104
288
|
else if (a === '--dir' && argv[i + 1]) {
|
|
105
289
|
out.dir = path.resolve(argv[++i]);
|
|
106
290
|
} else if (!a.startsWith('-')) out.restPositional.push(a);
|
|
@@ -110,15 +294,35 @@ function parseInstallArgs(argv) {
|
|
|
110
294
|
break;
|
|
111
295
|
}
|
|
112
296
|
}
|
|
297
|
+
if (out.globalCli && out.noGlobalCli) {
|
|
298
|
+
out.conflictFlags = 'Não use --global-cli (-g) e --no-global-cli (-l) ao mesmo tempo';
|
|
299
|
+
}
|
|
300
|
+
if (!out.conflictFlags && argv.includes('--global') && argv.includes('--local')) {
|
|
301
|
+
out.conflictFlags = 'Não use --global e --local ao mesmo tempo';
|
|
302
|
+
}
|
|
303
|
+
if (!out.conflictFlags && out.explicitConfigDir) {
|
|
304
|
+
const ideCount = [out.cursor, out.copilot, out.copilotCli].filter(Boolean).length;
|
|
305
|
+
if (out.oxeOnly || ideCount !== 1) {
|
|
306
|
+
out.conflictFlags =
|
|
307
|
+
'--config-dir exige exatamente um entre --cursor, --copilot e --copilot-cli (e não combina com --oxe-only)';
|
|
308
|
+
}
|
|
309
|
+
}
|
|
113
310
|
if (out.oxeOnly) {
|
|
114
311
|
out.cursor = false;
|
|
115
312
|
out.copilot = false;
|
|
313
|
+
out.copilotCli = false;
|
|
116
314
|
out.vscode = false;
|
|
117
315
|
out.commands = false;
|
|
118
316
|
out.agents = false;
|
|
119
|
-
|
|
317
|
+
out.integrationsUnset = false;
|
|
318
|
+
} else if (out.all) {
|
|
120
319
|
out.cursor = true;
|
|
121
320
|
out.copilot = true;
|
|
321
|
+
out.integrationsUnset = false;
|
|
322
|
+
} else if (!out.cursor && !out.copilot && !out.copilotCli && !out.vscode) {
|
|
323
|
+
out.integrationsUnset = true;
|
|
324
|
+
} else {
|
|
325
|
+
out.integrationsUnset = false;
|
|
122
326
|
}
|
|
123
327
|
if (out.restPositional.length) out.dir = path.resolve(out.restPositional[0]);
|
|
124
328
|
return out;
|
|
@@ -134,6 +338,16 @@ function readPkgVersion() {
|
|
|
134
338
|
}
|
|
135
339
|
}
|
|
136
340
|
|
|
341
|
+
function readPkgName() {
|
|
342
|
+
try {
|
|
343
|
+
const p = path.join(PKG_ROOT, 'package.json');
|
|
344
|
+
const j = JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
345
|
+
return typeof j.name === 'string' ? j.name : 'oxe-cc';
|
|
346
|
+
} catch {
|
|
347
|
+
return 'oxe-cc';
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
137
351
|
function readMinNode() {
|
|
138
352
|
try {
|
|
139
353
|
const p = path.join(PKG_ROOT, 'package.json');
|
|
@@ -147,6 +361,228 @@ function readMinNode() {
|
|
|
147
361
|
}
|
|
148
362
|
}
|
|
149
363
|
|
|
364
|
+
/** @param {string} filePath */
|
|
365
|
+
function expandTilde(filePath) {
|
|
366
|
+
if (filePath && typeof filePath === 'string' && filePath.startsWith('~/')) {
|
|
367
|
+
return path.join(os.homedir(), filePath.slice(2));
|
|
368
|
+
}
|
|
369
|
+
return filePath;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/** GSD-style: Windows-native Node on WSL breaks paths — abort with guidance. */
|
|
373
|
+
function assertNotWslWindowsNode() {
|
|
374
|
+
if (process.platform !== 'win32') return;
|
|
375
|
+
let isWsl = false;
|
|
376
|
+
try {
|
|
377
|
+
if (process.env.WSL_DISTRO_NAME) isWsl = true;
|
|
378
|
+
else if (fs.existsSync('/proc/version')) {
|
|
379
|
+
const pv = fs.readFileSync('/proc/version', 'utf8').toLowerCase();
|
|
380
|
+
if (pv.includes('microsoft') || pv.includes('wsl')) isWsl = true;
|
|
381
|
+
}
|
|
382
|
+
} catch {
|
|
383
|
+
/* ignore */
|
|
384
|
+
}
|
|
385
|
+
if (!isWsl) return;
|
|
386
|
+
console.error(`
|
|
387
|
+
${yellow}Node.js do Windows detectado dentro do WSL.${reset}
|
|
388
|
+
|
|
389
|
+
Isso quebra caminhos (HOME / instalação). Instale o Node nativo no WSL e execute o comando de novo.
|
|
390
|
+
`);
|
|
391
|
+
process.exit(1);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/** @param {InstallOpts} opts */
|
|
395
|
+
function cursorUserDir(opts) {
|
|
396
|
+
if (opts.explicitConfigDir && opts.cursor) return path.resolve(expandTilde(opts.explicitConfigDir));
|
|
397
|
+
if (process.env.CURSOR_CONFIG_DIR) return expandTilde(process.env.CURSOR_CONFIG_DIR);
|
|
398
|
+
return path.join(os.homedir(), '.cursor');
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/** @param {InstallOpts} opts */
|
|
402
|
+
function copilotUserDir(opts) {
|
|
403
|
+
if (opts.explicitConfigDir && opts.copilot) return path.resolve(expandTilde(opts.explicitConfigDir));
|
|
404
|
+
if (process.env.COPILOT_CONFIG_DIR) return expandTilde(process.env.COPILOT_CONFIG_DIR);
|
|
405
|
+
return path.join(os.homedir(), '.copilot');
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/** @param {InstallOpts} opts */
|
|
409
|
+
function claudeUserDir(opts) {
|
|
410
|
+
if (opts.explicitConfigDir && opts.copilotCli) return path.resolve(expandTilde(opts.explicitConfigDir));
|
|
411
|
+
if (process.env.CLAUDE_CONFIG_DIR) return expandTilde(process.env.CLAUDE_CONFIG_DIR);
|
|
412
|
+
return path.join(os.homedir(), '.claude');
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/** Layout “clássico”: pasta `oxe/` na raiz do repo. Caso contrário: só `.oxe/` (workflows em `.oxe/workflows`). */
|
|
416
|
+
function useFullRepoLayout(opts) {
|
|
417
|
+
return opts.installAssetsGlobal === true;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/** @param {string} content */
|
|
421
|
+
function adjustWorkflowPathsForNestedLayout(content) {
|
|
422
|
+
return content
|
|
423
|
+
.replace(/\boxe\/workflows\//g, '.oxe/workflows/')
|
|
424
|
+
.replace(/\boxe\/templates\//g, '.oxe/templates/');
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function isTextAssetForPathRewrite(fileName) {
|
|
428
|
+
return (
|
|
429
|
+
fileName.endsWith('.md') ||
|
|
430
|
+
fileName.endsWith('.mdc') ||
|
|
431
|
+
fileName.endsWith('.prompt.md')
|
|
432
|
+
);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function escapeForRegExp(s) {
|
|
436
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* GSD-style merge into copilot-instructions.md (user-level).
|
|
441
|
+
* @param {string} srcPath
|
|
442
|
+
* @param {string} destPath
|
|
443
|
+
* @param {{ dryRun: boolean, force: boolean }} opts
|
|
444
|
+
* @param {boolean} idePathRewrite
|
|
445
|
+
*/
|
|
446
|
+
function installMergedCopilotInstructions(srcPath, destPath, opts, idePathRewrite) {
|
|
447
|
+
let body = fs.readFileSync(srcPath, 'utf8');
|
|
448
|
+
if (idePathRewrite) body = adjustWorkflowPathsForNestedLayout(body);
|
|
449
|
+
const block = `${OXE_INST_BEGIN}\n${body.trim()}\n${OXE_INST_END}\n`;
|
|
450
|
+
if (opts.dryRun) {
|
|
451
|
+
console.log(`${dim}fusão${reset} copilot-instructions.md → ${destPath}`);
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
if (!fs.existsSync(destPath)) {
|
|
455
|
+
ensureDir(path.dirname(destPath));
|
|
456
|
+
fs.writeFileSync(destPath, block, 'utf8');
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
const existing = fs.readFileSync(destPath, 'utf8');
|
|
460
|
+
if (!opts.force) {
|
|
461
|
+
if (existing.includes(OXE_INST_BEGIN)) {
|
|
462
|
+
console.log(`${dim}omitido${reset} ${destPath} (bloco OXE já existe — use --force para atualizar)`);
|
|
463
|
+
} else {
|
|
464
|
+
console.log(`${dim}omitido${reset} ${destPath} (arquivo existe — use --force para acrescentar o bloco OXE)`);
|
|
465
|
+
}
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
ensureDir(path.dirname(destPath));
|
|
469
|
+
let merged;
|
|
470
|
+
if (existing.includes(OXE_INST_BEGIN)) {
|
|
471
|
+
const re = new RegExp(`${escapeForRegExp(OXE_INST_BEGIN)}[\\s\\S]*?${escapeForRegExp(OXE_INST_END)}`, 'm');
|
|
472
|
+
merged = existing.replace(re, block.trim());
|
|
473
|
+
} else {
|
|
474
|
+
merged = `${existing.trimEnd()}\n\n${block}`;
|
|
475
|
+
}
|
|
476
|
+
fs.writeFileSync(destPath, merged, 'utf8');
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function canInstallPrompt() {
|
|
480
|
+
return (
|
|
481
|
+
process.stdin.isTTY === true &&
|
|
482
|
+
process.stdout.isTTY === true &&
|
|
483
|
+
process.env.OXE_NO_PROMPT !== '1' &&
|
|
484
|
+
process.env.OXE_NO_PROMPT !== 'true'
|
|
485
|
+
);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/** @returns {Promise<{ cursor: boolean, copilot: boolean, copilotCli: boolean, vscode: boolean, commands: boolean, agents: boolean }>} */
|
|
489
|
+
async function promptIntegrationProfile() {
|
|
490
|
+
const rl = readlinePromises.createInterface({ input: process.stdin, output: process.stdout });
|
|
491
|
+
const c = useAnsiColors();
|
|
492
|
+
try {
|
|
493
|
+
console.log(` ${c ? yellow : ''}Onde você quer integrar o OXE?${c ? reset : ''}
|
|
494
|
+
${c ? cyan : ''}1${c ? reset : ''}) ${c ? dim : ''}Cursor + GitHub Copilot${c ? reset : ''} ${c ? dim : ''}(recomendado)${c ? reset : ''}
|
|
495
|
+
${c ? cyan : ''}2${c ? reset : ''}) ${c ? dim : ''}Só Cursor${c ? reset : ''}
|
|
496
|
+
${c ? cyan : ''}3${c ? reset : ''}) ${c ? dim : ''}Só Copilot${c ? reset : ''} ${c ? dim : ''}(VS Code)${c ? reset : ''}
|
|
497
|
+
${c ? cyan : ''}4${c ? reset : ''}) ${c ? dim : ''}Cursor + Copilot + comandos na CLI${c ? reset : ''} ${c ? dim : ''}(~/.claude e ~/.copilot)${c ? reset : ''}
|
|
498
|
+
${c ? cyan : ''}5${c ? reset : ''}) ${c ? dim : ''}Só o núcleo${c ? reset : ''} ${c ? dim : ''}(apenas .oxe/ com workflows, sem IDE)${c ? reset : ''}
|
|
499
|
+
`);
|
|
500
|
+
const answer = await rl.question(` ${c ? cyan : ''}Escolha${c ? reset : ''} ${c ? dim : ''}[1]${c ? reset : ''}: `);
|
|
501
|
+
const choice = (answer || '1').trim();
|
|
502
|
+
if (choice === '5') {
|
|
503
|
+
return { cursor: false, copilot: false, copilotCli: false, vscode: false, commands: false, agents: false };
|
|
504
|
+
}
|
|
505
|
+
if (choice === '2') {
|
|
506
|
+
return { cursor: true, copilot: false, copilotCli: false, vscode: false, commands: true, agents: true };
|
|
507
|
+
}
|
|
508
|
+
if (choice === '3') {
|
|
509
|
+
return { cursor: false, copilot: true, copilotCli: false, vscode: false, commands: true, agents: true };
|
|
510
|
+
}
|
|
511
|
+
if (choice === '4') {
|
|
512
|
+
return { cursor: true, copilot: true, copilotCli: true, vscode: false, commands: true, agents: true };
|
|
513
|
+
}
|
|
514
|
+
return { cursor: true, copilot: true, copilotCli: false, vscode: false, commands: true, agents: true };
|
|
515
|
+
} finally {
|
|
516
|
+
rl.close();
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/** @param {InstallOpts} opts */
|
|
521
|
+
async function promptInstallScope(opts) {
|
|
522
|
+
const hasIde = opts.cursor || opts.copilot || opts.copilotCli;
|
|
523
|
+
if (!hasIde) return;
|
|
524
|
+
const rl = readlinePromises.createInterface({ input: process.stdin, output: process.stdout });
|
|
525
|
+
const c = useAnsiColors();
|
|
526
|
+
try {
|
|
527
|
+
console.log(` ${c ? yellow : ''}Como organizar o OXE no repositório?${c ? reset : ''}
|
|
528
|
+
${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 : ''}
|
|
529
|
+
|
|
530
|
+
${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 : ''}
|
|
531
|
+
${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 : ''}
|
|
532
|
+
`);
|
|
533
|
+
const answer = await rl.question(` ${c ? cyan : ''}Escolha${c ? reset : ''} ${c ? dim : ''}[1]${c ? reset : ''}: `);
|
|
534
|
+
const choice = (answer || '1').trim();
|
|
535
|
+
opts.installAssetsGlobal = choice !== '2';
|
|
536
|
+
} finally {
|
|
537
|
+
rl.close();
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/** @param {InstallOpts} opts */
|
|
542
|
+
async function resolveInteractiveInstall(opts) {
|
|
543
|
+
if (opts.dryRun) {
|
|
544
|
+
if (opts.integrationsUnset) {
|
|
545
|
+
opts.cursor = true;
|
|
546
|
+
opts.copilot = true;
|
|
547
|
+
opts.integrationsUnset = false;
|
|
548
|
+
}
|
|
549
|
+
if (!opts.explicitScope && (opts.cursor || opts.copilot || opts.copilotCli)) {
|
|
550
|
+
opts.installAssetsGlobal = false;
|
|
551
|
+
}
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
const can = canInstallPrompt();
|
|
556
|
+
|
|
557
|
+
if (opts.integrationsUnset) {
|
|
558
|
+
if (can) {
|
|
559
|
+
const p = await promptIntegrationProfile();
|
|
560
|
+
Object.assign(opts, p);
|
|
561
|
+
opts.integrationsUnset = false;
|
|
562
|
+
} else {
|
|
563
|
+
opts.cursor = true;
|
|
564
|
+
opts.copilot = true;
|
|
565
|
+
opts.integrationsUnset = false;
|
|
566
|
+
const c = useAnsiColors();
|
|
567
|
+
console.log(
|
|
568
|
+
`\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 : ''}--oxe-only${c ? reset : ''}, ${c ? cyan : ''}OXE_NO_PROMPT=1${c ? reset : ''}.\n`
|
|
569
|
+
);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
const hasIde = opts.cursor || opts.copilot || opts.copilotCli;
|
|
574
|
+
if (hasIde && !opts.explicitScope) {
|
|
575
|
+
if (can) await promptInstallScope(opts);
|
|
576
|
+
else {
|
|
577
|
+
opts.installAssetsGlobal = false;
|
|
578
|
+
const c = useAnsiColors();
|
|
579
|
+
console.log(
|
|
580
|
+
`\n ${c ? yellow : ''}Terminal não interativo${c ? reset : ''} — layout do repo: só ${c ? cyan : ''}.oxe/${c ? reset : ''} (opção 2). Use ${c ? cyan : ''}--global${c ? reset : ''} para também criar ${c ? cyan : ''}oxe/${c ? reset : ''} na raiz.\n`
|
|
581
|
+
);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
150
586
|
function ensureDir(p) {
|
|
151
587
|
fs.mkdirSync(p, { recursive: true });
|
|
152
588
|
}
|
|
@@ -161,24 +597,58 @@ function copyFile(src, dest, opts) {
|
|
|
161
597
|
fs.copyFileSync(src, dest);
|
|
162
598
|
}
|
|
163
599
|
|
|
164
|
-
/**
|
|
165
|
-
|
|
600
|
+
/**
|
|
601
|
+
* @param {string} src
|
|
602
|
+
* @param {string} dest
|
|
603
|
+
* @param {{ dryRun: boolean, force: boolean }} opts
|
|
604
|
+
* @param {boolean} pathRewriteNested
|
|
605
|
+
*/
|
|
606
|
+
function copyFileMaybeRewrite(src, dest, opts, pathRewriteNested) {
|
|
607
|
+
if (opts.dryRun) {
|
|
608
|
+
console.log(`${dim}file${reset} ${src} → ${dest}`);
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
if (fs.existsSync(dest) && !opts.force) {
|
|
612
|
+
console.log(`${dim}omitido${reset} ${dest} (já existe)`);
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
ensureDir(path.dirname(dest));
|
|
616
|
+
if (pathRewriteNested && isTextAssetForPathRewrite(path.basename(src))) {
|
|
617
|
+
const t = adjustWorkflowPathsForNestedLayout(fs.readFileSync(src, 'utf8'));
|
|
618
|
+
fs.writeFileSync(dest, t, 'utf8');
|
|
619
|
+
} else {
|
|
620
|
+
fs.copyFileSync(src, dest);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
/**
|
|
625
|
+
* @param {string} srcDir
|
|
626
|
+
* @param {string} destDir
|
|
627
|
+
* @param {{ dryRun: boolean, force: boolean }} opts
|
|
628
|
+
* @param {boolean} [pathRewriteNested]
|
|
629
|
+
*/
|
|
630
|
+
function copyDir(srcDir, destDir, opts, pathRewriteNested = false) {
|
|
166
631
|
if (!fs.existsSync(srcDir)) return;
|
|
167
632
|
ensureDir(destDir);
|
|
168
633
|
const entries = fs.readdirSync(srcDir, { withFileTypes: true });
|
|
169
634
|
for (const e of entries) {
|
|
170
635
|
const s = path.join(srcDir, e.name);
|
|
171
636
|
const d = path.join(destDir, e.name);
|
|
172
|
-
if (e.isDirectory()) copyDir(s, d, opts);
|
|
637
|
+
if (e.isDirectory()) copyDir(s, d, opts, pathRewriteNested);
|
|
173
638
|
else {
|
|
174
639
|
if (fs.existsSync(d) && !opts.force) {
|
|
175
|
-
console.log(`${dim}
|
|
640
|
+
console.log(`${dim}omitido${reset} ${d} (já existe — use --force para substituir)`);
|
|
176
641
|
continue;
|
|
177
642
|
}
|
|
178
643
|
if (opts.dryRun) console.log(`${dim}file${reset} ${s} → ${d}`);
|
|
179
644
|
else {
|
|
180
645
|
ensureDir(path.dirname(d));
|
|
181
|
-
|
|
646
|
+
if (pathRewriteNested && isTextAssetForPathRewrite(e.name)) {
|
|
647
|
+
const t = adjustWorkflowPathsForNestedLayout(fs.readFileSync(s, 'utf8'));
|
|
648
|
+
fs.writeFileSync(d, t, 'utf8');
|
|
649
|
+
} else {
|
|
650
|
+
fs.copyFileSync(s, d);
|
|
651
|
+
}
|
|
182
652
|
}
|
|
183
653
|
}
|
|
184
654
|
}
|
|
@@ -198,7 +668,7 @@ function bootstrapOxe(target, opts) {
|
|
|
198
668
|
const configDest = path.join(oxeDir, 'config.json');
|
|
199
669
|
|
|
200
670
|
if (!fs.existsSync(stateSrc)) {
|
|
201
|
-
console.error(`${yellow}
|
|
671
|
+
console.error(`${yellow}aviso:${reset} modelo ausente: ${stateSrc}`);
|
|
202
672
|
return;
|
|
203
673
|
}
|
|
204
674
|
|
|
@@ -213,7 +683,7 @@ function bootstrapOxe(target, opts) {
|
|
|
213
683
|
copyFile(stateSrc, stateDest, { dryRun: false });
|
|
214
684
|
console.log(`${green}init${reset} ${stateDest}`);
|
|
215
685
|
} else {
|
|
216
|
-
console.log(`${dim}
|
|
686
|
+
console.log(`${dim}omitido${reset} ${stateDest} (já existe — use --force para substituir)`);
|
|
217
687
|
}
|
|
218
688
|
|
|
219
689
|
if (fs.existsSync(configSrc)) {
|
|
@@ -221,28 +691,133 @@ function bootstrapOxe(target, opts) {
|
|
|
221
691
|
copyFile(configSrc, configDest, { dryRun: false });
|
|
222
692
|
console.log(`${green}init${reset} ${configDest}`);
|
|
223
693
|
} else {
|
|
224
|
-
console.log(`${dim}
|
|
694
|
+
console.log(`${dim}omitido${reset} ${configDest} (já existe — use --force para substituir)`);
|
|
225
695
|
}
|
|
226
696
|
}
|
|
227
697
|
}
|
|
228
698
|
|
|
699
|
+
/** @param {string} targetProject */
|
|
700
|
+
function resolveWorkflowsDir(targetProject) {
|
|
701
|
+
const nested = path.join(targetProject, '.oxe', 'workflows');
|
|
702
|
+
const root = path.join(targetProject, 'oxe', 'workflows');
|
|
703
|
+
if (fs.existsSync(nested)) return nested;
|
|
704
|
+
if (fs.existsSync(root)) return root;
|
|
705
|
+
return null;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
/**
|
|
709
|
+
* Doctor / status: config estendida, fase STATE, scan antigo, SUMMARY, SPEC/PLAN.
|
|
710
|
+
* @param {string} target
|
|
711
|
+
* @param {boolean} c
|
|
712
|
+
*/
|
|
713
|
+
function printOxeHealthDiagnostics(target, c) {
|
|
714
|
+
const r = oxeHealth.buildHealthReport(target);
|
|
715
|
+
const { config } = oxeHealth.loadOxeConfigMerged(target);
|
|
716
|
+
|
|
717
|
+
console.log(`\n ${c ? cyan : ''}▸ Coerência .oxe/ e config${reset}`);
|
|
718
|
+
|
|
719
|
+
if (r.configParseError) {
|
|
720
|
+
console.log(` ${red}FALHA${reset} config.json: ${r.configParseError}`);
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
for (const err of r.typeErrors) {
|
|
725
|
+
console.log(` ${yellow}AVISO${reset} ${err}`);
|
|
726
|
+
}
|
|
727
|
+
if (r.unknownConfigKeys.length) {
|
|
728
|
+
console.log(
|
|
729
|
+
` ${yellow}AVISO${reset} Chaves desconhecidas em .oxe/config.json (não usadas pelo oxe-cc): ${r.unknownConfigKeys.join(', ')}`
|
|
730
|
+
);
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
if (r.phase) {
|
|
734
|
+
console.log(` ${c ? dim : ''}Fase (STATE.md):${c ? reset : ''} ${r.phase}`);
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
if (config.scan_max_age_days > 0 && r.scanDate && r.stale.stale) {
|
|
738
|
+
console.log(
|
|
739
|
+
` ${yellow}AVISO${reset} Último scan há ~${r.stale.days} dia(s) (limite: ${config.scan_max_age_days}) — considere ${cyan}/oxe-scan${reset}`
|
|
740
|
+
);
|
|
741
|
+
} else if (config.scan_max_age_days > 0 && !r.scanDate && fs.existsSync(path.join(target, '.oxe', 'STATE.md'))) {
|
|
742
|
+
console.log(
|
|
743
|
+
` ${dim}Obs.:${reset} Preencha **Data:** em STATE.md (secção Último scan) para o aviso de scan antigo, ou use scan_max_age_days: 0`
|
|
744
|
+
);
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
if (Array.isArray(config.scan_focus_globs) && config.scan_focus_globs.length) {
|
|
748
|
+
console.log(` ${c ? dim : ''}Scan (foco em .oxe/config):${c ? reset : ''} ${config.scan_focus_globs.join(', ')}`);
|
|
749
|
+
}
|
|
750
|
+
if (Array.isArray(config.scan_ignore_globs) && config.scan_ignore_globs.length) {
|
|
751
|
+
console.log(` ${c ? dim : ''}Scan (ignorar):${c ? reset : ''} ${config.scan_ignore_globs.join(', ')}`);
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
for (const w of r.phaseWarn) {
|
|
755
|
+
console.log(` ${yellow}AVISO${reset} ${w}`);
|
|
756
|
+
}
|
|
757
|
+
if (r.summaryGapWarn) {
|
|
758
|
+
console.log(` ${yellow}AVISO${reset} ${r.summaryGapWarn}`);
|
|
759
|
+
}
|
|
760
|
+
for (const w of r.specWarn) {
|
|
761
|
+
console.log(` ${yellow}AVISO${reset} ${w}`);
|
|
762
|
+
}
|
|
763
|
+
for (const w of r.planWarn) {
|
|
764
|
+
console.log(` ${yellow}AVISO${reset} ${w}`);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
/**
|
|
769
|
+
* @param {string} target
|
|
770
|
+
*/
|
|
771
|
+
function runStatus(target) {
|
|
772
|
+
printSection('OXE ▸ status');
|
|
773
|
+
const c = useAnsiColors();
|
|
774
|
+
console.log(` ${c ? green : ''}Projeto:${c ? reset : ''} ${c ? cyan : ''}${target}${c ? reset : ''}`);
|
|
775
|
+
|
|
776
|
+
const wfTgt = resolveWorkflowsDir(target);
|
|
777
|
+
if (!wfTgt) {
|
|
778
|
+
console.log(` ${yellow}AVISO${reset} Workflows OXE não encontrados — ${cyan}npx oxe-cc@latest${reset}`);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
printOxeHealthDiagnostics(target, c);
|
|
782
|
+
|
|
783
|
+
const { config } = oxeHealth.loadOxeConfigMerged(target);
|
|
784
|
+
const next = oxeHealth.suggestNextStep(target, { discuss_before_plan: config.discuss_before_plan });
|
|
785
|
+
|
|
786
|
+
console.log(`\n ${c ? yellow : ''}Próximo passo sugerido (único)${reset}`);
|
|
787
|
+
console.log(` ${c ? dim : ''}Passo:${c ? reset : ''} ${c ? green : ''}${next.step}${reset}`);
|
|
788
|
+
console.log(` ${c ? dim : ''}No Cursor:${c ? reset : ''} ${c ? cyan : ''}${next.cursorCmd}${reset}`);
|
|
789
|
+
console.log(` ${c ? dim : ''}Motivo:${c ? reset : ''} ${next.reason}`);
|
|
790
|
+
|
|
791
|
+
printSummaryAndNextSteps(c, {
|
|
792
|
+
bullets: [`Artefatos em jogo: ${next.artifacts.join(', ')}`],
|
|
793
|
+
nextSteps: [
|
|
794
|
+
{ desc: 'Diagnóstico completo (inclui pacote de workflows):', cmd: 'npx oxe-cc doctor' },
|
|
795
|
+
{ desc: 'Ação sugerida no agente:', cmd: next.cursorCmd },
|
|
796
|
+
],
|
|
797
|
+
dryRun: false,
|
|
798
|
+
});
|
|
799
|
+
console.log(` ${c ? green : ''}✓${c ? reset : ''} status concluído.\n`);
|
|
800
|
+
}
|
|
801
|
+
|
|
229
802
|
/** @param {string} target */
|
|
230
803
|
function runDoctor(target) {
|
|
804
|
+
printSection('OXE ▸ doctor');
|
|
231
805
|
const v = process.versions.node;
|
|
232
806
|
const major = parseInt(v.split('.')[0], 10);
|
|
233
807
|
const minNode = readMinNode();
|
|
234
|
-
|
|
235
|
-
console.log(`
|
|
808
|
+
const c = useAnsiColors();
|
|
809
|
+
console.log(` ${c ? green : ''}Projeto:${c ? reset : ''} ${c ? cyan : ''}${target}${c ? reset : ''}`);
|
|
810
|
+
console.log(` Node.js ${v} (mínimo exigido pelo pacote: ${minNode})`);
|
|
236
811
|
if (major < minNode) {
|
|
237
|
-
console.log(`${red}
|
|
812
|
+
console.log(`${red}FALHA${reset} Versão do Node abaixo do exigido em engines do pacote`);
|
|
238
813
|
process.exit(1);
|
|
239
814
|
}
|
|
240
815
|
console.log(`${green}OK${reset} Node.js`);
|
|
241
816
|
|
|
242
817
|
const wfPkg = path.join(PKG_ROOT, 'oxe', 'workflows');
|
|
243
|
-
const wfTgt =
|
|
818
|
+
const wfTgt = resolveWorkflowsDir(target);
|
|
244
819
|
if (!fs.existsSync(wfPkg)) {
|
|
245
|
-
console.log(`${red}
|
|
820
|
+
console.log(`${red}FALHA${reset} Workflows do pacote npm ausentes: ${wfPkg}`);
|
|
246
821
|
process.exit(1);
|
|
247
822
|
}
|
|
248
823
|
const expected = fs
|
|
@@ -250,8 +825,10 @@ function runDoctor(target) {
|
|
|
250
825
|
.filter((f) => f.endsWith('.md'))
|
|
251
826
|
.sort();
|
|
252
827
|
|
|
253
|
-
if (!
|
|
254
|
-
console.log(
|
|
828
|
+
if (!wfTgt) {
|
|
829
|
+
console.log(
|
|
830
|
+
`${yellow}AVISO${reset} Não há oxe/workflows/ nem .oxe/workflows/ neste projeto — rode ${cyan}npx oxe-cc@latest${reset} para instalar.`
|
|
831
|
+
);
|
|
255
832
|
process.exit(1);
|
|
256
833
|
}
|
|
257
834
|
|
|
@@ -263,27 +840,36 @@ function runDoctor(target) {
|
|
|
263
840
|
const extra = actual.filter((f) => !expected.includes(f));
|
|
264
841
|
|
|
265
842
|
if (missing.length) {
|
|
266
|
-
console.log(`${red}
|
|
843
|
+
console.log(`${red}FALHA${reset} Faltam workflows em relação ao pacote: ${missing.join(', ')}`);
|
|
267
844
|
process.exit(1);
|
|
268
845
|
}
|
|
269
|
-
if (extra.length)
|
|
270
|
-
|
|
846
|
+
if (extra.length) {
|
|
847
|
+
console.log(
|
|
848
|
+
`${dim}Obs.:${reset} Há workflows extras no projeto (ok em forks): ${extra.join(', ')}`
|
|
849
|
+
);
|
|
850
|
+
}
|
|
851
|
+
const wfLabel = wfTgt.includes(`${path.sep}.oxe${path.sep}`) ? '.oxe/workflows' : 'oxe/workflows';
|
|
852
|
+
console.log(`${green}OK${reset} ${wfLabel} contém os ${expected.length} arquivos esperados do pacote`);
|
|
271
853
|
|
|
272
854
|
const oxeState = path.join(target, '.oxe', 'STATE.md');
|
|
273
|
-
if (fs.existsSync(oxeState)) console.log(`${green}OK${reset} .oxe/STATE.md
|
|
274
|
-
else
|
|
855
|
+
if (fs.existsSync(oxeState)) console.log(`${green}OK${reset} .oxe/STATE.md encontrado`);
|
|
856
|
+
else {
|
|
857
|
+
console.log(
|
|
858
|
+
`${dim}Obs.:${reset} .oxe/STATE.md ausente — rode ${cyan}oxe-cc init-oxe${reset} ou instale sem ${cyan}--no-init-oxe${reset}`
|
|
859
|
+
);
|
|
860
|
+
}
|
|
275
861
|
|
|
276
862
|
const cfgPath = path.join(target, '.oxe', 'config.json');
|
|
277
863
|
if (fs.existsSync(cfgPath)) {
|
|
278
864
|
try {
|
|
279
865
|
JSON.parse(fs.readFileSync(cfgPath, 'utf8'));
|
|
280
|
-
console.log(`${green}OK${reset} .oxe/config.json (
|
|
866
|
+
console.log(`${green}OK${reset} .oxe/config.json (JSON válido)`);
|
|
281
867
|
} catch (e) {
|
|
282
|
-
console.log(`${red}
|
|
868
|
+
console.log(`${red}FALHA${reset} .oxe/config.json com JSON inválido: ${e.message}`);
|
|
283
869
|
process.exit(1);
|
|
284
870
|
}
|
|
285
871
|
} else {
|
|
286
|
-
console.log(`${dim}
|
|
872
|
+
console.log(`${dim}Obs.:${reset} .oxe/config.json ausente (opcional — ver oxe/templates/CONFIG.md)`);
|
|
287
873
|
}
|
|
288
874
|
|
|
289
875
|
const cbDir = path.join(target, '.oxe', 'codebase');
|
|
@@ -300,134 +886,744 @@ function runDoctor(target) {
|
|
|
300
886
|
const missingMaps = expectedMaps.filter((f) => !fs.existsSync(path.join(cbDir, f)));
|
|
301
887
|
if (missingMaps.length) {
|
|
302
888
|
console.log(
|
|
303
|
-
`${yellow}
|
|
889
|
+
`${yellow}Obs.:${reset} Mapa do codebase incompleto — faltam em .oxe/codebase/: ${missingMaps.join(', ')} (rode ${cyan}/oxe-scan${reset})`
|
|
890
|
+
);
|
|
891
|
+
} else {
|
|
892
|
+
console.log(`${green}OK${reset} .oxe/codebase/ com os ${expectedMaps.length} mapas esperados`);
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
printOxeHealthDiagnostics(target, c);
|
|
897
|
+
|
|
898
|
+
console.log(`\n ${green}Diagnóstico OK — nenhum bloqueio crítico encontrado.${reset}`);
|
|
899
|
+
printSummaryAndNextSteps(c, {
|
|
900
|
+
bullets: [
|
|
901
|
+
`Projeto em ${target}`,
|
|
902
|
+
`Workflows conferidos em ${wfLabel}`,
|
|
903
|
+
'Node.js e (quando existir) config.json validados',
|
|
904
|
+
],
|
|
905
|
+
nextSteps: [
|
|
906
|
+
{ desc: 'Mapear ou atualizar o codebase no agente:', cmd: '/oxe-scan' },
|
|
907
|
+
{ desc: 'Ver ajuda e ordem dos passos OXE:', cmd: '/oxe-help' },
|
|
908
|
+
{ desc: 'Reinstalar ou atualizar arquivos do OXE:', cmd: 'npx oxe-cc@latest --force' },
|
|
909
|
+
],
|
|
910
|
+
dryRun: false,
|
|
911
|
+
});
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
/**
|
|
915
|
+
* npm install -g oxe-cc@version (same version as this running CLI).
|
|
916
|
+
* @returns {boolean}
|
|
917
|
+
*/
|
|
918
|
+
function installGlobalCliPackage() {
|
|
919
|
+
const name = readPkgName();
|
|
920
|
+
const ver = readPkgVersion();
|
|
921
|
+
const spec = `${name}@${ver}`;
|
|
922
|
+
const c = useAnsiColors();
|
|
923
|
+
const dimOrEmpty = c ? dim : '';
|
|
924
|
+
const resetOrEmpty = c ? reset : '';
|
|
925
|
+
console.log(`\n ${dimOrEmpty}npm install -g ${spec}${resetOrEmpty}\n`);
|
|
926
|
+
const r = spawnSync('npm', ['install', '-g', spec], {
|
|
927
|
+
stdio: 'inherit',
|
|
928
|
+
shell: true,
|
|
929
|
+
env: process.env,
|
|
930
|
+
});
|
|
931
|
+
if (r.status === 0) {
|
|
932
|
+
console.log(
|
|
933
|
+
`\n ${c ? green : ''}✓${c ? reset : ''} ${c ? cyan : ''}oxe-cc${c ? reset : ''} disponível globalmente (execute ${c ? cyan : ''}oxe-cc --help${c ? reset : ''} em qualquer pasta).\n`
|
|
934
|
+
);
|
|
935
|
+
return true;
|
|
936
|
+
}
|
|
937
|
+
console.log(
|
|
938
|
+
`\n ${c ? yellow : ''}⚠${c ? reset : ''} npm install -g falhou. Tente manualmente: ${c ? cyan : ''}npm install -g ${spec}${c ? reset : ''}\n`
|
|
939
|
+
);
|
|
940
|
+
return false;
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
/**
|
|
944
|
+
* After copying OXE into the project: optionally install the CLI globally (like GSD’s “where to install” choice).
|
|
945
|
+
* @param {InstallOpts} opts
|
|
946
|
+
* @returns {Promise<void>}
|
|
947
|
+
*/
|
|
948
|
+
function maybePromptGlobalCli(opts) {
|
|
949
|
+
if (opts.oxeOnly) return Promise.resolve();
|
|
950
|
+
if (opts.dryRun) {
|
|
951
|
+
if (useAnsiColors()) console.log(`${dim} (dry-run — pergunta do CLI global ignorada neste modo)${reset}`);
|
|
952
|
+
return Promise.resolve();
|
|
953
|
+
}
|
|
954
|
+
if (opts.globalCli) {
|
|
955
|
+
installGlobalCliPackage();
|
|
956
|
+
return Promise.resolve();
|
|
957
|
+
}
|
|
958
|
+
if (opts.noGlobalCli) return Promise.resolve();
|
|
959
|
+
if (process.env.OXE_NO_PROMPT === '1' || process.env.OXE_NO_PROMPT === 'true') return Promise.resolve();
|
|
960
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
961
|
+
const c = useAnsiColors();
|
|
962
|
+
if (c) {
|
|
963
|
+
console.log(
|
|
964
|
+
`\n ${yellow}Terminal não interativo${reset} — sem pergunta de CLI global. Use ${cyan}npx oxe-cc@latest${reset} ou ${cyan}--global-cli${reset}.\n`
|
|
304
965
|
);
|
|
305
966
|
} else {
|
|
306
|
-
console.log(
|
|
967
|
+
console.log(
|
|
968
|
+
'\nTerminal não interativo — pergunta do CLI global ignorada. Use npx oxe-cc@latest ou --global-cli.\n'
|
|
969
|
+
);
|
|
307
970
|
}
|
|
971
|
+
return Promise.resolve();
|
|
308
972
|
}
|
|
309
973
|
|
|
310
|
-
|
|
974
|
+
const c = useAnsiColors();
|
|
975
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
976
|
+
|
|
977
|
+
console.log(
|
|
978
|
+
` ${c ? yellow : ''}Instalar o comando oxe-cc globalmente?${c ? reset : ''}
|
|
979
|
+
(Os arquivos OXE já foram copiados para o projeto.)
|
|
980
|
+
|
|
981
|
+
${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 : ''}
|
|
982
|
+
${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 : ''}
|
|
983
|
+
`
|
|
984
|
+
);
|
|
985
|
+
|
|
986
|
+
return new Promise((resolve) => {
|
|
987
|
+
rl.question(` ${c ? cyan : ''}Escolha${c ? reset : ''} ${c ? dim : ''}[1]${c ? reset : ''}: `, (answer) => {
|
|
988
|
+
rl.close();
|
|
989
|
+
const choice = (answer || '1').trim();
|
|
990
|
+
if (choice === '2') installGlobalCliPackage();
|
|
991
|
+
else {
|
|
992
|
+
console.log(
|
|
993
|
+
`\n ${c ? green : ''}✓${c ? reset : ''} Para atualizar workflows: ${c ? cyan : ''}npx oxe-cc@latest --force${c ? reset : ''} ou ${c ? cyan : ''}npx oxe-cc update${c ? reset : ''} na raiz do projeto.\n`
|
|
994
|
+
);
|
|
995
|
+
}
|
|
996
|
+
resolve();
|
|
997
|
+
});
|
|
998
|
+
});
|
|
311
999
|
}
|
|
312
1000
|
|
|
313
1001
|
function usage() {
|
|
314
1002
|
console.log(`
|
|
315
|
-
${cyan}oxe-cc${reset} —
|
|
316
|
-
|
|
317
|
-
${green}
|
|
318
|
-
npx oxe-cc@latest [
|
|
319
|
-
npx oxe-cc@latest --dir /
|
|
320
|
-
npx oxe-cc doctor [
|
|
321
|
-
npx oxe-cc
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
--
|
|
328
|
-
--
|
|
329
|
-
--
|
|
330
|
-
--
|
|
331
|
-
--
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
--dir <
|
|
1003
|
+
${cyan}oxe-cc${reset} — instala workflows OXE (Cursor + GitHub Copilot) no projeto
|
|
1004
|
+
|
|
1005
|
+
${green}Uso:${reset}
|
|
1006
|
+
npx oxe-cc@latest [opções] [pasta-do-projeto]
|
|
1007
|
+
npx oxe-cc@latest --dir /caminho/do/projeto
|
|
1008
|
+
npx oxe-cc doctor [opções] [pasta-do-projeto]
|
|
1009
|
+
npx oxe-cc status [opções] [pasta-do-projeto]
|
|
1010
|
+
npx oxe-cc init-oxe [opções] [pasta-do-projeto]
|
|
1011
|
+
npx oxe-cc uninstall [opções] [pasta-do-projeto]
|
|
1012
|
+
npx oxe-cc update [opções] [argumentos extras…]
|
|
1013
|
+
|
|
1014
|
+
${green}uninstall${reset} (remove OXE da pasta do usuário + pastas de workflows no repo)
|
|
1015
|
+
--cursor / --copilot / --copilot-cli só essa integração (omissão = todas)
|
|
1016
|
+
--ide-only não apagar .oxe/workflows, oxe/, etc. no projeto
|
|
1017
|
+
--config-dir <caminho> com exatamente uma flag IDE acima (estilo GSD)
|
|
1018
|
+
--dry-run
|
|
1019
|
+
--dir <pasta> raiz do projeto (padrão: diretório atual)
|
|
1020
|
+
|
|
1021
|
+
${green}update${reset} (executa npx oxe-cc@latest --force na pasta do projeto)
|
|
1022
|
+
--dir <pasta> pasta em que o npx roda (padrão: atual)
|
|
1023
|
+
--dry-run mostra o comando sem executar
|
|
1024
|
+
[argumentos extras…] repassados ao oxe-cc (ex.: --cursor --global)
|
|
1025
|
+
|
|
1026
|
+
${green}Opções da instalação:${reset}
|
|
1027
|
+
--cursor Copia comandos e regras para ~/.cursor (padrão com --all)
|
|
1028
|
+
--copilot Mescla instruções + prompts em ~/.copilot (não fica .github/ no repo)
|
|
1029
|
+
--copilot-cli Copia comandos para ~/.claude/commands e ~/.copilot/commands (CLI — experimental)
|
|
1030
|
+
--vscode Também copia .vscode/settings.json (chat.promptFiles)
|
|
1031
|
+
--all, -a Cursor + Copilot (padrão se não passar --cursor nem --copilot)
|
|
1032
|
+
--no-commands Não copia commands/oxe
|
|
1033
|
+
--no-agents Não copia AGENTS.md
|
|
1034
|
+
--no-init-oxe Não cria .oxe/STATE.md + .oxe/codebase/ após copiar workflows
|
|
1035
|
+
--oxe-only Só .oxe/workflows e templates (sem Cursor, Copilot, commands, AGENTS.md)
|
|
1036
|
+
--global Layout clássico: oxe/ na raiz + .oxe/; IDE em ~/.cursor, ~/.copilot, ~/.claude
|
|
1037
|
+
--local Layout mínimo (padrão): só .oxe/ com .oxe/workflows; IDE nas pastas do usuário
|
|
1038
|
+
--global-cli, -g Depois da cópia: npm install -g oxe-cc@<versão> (sem pergunta)
|
|
1039
|
+
--no-global-cli, -l Não pergunta pelo CLI global (recomendado em CI)
|
|
1040
|
+
--force, -f Sobrescreve arquivos existentes
|
|
1041
|
+
--dry-run Lista ações sem gravar
|
|
1042
|
+
--config-dir, -c <pasta> Só com exatamente um de --cursor, --copilot, --copilot-cli
|
|
1043
|
+
--dir <pasta> Pasta de destino (padrão: diretório atual)
|
|
335
1044
|
-h, --help
|
|
336
1045
|
-v, --version
|
|
337
1046
|
|
|
338
|
-
${green}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
1047
|
+
${green}status${reset} (coerência .oxe/ + um próximo passo sugerido; não exige pacote de workflows completo)
|
|
1048
|
+
--dir <pasta> raiz do projeto (padrão: diretório atual)
|
|
1049
|
+
|
|
1050
|
+
${green}Atualizar (projeto já tem OXE):${reset}
|
|
1051
|
+
npx oxe-cc update
|
|
1052
|
+
npx oxe-cc@latest --force
|
|
1053
|
+
npm install -g oxe-cc@latest && oxe-cc --force
|
|
1054
|
+
npx clear-npx-cache # se o npx ficar preso em tarball antigo (npm 7+)
|
|
342
1055
|
|
|
343
|
-
${green}
|
|
1056
|
+
${green}Exemplos:${reset}
|
|
344
1057
|
npx oxe-cc@latest
|
|
345
|
-
npx oxe-cc@latest ./
|
|
1058
|
+
npx oxe-cc@latest ./meu-app
|
|
346
1059
|
npx oxe-cc@latest --cursor --dry-run
|
|
1060
|
+
npx oxe-cc@latest --copilot --copilot-cli
|
|
347
1061
|
npx oxe-cc doctor
|
|
348
|
-
npx oxe-cc init-oxe --dir ./
|
|
1062
|
+
npx oxe-cc init-oxe --dir ./meu-app
|
|
1063
|
+
npx oxe-cc uninstall --dir .
|
|
349
1064
|
`);
|
|
350
1065
|
}
|
|
351
1066
|
|
|
352
1067
|
function runInstall(opts) {
|
|
353
1068
|
const target = opts.dir;
|
|
354
1069
|
if (!opts.dryRun && !fs.existsSync(target)) {
|
|
355
|
-
console.error(`${yellow}
|
|
1070
|
+
console.error(`${yellow}Diretório não encontrado: ${target}${reset}`);
|
|
356
1071
|
process.exit(1);
|
|
357
1072
|
}
|
|
358
1073
|
|
|
359
|
-
|
|
360
|
-
|
|
1074
|
+
assertNotWslWindowsNode();
|
|
1075
|
+
const home = os.homedir();
|
|
1076
|
+
const prevManifest = oxeManifest.loadFileManifest(home);
|
|
1077
|
+
oxeManifest.backupModifiedFromManifest(home, prevManifest, opts, { yellow, cyan, dim, reset });
|
|
1078
|
+
|
|
1079
|
+
printSection('OXE ▸ Instalação no projeto');
|
|
1080
|
+
const c = useAnsiColors();
|
|
1081
|
+
const fullLayout = useFullRepoLayout(opts);
|
|
1082
|
+
const idePathRewrite = !fullLayout;
|
|
1083
|
+
|
|
1084
|
+
console.log(` ${c ? green : ''}Destino:${c ? reset : ''} ${c ? cyan : ''}${target}${c ? reset : ''}`);
|
|
1085
|
+
if (opts.dryRun) console.log(` ${c ? yellow : ''}(dry-run)${c ? reset : ''}`);
|
|
1086
|
+
|
|
1087
|
+
if (fullLayout) {
|
|
1088
|
+
console.log(
|
|
1089
|
+
` ${c ? dim : ''}Layout repo:${c ? reset : ''} ${c ? yellow : ''}oxe/${c ? reset : ''} na raiz + ${c ? yellow : ''}.oxe/${c ? reset : ''}`
|
|
1090
|
+
);
|
|
1091
|
+
} else {
|
|
1092
|
+
console.log(
|
|
1093
|
+
` ${c ? dim : ''}Layout repo:${c ? reset : ''} ${c ? yellow : ''}só .oxe/${c ? reset : ''} ${c ? dim : ''}(${c ? cyan : ''}.oxe/workflows${c ? dim : ''})${c ? reset : ''}`
|
|
1094
|
+
);
|
|
1095
|
+
}
|
|
1096
|
+
const ideAny = opts.cursor || opts.copilot || opts.copilotCli;
|
|
1097
|
+
if (ideAny) {
|
|
1098
|
+
console.log(
|
|
1099
|
+
` ${c ? dim : ''}Integrações IDE:${c ? reset : ''} ${c ? yellow : ''}~/.cursor${c ? reset : ''}, ${c ? yellow : ''}~/.copilot${c ? reset : ''}, ${c ? yellow : ''}~/.claude${c ? reset : ''} ${c ? dim : ''}(conforme opções)${c ? reset : ''}`
|
|
1100
|
+
);
|
|
1101
|
+
}
|
|
361
1102
|
|
|
362
1103
|
const copyOpts = { dryRun: opts.dryRun, force: opts.force };
|
|
363
1104
|
|
|
364
|
-
|
|
1105
|
+
if (fullLayout) {
|
|
1106
|
+
copyDir(path.join(PKG_ROOT, 'oxe'), path.join(target, 'oxe'), copyOpts, false);
|
|
1107
|
+
} else {
|
|
1108
|
+
const nested = path.join(target, '.oxe');
|
|
1109
|
+
copyDir(path.join(PKG_ROOT, 'oxe', 'workflows'), path.join(nested, 'workflows'), copyOpts, true);
|
|
1110
|
+
copyDir(path.join(PKG_ROOT, 'oxe', 'templates'), path.join(nested, 'templates'), copyOpts, true);
|
|
1111
|
+
}
|
|
365
1112
|
|
|
1113
|
+
const cursorBase = cursorUserDir(opts);
|
|
366
1114
|
if (opts.cursor) {
|
|
367
1115
|
const cCmd = path.join(PKG_ROOT, '.cursor', 'commands');
|
|
368
1116
|
const cRules = path.join(PKG_ROOT, '.cursor', 'rules');
|
|
369
|
-
if (fs.existsSync(cCmd)) copyDir(cCmd, path.join(
|
|
370
|
-
if (fs.existsSync(cRules)) copyDir(cRules, path.join(
|
|
1117
|
+
if (fs.existsSync(cCmd)) copyDir(cCmd, path.join(cursorBase, 'commands'), copyOpts, idePathRewrite);
|
|
1118
|
+
if (fs.existsSync(cRules)) copyDir(cRules, path.join(cursorBase, 'rules'), copyOpts, idePathRewrite);
|
|
371
1119
|
}
|
|
372
1120
|
|
|
1121
|
+
if (opts.copilotCli) {
|
|
1122
|
+
const cCmd = path.join(PKG_ROOT, '.cursor', 'commands');
|
|
1123
|
+
const clDest = path.join(claudeUserDir(opts), 'commands');
|
|
1124
|
+
const cpCmdDest = path.join(copilotUserDir(opts), 'commands');
|
|
1125
|
+
if (fs.existsSync(cCmd)) {
|
|
1126
|
+
console.log(
|
|
1127
|
+
` ${c ? green : ''}cli${c ? reset : ''} ${c ? dim : ''}comandos:${c ? reset : ''} ${c ? cyan : ''}${clDest}${c ? reset : ''} ${c ? dim : ''}+${c ? reset : ''} ${c ? cyan : ''}${cpCmdDest}${c ? reset : ''} ${c ? dim : ''}(~/.claude + ~/.copilot)${c ? reset : ''}`
|
|
1128
|
+
);
|
|
1129
|
+
copyDir(cCmd, clDest, copyOpts, idePathRewrite);
|
|
1130
|
+
copyDir(cCmd, cpCmdDest, copyOpts, idePathRewrite);
|
|
1131
|
+
} else {
|
|
1132
|
+
console.warn(`${yellow}aviso:${reset} pasta ausente ${cCmd} — ignorando --copilot-cli`);
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
const copilotRoot = copilotUserDir(opts);
|
|
373
1137
|
if (opts.copilot) {
|
|
374
1138
|
const gh = path.join(PKG_ROOT, '.github');
|
|
375
1139
|
const inst = path.join(gh, 'copilot-instructions.md');
|
|
376
1140
|
const prompts = path.join(gh, 'prompts');
|
|
377
1141
|
if (fs.existsSync(inst)) {
|
|
378
|
-
const dest = path.join(
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
}
|
|
1142
|
+
const dest = path.join(copilotRoot, 'copilot-instructions.md');
|
|
1143
|
+
installMergedCopilotInstructions(inst, dest, copyOpts, idePathRewrite);
|
|
1144
|
+
}
|
|
1145
|
+
if (fs.existsSync(prompts)) {
|
|
1146
|
+
copyDir(prompts, path.join(copilotRoot, 'prompts'), copyOpts, idePathRewrite);
|
|
384
1147
|
}
|
|
385
|
-
if (fs.existsSync(prompts)) copyDir(prompts, path.join(target, '.github', 'prompts'), copyOpts);
|
|
386
1148
|
}
|
|
387
1149
|
|
|
388
|
-
if (opts.vscode) {
|
|
1150
|
+
if (opts.vscode && fullLayout) {
|
|
389
1151
|
const vs = path.join(PKG_ROOT, '.vscode', 'settings.json');
|
|
390
1152
|
if (fs.existsSync(vs)) {
|
|
391
1153
|
const dest = path.join(target, '.vscode', 'settings.json');
|
|
392
1154
|
if (opts.dryRun) console.log(`${dim}file${reset} ${vs} → ${dest}`);
|
|
393
1155
|
else {
|
|
394
|
-
if (fs.existsSync(dest) && !opts.force) console.log(`${dim}
|
|
1156
|
+
if (fs.existsSync(dest) && !opts.force) console.log(`${dim}omitido${reset} ${dest} (já existe)`);
|
|
395
1157
|
else copyFile(vs, dest, copyOpts);
|
|
396
1158
|
}
|
|
397
1159
|
}
|
|
398
1160
|
}
|
|
399
1161
|
|
|
400
|
-
if (opts.commands) {
|
|
1162
|
+
if (opts.commands && fullLayout) {
|
|
401
1163
|
const cmdSrc = path.join(PKG_ROOT, 'commands', 'oxe');
|
|
402
1164
|
const cmdDest = path.join(target, 'commands', 'oxe');
|
|
403
|
-
if (fs.existsSync(cmdSrc)) copyDir(cmdSrc, cmdDest, copyOpts);
|
|
1165
|
+
if (fs.existsSync(cmdSrc)) copyDir(cmdSrc, cmdDest, copyOpts, idePathRewrite);
|
|
404
1166
|
}
|
|
405
1167
|
|
|
406
|
-
if (opts.agents) {
|
|
1168
|
+
if (opts.agents && fullLayout) {
|
|
407
1169
|
const agents = path.join(PKG_ROOT, 'AGENTS.md');
|
|
408
1170
|
if (fs.existsSync(agents)) {
|
|
409
1171
|
const dest = path.join(target, 'AGENTS.md');
|
|
410
1172
|
if (opts.dryRun) console.log(`${dim}file${reset} ${agents} → ${dest}`);
|
|
411
|
-
else if (fs.existsSync(dest) && !opts.force) console.log(`${dim}
|
|
412
|
-
else
|
|
1173
|
+
else if (fs.existsSync(dest) && !opts.force) console.log(`${dim}omitido${reset} ${dest} (já existe)`);
|
|
1174
|
+
else copyFileMaybeRewrite(agents, dest, copyOpts, idePathRewrite);
|
|
413
1175
|
}
|
|
414
1176
|
}
|
|
415
1177
|
|
|
416
1178
|
if (!opts.noInitOxe) bootstrapOxe(target, { dryRun: opts.dryRun, force: opts.force });
|
|
417
1179
|
|
|
418
|
-
|
|
419
|
-
|
|
1180
|
+
if (!opts.dryRun && (opts.cursor || opts.copilot || opts.copilotCli)) {
|
|
1181
|
+
const nextFiles = {};
|
|
1182
|
+
const addTracked = (root, nameFilter) => {
|
|
1183
|
+
if (!fs.existsSync(root)) return;
|
|
1184
|
+
const files = oxeManifest.collectFilesRecursive(root, nameFilter);
|
|
1185
|
+
for (const f of files) {
|
|
1186
|
+
try {
|
|
1187
|
+
nextFiles[f] = oxeManifest.sha256File(f);
|
|
1188
|
+
} catch {
|
|
1189
|
+
/* skip */
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
};
|
|
1193
|
+
if (opts.cursor) {
|
|
1194
|
+
addTracked(path.join(cursorBase, 'commands'), (n) => n.startsWith('oxe-') && n.endsWith('.md'));
|
|
1195
|
+
addTracked(path.join(cursorBase, 'rules'), (n) => n.includes('oxe') && (n.endsWith('.mdc') || n.endsWith('.md')));
|
|
1196
|
+
}
|
|
1197
|
+
if (opts.copilot) {
|
|
1198
|
+
const instP = path.join(copilotRoot, 'copilot-instructions.md');
|
|
1199
|
+
if (fs.existsSync(instP)) {
|
|
1200
|
+
try {
|
|
1201
|
+
nextFiles[instP] = oxeManifest.sha256File(instP);
|
|
1202
|
+
} catch {
|
|
1203
|
+
/* skip */
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
addTracked(path.join(copilotRoot, 'prompts'), (n) => n.startsWith('oxe-'));
|
|
1207
|
+
}
|
|
1208
|
+
if (opts.copilotCli) {
|
|
1209
|
+
addTracked(path.join(claudeUserDir(opts), 'commands'), (n) => n.startsWith('oxe-') && n.endsWith('.md'));
|
|
1210
|
+
addTracked(path.join(copilotRoot, 'commands'), (n) => n.startsWith('oxe-') && n.endsWith('.md'));
|
|
1211
|
+
}
|
|
1212
|
+
const mergedManifest = { ...prevManifest, ...nextFiles };
|
|
1213
|
+
oxeManifest.writeFileManifest(home, mergedManifest, readPkgVersion());
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
printSummaryAndNextSteps(
|
|
1217
|
+
c,
|
|
1218
|
+
buildInstallSummary(opts, fullLayout, cursorBase, copilotRoot, claudeUserDir(opts))
|
|
1219
|
+
);
|
|
1220
|
+
console.log(` ${c ? green : ''}✓${c ? reset : ''} Instalação concluída com sucesso.\n`);
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
/** @typedef {{ help: boolean, dryRun: boolean, cursor: boolean, copilot: boolean, copilotCli: boolean, ideExplicit: boolean, noProject: boolean, dir: string, explicitConfigDir: string | null, parseError: boolean, unknownFlag: string, conflictFlags: string }} UninstallOpts */
|
|
1224
|
+
|
|
1225
|
+
/**
|
|
1226
|
+
* @param {string[]} argv
|
|
1227
|
+
* @returns {UninstallOpts}
|
|
1228
|
+
*/
|
|
1229
|
+
function parseUninstallArgs(argv) {
|
|
1230
|
+
/** @type {UninstallOpts} */
|
|
1231
|
+
const out = {
|
|
1232
|
+
help: false,
|
|
1233
|
+
dryRun: false,
|
|
1234
|
+
cursor: false,
|
|
1235
|
+
copilot: false,
|
|
1236
|
+
copilotCli: false,
|
|
1237
|
+
ideExplicit: false,
|
|
1238
|
+
noProject: false,
|
|
1239
|
+
dir: process.cwd(),
|
|
1240
|
+
explicitConfigDir: null,
|
|
1241
|
+
parseError: false,
|
|
1242
|
+
unknownFlag: '',
|
|
1243
|
+
conflictFlags: '',
|
|
1244
|
+
};
|
|
1245
|
+
const rest = [];
|
|
1246
|
+
for (let i = 0; i < argv.length; i++) {
|
|
1247
|
+
const a = argv[i];
|
|
1248
|
+
if (a === '-h' || a === '--help') out.help = true;
|
|
1249
|
+
else if (a === '--dry-run') out.dryRun = true;
|
|
1250
|
+
else if ((a === '--config-dir' || a === '-c') && argv[i + 1]) {
|
|
1251
|
+
out.explicitConfigDir = path.resolve(expandTilde(argv[++i]));
|
|
1252
|
+
} else if (a === '--cursor') {
|
|
1253
|
+
out.cursor = true;
|
|
1254
|
+
out.ideExplicit = true;
|
|
1255
|
+
} else if (a === '--copilot') {
|
|
1256
|
+
out.copilot = true;
|
|
1257
|
+
out.ideExplicit = true;
|
|
1258
|
+
} else if (a === '--copilot-cli') {
|
|
1259
|
+
out.copilotCli = true;
|
|
1260
|
+
out.ideExplicit = true;
|
|
1261
|
+
} else if (a === '--ide-only') out.noProject = true;
|
|
1262
|
+
else if (a === '--dir' && argv[i + 1]) out.dir = path.resolve(argv[++i]);
|
|
1263
|
+
else if (!a.startsWith('-')) rest.push(path.resolve(a));
|
|
1264
|
+
else {
|
|
1265
|
+
out.parseError = true;
|
|
1266
|
+
out.unknownFlag = a;
|
|
1267
|
+
break;
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
if (rest.length) out.dir = rest[0];
|
|
1271
|
+
if (!out.ideExplicit) {
|
|
1272
|
+
out.cursor = true;
|
|
1273
|
+
out.copilot = true;
|
|
1274
|
+
out.copilotCli = true;
|
|
1275
|
+
}
|
|
1276
|
+
if (!out.conflictFlags && out.explicitConfigDir) {
|
|
1277
|
+
const n = [out.cursor, out.copilot, out.copilotCli].filter(Boolean).length;
|
|
1278
|
+
if (n !== 1) {
|
|
1279
|
+
out.conflictFlags =
|
|
1280
|
+
'--config-dir exige exatamente um entre --cursor, --copilot e --copilot-cli';
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
return out;
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
/**
|
|
1287
|
+
* @param {string} destPath
|
|
1288
|
+
* @param {{ dryRun: boolean }} opts
|
|
1289
|
+
*/
|
|
1290
|
+
function stripOxeFromCopilotInstructions(destPath, opts) {
|
|
1291
|
+
if (!fs.existsSync(destPath)) return;
|
|
1292
|
+
const existing = fs.readFileSync(destPath, 'utf8');
|
|
1293
|
+
if (!existing.includes(OXE_INST_BEGIN)) return;
|
|
1294
|
+
if (opts.dryRun) {
|
|
1295
|
+
console.log(`${dim}strip${reset} bloco OXE em ${destPath}`);
|
|
1296
|
+
return;
|
|
1297
|
+
}
|
|
1298
|
+
const re = new RegExp(
|
|
1299
|
+
`\\n?${escapeForRegExp(OXE_INST_BEGIN)}[\\s\\S]*?${escapeForRegExp(OXE_INST_END)}\\n?`,
|
|
1300
|
+
'm'
|
|
420
1301
|
);
|
|
1302
|
+
const merged = existing.replace(re, '\n').replace(/\n{3,}/g, '\n\n').trimEnd();
|
|
1303
|
+
fs.writeFileSync(destPath, merged ? `${merged}\n` : '\n', 'utf8');
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
/**
|
|
1307
|
+
* @param {string} filePath
|
|
1308
|
+
* @param {{ dryRun: boolean }} opts
|
|
1309
|
+
*/
|
|
1310
|
+
function unlinkQuiet(filePath, opts) {
|
|
1311
|
+
if (!fs.existsSync(filePath)) return;
|
|
1312
|
+
if (opts.dryRun) {
|
|
1313
|
+
console.log(`${dim}rm${reset} ${filePath}`);
|
|
1314
|
+
return;
|
|
1315
|
+
}
|
|
1316
|
+
fs.unlinkSync(filePath);
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
/**
|
|
1320
|
+
* @param {string} dirPath
|
|
1321
|
+
* @param {{ dryRun: boolean }} opts
|
|
1322
|
+
*/
|
|
1323
|
+
function rmDirIfEmpty(dirPath, opts) {
|
|
1324
|
+
if (!fs.existsSync(dirPath) || opts.dryRun) return;
|
|
1325
|
+
try {
|
|
1326
|
+
const n = fs.readdirSync(dirPath);
|
|
1327
|
+
if (n.length === 0) fs.rmdirSync(dirPath);
|
|
1328
|
+
} catch {
|
|
1329
|
+
/* ignore */
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
/**
|
|
1334
|
+
* @param {UninstallOpts} u
|
|
1335
|
+
*/
|
|
1336
|
+
function runUninstall(u) {
|
|
1337
|
+
assertNotWslWindowsNode();
|
|
1338
|
+
const c = useAnsiColors();
|
|
1339
|
+
const home = os.homedir();
|
|
1340
|
+
const ideOpts = /** @type {InstallOpts} */ ({
|
|
1341
|
+
help: false,
|
|
1342
|
+
version: false,
|
|
1343
|
+
cursor: u.cursor,
|
|
1344
|
+
copilot: u.copilot,
|
|
1345
|
+
copilotCli: u.copilotCli,
|
|
1346
|
+
vscode: false,
|
|
1347
|
+
commands: false,
|
|
1348
|
+
agents: false,
|
|
1349
|
+
force: true,
|
|
1350
|
+
dryRun: u.dryRun,
|
|
1351
|
+
dir: u.dir,
|
|
1352
|
+
all: false,
|
|
1353
|
+
noInitOxe: true,
|
|
1354
|
+
oxeOnly: false,
|
|
1355
|
+
globalCli: false,
|
|
1356
|
+
noGlobalCli: true,
|
|
1357
|
+
installAssetsGlobal: false,
|
|
1358
|
+
explicitScope: true,
|
|
1359
|
+
integrationsUnset: false,
|
|
1360
|
+
explicitConfigDir: u.explicitConfigDir,
|
|
1361
|
+
parseError: false,
|
|
1362
|
+
unknownFlag: '',
|
|
1363
|
+
conflictFlags: '',
|
|
1364
|
+
});
|
|
1365
|
+
|
|
1366
|
+
printSection('OXE ▸ uninstall');
|
|
1367
|
+
console.log(` ${c ? green : ''}Projeto:${c ? reset : ''} ${c ? cyan : ''}${u.dir}${c ? reset : ''}`);
|
|
1368
|
+
if (u.dryRun) console.log(` ${c ? yellow : ''}(dry-run)${c ? reset : ''}`);
|
|
1369
|
+
|
|
1370
|
+
const removedPaths = [];
|
|
1371
|
+
|
|
1372
|
+
if (u.cursor) {
|
|
1373
|
+
const base = cursorUserDir(ideOpts);
|
|
1374
|
+
const cmdDir = path.join(base, 'commands');
|
|
1375
|
+
const ruleDir = path.join(base, 'rules');
|
|
1376
|
+
if (fs.existsSync(cmdDir)) {
|
|
1377
|
+
for (const name of fs.readdirSync(cmdDir)) {
|
|
1378
|
+
if (name.startsWith('oxe-') && name.endsWith('.md')) {
|
|
1379
|
+
const p = path.join(cmdDir, name);
|
|
1380
|
+
unlinkQuiet(p, u);
|
|
1381
|
+
removedPaths.push(p);
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
if (fs.existsSync(ruleDir)) {
|
|
1386
|
+
for (const name of fs.readdirSync(ruleDir)) {
|
|
1387
|
+
if (name.includes('oxe') && (name.endsWith('.mdc') || name.endsWith('.md'))) {
|
|
1388
|
+
const p = path.join(ruleDir, name);
|
|
1389
|
+
unlinkQuiet(p, u);
|
|
1390
|
+
removedPaths.push(p);
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
if (u.copilot) {
|
|
1397
|
+
const root = copilotUserDir(ideOpts);
|
|
1398
|
+
const inst = path.join(root, 'copilot-instructions.md');
|
|
1399
|
+
stripOxeFromCopilotInstructions(inst, u);
|
|
1400
|
+
const pr = path.join(root, 'prompts');
|
|
1401
|
+
if (fs.existsSync(pr)) {
|
|
1402
|
+
for (const name of fs.readdirSync(pr)) {
|
|
1403
|
+
if (name.startsWith('oxe-')) {
|
|
1404
|
+
const p = path.join(pr, name);
|
|
1405
|
+
unlinkQuiet(p, u);
|
|
1406
|
+
removedPaths.push(p);
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
if (u.copilotCli) {
|
|
1413
|
+
for (const base of [claudeUserDir(ideOpts), copilotUserDir(ideOpts)]) {
|
|
1414
|
+
const cmdDir = path.join(base, 'commands');
|
|
1415
|
+
if (!fs.existsSync(cmdDir)) continue;
|
|
1416
|
+
for (const name of fs.readdirSync(cmdDir)) {
|
|
1417
|
+
if (name.startsWith('oxe-') && name.endsWith('.md')) {
|
|
1418
|
+
const p = path.join(cmdDir, name);
|
|
1419
|
+
unlinkQuiet(p, u);
|
|
1420
|
+
removedPaths.push(p);
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
if (!u.noProject) {
|
|
1427
|
+
const target = u.dir;
|
|
1428
|
+
const nestedWf = path.join(target, '.oxe', 'workflows');
|
|
1429
|
+
const nestedTpl = path.join(target, '.oxe', 'templates');
|
|
1430
|
+
const globalOxe = path.join(target, 'oxe');
|
|
1431
|
+
const globalCmd = path.join(target, 'commands', 'oxe');
|
|
1432
|
+
|
|
1433
|
+
const rmTree = (p) => {
|
|
1434
|
+
if (!fs.existsSync(p)) return;
|
|
1435
|
+
if (u.dryRun) {
|
|
1436
|
+
console.log(`${dim}rm -r${reset} ${p}`);
|
|
1437
|
+
return;
|
|
1438
|
+
}
|
|
1439
|
+
fs.rmSync(p, { recursive: true, force: true });
|
|
1440
|
+
};
|
|
1441
|
+
|
|
1442
|
+
if (fs.existsSync(nestedWf)) rmTree(nestedWf);
|
|
1443
|
+
if (fs.existsSync(nestedTpl)) rmTree(nestedTpl);
|
|
1444
|
+
if (fs.existsSync(globalOxe)) rmTree(globalOxe);
|
|
1445
|
+
if (fs.existsSync(globalCmd)) rmTree(globalCmd);
|
|
1446
|
+
|
|
1447
|
+
if (!u.dryRun) {
|
|
1448
|
+
rmDirIfEmpty(path.join(target, '.oxe', 'templates'), u);
|
|
1449
|
+
rmDirIfEmpty(path.join(target, '.oxe', 'workflows'), u);
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
if (!u.dryRun && (u.cursor || u.copilot || u.copilotCli)) {
|
|
1454
|
+
const prev = oxeManifest.loadFileManifest(home);
|
|
1455
|
+
const next = { ...prev };
|
|
1456
|
+
for (const p of removedPaths) delete next[p];
|
|
1457
|
+
if (u.copilot) {
|
|
1458
|
+
const instPath = path.join(copilotUserDir(ideOpts), 'copilot-instructions.md');
|
|
1459
|
+
if (fs.existsSync(instPath)) {
|
|
1460
|
+
try {
|
|
1461
|
+
next[instPath] = oxeManifest.sha256File(instPath);
|
|
1462
|
+
} catch {
|
|
1463
|
+
delete next[instPath];
|
|
1464
|
+
}
|
|
1465
|
+
} else {
|
|
1466
|
+
delete next[instPath];
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1469
|
+
oxeManifest.writeFileManifest(home, next, readPkgVersion());
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
printSummaryAndNextSteps(c, buildUninstallFooter(u));
|
|
1473
|
+
console.log(` ${c ? green : ''}✓${c ? reset : ''} Desinstalação concluída com sucesso.\n`);
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
/** @typedef {{ help: boolean, dryRun: boolean, dir: string, rest: string[], parseError: boolean, unknownFlag: string }} UpdateOpts */
|
|
1477
|
+
|
|
1478
|
+
/**
|
|
1479
|
+
* @param {string[]} argv
|
|
1480
|
+
* @returns {UpdateOpts}
|
|
1481
|
+
*/
|
|
1482
|
+
function parseUpdateArgs(argv) {
|
|
1483
|
+
/** @type {UpdateOpts} */
|
|
1484
|
+
const out = {
|
|
1485
|
+
help: false,
|
|
1486
|
+
dryRun: false,
|
|
1487
|
+
dir: process.cwd(),
|
|
1488
|
+
rest: [],
|
|
1489
|
+
parseError: false,
|
|
1490
|
+
unknownFlag: '',
|
|
1491
|
+
};
|
|
1492
|
+
let dirExplicit = false;
|
|
1493
|
+
let firstPositionalConsumed = false;
|
|
1494
|
+
for (let i = 0; i < argv.length; i++) {
|
|
1495
|
+
const a = argv[i];
|
|
1496
|
+
if (a === '-h' || a === '--help') out.help = true;
|
|
1497
|
+
else if (a === '--dry-run') out.dryRun = true;
|
|
1498
|
+
else if (a === '--dir' && argv[i + 1]) {
|
|
1499
|
+
out.dir = path.resolve(argv[++i]);
|
|
1500
|
+
dirExplicit = true;
|
|
1501
|
+
} else if (!a.startsWith('-')) {
|
|
1502
|
+
if (!dirExplicit && !firstPositionalConsumed) {
|
|
1503
|
+
out.dir = path.resolve(a);
|
|
1504
|
+
firstPositionalConsumed = true;
|
|
1505
|
+
} else out.rest.push(a);
|
|
1506
|
+
} else {
|
|
1507
|
+
out.parseError = true;
|
|
1508
|
+
out.unknownFlag = a;
|
|
1509
|
+
break;
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
return out;
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
/**
|
|
1516
|
+
* @param {UpdateOpts} u
|
|
1517
|
+
*/
|
|
1518
|
+
function runUpdate(u) {
|
|
1519
|
+
assertNotWslWindowsNode();
|
|
1520
|
+
const c = useAnsiColors();
|
|
1521
|
+
if (u.dryRun) {
|
|
1522
|
+
printSection('OXE ▸ update (simulação)');
|
|
1523
|
+
console.log(` ${dim}Comando que seria executado:${reset}`);
|
|
1524
|
+
console.log(` ${cyan}npx -y oxe-cc@latest --force --no-global-cli -l${reset} ${u.rest.join(' ')}`);
|
|
1525
|
+
console.log(` ${dim}Diretório:${reset} ${u.dir}`);
|
|
1526
|
+
printSummaryAndNextSteps(c, {
|
|
1527
|
+
bullets: ['[simulação] O npx baixaria o pacote oxe-cc@latest e rodaria a instalação com --force.'],
|
|
1528
|
+
nextSteps: [
|
|
1529
|
+
{ desc: 'Rodar de verdade (sem --dry-run), na pasta do projeto:', cmd: 'npx oxe-cc update' },
|
|
1530
|
+
{ desc: 'Depois, validar:', cmd: 'npx oxe-cc doctor' },
|
|
1531
|
+
],
|
|
1532
|
+
dryRun: true,
|
|
1533
|
+
});
|
|
1534
|
+
return;
|
|
1535
|
+
}
|
|
1536
|
+
printSection('OXE ▸ update');
|
|
1537
|
+
const args = ['-y', 'oxe-cc@latest', '--force', '--no-global-cli', '-l', ...u.rest];
|
|
1538
|
+
const r = spawnSync('npx', args, {
|
|
1539
|
+
cwd: u.dir,
|
|
1540
|
+
stdio: 'inherit',
|
|
1541
|
+
env: { ...process.env },
|
|
1542
|
+
shell: process.platform === 'win32',
|
|
1543
|
+
});
|
|
1544
|
+
if (r.error) {
|
|
1545
|
+
console.error(`${red}Falha ao executar npx:${reset}`, r.error.message);
|
|
1546
|
+
process.exit(1);
|
|
1547
|
+
}
|
|
1548
|
+
if (r.status !== 0 && r.status !== null) process.exit(r.status);
|
|
1549
|
+
printSummaryAndNextSteps(c, {
|
|
1550
|
+
bullets: [
|
|
1551
|
+
'Pacote oxe-cc atualizado via npx (--force); arquivos do projeto e integrações foram alinhados à versão publicada.',
|
|
1552
|
+
],
|
|
1553
|
+
nextSteps: [
|
|
1554
|
+
{ desc: 'Validar workflows e .oxe na raiz do projeto:', cmd: 'npx oxe-cc doctor' },
|
|
1555
|
+
{ desc: 'Retomar o fluxo no agente:', cmd: '/oxe-scan' },
|
|
1556
|
+
{ desc: 'Ajuda geral no chat:', cmd: '/oxe-help' },
|
|
1557
|
+
],
|
|
1558
|
+
dryRun: false,
|
|
1559
|
+
});
|
|
1560
|
+
console.log(` ${c ? green : ''}✓${c ? reset : ''} Atualização concluída com sucesso.\n`);
|
|
421
1561
|
}
|
|
422
1562
|
|
|
423
|
-
function main() {
|
|
1563
|
+
async function main() {
|
|
424
1564
|
const argv = process.argv.slice(2);
|
|
425
1565
|
let command = 'install';
|
|
426
|
-
if (
|
|
1566
|
+
if (
|
|
1567
|
+
argv[0] === 'doctor' ||
|
|
1568
|
+
argv[0] === 'status' ||
|
|
1569
|
+
argv[0] === 'init-oxe' ||
|
|
1570
|
+
argv[0] === 'uninstall' ||
|
|
1571
|
+
argv[0] === 'update'
|
|
1572
|
+
) {
|
|
427
1573
|
command = argv[0];
|
|
428
1574
|
argv.shift();
|
|
429
1575
|
}
|
|
430
1576
|
|
|
1577
|
+
if (command === 'uninstall') {
|
|
1578
|
+
const u = parseUninstallArgs(argv);
|
|
1579
|
+
if (u.help) {
|
|
1580
|
+
printBanner();
|
|
1581
|
+
usage();
|
|
1582
|
+
process.exit(0);
|
|
1583
|
+
}
|
|
1584
|
+
if (u.conflictFlags) {
|
|
1585
|
+
printBanner();
|
|
1586
|
+
console.error(`${red}${u.conflictFlags}${reset}`);
|
|
1587
|
+
usage();
|
|
1588
|
+
process.exit(1);
|
|
1589
|
+
}
|
|
1590
|
+
if (u.parseError) {
|
|
1591
|
+
printBanner();
|
|
1592
|
+
console.error(`${red}Opção desconhecida:${reset} ${u.unknownFlag}`);
|
|
1593
|
+
usage();
|
|
1594
|
+
process.exit(1);
|
|
1595
|
+
}
|
|
1596
|
+
printBanner();
|
|
1597
|
+
if (!u.dryRun && !fs.existsSync(u.dir)) {
|
|
1598
|
+
console.error(`${yellow}Diretório não encontrado: ${u.dir}${reset}`);
|
|
1599
|
+
process.exit(1);
|
|
1600
|
+
}
|
|
1601
|
+
runUninstall(u);
|
|
1602
|
+
return;
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
if (command === 'update') {
|
|
1606
|
+
const u = parseUpdateArgs(argv);
|
|
1607
|
+
if (u.help) {
|
|
1608
|
+
printBanner();
|
|
1609
|
+
usage();
|
|
1610
|
+
process.exit(0);
|
|
1611
|
+
}
|
|
1612
|
+
if (u.parseError) {
|
|
1613
|
+
printBanner();
|
|
1614
|
+
console.error(`${red}Opção desconhecida:${reset} ${u.unknownFlag}`);
|
|
1615
|
+
usage();
|
|
1616
|
+
process.exit(1);
|
|
1617
|
+
}
|
|
1618
|
+
printBanner();
|
|
1619
|
+
if (!u.dryRun && !fs.existsSync(u.dir)) {
|
|
1620
|
+
console.error(`${yellow}Diretório não encontrado: ${u.dir}${reset}`);
|
|
1621
|
+
process.exit(1);
|
|
1622
|
+
}
|
|
1623
|
+
runUpdate(u);
|
|
1624
|
+
return;
|
|
1625
|
+
}
|
|
1626
|
+
|
|
431
1627
|
const opts = parseInstallArgs(argv);
|
|
432
1628
|
|
|
433
1629
|
if (opts.version) {
|
|
@@ -435,9 +1631,16 @@ function main() {
|
|
|
435
1631
|
process.exit(0);
|
|
436
1632
|
}
|
|
437
1633
|
|
|
1634
|
+
if (opts.conflictFlags) {
|
|
1635
|
+
printBanner();
|
|
1636
|
+
console.error(`${red}${opts.conflictFlags}${reset}`);
|
|
1637
|
+
usage();
|
|
1638
|
+
process.exit(1);
|
|
1639
|
+
}
|
|
1640
|
+
|
|
438
1641
|
if (opts.parseError) {
|
|
439
1642
|
printBanner();
|
|
440
|
-
console.error(`${red}
|
|
1643
|
+
console.error(`${red}Opção desconhecida:${reset} ${opts.unknownFlag}`);
|
|
441
1644
|
usage();
|
|
442
1645
|
process.exit(1);
|
|
443
1646
|
}
|
|
@@ -453,26 +1656,54 @@ function main() {
|
|
|
453
1656
|
const target = opts.dir;
|
|
454
1657
|
if (command === 'doctor') {
|
|
455
1658
|
if (!fs.existsSync(target)) {
|
|
456
|
-
console.error(`${yellow}
|
|
1659
|
+
console.error(`${yellow}Diretório não encontrado: ${target}${reset}`);
|
|
457
1660
|
process.exit(1);
|
|
458
1661
|
}
|
|
459
1662
|
runDoctor(target);
|
|
460
1663
|
return;
|
|
461
1664
|
}
|
|
462
1665
|
|
|
1666
|
+
if (command === 'status') {
|
|
1667
|
+
if (!fs.existsSync(target)) {
|
|
1668
|
+
console.error(`${yellow}Diretório não encontrado: ${target}${reset}`);
|
|
1669
|
+
process.exit(1);
|
|
1670
|
+
}
|
|
1671
|
+
printBanner();
|
|
1672
|
+
runStatus(target);
|
|
1673
|
+
return;
|
|
1674
|
+
}
|
|
1675
|
+
|
|
463
1676
|
if (command === 'init-oxe') {
|
|
464
1677
|
if (!opts.dryRun && !fs.existsSync(target)) {
|
|
465
|
-
console.error(`${yellow}
|
|
1678
|
+
console.error(`${yellow}Diretório não encontrado: ${target}${reset}`);
|
|
466
1679
|
process.exit(1);
|
|
467
1680
|
}
|
|
468
|
-
|
|
469
|
-
|
|
1681
|
+
printSection('OXE ▸ init-oxe');
|
|
1682
|
+
const c0 = useAnsiColors();
|
|
1683
|
+
console.log(` ${c0 ? green : ''}Destino:${c0 ? reset : ''} ${c0 ? cyan : ''}${target}${c0 ? reset : ''}`);
|
|
1684
|
+
if (opts.dryRun) console.log(` ${c0 ? yellow : ''}(dry-run)${c0 ? reset : ''}`);
|
|
470
1685
|
bootstrapOxe(target, { dryRun: opts.dryRun, force: opts.force });
|
|
471
|
-
|
|
1686
|
+
printSummaryAndNextSteps(c0, {
|
|
1687
|
+
bullets: opts.dryRun
|
|
1688
|
+
? ['[simulação] Seriam criados ou atualizados .oxe/STATE.md, .oxe/config.json e .oxe/codebase/']
|
|
1689
|
+
: ['.oxe/STATE.md, .oxe/config.json e pasta .oxe/codebase/ (criados ou atualizados conforme --force)'],
|
|
1690
|
+
nextSteps: [
|
|
1691
|
+
{ desc: 'Validar o projeto:', cmd: 'npx oxe-cc doctor' },
|
|
1692
|
+
{ desc: 'Instalar integrações Cursor/Copilot (se ainda não fez):', cmd: 'npx oxe-cc@latest' },
|
|
1693
|
+
{ desc: 'Começar o fluxo no agente:', cmd: '/oxe-scan' },
|
|
1694
|
+
],
|
|
1695
|
+
dryRun: opts.dryRun,
|
|
1696
|
+
});
|
|
1697
|
+
console.log(` ${c0 ? green : ''}✓${c0 ? reset : ''} init-oxe concluído com sucesso.\n`);
|
|
472
1698
|
return;
|
|
473
1699
|
}
|
|
474
1700
|
|
|
1701
|
+
await resolveInteractiveInstall(opts);
|
|
475
1702
|
runInstall(opts);
|
|
1703
|
+
await maybePromptGlobalCli(opts);
|
|
476
1704
|
}
|
|
477
1705
|
|
|
478
|
-
main()
|
|
1706
|
+
main().catch((err) => {
|
|
1707
|
+
console.error(err);
|
|
1708
|
+
process.exit(1);
|
|
1709
|
+
});
|