@onion-architect-ai/cli 4.1.0-beta.1
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/README.md +67 -0
- package/bin/onion.js +8 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +1952 -0
- package/dist/cli.js.map +1 -0
- package/package.json +74 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,1952 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs2 from 'fs-extra';
|
|
3
|
+
import path8 from 'path';
|
|
4
|
+
import yaml5 from 'yaml';
|
|
5
|
+
import { Command } from 'commander';
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import inquirer from 'inquirer';
|
|
8
|
+
import ora from 'ora';
|
|
9
|
+
|
|
10
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
11
|
+
var __esm = (fn, res) => function __init() {
|
|
12
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
// src/constants.ts
|
|
16
|
+
var ONION_VERSION, SUPPORTED_IDES;
|
|
17
|
+
var init_constants = __esm({
|
|
18
|
+
"src/constants.ts"() {
|
|
19
|
+
ONION_VERSION = "4.1.0-beta.1";
|
|
20
|
+
SUPPORTED_IDES = [
|
|
21
|
+
{
|
|
22
|
+
id: "cursor",
|
|
23
|
+
name: "Cursor",
|
|
24
|
+
detector: ".cursor",
|
|
25
|
+
loader: "cursor.js",
|
|
26
|
+
configFile: "settings.json"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
id: "windsurf",
|
|
30
|
+
name: "Windsurf",
|
|
31
|
+
detector: ".windsurf",
|
|
32
|
+
loader: "windsurf.ts",
|
|
33
|
+
configFile: "settings.yml"
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: "claude-code",
|
|
37
|
+
name: "Claude Code",
|
|
38
|
+
detector: ".claude",
|
|
39
|
+
loader: "claude.py",
|
|
40
|
+
configFile: "config.json"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: "vscode",
|
|
44
|
+
name: "VS Code (GitHub Copilot)",
|
|
45
|
+
detector: ".vscode",
|
|
46
|
+
loader: "vscode.js",
|
|
47
|
+
configFile: "settings.json"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: "cline",
|
|
51
|
+
name: "Cline (VS Code)",
|
|
52
|
+
detector: ".vscode/extensions",
|
|
53
|
+
loader: "cline.js",
|
|
54
|
+
configFile: "cline-config.json"
|
|
55
|
+
}
|
|
56
|
+
];
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// src/core/validator.ts
|
|
61
|
+
function validateContextName(name) {
|
|
62
|
+
if (!name || typeof name !== "string") {
|
|
63
|
+
throw new Error("Nome do contexto \xE9 obrigat\xF3rio");
|
|
64
|
+
}
|
|
65
|
+
const trimmed = name.trim();
|
|
66
|
+
if (trimmed.length < 3 || trimmed.length > 20) {
|
|
67
|
+
throw new Error("Nome do contexto deve ter entre 3 e 20 caracteres");
|
|
68
|
+
}
|
|
69
|
+
if (!/^[a-z][a-z0-9-]*$/.test(trimmed)) {
|
|
70
|
+
throw new Error(
|
|
71
|
+
"Nome do contexto deve come\xE7ar com letra min\xFAscula e conter apenas letras, n\xFAmeros e h\xEDfens"
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
if (trimmed.endsWith("-")) {
|
|
75
|
+
throw new Error("Nome do contexto n\xE3o pode terminar com h\xEDfen");
|
|
76
|
+
}
|
|
77
|
+
const reserved = ["core", "ide", "contexts", "config", "help"];
|
|
78
|
+
if (reserved.includes(trimmed)) {
|
|
79
|
+
throw new Error(`"${trimmed}" \xE9 uma palavra reservada do sistema`);
|
|
80
|
+
}
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
function validateIDEName(ide) {
|
|
84
|
+
if (!ide || typeof ide !== "string") {
|
|
85
|
+
throw new Error("Nome do IDE \xE9 obrigat\xF3rio");
|
|
86
|
+
}
|
|
87
|
+
const supportedIds = SUPPORTED_IDES.map((i) => i.id);
|
|
88
|
+
if (!supportedIds.includes(ide.toLowerCase())) {
|
|
89
|
+
throw new Error(
|
|
90
|
+
`IDE "${ide}" n\xE3o \xE9 suportado. IDEs dispon\xEDveis: ${supportedIds.join(", ")}`
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
function validateConfig(config) {
|
|
96
|
+
if (!config || typeof config !== "object") {
|
|
97
|
+
throw new Error("Configura\xE7\xE3o inv\xE1lida");
|
|
98
|
+
}
|
|
99
|
+
const cfg = config;
|
|
100
|
+
const required = ["version", "contexts", "ides"];
|
|
101
|
+
for (const field of required) {
|
|
102
|
+
if (!cfg[field]) {
|
|
103
|
+
throw new Error(`Campo obrigat\xF3rio ausente no config: ${field}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (!Array.isArray(cfg.contexts)) {
|
|
107
|
+
throw new Error('Campo "contexts" deve ser um array');
|
|
108
|
+
}
|
|
109
|
+
if (!Array.isArray(cfg.ides)) {
|
|
110
|
+
throw new Error('Campo "ides" deve ser um array');
|
|
111
|
+
}
|
|
112
|
+
for (const ctx of cfg.contexts) {
|
|
113
|
+
try {
|
|
114
|
+
validateContextName(ctx);
|
|
115
|
+
} catch (err) {
|
|
116
|
+
throw new Error(`Contexto inv\xE1lido "${ctx}": ${err.message}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
for (const ide of cfg.ides) {
|
|
120
|
+
try {
|
|
121
|
+
validateIDEName(ide);
|
|
122
|
+
} catch (err) {
|
|
123
|
+
throw new Error(`IDE inv\xE1lido "${ide}": ${err.message}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
var init_validator = __esm({
|
|
129
|
+
"src/core/validator.ts"() {
|
|
130
|
+
init_constants();
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
async function readConfig(projectRoot) {
|
|
134
|
+
const configPath = path8.join(projectRoot, CONFIG_FILENAME);
|
|
135
|
+
if (!await fs2.pathExists(configPath)) {
|
|
136
|
+
throw new Error(`Arquivo ${CONFIG_FILENAME} n\xE3o encontrado em ${projectRoot}`);
|
|
137
|
+
}
|
|
138
|
+
try {
|
|
139
|
+
const content = await fs2.readFile(configPath, "utf8");
|
|
140
|
+
const config = yaml5.parse(content);
|
|
141
|
+
validateConfig(config);
|
|
142
|
+
return config;
|
|
143
|
+
} catch (error) {
|
|
144
|
+
if (error.message.includes("n\xE3o encontrado")) {
|
|
145
|
+
throw error;
|
|
146
|
+
}
|
|
147
|
+
throw new Error(`Erro ao ler ${CONFIG_FILENAME}: ${error.message}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
async function createConfig(projectRoot, data) {
|
|
151
|
+
const configPath = path8.join(projectRoot, CONFIG_FILENAME);
|
|
152
|
+
if (await fs2.pathExists(configPath)) {
|
|
153
|
+
throw new Error(`Arquivo ${CONFIG_FILENAME} j\xE1 existe. Use updateConfig() para modificar.`);
|
|
154
|
+
}
|
|
155
|
+
validateConfig(data);
|
|
156
|
+
try {
|
|
157
|
+
const yamlContent = yaml5.stringify(data, {
|
|
158
|
+
indent: 2,
|
|
159
|
+
lineWidth: 0
|
|
160
|
+
// Sem quebra de linha
|
|
161
|
+
});
|
|
162
|
+
await fs2.writeFile(configPath, yamlContent, "utf8");
|
|
163
|
+
} catch (error) {
|
|
164
|
+
throw new Error(`Erro ao criar ${CONFIG_FILENAME}: ${error.message}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
async function updateConfig(projectRoot, updates) {
|
|
168
|
+
const current = await readConfig(projectRoot);
|
|
169
|
+
const merged = deepMerge(
|
|
170
|
+
current,
|
|
171
|
+
updates
|
|
172
|
+
);
|
|
173
|
+
validateConfig(merged);
|
|
174
|
+
const configPath = path8.join(projectRoot, CONFIG_FILENAME);
|
|
175
|
+
const yamlContent = yaml5.stringify(merged, {
|
|
176
|
+
indent: 2,
|
|
177
|
+
lineWidth: 0
|
|
178
|
+
});
|
|
179
|
+
await fs2.writeFile(configPath, yamlContent, "utf8");
|
|
180
|
+
return merged;
|
|
181
|
+
}
|
|
182
|
+
async function addContext(projectRoot, contextName) {
|
|
183
|
+
const config = await readConfig(projectRoot);
|
|
184
|
+
if (config.contexts.includes(contextName)) {
|
|
185
|
+
throw new Error(`Contexto "${contextName}" j\xE1 existe na configura\xE7\xE3o`);
|
|
186
|
+
}
|
|
187
|
+
return await updateConfig(projectRoot, {
|
|
188
|
+
contexts: [...config.contexts, contextName]
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
async function addIDE(projectRoot, ideName) {
|
|
192
|
+
const config = await readConfig(projectRoot);
|
|
193
|
+
if (config.ides.includes(ideName)) {
|
|
194
|
+
throw new Error(`IDE "${ideName}" j\xE1 existe na configura\xE7\xE3o`);
|
|
195
|
+
}
|
|
196
|
+
return await updateConfig(projectRoot, {
|
|
197
|
+
ides: [...config.ides, ideName]
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
function deepMerge(target, source) {
|
|
201
|
+
const result = { ...target };
|
|
202
|
+
for (const key in source) {
|
|
203
|
+
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
204
|
+
const targetValue = result[key];
|
|
205
|
+
const sourceValue = source[key];
|
|
206
|
+
if (Array.isArray(targetValue) && Array.isArray(sourceValue)) {
|
|
207
|
+
result[key] = [.../* @__PURE__ */ new Set([...targetValue, ...sourceValue])];
|
|
208
|
+
} else if (typeof targetValue === "object" && targetValue !== null && typeof sourceValue === "object" && sourceValue !== null && !Array.isArray(targetValue) && !Array.isArray(sourceValue)) {
|
|
209
|
+
result[key] = deepMerge(
|
|
210
|
+
targetValue,
|
|
211
|
+
sourceValue
|
|
212
|
+
);
|
|
213
|
+
} else {
|
|
214
|
+
result[key] = sourceValue;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return result;
|
|
219
|
+
}
|
|
220
|
+
function createDefaultConfig(options = {}) {
|
|
221
|
+
return {
|
|
222
|
+
version: options.version || "4.0.0",
|
|
223
|
+
contexts: options.contexts || [],
|
|
224
|
+
ides: options.ides || ["cursor"],
|
|
225
|
+
integrations: options.integrations || {},
|
|
226
|
+
created: options.created || (/* @__PURE__ */ new Date()).toISOString(),
|
|
227
|
+
...options
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
var CONFIG_FILENAME;
|
|
231
|
+
var init_config = __esm({
|
|
232
|
+
"src/core/config.ts"() {
|
|
233
|
+
init_validator();
|
|
234
|
+
CONFIG_FILENAME = ".onion-config.yml";
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
async function init(options = {}) {
|
|
238
|
+
try {
|
|
239
|
+
const projectRoot = process.cwd();
|
|
240
|
+
console.log("");
|
|
241
|
+
console.log(chalk.magenta.bold("\u{1F9C5} Initializing Onion System v4..."));
|
|
242
|
+
console.log("");
|
|
243
|
+
if (fs2.existsSync(path8.join(projectRoot, ".onion"))) {
|
|
244
|
+
console.log(chalk.yellow("\u26A0\uFE0F .onion/ already exists!"));
|
|
245
|
+
console.log(chalk.gray('Use "onion migrate" to upgrade from v3'));
|
|
246
|
+
process.exit(1);
|
|
247
|
+
}
|
|
248
|
+
const onionRoot = path8.resolve(import.meta.dirname, "../../../..");
|
|
249
|
+
const sourceOnion = path8.join(onionRoot, ".onion");
|
|
250
|
+
if (!fs2.existsSync(sourceOnion)) {
|
|
251
|
+
console.log(chalk.red("\u274C Could not find Onion source structure"));
|
|
252
|
+
console.log(chalk.gray(`Expected: ${sourceOnion}`));
|
|
253
|
+
process.exit(1);
|
|
254
|
+
}
|
|
255
|
+
console.log(chalk.cyan("\u{1F4C1} Creating .onion/ structure..."));
|
|
256
|
+
fs2.copySync(sourceOnion, path8.join(projectRoot, ".onion"), {
|
|
257
|
+
dereference: true
|
|
258
|
+
// Resolve symlinks
|
|
259
|
+
});
|
|
260
|
+
console.log(chalk.green("\u2713 Created .onion/"));
|
|
261
|
+
console.log(chalk.cyan("\u{1F3AF} Setting up Cursor IDE integration..."));
|
|
262
|
+
const cursorDir = path8.join(projectRoot, ".cursor");
|
|
263
|
+
fs2.ensureDirSync(cursorDir);
|
|
264
|
+
const sourceCursorCommands = path8.join(onionRoot, ".cursor/commands");
|
|
265
|
+
if (fs2.existsSync(sourceCursorCommands)) {
|
|
266
|
+
fs2.copySync(sourceCursorCommands, path8.join(cursorDir, "commands"), {
|
|
267
|
+
dereference: true
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
const sourceCursorAgents = path8.join(onionRoot, ".cursor/agents");
|
|
271
|
+
if (fs2.existsSync(sourceCursorAgents)) {
|
|
272
|
+
fs2.copySync(sourceCursorAgents, path8.join(cursorDir, "agents"), {
|
|
273
|
+
dereference: true
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
const sourceCursorRules = path8.join(onionRoot, ".cursor/rules");
|
|
277
|
+
if (fs2.existsSync(sourceCursorRules)) {
|
|
278
|
+
fs2.copySync(sourceCursorRules, path8.join(cursorDir, "rules"), {
|
|
279
|
+
dereference: true
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
console.log(chalk.green("\u2713 Created .cursor/"));
|
|
283
|
+
console.log(chalk.cyan("\u2699\uFE0F Creating configuration..."));
|
|
284
|
+
const config = `# Onion System v4 Configuration
|
|
285
|
+
version: 4.0.0
|
|
286
|
+
created: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
287
|
+
project_type: monorepo
|
|
288
|
+
|
|
289
|
+
contexts:
|
|
290
|
+
- name: business
|
|
291
|
+
enabled: true
|
|
292
|
+
description: Product specs, features, tasks
|
|
293
|
+
- name: technical
|
|
294
|
+
enabled: true
|
|
295
|
+
description: Development, architecture, PRs
|
|
296
|
+
|
|
297
|
+
ides:
|
|
298
|
+
- name: cursor
|
|
299
|
+
enabled: true
|
|
300
|
+
path: .cursor/
|
|
301
|
+
|
|
302
|
+
integrations:
|
|
303
|
+
task_manager:
|
|
304
|
+
provider: none
|
|
305
|
+
transcription:
|
|
306
|
+
provider: none
|
|
307
|
+
`;
|
|
308
|
+
fs2.writeFileSync(path8.join(projectRoot, ".onion-config.yml"), config, "utf8");
|
|
309
|
+
console.log(chalk.green("\u2713 Created .onion-config.yml"));
|
|
310
|
+
const readme = `# \u{1F9C5} Onion System v4
|
|
311
|
+
|
|
312
|
+
This project uses Onion System v4 for development.
|
|
313
|
+
|
|
314
|
+
## Quick Start
|
|
315
|
+
|
|
316
|
+
### Available Commands
|
|
317
|
+
|
|
318
|
+
**Business Context:**
|
|
319
|
+
\`\`\`
|
|
320
|
+
/business/help # Show all business commands
|
|
321
|
+
/business/spec # Create product spec
|
|
322
|
+
/business/task # Create task
|
|
323
|
+
/business/estimate # Estimate story points
|
|
324
|
+
\`\`\`
|
|
325
|
+
|
|
326
|
+
**Technical Context:**
|
|
327
|
+
\`\`\`
|
|
328
|
+
/technical/help # Show all technical commands
|
|
329
|
+
/technical/plan # Plan development
|
|
330
|
+
/technical/work # Start working on task
|
|
331
|
+
/technical/pr # Create pull request
|
|
332
|
+
\`\`\`
|
|
333
|
+
|
|
334
|
+
**Global:**
|
|
335
|
+
\`\`\`
|
|
336
|
+
/help # Show global help
|
|
337
|
+
\`\`\`
|
|
338
|
+
|
|
339
|
+
## Documentation
|
|
340
|
+
|
|
341
|
+
- Full docs: https://github.com/your-org/onion-v4
|
|
342
|
+
- Configuration: .onion-config.yml
|
|
343
|
+
- Structure: .onion/
|
|
344
|
+
|
|
345
|
+
## Learn More
|
|
346
|
+
|
|
347
|
+
- [Installation Guide](https://github.com/your-org/onion-v4/docs/onion/INSTALLATION.md)
|
|
348
|
+
- [Release Notes](https://github.com/your-org/onion-v4/docs/onion/RELEASE-NOTES-v4.0-beta.md)
|
|
349
|
+
`;
|
|
350
|
+
fs2.writeFileSync(path8.join(projectRoot, ".onion", "README.md"), readme, "utf8");
|
|
351
|
+
console.log("");
|
|
352
|
+
console.log(chalk.green.bold("\u2705 Onion System initialized successfully!"));
|
|
353
|
+
console.log("");
|
|
354
|
+
console.log(chalk.cyan("\u{1F4DA} Next steps:"));
|
|
355
|
+
console.log("");
|
|
356
|
+
console.log(" 1. Restart Cursor IDE");
|
|
357
|
+
console.log(" 2. Try a command:");
|
|
358
|
+
console.log(chalk.yellow(" /business/help"));
|
|
359
|
+
console.log(chalk.yellow(" /technical/help"));
|
|
360
|
+
console.log(" 3. Start developing:");
|
|
361
|
+
console.log(chalk.yellow(' /business/spec "my-feature"'));
|
|
362
|
+
console.log(chalk.yellow(" /technical/work"));
|
|
363
|
+
console.log("");
|
|
364
|
+
console.log(chalk.gray("Need help? Run: /help"));
|
|
365
|
+
console.log("");
|
|
366
|
+
} catch (error) {
|
|
367
|
+
console.log("");
|
|
368
|
+
console.log(chalk.red.bold("\u274C Initialization failed:"));
|
|
369
|
+
console.log(chalk.red(error instanceof Error ? error.message : String(error)));
|
|
370
|
+
console.log("");
|
|
371
|
+
if (options.debug) {
|
|
372
|
+
console.error(error);
|
|
373
|
+
}
|
|
374
|
+
process.exit(1);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
var Logger = class {
|
|
378
|
+
spinner = null;
|
|
379
|
+
// Títulos
|
|
380
|
+
title(text) {
|
|
381
|
+
console.log("\n" + chalk.cyan.bold("\u2501".repeat(60)));
|
|
382
|
+
console.log(chalk.cyan.bold(` ${text}`));
|
|
383
|
+
console.log(chalk.cyan.bold("\u2501".repeat(60)));
|
|
384
|
+
}
|
|
385
|
+
// Seções
|
|
386
|
+
section(text) {
|
|
387
|
+
console.log("\n" + chalk.white.bold(text));
|
|
388
|
+
}
|
|
389
|
+
// Sucesso
|
|
390
|
+
success(text) {
|
|
391
|
+
console.log(chalk.green("\u2705 ") + text);
|
|
392
|
+
}
|
|
393
|
+
// Info
|
|
394
|
+
info(text) {
|
|
395
|
+
console.log(chalk.blue("\u2139\uFE0F ") + text);
|
|
396
|
+
}
|
|
397
|
+
// Warning
|
|
398
|
+
warn(text) {
|
|
399
|
+
console.log(chalk.yellow("\u26A0\uFE0F ") + text);
|
|
400
|
+
}
|
|
401
|
+
// Erro
|
|
402
|
+
error(text) {
|
|
403
|
+
console.log(chalk.red("\u274C ") + text);
|
|
404
|
+
}
|
|
405
|
+
// Spinner
|
|
406
|
+
startSpinner(text) {
|
|
407
|
+
this.spinner = ora(text).start();
|
|
408
|
+
}
|
|
409
|
+
stopSpinner(success = true, text = null) {
|
|
410
|
+
if (!this.spinner) return;
|
|
411
|
+
if (success) {
|
|
412
|
+
this.spinner.succeed(text ?? void 0);
|
|
413
|
+
} else {
|
|
414
|
+
this.spinner.fail(text ?? void 0);
|
|
415
|
+
}
|
|
416
|
+
this.spinner = null;
|
|
417
|
+
}
|
|
418
|
+
// Lista
|
|
419
|
+
list(items, prefix = " \u221F") {
|
|
420
|
+
items.forEach((item) => {
|
|
421
|
+
console.log(chalk.gray(prefix) + " " + item);
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
// Quebra
|
|
425
|
+
break() {
|
|
426
|
+
console.log();
|
|
427
|
+
}
|
|
428
|
+
};
|
|
429
|
+
var logger = new Logger();
|
|
430
|
+
async function detectOnionV4Structure(projectRoot) {
|
|
431
|
+
try {
|
|
432
|
+
const onionDir = path8.join(projectRoot, ".onion");
|
|
433
|
+
const configFile = path8.join(projectRoot, ".onion-config.yml");
|
|
434
|
+
if (!await fs2.pathExists(onionDir)) {
|
|
435
|
+
return null;
|
|
436
|
+
}
|
|
437
|
+
if (!await fs2.pathExists(configFile)) {
|
|
438
|
+
return null;
|
|
439
|
+
}
|
|
440
|
+
const contextsDir = path8.join(onionDir, "contexts");
|
|
441
|
+
if (!await fs2.pathExists(contextsDir)) {
|
|
442
|
+
return null;
|
|
443
|
+
}
|
|
444
|
+
const structure = {
|
|
445
|
+
version: "v4",
|
|
446
|
+
root: projectRoot,
|
|
447
|
+
onionDir,
|
|
448
|
+
configFile,
|
|
449
|
+
contextsDir,
|
|
450
|
+
coreDir: path8.join(onionDir, "core"),
|
|
451
|
+
ideDir: path8.join(onionDir, "ide"),
|
|
452
|
+
contexts: [],
|
|
453
|
+
ides: []
|
|
454
|
+
};
|
|
455
|
+
try {
|
|
456
|
+
const contextDirs = await fs2.readdir(contextsDir);
|
|
457
|
+
const validContexts = [];
|
|
458
|
+
for (const name of contextDirs) {
|
|
459
|
+
const stat = await fs2.stat(path8.join(contextsDir, name));
|
|
460
|
+
if (stat.isDirectory()) {
|
|
461
|
+
validContexts.push(name);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
structure.contexts = validContexts;
|
|
465
|
+
} catch {
|
|
466
|
+
structure.contexts = [];
|
|
467
|
+
}
|
|
468
|
+
const ideDir = path8.join(onionDir, "ide");
|
|
469
|
+
if (await fs2.pathExists(ideDir)) {
|
|
470
|
+
try {
|
|
471
|
+
const ideDirs = await fs2.readdir(ideDir);
|
|
472
|
+
const validIdes = [];
|
|
473
|
+
for (const name of ideDirs) {
|
|
474
|
+
const stat = await fs2.stat(path8.join(ideDir, name));
|
|
475
|
+
if (stat.isDirectory()) {
|
|
476
|
+
validIdes.push(name);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
structure.ides = validIdes;
|
|
480
|
+
} catch {
|
|
481
|
+
structure.ides = [];
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
return structure;
|
|
485
|
+
} catch {
|
|
486
|
+
return null;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
async function detectOnionV3Structure(projectRoot) {
|
|
490
|
+
try {
|
|
491
|
+
const cursorDir = path8.join(projectRoot, ".cursor");
|
|
492
|
+
const commandsDir = path8.join(cursorDir, "commands");
|
|
493
|
+
const agentsDir = path8.join(cursorDir, "agents");
|
|
494
|
+
if (!await fs2.pathExists(cursorDir)) {
|
|
495
|
+
return null;
|
|
496
|
+
}
|
|
497
|
+
const hasCommands = await fs2.pathExists(commandsDir);
|
|
498
|
+
const hasAgents = await fs2.pathExists(agentsDir);
|
|
499
|
+
if (!hasCommands && !hasAgents) {
|
|
500
|
+
return null;
|
|
501
|
+
}
|
|
502
|
+
const onionDir = path8.join(projectRoot, ".onion");
|
|
503
|
+
if (await fs2.pathExists(onionDir)) {
|
|
504
|
+
return null;
|
|
505
|
+
}
|
|
506
|
+
const structure = {
|
|
507
|
+
version: "v3",
|
|
508
|
+
root: projectRoot,
|
|
509
|
+
cursorDir,
|
|
510
|
+
commandsDir,
|
|
511
|
+
agentsDir,
|
|
512
|
+
commands: {},
|
|
513
|
+
agents: {},
|
|
514
|
+
hasRules: false,
|
|
515
|
+
hasSessions: false
|
|
516
|
+
};
|
|
517
|
+
if (hasCommands) {
|
|
518
|
+
try {
|
|
519
|
+
const categories = await fs2.readdir(commandsDir);
|
|
520
|
+
for (const category of categories) {
|
|
521
|
+
const categoryPath = path8.join(commandsDir, category);
|
|
522
|
+
const stat = await fs2.stat(categoryPath);
|
|
523
|
+
if (stat.isDirectory()) {
|
|
524
|
+
const files = await fs2.readdir(categoryPath);
|
|
525
|
+
structure.commands[category] = files.filter((f) => f.endsWith(".md"));
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
} catch {
|
|
529
|
+
structure.commands = {};
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
if (hasAgents) {
|
|
533
|
+
try {
|
|
534
|
+
const categories = await fs2.readdir(agentsDir);
|
|
535
|
+
for (const category of categories) {
|
|
536
|
+
const categoryPath = path8.join(agentsDir, category);
|
|
537
|
+
const stat = await fs2.stat(categoryPath);
|
|
538
|
+
if (stat.isDirectory()) {
|
|
539
|
+
const files = await fs2.readdir(categoryPath);
|
|
540
|
+
structure.agents[category] = files.filter((f) => f.endsWith(".md"));
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
} catch {
|
|
544
|
+
structure.agents = {};
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
const rulesDir = path8.join(cursorDir, "rules");
|
|
548
|
+
structure.hasRules = await fs2.pathExists(rulesDir);
|
|
549
|
+
const sessionsDir = path8.join(cursorDir, "sessions");
|
|
550
|
+
structure.hasSessions = await fs2.pathExists(sessionsDir);
|
|
551
|
+
return structure;
|
|
552
|
+
} catch {
|
|
553
|
+
return null;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
function validateMigrationEligibility(v3Structure) {
|
|
557
|
+
const issues = [];
|
|
558
|
+
if (!v3Structure || v3Structure.version !== "v3") {
|
|
559
|
+
return { canMigrate: false, issues: ["N\xE3o \xE9 um projeto Onion v3"] };
|
|
560
|
+
}
|
|
561
|
+
if (v3Structure.root) {
|
|
562
|
+
const onionPath = path8.join(v3Structure.root, ".onion");
|
|
563
|
+
if (fs2.existsSync(onionPath)) {
|
|
564
|
+
issues.push("Projeto j\xE1 tem estrutura .onion/ (poss\xEDvel v4)");
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
const hasCommands = Object.keys(v3Structure.commands || {}).length > 0;
|
|
568
|
+
const hasAgents = Object.keys(v3Structure.agents || {}).length > 0;
|
|
569
|
+
if (!hasCommands && !hasAgents) {
|
|
570
|
+
issues.push("Projeto n\xE3o tem comandos nem agentes para migrar");
|
|
571
|
+
}
|
|
572
|
+
const canMigrate = issues.length === 0;
|
|
573
|
+
return { canMigrate, issues };
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
// src/commands/add.ts
|
|
577
|
+
init_config();
|
|
578
|
+
init_validator();
|
|
579
|
+
async function generateCoreStructure(projectRoot) {
|
|
580
|
+
const onionRoot = path8.join(projectRoot, ".onion");
|
|
581
|
+
const corePaths = [
|
|
582
|
+
"core/knowbase/concepts",
|
|
583
|
+
"core/knowbase/frameworks",
|
|
584
|
+
"core/knowbase/tools",
|
|
585
|
+
"core/knowbase/learnings",
|
|
586
|
+
"core/agents",
|
|
587
|
+
"core/commands",
|
|
588
|
+
"core/rules",
|
|
589
|
+
"core/utils"
|
|
590
|
+
];
|
|
591
|
+
for (const p of corePaths) {
|
|
592
|
+
await fs2.ensureDir(path8.join(onionRoot, p));
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
async function generateContextStructure(projectRoot, contextName, options = {}) {
|
|
596
|
+
const onionRoot = path8.join(projectRoot, ".onion");
|
|
597
|
+
const contextRoot = path8.join(onionRoot, "contexts", contextName);
|
|
598
|
+
const basePaths = [
|
|
599
|
+
"knowbase",
|
|
600
|
+
"agents",
|
|
601
|
+
"commands/starter",
|
|
602
|
+
"commands/intermediate",
|
|
603
|
+
"commands/advanced",
|
|
604
|
+
"sessions"
|
|
605
|
+
];
|
|
606
|
+
for (const p of basePaths) {
|
|
607
|
+
await fs2.ensureDir(path8.join(contextRoot, p));
|
|
608
|
+
}
|
|
609
|
+
if (options.includeREADME !== false) {
|
|
610
|
+
await generateContextREADME(projectRoot, contextName);
|
|
611
|
+
}
|
|
612
|
+
if (options.includeConfig !== false) {
|
|
613
|
+
await generateContextConfig(projectRoot, contextName, { type: options.type });
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
async function generateContextREADME(projectRoot, contextName) {
|
|
617
|
+
const readmePath = path8.join(projectRoot, ".onion/contexts", contextName, "README.md");
|
|
618
|
+
const content = `# ${capitalizeFirst(contextName)} Context
|
|
619
|
+
|
|
620
|
+
> **Onion v4.0** | Multi-Context Development Orchestrator
|
|
621
|
+
|
|
622
|
+
---
|
|
623
|
+
|
|
624
|
+
## \u{1F3AF} Sobre Este Contexto
|
|
625
|
+
|
|
626
|
+
O contexto **${contextName}** \xE9 dedicado a [descrever prop\xF3sito].
|
|
627
|
+
|
|
628
|
+
## \u{1F680} Quick Start
|
|
629
|
+
|
|
630
|
+
### Comandos Starter (80% dos casos)
|
|
631
|
+
|
|
632
|
+
\`\`\`bash
|
|
633
|
+
/${contextName}/[comando-starter]
|
|
634
|
+
\`\`\`
|
|
635
|
+
|
|
636
|
+
### Ver todos os comandos
|
|
637
|
+
|
|
638
|
+
\`\`\`bash
|
|
639
|
+
/${contextName}/help
|
|
640
|
+
\`\`\`
|
|
641
|
+
|
|
642
|
+
---
|
|
643
|
+
|
|
644
|
+
**Vers\xE3o**: 4.0.0
|
|
645
|
+
**Criado**: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}
|
|
646
|
+
`;
|
|
647
|
+
await fs2.writeFile(readmePath, content, "utf-8");
|
|
648
|
+
}
|
|
649
|
+
async function generateContextConfig(projectRoot, contextName, config = {}) {
|
|
650
|
+
const configPath = path8.join(
|
|
651
|
+
projectRoot,
|
|
652
|
+
".onion/contexts",
|
|
653
|
+
contextName,
|
|
654
|
+
".context-config.yml"
|
|
655
|
+
);
|
|
656
|
+
const defaultConfig = {
|
|
657
|
+
context: {
|
|
658
|
+
name: contextName,
|
|
659
|
+
version: "4.0.0",
|
|
660
|
+
type: config.type || "custom"
|
|
661
|
+
},
|
|
662
|
+
integrations: config.integrations || {}
|
|
663
|
+
};
|
|
664
|
+
await fs2.writeFile(configPath, yaml5.stringify(defaultConfig), "utf-8");
|
|
665
|
+
}
|
|
666
|
+
async function generateStarterCommands(projectRoot, contextName, contextType) {
|
|
667
|
+
const starterPath = path8.join(
|
|
668
|
+
projectRoot,
|
|
669
|
+
".onion/contexts",
|
|
670
|
+
contextName,
|
|
671
|
+
"commands/starter"
|
|
672
|
+
);
|
|
673
|
+
const starterCommands = [
|
|
674
|
+
{
|
|
675
|
+
name: "help",
|
|
676
|
+
description: `Show ${contextName} context help`,
|
|
677
|
+
content: generateHelpCommandContent(contextName, contextType)
|
|
678
|
+
},
|
|
679
|
+
{
|
|
680
|
+
name: "warm-up",
|
|
681
|
+
description: `Warm up ${contextName} context`,
|
|
682
|
+
content: generateWarmUpCommandContent(contextName)
|
|
683
|
+
}
|
|
684
|
+
];
|
|
685
|
+
if (contextType === "business") {
|
|
686
|
+
starterCommands.push(
|
|
687
|
+
{
|
|
688
|
+
name: "spec",
|
|
689
|
+
description: "Create product specification",
|
|
690
|
+
content: generateBusinessSpecContent()
|
|
691
|
+
},
|
|
692
|
+
{
|
|
693
|
+
name: "task",
|
|
694
|
+
description: "Create task with story points",
|
|
695
|
+
content: generateBusinessTaskContent()
|
|
696
|
+
}
|
|
697
|
+
);
|
|
698
|
+
} else if (contextType === "technical") {
|
|
699
|
+
starterCommands.push(
|
|
700
|
+
{
|
|
701
|
+
name: "plan",
|
|
702
|
+
description: "Create development plan",
|
|
703
|
+
content: generateTechnicalPlanContent()
|
|
704
|
+
},
|
|
705
|
+
{
|
|
706
|
+
name: "work",
|
|
707
|
+
description: "Continue work on feature",
|
|
708
|
+
content: generateTechnicalWorkContent()
|
|
709
|
+
}
|
|
710
|
+
);
|
|
711
|
+
}
|
|
712
|
+
for (const cmd of starterCommands) {
|
|
713
|
+
const filePath = path8.join(starterPath, `${cmd.name}.md`);
|
|
714
|
+
await fs2.writeFile(filePath, cmd.content, "utf-8");
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
async function generateIDELoader(projectRoot, ideName, config = {}) {
|
|
718
|
+
const loaderPath = path8.join(projectRoot, ".onion/ide", ideName);
|
|
719
|
+
await fs2.ensureDir(loaderPath);
|
|
720
|
+
{
|
|
721
|
+
await generateCursorLoader(projectRoot, config);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
async function generateDocsStructure(projectRoot, contexts = []) {
|
|
725
|
+
await fs2.ensureDir(path8.join(projectRoot, "docs/onion"));
|
|
726
|
+
for (const ctx of contexts) {
|
|
727
|
+
await fs2.ensureDir(path8.join(projectRoot, `docs/${ctx}-context`));
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
function capitalizeFirst(str) {
|
|
731
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
732
|
+
}
|
|
733
|
+
function generateHelpCommandContent(contextName, contextType) {
|
|
734
|
+
return `---
|
|
735
|
+
name: help
|
|
736
|
+
description: Show ${contextName} context commands by level
|
|
737
|
+
model: sonnet
|
|
738
|
+
category: ${contextType}
|
|
739
|
+
tags: [help, onboarding]
|
|
740
|
+
version: "4.0.0"
|
|
741
|
+
updated: "${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}"
|
|
742
|
+
level: starter
|
|
743
|
+
context: ${contextName}
|
|
744
|
+
---
|
|
745
|
+
|
|
746
|
+
# ${capitalizeFirst(contextName)} Context Help
|
|
747
|
+
|
|
748
|
+
List all commands in ${contextName} context organized by level.
|
|
749
|
+
|
|
750
|
+
Run: \`/${contextName}/help\` or \`/${contextName}/help --level=starter\`
|
|
751
|
+
`;
|
|
752
|
+
}
|
|
753
|
+
function generateWarmUpCommandContent(contextName) {
|
|
754
|
+
return `---
|
|
755
|
+
name: warm-up
|
|
756
|
+
description: Warm up ${contextName} context with project information
|
|
757
|
+
model: sonnet
|
|
758
|
+
category: ${contextName}
|
|
759
|
+
tags: [context, warm-up]
|
|
760
|
+
version: "4.0.0"
|
|
761
|
+
updated: "${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}"
|
|
762
|
+
level: starter
|
|
763
|
+
context: ${contextName}
|
|
764
|
+
---
|
|
765
|
+
|
|
766
|
+
# Warm Up ${capitalizeFirst(contextName)} Context
|
|
767
|
+
|
|
768
|
+
Load project context and recent activity.
|
|
769
|
+
`;
|
|
770
|
+
}
|
|
771
|
+
function generateBusinessSpecContent() {
|
|
772
|
+
return `---
|
|
773
|
+
name: spec
|
|
774
|
+
description: Create product specification
|
|
775
|
+
model: sonnet
|
|
776
|
+
category: business
|
|
777
|
+
tags: [specification, product]
|
|
778
|
+
version: "4.0.0"
|
|
779
|
+
level: starter
|
|
780
|
+
context: business
|
|
781
|
+
---
|
|
782
|
+
|
|
783
|
+
# Create Product Specification
|
|
784
|
+
|
|
785
|
+
Create detailed product specification for a feature.
|
|
786
|
+
`;
|
|
787
|
+
}
|
|
788
|
+
function generateBusinessTaskContent() {
|
|
789
|
+
return `---
|
|
790
|
+
name: task
|
|
791
|
+
description: Create task with story points
|
|
792
|
+
model: sonnet
|
|
793
|
+
category: business
|
|
794
|
+
tags: [task, story-points]
|
|
795
|
+
version: "4.0.0"
|
|
796
|
+
level: starter
|
|
797
|
+
context: business
|
|
798
|
+
---
|
|
799
|
+
|
|
800
|
+
# Create Task
|
|
801
|
+
|
|
802
|
+
Create task in task manager with story points and acceptance criteria.
|
|
803
|
+
`;
|
|
804
|
+
}
|
|
805
|
+
function generateTechnicalPlanContent() {
|
|
806
|
+
return `---
|
|
807
|
+
name: plan
|
|
808
|
+
description: Create development plan
|
|
809
|
+
model: sonnet
|
|
810
|
+
category: technical
|
|
811
|
+
tags: [planning, development]
|
|
812
|
+
version: "4.0.0"
|
|
813
|
+
level: starter
|
|
814
|
+
context: technical
|
|
815
|
+
---
|
|
816
|
+
|
|
817
|
+
# Create Development Plan
|
|
818
|
+
|
|
819
|
+
Create structured development plan with phases and tasks.
|
|
820
|
+
`;
|
|
821
|
+
}
|
|
822
|
+
function generateTechnicalWorkContent() {
|
|
823
|
+
return `---
|
|
824
|
+
name: work
|
|
825
|
+
description: Continue work on active feature
|
|
826
|
+
model: sonnet
|
|
827
|
+
category: technical
|
|
828
|
+
tags: [development, workflow]
|
|
829
|
+
version: "4.0.0"
|
|
830
|
+
level: starter
|
|
831
|
+
context: technical
|
|
832
|
+
---
|
|
833
|
+
|
|
834
|
+
# Continue Work
|
|
835
|
+
|
|
836
|
+
Continue development on active feature, reading session and identifying next phase.
|
|
837
|
+
`;
|
|
838
|
+
}
|
|
839
|
+
async function generateCursorLoader(projectRoot, config) {
|
|
840
|
+
const cursorRoot = path8.join(projectRoot, ".cursor");
|
|
841
|
+
await fs2.ensureDir(cursorRoot);
|
|
842
|
+
for (const ctx of config.contexts || []) {
|
|
843
|
+
await fs2.ensureDir(path8.join(cursorRoot, "commands", ctx));
|
|
844
|
+
await fs2.ensureDir(path8.join(cursorRoot, "agents", ctx));
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
var LoadersGenerator = class {
|
|
848
|
+
projectRoot;
|
|
849
|
+
config;
|
|
850
|
+
constructor(projectRoot, config) {
|
|
851
|
+
this.projectRoot = projectRoot;
|
|
852
|
+
this.config = config;
|
|
853
|
+
}
|
|
854
|
+
async generate() {
|
|
855
|
+
for (const ide of this.config.ides) {
|
|
856
|
+
if (ide === "universal") {
|
|
857
|
+
await this.generateUniversalAgentsMd();
|
|
858
|
+
} else {
|
|
859
|
+
await this.generateIDELoader(ide);
|
|
860
|
+
await this.createIDERootStructure(ide);
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
async generateIDELoader(ide) {
|
|
865
|
+
logger.startSpinner(`Generating ${ide} loader...`);
|
|
866
|
+
const loaderPath = path8.join(this.projectRoot, ".onion", "ide", ide);
|
|
867
|
+
await fs2.ensureDir(loaderPath);
|
|
868
|
+
if (ide === "cursor") {
|
|
869
|
+
await this.generateCursorLoader(loaderPath);
|
|
870
|
+
} else if (ide === "windsurf") {
|
|
871
|
+
await this.generateWindsurfLoader(loaderPath);
|
|
872
|
+
} else if (ide === "claude-code" || ide === "claude") {
|
|
873
|
+
await this.generateClaudeLoader(loaderPath);
|
|
874
|
+
}
|
|
875
|
+
logger.stopSpinner(true, `Created ${ide} loader`);
|
|
876
|
+
}
|
|
877
|
+
async generateCursorLoader(loaderPath) {
|
|
878
|
+
const settings = {
|
|
879
|
+
onion: {
|
|
880
|
+
enabled: true,
|
|
881
|
+
root: ".onion",
|
|
882
|
+
loader: ".onion/ide/cursor/onion-loader.js",
|
|
883
|
+
version: "4.0.0-beta.1"
|
|
884
|
+
}
|
|
885
|
+
};
|
|
886
|
+
await fs2.writeJson(path8.join(loaderPath, "settings.json"), settings, { spaces: 2 });
|
|
887
|
+
const readme = `# Cursor IDE Loader
|
|
888
|
+
|
|
889
|
+
This loader syncs Onion resources to Cursor IDE.
|
|
890
|
+
|
|
891
|
+
## Usage
|
|
892
|
+
|
|
893
|
+
The loader automatically syncs commands and agents from \`.onion/\` to \`.cursor/\`.
|
|
894
|
+
|
|
895
|
+
## Manual Sync
|
|
896
|
+
|
|
897
|
+
Run: \`node .onion/ide/cursor/onion-loader.js\`
|
|
898
|
+
`;
|
|
899
|
+
await fs2.writeFile(path8.join(loaderPath, "README.md"), readme, "utf-8");
|
|
900
|
+
}
|
|
901
|
+
async generateWindsurfLoader(loaderPath) {
|
|
902
|
+
const windsurfConfig = {
|
|
903
|
+
onion: {
|
|
904
|
+
version: "4.0.0-beta.1",
|
|
905
|
+
generated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
906
|
+
contexts: this.config.contexts
|
|
907
|
+
}
|
|
908
|
+
};
|
|
909
|
+
const configPath = path8.join(this.projectRoot, "windsurf.config.yml");
|
|
910
|
+
await fs2.writeFile(configPath, yaml5.stringify(windsurfConfig), "utf-8");
|
|
911
|
+
const readme = `# Windsurf IDE Integration
|
|
912
|
+
|
|
913
|
+
This loader generates windsurf.config.yml for Windsurf IDE.
|
|
914
|
+
|
|
915
|
+
## Files
|
|
916
|
+
|
|
917
|
+
- \`windsurf.config.yml\` - Configuration file (in project root)
|
|
918
|
+
|
|
919
|
+
## Usage
|
|
920
|
+
|
|
921
|
+
The config is auto-generated during \`onion init\` or \`onion add windsurf\`.
|
|
922
|
+
`;
|
|
923
|
+
await fs2.writeFile(path8.join(loaderPath, "README.md"), readme, "utf-8");
|
|
924
|
+
}
|
|
925
|
+
async generateClaudeLoader(loaderPath) {
|
|
926
|
+
const claudeConfig = {
|
|
927
|
+
onion: {
|
|
928
|
+
version: "4.0.0-beta.1",
|
|
929
|
+
generated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
930
|
+
contexts: this.config.contexts
|
|
931
|
+
}
|
|
932
|
+
};
|
|
933
|
+
const configPath = path8.join(this.projectRoot, "claude.config.json");
|
|
934
|
+
await fs2.writeJson(configPath, claudeConfig, { spaces: 2 });
|
|
935
|
+
const readme = `# Claude Code IDE Integration
|
|
936
|
+
|
|
937
|
+
This loader generates claude.config.json for Claude Code IDE.
|
|
938
|
+
|
|
939
|
+
## Files
|
|
940
|
+
|
|
941
|
+
- \`claude.config.json\` - Configuration file (in project root)
|
|
942
|
+
|
|
943
|
+
## Usage
|
|
944
|
+
|
|
945
|
+
The config is auto-generated during \`onion init\` or \`onion add claude-code\`.
|
|
946
|
+
`;
|
|
947
|
+
await fs2.writeFile(path8.join(loaderPath, "README.md"), readme, "utf-8");
|
|
948
|
+
}
|
|
949
|
+
async generateUniversalAgentsMd() {
|
|
950
|
+
logger.startSpinner("Generating universal AGENTS.md...");
|
|
951
|
+
const agentsMdContent = `# Sistema Onion - Universal Agents
|
|
952
|
+
|
|
953
|
+
Este arquivo serve como fallback para IDEs que n\xE3o possuem suporte nativo ao Sistema Onion.
|
|
954
|
+
|
|
955
|
+
## Comandos Dispon\xEDveis
|
|
956
|
+
|
|
957
|
+
### Core Commands
|
|
958
|
+
- \`/onion\` - Orquestrador principal
|
|
959
|
+
- \`/warm-up\` - Preparar contexto
|
|
960
|
+
|
|
961
|
+
${this.config.contexts.map(
|
|
962
|
+
(ctx) => `
|
|
963
|
+
### ${ctx.charAt(0).toUpperCase() + ctx.slice(1)} Context
|
|
964
|
+
- \`/${ctx}/spec\` - Criar especifica\xE7\xE3o
|
|
965
|
+
- \`/${ctx}/help\` - Ajuda do contexto
|
|
966
|
+
`
|
|
967
|
+
).join("")}
|
|
968
|
+
|
|
969
|
+
## Como Usar
|
|
970
|
+
|
|
971
|
+
Mencione os comandos acima no chat do seu IDE.
|
|
972
|
+
|
|
973
|
+
## Configura\xE7\xE3o
|
|
974
|
+
|
|
975
|
+
Este projeto usa Sistema Onion v4.0. Para melhor experi\xEAncia, use:
|
|
976
|
+
- Cursor IDE
|
|
977
|
+
- Windsurf
|
|
978
|
+
- Claude Code
|
|
979
|
+
|
|
980
|
+
Ou acesse a documenta\xE7\xE3o em \`.onion/README.md\`
|
|
981
|
+
`;
|
|
982
|
+
const universalPath = path8.join(this.projectRoot, ".onion", "ide", "universal");
|
|
983
|
+
await fs2.ensureDir(universalPath);
|
|
984
|
+
await fs2.writeFile(path8.join(universalPath, "AGENTS.md"), agentsMdContent, "utf-8");
|
|
985
|
+
logger.stopSpinner(true, "Created universal AGENTS.md");
|
|
986
|
+
}
|
|
987
|
+
// Criar estrutura na raiz do projeto para o IDE reconhecer
|
|
988
|
+
async createIDERootStructure(ide) {
|
|
989
|
+
if (ide === "cursor") {
|
|
990
|
+
await this.createCursorRootStructure();
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
async createCursorRootStructure() {
|
|
994
|
+
logger.startSpinner("Creating .cursor/ structure...");
|
|
995
|
+
const cursorRoot = path8.join(this.projectRoot, ".cursor");
|
|
996
|
+
await fs2.ensureDir(cursorRoot);
|
|
997
|
+
const commandsPath = path8.join(cursorRoot, "commands");
|
|
998
|
+
const agentsPath = path8.join(cursorRoot, "agents");
|
|
999
|
+
await fs2.ensureDir(commandsPath);
|
|
1000
|
+
await fs2.ensureDir(agentsPath);
|
|
1001
|
+
for (const context of this.config.contexts) {
|
|
1002
|
+
await fs2.ensureDir(path8.join(commandsPath, context));
|
|
1003
|
+
await fs2.ensureDir(path8.join(agentsPath, context));
|
|
1004
|
+
await this.createStarterCommands(context, path8.join(commandsPath, context));
|
|
1005
|
+
}
|
|
1006
|
+
const cursorRulesContent = `# Sistema Onion v4.0
|
|
1007
|
+
|
|
1008
|
+
Este projeto usa o Sistema Onion - Multi-Context Development Orchestrator.
|
|
1009
|
+
|
|
1010
|
+
## Estrutura
|
|
1011
|
+
|
|
1012
|
+
- **Comandos**: .cursor/commands/
|
|
1013
|
+
- **Agentes**: .cursor/agents/
|
|
1014
|
+
- **Fonte**: .onion/
|
|
1015
|
+
|
|
1016
|
+
## Contextos Dispon\xEDveis
|
|
1017
|
+
|
|
1018
|
+
${this.config.contexts.map((ctx) => `- **${ctx}**: /${ctx}/*`).join("\n")}
|
|
1019
|
+
|
|
1020
|
+
## Documenta\xE7\xE3o
|
|
1021
|
+
|
|
1022
|
+
Consulte \`.onion/README.md\` para mais informa\xE7\xF5es.
|
|
1023
|
+
|
|
1024
|
+
---
|
|
1025
|
+
|
|
1026
|
+
**Gerado por**: @onion/cli v4.0.0
|
|
1027
|
+
`;
|
|
1028
|
+
await fs2.writeFile(path8.join(cursorRoot, ".cursorrules"), cursorRulesContent, "utf-8");
|
|
1029
|
+
logger.stopSpinner(true, "Created .cursor/ structure");
|
|
1030
|
+
}
|
|
1031
|
+
async createStarterCommands(context, commandsPath) {
|
|
1032
|
+
const helpContent = `---
|
|
1033
|
+
name: help
|
|
1034
|
+
description: Ajuda do contexto ${context}
|
|
1035
|
+
category: ${context}
|
|
1036
|
+
version: "4.0.0"
|
|
1037
|
+
---
|
|
1038
|
+
|
|
1039
|
+
# \u{1F4DA} ${context.charAt(0).toUpperCase() + context.slice(1)} Context - Help
|
|
1040
|
+
|
|
1041
|
+
Comandos dispon\xEDveis neste contexto.
|
|
1042
|
+
|
|
1043
|
+
## \u{1F7E2} Starter Commands (essenciais)
|
|
1044
|
+
|
|
1045
|
+
- \`/${context}/help\` - Esta ajuda
|
|
1046
|
+
|
|
1047
|
+
## \u{1F4D6} Documenta\xE7\xE3o
|
|
1048
|
+
|
|
1049
|
+
Veja documenta\xE7\xE3o completa em \`docs/${context}-context/\`
|
|
1050
|
+
|
|
1051
|
+
---
|
|
1052
|
+
|
|
1053
|
+
**Fonte**: .onion/contexts/${context}/commands/starter/help.md
|
|
1054
|
+
**Auto-gerado por**: @onion/cli
|
|
1055
|
+
`;
|
|
1056
|
+
await fs2.writeFile(path8.join(commandsPath, "help.md"), helpContent, "utf-8");
|
|
1057
|
+
}
|
|
1058
|
+
};
|
|
1059
|
+
|
|
1060
|
+
// src/commands/add.ts
|
|
1061
|
+
async function add(options = {}) {
|
|
1062
|
+
try {
|
|
1063
|
+
const projectRoot = process.cwd();
|
|
1064
|
+
const project = await detectOnionV4Structure(projectRoot);
|
|
1065
|
+
if (!project) {
|
|
1066
|
+
logger.error("\u274C This is not an Onion v4 project");
|
|
1067
|
+
logger.info('Run "onion init" first to initialize the system');
|
|
1068
|
+
process.exit(1);
|
|
1069
|
+
}
|
|
1070
|
+
logger.title("\u{1F9C5} Add to Onion System");
|
|
1071
|
+
logger.break();
|
|
1072
|
+
const { type } = await inquirer.prompt([
|
|
1073
|
+
{
|
|
1074
|
+
type: "list",
|
|
1075
|
+
name: "type",
|
|
1076
|
+
message: "What would you like to add?",
|
|
1077
|
+
choices: [
|
|
1078
|
+
{ name: "\u{1F4E6} New Context", value: "context" },
|
|
1079
|
+
{ name: "\u{1F4BB} New IDE Integration", value: "ide" },
|
|
1080
|
+
{ name: "\u274C Cancel", value: "cancel" }
|
|
1081
|
+
]
|
|
1082
|
+
}
|
|
1083
|
+
]);
|
|
1084
|
+
if (type === "cancel") {
|
|
1085
|
+
logger.info("Cancelled");
|
|
1086
|
+
return;
|
|
1087
|
+
}
|
|
1088
|
+
if (type === "context") {
|
|
1089
|
+
await addContext2(project);
|
|
1090
|
+
} else if (type === "ide") {
|
|
1091
|
+
await addIDE2(project);
|
|
1092
|
+
}
|
|
1093
|
+
} catch (error) {
|
|
1094
|
+
logger.break();
|
|
1095
|
+
logger.error("\u274C Add command failed:");
|
|
1096
|
+
logger.error(error.message);
|
|
1097
|
+
if (options.debug) {
|
|
1098
|
+
console.error(error);
|
|
1099
|
+
}
|
|
1100
|
+
process.exit(1);
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
async function addContext2(project) {
|
|
1104
|
+
const { contextName } = await inquirer.prompt([
|
|
1105
|
+
{
|
|
1106
|
+
type: "input",
|
|
1107
|
+
name: "contextName",
|
|
1108
|
+
message: 'Context name (lowercase, e.g., "customer-success"):',
|
|
1109
|
+
validate: (input) => {
|
|
1110
|
+
try {
|
|
1111
|
+
validateContextName(input);
|
|
1112
|
+
return true;
|
|
1113
|
+
} catch (err) {
|
|
1114
|
+
return err.message;
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
]);
|
|
1119
|
+
const config = await readConfig(project.root);
|
|
1120
|
+
if (config.contexts.includes(contextName)) {
|
|
1121
|
+
logger.error(`\u274C Context "${contextName}" already exists`);
|
|
1122
|
+
process.exit(1);
|
|
1123
|
+
}
|
|
1124
|
+
const { contextType } = await inquirer.prompt([
|
|
1125
|
+
{
|
|
1126
|
+
type: "list",
|
|
1127
|
+
name: "contextType",
|
|
1128
|
+
message: "Context type:",
|
|
1129
|
+
choices: [
|
|
1130
|
+
{ name: "\u{1F4CA} Business/Product", value: "business" },
|
|
1131
|
+
{ name: "\u2699\uFE0F Technical/Engineering", value: "technical" },
|
|
1132
|
+
{ name: "\u{1F3A8} Custom", value: "custom" }
|
|
1133
|
+
]
|
|
1134
|
+
}
|
|
1135
|
+
]);
|
|
1136
|
+
logger.break();
|
|
1137
|
+
logger.startSpinner(`Creating context "${contextName}"...`);
|
|
1138
|
+
try {
|
|
1139
|
+
await generateContextStructure(project.root, contextName, {
|
|
1140
|
+
includeREADME: true,
|
|
1141
|
+
includeConfig: true
|
|
1142
|
+
});
|
|
1143
|
+
await generateStarterCommands(project.root, contextName, contextType);
|
|
1144
|
+
await addContext(project.root, contextName);
|
|
1145
|
+
for (const ide of config.ides) {
|
|
1146
|
+
const loaderConfig = {
|
|
1147
|
+
contexts: config.contexts,
|
|
1148
|
+
ides: [ide]
|
|
1149
|
+
};
|
|
1150
|
+
const loadersGen = new LoadersGenerator(project.root, loaderConfig);
|
|
1151
|
+
await loadersGen.generateIDELoader(ide);
|
|
1152
|
+
if (ide === "cursor") {
|
|
1153
|
+
try {
|
|
1154
|
+
const loaderPath = path8.join(project.root, ".onion/ide/cursor/onion-loader.js");
|
|
1155
|
+
if (fs2.existsSync(loaderPath)) {
|
|
1156
|
+
const loaderModule = await import(loaderPath);
|
|
1157
|
+
const loader = loaderModule.getLoader(project.root);
|
|
1158
|
+
loader.syncToCursor();
|
|
1159
|
+
}
|
|
1160
|
+
} catch {
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
logger.stopSpinner(true, `Context "${contextName}" created successfully`);
|
|
1165
|
+
logger.break();
|
|
1166
|
+
logger.success("Next steps:");
|
|
1167
|
+
logger.info(` 1. Explore: /${contextName}/help`);
|
|
1168
|
+
logger.info(` 2. Add commands: .onion/contexts/${contextName}/commands/`);
|
|
1169
|
+
logger.info(` 3. Add agents: .onion/contexts/${contextName}/agents/`);
|
|
1170
|
+
logger.break();
|
|
1171
|
+
} catch (error) {
|
|
1172
|
+
logger.stopSpinner(false, "Failed to create context");
|
|
1173
|
+
throw error;
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
async function addIDE2(project) {
|
|
1177
|
+
const config = await readConfig(project.root);
|
|
1178
|
+
const availableIDEs = ["cursor", "windsurf", "claude"];
|
|
1179
|
+
const notConfigured = availableIDEs.filter((ide) => !config.ides.includes(ide));
|
|
1180
|
+
if (notConfigured.length === 0) {
|
|
1181
|
+
logger.warn("\u26A0\uFE0F All supported IDEs are already configured");
|
|
1182
|
+
logger.info("Configured IDEs: " + config.ides.join(", "));
|
|
1183
|
+
return;
|
|
1184
|
+
}
|
|
1185
|
+
const { ideName } = await inquirer.prompt([
|
|
1186
|
+
{
|
|
1187
|
+
type: "list",
|
|
1188
|
+
name: "ideName",
|
|
1189
|
+
message: "Which IDE would you like to add?",
|
|
1190
|
+
choices: notConfigured.map((ide) => ({
|
|
1191
|
+
name: capitalizeFirst2(ide),
|
|
1192
|
+
value: ide
|
|
1193
|
+
}))
|
|
1194
|
+
}
|
|
1195
|
+
]);
|
|
1196
|
+
logger.break();
|
|
1197
|
+
logger.startSpinner(`Configuring ${ideName}...`);
|
|
1198
|
+
try {
|
|
1199
|
+
const loaderConfig = {
|
|
1200
|
+
contexts: config.contexts,
|
|
1201
|
+
ides: [ideName]
|
|
1202
|
+
};
|
|
1203
|
+
const loadersGen = new LoadersGenerator(project.root, loaderConfig);
|
|
1204
|
+
await loadersGen.generateIDELoader(ideName);
|
|
1205
|
+
await addIDE(project.root, ideName);
|
|
1206
|
+
if (ideName === "cursor") {
|
|
1207
|
+
try {
|
|
1208
|
+
const loaderPath = path8.join(project.root, ".onion/ide/cursor/onion-loader.js");
|
|
1209
|
+
if (fs2.existsSync(loaderPath)) {
|
|
1210
|
+
const loaderModule = await import(loaderPath);
|
|
1211
|
+
const loader = loaderModule.getLoader(project.root);
|
|
1212
|
+
const result = loader.syncToCursor();
|
|
1213
|
+
logger.info(` Synced ${result.commandsSynced} commands and ${result.agentsSynced} agents`);
|
|
1214
|
+
}
|
|
1215
|
+
} catch (err) {
|
|
1216
|
+
logger.warn(` Could not sync: ${err.message}`);
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
logger.stopSpinner(true, `IDE "${ideName}" configured successfully`);
|
|
1220
|
+
logger.break();
|
|
1221
|
+
logger.success("Next steps:");
|
|
1222
|
+
if (ideName === "cursor") {
|
|
1223
|
+
logger.info(" 1. Restart Cursor to load commands");
|
|
1224
|
+
logger.info(" 2. Test: Run any command like /business/help");
|
|
1225
|
+
} else if (ideName === "windsurf") {
|
|
1226
|
+
logger.info(" 1. Restart Windsurf");
|
|
1227
|
+
logger.info(" 2. Commands will be available via Windsurf interface");
|
|
1228
|
+
} else if (ideName === "claude") {
|
|
1229
|
+
logger.info(" 1. Restart Claude Code");
|
|
1230
|
+
logger.info(" 2. Commands available via @onion agent");
|
|
1231
|
+
}
|
|
1232
|
+
logger.break();
|
|
1233
|
+
} catch (error) {
|
|
1234
|
+
logger.stopSpinner(false, "Failed to configure IDE");
|
|
1235
|
+
throw error;
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
function capitalizeFirst2(str) {
|
|
1239
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
1240
|
+
}
|
|
1241
|
+
init_config();
|
|
1242
|
+
async function detectV3Commands(projectRoot) {
|
|
1243
|
+
const v3 = await detectOnionV3Structure(projectRoot);
|
|
1244
|
+
if (!v3) {
|
|
1245
|
+
return [];
|
|
1246
|
+
}
|
|
1247
|
+
const commands = [];
|
|
1248
|
+
for (const [category, files] of Object.entries(v3.commands)) {
|
|
1249
|
+
const categoryPath = path8.join(v3.commandsDir, category);
|
|
1250
|
+
for (const file of files) {
|
|
1251
|
+
const filePath = path8.join(categoryPath, file);
|
|
1252
|
+
const metadata = await extractCommandMetadata(filePath);
|
|
1253
|
+
commands.push({
|
|
1254
|
+
name: path8.basename(file, ".md"),
|
|
1255
|
+
category,
|
|
1256
|
+
path: filePath,
|
|
1257
|
+
relativePath: path8.relative(projectRoot, filePath),
|
|
1258
|
+
metadata
|
|
1259
|
+
});
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
return commands;
|
|
1263
|
+
}
|
|
1264
|
+
async function detectV3Agents(projectRoot) {
|
|
1265
|
+
const v3 = await detectOnionV3Structure(projectRoot);
|
|
1266
|
+
if (!v3) {
|
|
1267
|
+
return [];
|
|
1268
|
+
}
|
|
1269
|
+
const agents = [];
|
|
1270
|
+
for (const [category, files] of Object.entries(v3.agents)) {
|
|
1271
|
+
const categoryPath = path8.join(v3.agentsDir, category);
|
|
1272
|
+
for (const file of files) {
|
|
1273
|
+
const filePath = path8.join(categoryPath, file);
|
|
1274
|
+
const metadata = await extractAgentMetadata(filePath);
|
|
1275
|
+
agents.push({
|
|
1276
|
+
name: path8.basename(file, ".md"),
|
|
1277
|
+
category,
|
|
1278
|
+
path: filePath,
|
|
1279
|
+
relativePath: path8.relative(projectRoot, filePath),
|
|
1280
|
+
metadata
|
|
1281
|
+
});
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
return agents;
|
|
1285
|
+
}
|
|
1286
|
+
async function detectV3CustomFiles(projectRoot) {
|
|
1287
|
+
const custom = [];
|
|
1288
|
+
const cursorDir = path8.join(projectRoot, ".cursor");
|
|
1289
|
+
if (!await fs2.pathExists(cursorDir)) {
|
|
1290
|
+
return custom;
|
|
1291
|
+
}
|
|
1292
|
+
const subdirs = ["commands", "agents", "rules", "utils"];
|
|
1293
|
+
for (const subdir of subdirs) {
|
|
1294
|
+
const dirPath = path8.join(cursorDir, subdir);
|
|
1295
|
+
if (await fs2.pathExists(dirPath)) {
|
|
1296
|
+
const files = await findCustomFiles(dirPath, projectRoot);
|
|
1297
|
+
custom.push(...files);
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
return custom;
|
|
1301
|
+
}
|
|
1302
|
+
async function analyzeV3Structure(projectRoot) {
|
|
1303
|
+
const commands = await detectV3Commands(projectRoot);
|
|
1304
|
+
const agents = await detectV3Agents(projectRoot);
|
|
1305
|
+
const customFiles = await detectV3CustomFiles(projectRoot);
|
|
1306
|
+
const v3 = await detectOnionV3Structure(projectRoot);
|
|
1307
|
+
const stats = {
|
|
1308
|
+
totalCommands: commands.length,
|
|
1309
|
+
totalAgents: agents.length,
|
|
1310
|
+
totalCustomFiles: customFiles.length,
|
|
1311
|
+
commandsByCategory: {},
|
|
1312
|
+
agentsByCategory: {},
|
|
1313
|
+
hasRules: v3?.hasRules || false,
|
|
1314
|
+
hasSessions: v3?.hasSessions || false
|
|
1315
|
+
};
|
|
1316
|
+
for (const cmd of commands) {
|
|
1317
|
+
stats.commandsByCategory[cmd.category] = (stats.commandsByCategory[cmd.category] || 0) + 1;
|
|
1318
|
+
}
|
|
1319
|
+
for (const agent of agents) {
|
|
1320
|
+
stats.agentsByCategory[agent.category] = (stats.agentsByCategory[agent.category] || 0) + 1;
|
|
1321
|
+
}
|
|
1322
|
+
return {
|
|
1323
|
+
commands,
|
|
1324
|
+
agents,
|
|
1325
|
+
customFiles,
|
|
1326
|
+
stats,
|
|
1327
|
+
v3Structure: v3
|
|
1328
|
+
};
|
|
1329
|
+
}
|
|
1330
|
+
async function extractCommandMetadata(filePath) {
|
|
1331
|
+
try {
|
|
1332
|
+
const content = await fs2.readFile(filePath, "utf-8");
|
|
1333
|
+
const yamlMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
1334
|
+
if (!yamlMatch || !yamlMatch[1]) {
|
|
1335
|
+
return { raw: true };
|
|
1336
|
+
}
|
|
1337
|
+
const metadata = yaml5.parse(yamlMatch[1]);
|
|
1338
|
+
return {
|
|
1339
|
+
...metadata,
|
|
1340
|
+
hasYAML: true,
|
|
1341
|
+
contentLength: content.length,
|
|
1342
|
+
linesCount: content.split("\n").length
|
|
1343
|
+
};
|
|
1344
|
+
} catch (error) {
|
|
1345
|
+
return {
|
|
1346
|
+
error: error.message,
|
|
1347
|
+
raw: true
|
|
1348
|
+
};
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
async function extractAgentMetadata(filePath) {
|
|
1352
|
+
try {
|
|
1353
|
+
const content = await fs2.readFile(filePath, "utf-8");
|
|
1354
|
+
const yamlMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
1355
|
+
if (!yamlMatch || !yamlMatch[1]) {
|
|
1356
|
+
return { raw: true };
|
|
1357
|
+
}
|
|
1358
|
+
const metadata = yaml5.parse(yamlMatch[1]);
|
|
1359
|
+
return {
|
|
1360
|
+
...metadata,
|
|
1361
|
+
hasYAML: true,
|
|
1362
|
+
contentLength: content.length,
|
|
1363
|
+
linesCount: content.split("\n").length
|
|
1364
|
+
};
|
|
1365
|
+
} catch (error) {
|
|
1366
|
+
return {
|
|
1367
|
+
error: error.message,
|
|
1368
|
+
raw: true
|
|
1369
|
+
};
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
async function findCustomFiles(dirPath, projectRoot) {
|
|
1373
|
+
const custom = [];
|
|
1374
|
+
try {
|
|
1375
|
+
const entries = await fs2.readdir(dirPath, { withFileTypes: true });
|
|
1376
|
+
for (const entry of entries) {
|
|
1377
|
+
const fullPath = path8.join(dirPath, entry.name);
|
|
1378
|
+
if (entry.isDirectory()) {
|
|
1379
|
+
const subFiles = await findCustomFiles(fullPath, projectRoot);
|
|
1380
|
+
custom.push(...subFiles);
|
|
1381
|
+
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
1382
|
+
const stats = await fs2.stat(fullPath);
|
|
1383
|
+
const content = await fs2.readFile(fullPath, "utf-8");
|
|
1384
|
+
if (content.length > 100) {
|
|
1385
|
+
custom.push({
|
|
1386
|
+
name: entry.name,
|
|
1387
|
+
path: fullPath,
|
|
1388
|
+
relativePath: path8.relative(projectRoot, fullPath),
|
|
1389
|
+
size: stats.size,
|
|
1390
|
+
modified: stats.mtime
|
|
1391
|
+
});
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
} catch {
|
|
1396
|
+
}
|
|
1397
|
+
return custom;
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
// src/migrator/mapper.ts
|
|
1401
|
+
var CATEGORY_TO_CONTEXT = {
|
|
1402
|
+
// Commands
|
|
1403
|
+
product: "business",
|
|
1404
|
+
business: "business",
|
|
1405
|
+
engineer: "technical",
|
|
1406
|
+
git: "technical",
|
|
1407
|
+
docs: "technical",
|
|
1408
|
+
test: "technical",
|
|
1409
|
+
meta: "core",
|
|
1410
|
+
validate: "core",
|
|
1411
|
+
quick: "core",
|
|
1412
|
+
global: "core",
|
|
1413
|
+
// Agents
|
|
1414
|
+
development: "technical",
|
|
1415
|
+
compliance: "business",
|
|
1416
|
+
review: "technical",
|
|
1417
|
+
testing: "technical",
|
|
1418
|
+
research: "business"
|
|
1419
|
+
};
|
|
1420
|
+
var KNOWN_STARTER_COMMANDS = [
|
|
1421
|
+
"spec",
|
|
1422
|
+
"task",
|
|
1423
|
+
"estimate",
|
|
1424
|
+
"refine",
|
|
1425
|
+
"warm-up",
|
|
1426
|
+
"work",
|
|
1427
|
+
"plan",
|
|
1428
|
+
"pr",
|
|
1429
|
+
"docs",
|
|
1430
|
+
"sync",
|
|
1431
|
+
"init",
|
|
1432
|
+
"help"
|
|
1433
|
+
];
|
|
1434
|
+
var KNOWN_ADVANCED_COMMANDS = [
|
|
1435
|
+
"bump",
|
|
1436
|
+
"hotfix",
|
|
1437
|
+
"e2e",
|
|
1438
|
+
"presentation",
|
|
1439
|
+
"branding",
|
|
1440
|
+
"analyze-pain-price",
|
|
1441
|
+
"transform-consolidated",
|
|
1442
|
+
"release-start",
|
|
1443
|
+
"release-finish",
|
|
1444
|
+
"feature-start",
|
|
1445
|
+
"feature-finish"
|
|
1446
|
+
];
|
|
1447
|
+
function mapCommandToContext(command) {
|
|
1448
|
+
const context = CATEGORY_TO_CONTEXT[command.category] || "custom";
|
|
1449
|
+
const level = inferCommandLevel(command);
|
|
1450
|
+
const newPath = `.onion/contexts/${context}/commands/${level}/${command.name}.md`;
|
|
1451
|
+
return {
|
|
1452
|
+
context,
|
|
1453
|
+
level,
|
|
1454
|
+
newPath,
|
|
1455
|
+
oldPath: command.path,
|
|
1456
|
+
name: command.name,
|
|
1457
|
+
category: command.category
|
|
1458
|
+
};
|
|
1459
|
+
}
|
|
1460
|
+
function mapAgentToContext(agent) {
|
|
1461
|
+
const context = CATEGORY_TO_CONTEXT[agent.category] || "custom";
|
|
1462
|
+
if (agent.name.includes("onion") || agent.name.includes("metaspec")) {
|
|
1463
|
+
return {
|
|
1464
|
+
context: "core",
|
|
1465
|
+
newPath: `.onion/core/agents/${agent.name}.md`,
|
|
1466
|
+
oldPath: agent.path,
|
|
1467
|
+
name: agent.name,
|
|
1468
|
+
category: agent.category
|
|
1469
|
+
};
|
|
1470
|
+
}
|
|
1471
|
+
const newPath = `.onion/contexts/${context}/agents/${agent.name}.md`;
|
|
1472
|
+
return {
|
|
1473
|
+
context,
|
|
1474
|
+
newPath,
|
|
1475
|
+
oldPath: agent.path,
|
|
1476
|
+
name: agent.name,
|
|
1477
|
+
category: agent.category
|
|
1478
|
+
};
|
|
1479
|
+
}
|
|
1480
|
+
function inferCommandLevel(command) {
|
|
1481
|
+
const name = command.name.toLowerCase();
|
|
1482
|
+
if (KNOWN_STARTER_COMMANDS.includes(name)) {
|
|
1483
|
+
return "starter";
|
|
1484
|
+
}
|
|
1485
|
+
if (KNOWN_ADVANCED_COMMANDS.includes(name)) {
|
|
1486
|
+
return "advanced";
|
|
1487
|
+
}
|
|
1488
|
+
if (command.metadata) {
|
|
1489
|
+
if (command.metadata.level) {
|
|
1490
|
+
return command.metadata.level;
|
|
1491
|
+
}
|
|
1492
|
+
const tags = command.metadata.tags || [];
|
|
1493
|
+
const description = (command.metadata.description || "").toLowerCase();
|
|
1494
|
+
if (tags.includes("starter") || tags.includes("basic") || name.includes("help") || name.includes("warm-up")) {
|
|
1495
|
+
return "starter";
|
|
1496
|
+
}
|
|
1497
|
+
if (tags.includes("advanced") || tags.includes("release") || name.includes("release") || name.includes("hotfix") || description.includes("advanced") || description.includes("complex")) {
|
|
1498
|
+
return "advanced";
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
return "intermediate";
|
|
1502
|
+
}
|
|
1503
|
+
function buildMigrationPlan(analysis) {
|
|
1504
|
+
const contextsSet = /* @__PURE__ */ new Set();
|
|
1505
|
+
const commands = [];
|
|
1506
|
+
const agents = [];
|
|
1507
|
+
const summary = {
|
|
1508
|
+
totalCommands: analysis.commands.length,
|
|
1509
|
+
totalAgents: analysis.agents.length,
|
|
1510
|
+
commandsByContext: {},
|
|
1511
|
+
agentsByContext: {},
|
|
1512
|
+
commandsByLevel: {
|
|
1513
|
+
starter: 0,
|
|
1514
|
+
intermediate: 0,
|
|
1515
|
+
advanced: 0
|
|
1516
|
+
}
|
|
1517
|
+
};
|
|
1518
|
+
for (const command of analysis.commands) {
|
|
1519
|
+
const mapping = mapCommandToContext(command);
|
|
1520
|
+
commands.push(mapping);
|
|
1521
|
+
contextsSet.add(mapping.context);
|
|
1522
|
+
summary.commandsByContext[mapping.context] = (summary.commandsByContext[mapping.context] || 0) + 1;
|
|
1523
|
+
const level = mapping.level;
|
|
1524
|
+
if (level in summary.commandsByLevel) {
|
|
1525
|
+
summary.commandsByLevel[level]++;
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
for (const agent of analysis.agents) {
|
|
1529
|
+
const mapping = mapAgentToContext(agent);
|
|
1530
|
+
agents.push(mapping);
|
|
1531
|
+
contextsSet.add(mapping.context);
|
|
1532
|
+
summary.agentsByContext[mapping.context] = (summary.agentsByContext[mapping.context] || 0) + 1;
|
|
1533
|
+
}
|
|
1534
|
+
return {
|
|
1535
|
+
commands,
|
|
1536
|
+
agents,
|
|
1537
|
+
contexts: Array.from(contextsSet),
|
|
1538
|
+
summary
|
|
1539
|
+
};
|
|
1540
|
+
}
|
|
1541
|
+
function validateMigrationPlan(plan) {
|
|
1542
|
+
const issues = [];
|
|
1543
|
+
if (plan.contexts.length === 0) {
|
|
1544
|
+
issues.push("Nenhum contexto identificado");
|
|
1545
|
+
}
|
|
1546
|
+
if (plan.commands.length === 0 && plan.agents.length === 0) {
|
|
1547
|
+
issues.push("Nenhum comando ou agente para migrar");
|
|
1548
|
+
}
|
|
1549
|
+
const newPaths = /* @__PURE__ */ new Set();
|
|
1550
|
+
for (const cmd of plan.commands) {
|
|
1551
|
+
if (newPaths.has(cmd.newPath)) {
|
|
1552
|
+
issues.push(`Path duplicado: ${cmd.newPath}`);
|
|
1553
|
+
}
|
|
1554
|
+
newPaths.add(cmd.newPath);
|
|
1555
|
+
}
|
|
1556
|
+
for (const agent of plan.agents) {
|
|
1557
|
+
if (newPaths.has(agent.newPath)) {
|
|
1558
|
+
issues.push(`Path duplicado: ${agent.newPath}`);
|
|
1559
|
+
}
|
|
1560
|
+
newPaths.add(agent.newPath);
|
|
1561
|
+
}
|
|
1562
|
+
return {
|
|
1563
|
+
valid: issues.length === 0,
|
|
1564
|
+
issues
|
|
1565
|
+
};
|
|
1566
|
+
}
|
|
1567
|
+
function generateMigrationReport(plan) {
|
|
1568
|
+
const lines = [];
|
|
1569
|
+
lines.push("# Plano de Migra\xE7\xE3o Onion v3 \u2192 v4\n");
|
|
1570
|
+
lines.push("## \u{1F4CA} Resumo\n");
|
|
1571
|
+
lines.push(`- **Comandos**: ${plan.summary.totalCommands}`);
|
|
1572
|
+
lines.push(`- **Agentes**: ${plan.summary.totalAgents}`);
|
|
1573
|
+
lines.push(`- **Contextos**: ${plan.contexts.join(", ")}
|
|
1574
|
+
`);
|
|
1575
|
+
lines.push("## \u{1F4E6} Comandos por Contexto\n");
|
|
1576
|
+
for (const [context, count] of Object.entries(plan.summary.commandsByContext)) {
|
|
1577
|
+
lines.push(`- **${context}**: ${count} comandos`);
|
|
1578
|
+
}
|
|
1579
|
+
lines.push("");
|
|
1580
|
+
lines.push("## \u{1F4CA} Comandos por N\xEDvel\n");
|
|
1581
|
+
lines.push(`- **Starter**: ${plan.summary.commandsByLevel.starter}`);
|
|
1582
|
+
lines.push(`- **Intermediate**: ${plan.summary.commandsByLevel.intermediate}`);
|
|
1583
|
+
lines.push(`- **Advanced**: ${plan.summary.commandsByLevel.advanced}
|
|
1584
|
+
`);
|
|
1585
|
+
if (plan.summary.totalAgents > 0) {
|
|
1586
|
+
lines.push("## \u{1F916} Agentes por Contexto\n");
|
|
1587
|
+
for (const [context, count] of Object.entries(plan.summary.agentsByContext)) {
|
|
1588
|
+
lines.push(`- **${context}**: ${count} agentes`);
|
|
1589
|
+
}
|
|
1590
|
+
lines.push("");
|
|
1591
|
+
}
|
|
1592
|
+
return lines.join("\n");
|
|
1593
|
+
}
|
|
1594
|
+
async function transformCommandFile(sourceFilePath, targetFilePath, targetContext, targetLevel) {
|
|
1595
|
+
const content = await fs2.readFile(sourceFilePath, "utf-8");
|
|
1596
|
+
const yamlMatch = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
1597
|
+
if (!yamlMatch || !yamlMatch[1] || yamlMatch[2] === void 0) {
|
|
1598
|
+
const newContent2 = createCommandYAMLHeader(path8.basename(sourceFilePath, ".md"), targetContext, targetLevel) + "\n\n" + content;
|
|
1599
|
+
await fs2.ensureDir(path8.dirname(targetFilePath));
|
|
1600
|
+
await fs2.writeFile(targetFilePath, newContent2, "utf-8");
|
|
1601
|
+
return;
|
|
1602
|
+
}
|
|
1603
|
+
const existingYAML = yaml5.parse(yamlMatch[1]);
|
|
1604
|
+
const body = yamlMatch[2];
|
|
1605
|
+
const updatedYAML = {
|
|
1606
|
+
...existingYAML,
|
|
1607
|
+
context: targetContext,
|
|
1608
|
+
level: targetLevel,
|
|
1609
|
+
version: existingYAML.version || "4.0.0",
|
|
1610
|
+
updated: (/* @__PURE__ */ new Date()).toISOString().split("T")[0]
|
|
1611
|
+
};
|
|
1612
|
+
const newContent = "---\n" + yaml5.stringify(updatedYAML) + "---\n" + body;
|
|
1613
|
+
await fs2.ensureDir(path8.dirname(targetFilePath));
|
|
1614
|
+
await fs2.writeFile(targetFilePath, newContent, "utf-8");
|
|
1615
|
+
}
|
|
1616
|
+
async function transformAgentFile(sourceFilePath, targetFilePath, targetContext) {
|
|
1617
|
+
const content = await fs2.readFile(sourceFilePath, "utf-8");
|
|
1618
|
+
const yamlMatch = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
1619
|
+
if (!yamlMatch || !yamlMatch[1] || yamlMatch[2] === void 0) {
|
|
1620
|
+
const newContent2 = createAgentYAMLHeader(path8.basename(sourceFilePath, ".md"), targetContext) + "\n\n" + content;
|
|
1621
|
+
await fs2.ensureDir(path8.dirname(targetFilePath));
|
|
1622
|
+
await fs2.writeFile(targetFilePath, newContent2, "utf-8");
|
|
1623
|
+
return;
|
|
1624
|
+
}
|
|
1625
|
+
const existingYAML = yaml5.parse(yamlMatch[1]);
|
|
1626
|
+
const body = yamlMatch[2];
|
|
1627
|
+
const updatedYAML = {
|
|
1628
|
+
...existingYAML,
|
|
1629
|
+
context: targetContext,
|
|
1630
|
+
version: existingYAML.version || "4.0.0",
|
|
1631
|
+
updated: (/* @__PURE__ */ new Date()).toISOString().split("T")[0]
|
|
1632
|
+
};
|
|
1633
|
+
const newContent = "---\n" + yaml5.stringify(updatedYAML) + "---\n" + body;
|
|
1634
|
+
await fs2.ensureDir(path8.dirname(targetFilePath));
|
|
1635
|
+
await fs2.writeFile(targetFilePath, newContent, "utf-8");
|
|
1636
|
+
}
|
|
1637
|
+
async function createBackwardCompatSymlink(projectRoot, v3Path, v4Path) {
|
|
1638
|
+
const v3FullPath = path8.join(projectRoot, v3Path);
|
|
1639
|
+
const v4FullPath = path8.join(projectRoot, v4Path);
|
|
1640
|
+
await fs2.ensureDir(path8.dirname(v3FullPath));
|
|
1641
|
+
const relativePath = path8.relative(path8.dirname(v3FullPath), v4FullPath);
|
|
1642
|
+
try {
|
|
1643
|
+
if (await fs2.pathExists(v3FullPath)) {
|
|
1644
|
+
await fs2.remove(v3FullPath);
|
|
1645
|
+
}
|
|
1646
|
+
await fs2.symlink(relativePath, v3FullPath);
|
|
1647
|
+
} catch {
|
|
1648
|
+
console.warn(`Warning: Could not create symlink ${v3Path} \u2192 ${v4Path}`);
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
async function createSymlinks(projectRoot, mappings) {
|
|
1652
|
+
let created = 0;
|
|
1653
|
+
for (const mapping of mappings) {
|
|
1654
|
+
try {
|
|
1655
|
+
await createBackwardCompatSymlink(projectRoot, mapping.oldPath, mapping.newPath);
|
|
1656
|
+
created++;
|
|
1657
|
+
} catch {
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
return created;
|
|
1661
|
+
}
|
|
1662
|
+
async function copyRules(projectRoot) {
|
|
1663
|
+
const v3RulesPath = path8.join(projectRoot, ".cursor/rules");
|
|
1664
|
+
const v4RulesPath = path8.join(projectRoot, ".onion/core/rules");
|
|
1665
|
+
if (!await fs2.pathExists(v3RulesPath)) {
|
|
1666
|
+
return 0;
|
|
1667
|
+
}
|
|
1668
|
+
const files = await fs2.readdir(v3RulesPath);
|
|
1669
|
+
let copied = 0;
|
|
1670
|
+
for (const file of files) {
|
|
1671
|
+
if (file.endsWith(".mdc") || file.endsWith(".md")) {
|
|
1672
|
+
await fs2.copy(path8.join(v3RulesPath, file), path8.join(v4RulesPath, file));
|
|
1673
|
+
copied++;
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
return copied;
|
|
1677
|
+
}
|
|
1678
|
+
async function copySessions(projectRoot, targetContext = "technical") {
|
|
1679
|
+
const v3SessionsPath = path8.join(projectRoot, ".cursor/sessions");
|
|
1680
|
+
const v4SessionsPath = path8.join(projectRoot, `.onion/contexts/${targetContext}/sessions`);
|
|
1681
|
+
if (!await fs2.pathExists(v3SessionsPath)) {
|
|
1682
|
+
return 0;
|
|
1683
|
+
}
|
|
1684
|
+
await fs2.copy(v3SessionsPath, v4SessionsPath);
|
|
1685
|
+
const dirs = await fs2.readdir(v3SessionsPath);
|
|
1686
|
+
let count = 0;
|
|
1687
|
+
for (const name of dirs) {
|
|
1688
|
+
const stat = await fs2.stat(path8.join(v3SessionsPath, name));
|
|
1689
|
+
if (stat.isDirectory()) {
|
|
1690
|
+
count++;
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
return count;
|
|
1694
|
+
}
|
|
1695
|
+
function createCommandYAMLHeader(name, context, level) {
|
|
1696
|
+
const header = {
|
|
1697
|
+
name,
|
|
1698
|
+
description: `${name} command`,
|
|
1699
|
+
model: "sonnet",
|
|
1700
|
+
category: context,
|
|
1701
|
+
tags: [level],
|
|
1702
|
+
version: "4.0.0",
|
|
1703
|
+
updated: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
|
|
1704
|
+
level,
|
|
1705
|
+
context
|
|
1706
|
+
};
|
|
1707
|
+
return "---\n" + yaml5.stringify(header) + "---";
|
|
1708
|
+
}
|
|
1709
|
+
function createAgentYAMLHeader(name, context) {
|
|
1710
|
+
const header = {
|
|
1711
|
+
name,
|
|
1712
|
+
description: `${name} agent`,
|
|
1713
|
+
model: "sonnet",
|
|
1714
|
+
category: context,
|
|
1715
|
+
tags: ["agent"],
|
|
1716
|
+
expertise: [],
|
|
1717
|
+
version: "4.0.0",
|
|
1718
|
+
updated: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
|
|
1719
|
+
context
|
|
1720
|
+
};
|
|
1721
|
+
return "---\n" + yaml5.stringify(header) + "---";
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1724
|
+
// src/commands/migrate.ts
|
|
1725
|
+
async function migrate(options = {}) {
|
|
1726
|
+
try {
|
|
1727
|
+
const projectRoot = process.cwd();
|
|
1728
|
+
logger.title("\u{1F9C5} Onion Migration: v3 \u2192 v4");
|
|
1729
|
+
logger.break();
|
|
1730
|
+
const v3 = await detectOnionV3Structure(projectRoot);
|
|
1731
|
+
if (!v3) {
|
|
1732
|
+
logger.error("\u274C This is not an Onion v3 project");
|
|
1733
|
+
logger.info("Looking for .cursor/ structure with commands/ and agents/");
|
|
1734
|
+
logger.break();
|
|
1735
|
+
logger.info("To initialize a new Onion v4 project, run: onion init");
|
|
1736
|
+
process.exit(1);
|
|
1737
|
+
}
|
|
1738
|
+
logger.success("\u2705 Onion v3 project detected");
|
|
1739
|
+
logger.break();
|
|
1740
|
+
const eligibility = validateMigrationEligibility(v3);
|
|
1741
|
+
if (!eligibility.canMigrate) {
|
|
1742
|
+
logger.error("\u274C Cannot migrate this project:");
|
|
1743
|
+
for (const issue of eligibility.issues) {
|
|
1744
|
+
logger.error(` \u2022 ${issue}`);
|
|
1745
|
+
}
|
|
1746
|
+
process.exit(1);
|
|
1747
|
+
}
|
|
1748
|
+
logger.startSpinner("Analyzing v3 structure...");
|
|
1749
|
+
const analysis = await analyzeV3Structure(projectRoot);
|
|
1750
|
+
logger.stopSpinner(true, "Analysis complete");
|
|
1751
|
+
logger.break();
|
|
1752
|
+
logger.info("\u{1F4CA} Found:");
|
|
1753
|
+
logger.info(` \u2022 ${analysis.stats.totalCommands} commands`);
|
|
1754
|
+
logger.info(` \u2022 ${analysis.stats.totalAgents} agents`);
|
|
1755
|
+
if (analysis.stats.hasRules) logger.info(" \u2022 Rules directory");
|
|
1756
|
+
if (analysis.stats.hasSessions) logger.info(" \u2022 Sessions directory");
|
|
1757
|
+
logger.break();
|
|
1758
|
+
const plan = buildMigrationPlan(analysis);
|
|
1759
|
+
const planValidation = validateMigrationPlan(plan);
|
|
1760
|
+
if (!planValidation.valid) {
|
|
1761
|
+
logger.error("\u274C Migration plan has issues:");
|
|
1762
|
+
for (const issue of planValidation.issues) {
|
|
1763
|
+
logger.error(` \u2022 ${issue}`);
|
|
1764
|
+
}
|
|
1765
|
+
process.exit(1);
|
|
1766
|
+
}
|
|
1767
|
+
logger.title("\u{1F5FA}\uFE0F Migration Plan:");
|
|
1768
|
+
logger.break();
|
|
1769
|
+
logger.info(`\u{1F4E6} Contexts to create: ${plan.contexts.join(", ")}`);
|
|
1770
|
+
logger.break();
|
|
1771
|
+
logger.info("\u{1F4DD} Commands:");
|
|
1772
|
+
for (const [context, count] of Object.entries(plan.summary.commandsByContext)) {
|
|
1773
|
+
logger.info(` \u2022 ${context}: ${count} commands`);
|
|
1774
|
+
}
|
|
1775
|
+
logger.break();
|
|
1776
|
+
logger.info("\u{1F916} Agents:");
|
|
1777
|
+
for (const [context, count] of Object.entries(plan.summary.agentsByContext)) {
|
|
1778
|
+
logger.info(` \u2022 ${context}: ${count} agents`);
|
|
1779
|
+
}
|
|
1780
|
+
logger.break();
|
|
1781
|
+
const { confirm } = await inquirer.prompt([
|
|
1782
|
+
{
|
|
1783
|
+
type: "confirm",
|
|
1784
|
+
name: "confirm",
|
|
1785
|
+
message: "Proceed with migration?",
|
|
1786
|
+
default: false
|
|
1787
|
+
}
|
|
1788
|
+
]);
|
|
1789
|
+
if (!confirm) {
|
|
1790
|
+
logger.warn("\u274C Migration cancelled");
|
|
1791
|
+
return;
|
|
1792
|
+
}
|
|
1793
|
+
logger.break();
|
|
1794
|
+
logger.title("\u{1F680} Starting migration...");
|
|
1795
|
+
logger.break();
|
|
1796
|
+
if (!options.noBackup) {
|
|
1797
|
+
logger.startSpinner("Creating backup...");
|
|
1798
|
+
const backupPath = path8.join(projectRoot, ".cursor-backup");
|
|
1799
|
+
await fs2.copy(path8.join(projectRoot, ".cursor"), backupPath);
|
|
1800
|
+
logger.stopSpinner(true, `Backup created: .cursor-backup/`);
|
|
1801
|
+
}
|
|
1802
|
+
logger.startSpinner("Creating v4 structure...");
|
|
1803
|
+
await generateCoreStructure(projectRoot);
|
|
1804
|
+
for (const contextName of plan.contexts) {
|
|
1805
|
+
await generateContextStructure(projectRoot, contextName, {
|
|
1806
|
+
includeREADME: true,
|
|
1807
|
+
includeConfig: true
|
|
1808
|
+
});
|
|
1809
|
+
}
|
|
1810
|
+
await generateIDELoader(projectRoot, "cursor", {
|
|
1811
|
+
contexts: plan.contexts
|
|
1812
|
+
});
|
|
1813
|
+
await generateDocsStructure(projectRoot, plan.contexts);
|
|
1814
|
+
logger.stopSpinner(true, "Structure created");
|
|
1815
|
+
logger.startSpinner(`Migrating ${plan.commands.length} commands...`);
|
|
1816
|
+
for (const mapping of plan.commands) {
|
|
1817
|
+
const targetPath = path8.join(projectRoot, mapping.newPath);
|
|
1818
|
+
await transformCommandFile(
|
|
1819
|
+
mapping.oldPath,
|
|
1820
|
+
targetPath,
|
|
1821
|
+
mapping.context,
|
|
1822
|
+
mapping.level
|
|
1823
|
+
);
|
|
1824
|
+
}
|
|
1825
|
+
logger.stopSpinner(true, `${plan.commands.length} commands migrated`);
|
|
1826
|
+
logger.startSpinner(`Migrating ${plan.agents.length} agents...`);
|
|
1827
|
+
for (const mapping of plan.agents) {
|
|
1828
|
+
const targetPath = path8.join(projectRoot, mapping.newPath);
|
|
1829
|
+
await transformAgentFile(mapping.oldPath, targetPath, mapping.context);
|
|
1830
|
+
}
|
|
1831
|
+
logger.stopSpinner(true, `${plan.agents.length} agents migrated`);
|
|
1832
|
+
if (analysis.stats.hasRules) {
|
|
1833
|
+
logger.startSpinner("Copying rules...");
|
|
1834
|
+
const copied = await copyRules(projectRoot);
|
|
1835
|
+
logger.stopSpinner(true, `${copied} rules copied`);
|
|
1836
|
+
}
|
|
1837
|
+
if (analysis.stats.hasSessions) {
|
|
1838
|
+
logger.startSpinner("Copying sessions...");
|
|
1839
|
+
await copySessions(projectRoot, "technical");
|
|
1840
|
+
logger.stopSpinner(true, `Sessions copied`);
|
|
1841
|
+
}
|
|
1842
|
+
logger.startSpinner("Creating symlinks for backward compatibility...");
|
|
1843
|
+
const symlinkMappings = [
|
|
1844
|
+
...plan.commands.map((m) => ({
|
|
1845
|
+
oldPath: path8.relative(projectRoot, m.oldPath),
|
|
1846
|
+
newPath: m.newPath
|
|
1847
|
+
})),
|
|
1848
|
+
...plan.agents.map((m) => ({
|
|
1849
|
+
oldPath: path8.relative(projectRoot, m.oldPath),
|
|
1850
|
+
newPath: m.newPath
|
|
1851
|
+
}))
|
|
1852
|
+
];
|
|
1853
|
+
const symlinkCount = await createSymlinks(projectRoot, symlinkMappings);
|
|
1854
|
+
logger.stopSpinner(true, `${symlinkCount} symlinks created`);
|
|
1855
|
+
logger.startSpinner("Creating .onion-config.yml...");
|
|
1856
|
+
const configData = createDefaultConfig({
|
|
1857
|
+
version: "4.0.0",
|
|
1858
|
+
contexts: plan.contexts,
|
|
1859
|
+
ides: ["cursor"],
|
|
1860
|
+
migrated: true,
|
|
1861
|
+
migratedFrom: "v3",
|
|
1862
|
+
migratedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1863
|
+
migration: {
|
|
1864
|
+
commandsMigrated: plan.commands.length,
|
|
1865
|
+
agentsMigrated: plan.agents.length,
|
|
1866
|
+
backupPath: options.noBackup ? null : ".cursor-backup/"
|
|
1867
|
+
}
|
|
1868
|
+
});
|
|
1869
|
+
await createConfig(projectRoot, configData);
|
|
1870
|
+
logger.stopSpinner(true, "Config created");
|
|
1871
|
+
const reportPath = path8.join(projectRoot, "docs/onion/MIGRATION-REPORT.md");
|
|
1872
|
+
await fs2.ensureDir(path8.dirname(reportPath));
|
|
1873
|
+
await fs2.writeFile(reportPath, generateMigrationReport(plan), "utf-8");
|
|
1874
|
+
logger.break();
|
|
1875
|
+
logger.title("\u2705 Migration completed successfully!");
|
|
1876
|
+
logger.break();
|
|
1877
|
+
logger.success("Summary:");
|
|
1878
|
+
logger.info(` \u2022 ${plan.commands.length} commands migrated`);
|
|
1879
|
+
logger.info(` \u2022 ${plan.agents.length} agents migrated`);
|
|
1880
|
+
logger.info(` \u2022 ${plan.contexts.length} contexts created`);
|
|
1881
|
+
logger.info(` \u2022 ${symlinkCount} symlinks for backward compatibility`);
|
|
1882
|
+
if (!options.noBackup) {
|
|
1883
|
+
logger.info(" \u2022 Backup saved: .cursor-backup/");
|
|
1884
|
+
}
|
|
1885
|
+
logger.break();
|
|
1886
|
+
logger.success("Next steps:");
|
|
1887
|
+
logger.info(" 1. Review migration report: docs/onion/MIGRATION-REPORT.md");
|
|
1888
|
+
logger.info(" 2. Test your commands (they still work via symlinks)");
|
|
1889
|
+
logger.info(" 3. Explore new structure: .onion/contexts/");
|
|
1890
|
+
logger.info(" 4. Read system guide: docs/onion/levels-system.md");
|
|
1891
|
+
logger.info(" 5. Use new help commands: /business/help, /technical/help");
|
|
1892
|
+
logger.break();
|
|
1893
|
+
logger.warn("\u26A0\uFE0F Important:");
|
|
1894
|
+
logger.info(" \u2022 .cursor/ is now deprecated (kept for backward compatibility)");
|
|
1895
|
+
logger.info(" \u2022 New commands/agents should be added to .onion/contexts/");
|
|
1896
|
+
logger.info(" \u2022 Backup can be removed after validation: rm -rf .cursor-backup");
|
|
1897
|
+
logger.break();
|
|
1898
|
+
} catch (error) {
|
|
1899
|
+
logger.break();
|
|
1900
|
+
logger.error("\u274C Migration failed:");
|
|
1901
|
+
logger.error(error.message);
|
|
1902
|
+
if (options.debug) {
|
|
1903
|
+
console.error(error);
|
|
1904
|
+
}
|
|
1905
|
+
logger.break();
|
|
1906
|
+
logger.info("\u{1F4A1} Troubleshooting:");
|
|
1907
|
+
logger.info(" \u2022 Backup preserved: .cursor-backup/");
|
|
1908
|
+
logger.info(" \u2022 Review error above");
|
|
1909
|
+
logger.info(" \u2022 Run with --debug for more details");
|
|
1910
|
+
logger.info(" \u2022 Report issue: https://github.com/onion-system/onion/issues");
|
|
1911
|
+
logger.break();
|
|
1912
|
+
process.exit(1);
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
|
|
1916
|
+
// src/commands/validate.ts
|
|
1917
|
+
async function validate(options = {}) {
|
|
1918
|
+
try {
|
|
1919
|
+
logger.info("Validating Onion structure...");
|
|
1920
|
+
logger.warn("Command not implemented yet - Coming soon!");
|
|
1921
|
+
} catch (error) {
|
|
1922
|
+
logger.error(`Validation failed: ${error.message}`);
|
|
1923
|
+
if (options.debug) {
|
|
1924
|
+
console.error(error);
|
|
1925
|
+
}
|
|
1926
|
+
process.exit(1);
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1930
|
+
// src/cli.ts
|
|
1931
|
+
init_constants();
|
|
1932
|
+
var program = new Command();
|
|
1933
|
+
console.log("");
|
|
1934
|
+
console.log(chalk.magenta("\u{1F9C5} Onion System CLI"));
|
|
1935
|
+
console.log(chalk.gray(`v${ONION_VERSION} - Multi-Context Development Orchestrator`));
|
|
1936
|
+
console.log("");
|
|
1937
|
+
program.name("onion").description("CLI for Onion System - Multi-Context Development Orchestrator").version(ONION_VERSION);
|
|
1938
|
+
program.command("init").description("Initialize new Onion project").option("-d, --debug", "Enable debug mode").action(async (options) => {
|
|
1939
|
+
await init(options);
|
|
1940
|
+
});
|
|
1941
|
+
program.command("add").description("Add context or IDE to existing project").option("-d, --debug", "Enable debug mode").action(async (options) => {
|
|
1942
|
+
await add(options);
|
|
1943
|
+
});
|
|
1944
|
+
program.command("migrate").description("Migrate from Onion v3 to v4").option("--no-backup", "Skip backup creation").option("-d, --debug", "Enable debug mode").action(async (options) => {
|
|
1945
|
+
await migrate(options);
|
|
1946
|
+
});
|
|
1947
|
+
program.command("validate").description("Validate Onion structure").option("-d, --debug", "Enable debug mode").action(async (options) => {
|
|
1948
|
+
await validate(options);
|
|
1949
|
+
});
|
|
1950
|
+
program.parse(process.argv);
|
|
1951
|
+
//# sourceMappingURL=cli.js.map
|
|
1952
|
+
//# sourceMappingURL=cli.js.map
|