azrole 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +548 -0
- package/bin/cli.js +561 -0
- package/docs/case-studies/book-manuscript.md +301 -0
- package/package.json +48 -0
- package/templates/agents/orchestrator.md +1868 -0
- package/templates/commands/dream.md +13 -0
- package/templates/commands/evolve.md +3 -0
- package/templates/commands/explain.md +13 -0
- package/templates/commands/fix.md +15 -0
- package/templates/commands/level-up.md +3 -0
- package/templates/commands/setup.md +26 -0
- package/templates/commands/ship.md +14 -0
- package/templates/commands/status.md +14 -0
package/bin/cli.js
ADDED
|
@@ -0,0 +1,561 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
|
|
7
|
+
const PKG = require(path.join(__dirname, '..', 'package.json'));
|
|
8
|
+
const TEMPLATES_DIR = path.join(__dirname, '..', 'templates');
|
|
9
|
+
|
|
10
|
+
const C = {
|
|
11
|
+
reset: '\x1b[0m',
|
|
12
|
+
bold: '\x1b[1m',
|
|
13
|
+
dim: '\x1b[2m',
|
|
14
|
+
green: '\x1b[32m',
|
|
15
|
+
red: '\x1b[31m',
|
|
16
|
+
yellow: '\x1b[33m',
|
|
17
|
+
cyan: '\x1b[36m',
|
|
18
|
+
white: '\x1b[37m',
|
|
19
|
+
magenta: '\x1b[35m',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
function log(msg) { console.log(msg); }
|
|
23
|
+
function success(msg) { log(`${C.green} ✓${C.reset} ${msg}`); }
|
|
24
|
+
function warn(msg) { log(`${C.yellow} !${C.reset} ${msg}`); }
|
|
25
|
+
function fail(msg) { log(`${C.red} ✗${C.reset} ${msg}`); }
|
|
26
|
+
function info(msg) { log(`${C.cyan} ›${C.reset} ${msg}`); }
|
|
27
|
+
|
|
28
|
+
// ─── CLI Configurations ───
|
|
29
|
+
|
|
30
|
+
const CLIS = {
|
|
31
|
+
claude: {
|
|
32
|
+
name: 'Claude Code',
|
|
33
|
+
globalDir: path.join(os.homedir(), '.claude'),
|
|
34
|
+
rulesFile: 'CLAUDE.md',
|
|
35
|
+
configDir: '.claude',
|
|
36
|
+
agentsDir: '.claude/agents',
|
|
37
|
+
skillsDir: '.claude/skills',
|
|
38
|
+
commandsDir: '.claude/commands',
|
|
39
|
+
memoryDir: '.claude/memory',
|
|
40
|
+
settingsFile: '.claude/settings.json',
|
|
41
|
+
mcpFile: '.mcp.json',
|
|
42
|
+
commandFormat: 'md',
|
|
43
|
+
argSyntax: '$ARGUMENTS',
|
|
44
|
+
detect: () => {
|
|
45
|
+
try {
|
|
46
|
+
require('child_process').execSync('claude --version', { stdio: 'pipe' });
|
|
47
|
+
return true;
|
|
48
|
+
} catch { return fs.existsSync(path.join(os.homedir(), '.claude')); }
|
|
49
|
+
},
|
|
50
|
+
installHint: 'npm install -g @anthropic-ai/claude-code',
|
|
51
|
+
},
|
|
52
|
+
codex: {
|
|
53
|
+
name: 'Codex CLI',
|
|
54
|
+
globalDir: path.join(os.homedir(), '.codex'),
|
|
55
|
+
rulesFile: 'AGENTS.md',
|
|
56
|
+
configDir: '.codex',
|
|
57
|
+
agentsDir: '.codex/agents',
|
|
58
|
+
skillsDir: '.agents/skills',
|
|
59
|
+
commandsDir: '.codex/commands',
|
|
60
|
+
memoryDir: '.codex/memory',
|
|
61
|
+
settingsFile: '.codex/config.toml',
|
|
62
|
+
mcpFile: '.mcp.json',
|
|
63
|
+
commandFormat: 'md',
|
|
64
|
+
argSyntax: '$ARGUMENTS',
|
|
65
|
+
detect: () => {
|
|
66
|
+
try {
|
|
67
|
+
require('child_process').execSync('codex --version', { stdio: 'pipe' });
|
|
68
|
+
return true;
|
|
69
|
+
} catch { return fs.existsSync(path.join(os.homedir(), '.codex')); }
|
|
70
|
+
},
|
|
71
|
+
installHint: 'npm install -g @openai/codex',
|
|
72
|
+
},
|
|
73
|
+
opencode: {
|
|
74
|
+
name: 'OpenCode',
|
|
75
|
+
globalDir: path.join(os.homedir(), '.config', 'opencode'),
|
|
76
|
+
rulesFile: 'AGENTS.md',
|
|
77
|
+
configDir: '.opencode',
|
|
78
|
+
agentsDir: '.opencode/agents',
|
|
79
|
+
skillsDir: '.opencode/skills',
|
|
80
|
+
commandsDir: '.opencode/commands',
|
|
81
|
+
memoryDir: '.opencode/memory',
|
|
82
|
+
settingsFile: 'opencode.json',
|
|
83
|
+
mcpFile: 'opencode.json',
|
|
84
|
+
commandFormat: 'md',
|
|
85
|
+
argSyntax: '$ARGUMENTS',
|
|
86
|
+
detect: () => {
|
|
87
|
+
try {
|
|
88
|
+
require('child_process').execSync('opencode --version', { stdio: 'pipe' });
|
|
89
|
+
return true;
|
|
90
|
+
} catch { return fs.existsSync(path.join(os.homedir(), '.config', 'opencode')); }
|
|
91
|
+
},
|
|
92
|
+
installHint: 'go install github.com/opencode-ai/opencode@latest',
|
|
93
|
+
},
|
|
94
|
+
gemini: {
|
|
95
|
+
name: 'Gemini CLI',
|
|
96
|
+
globalDir: path.join(os.homedir(), '.gemini'),
|
|
97
|
+
rulesFile: 'GEMINI.md',
|
|
98
|
+
configDir: '.gemini',
|
|
99
|
+
agentsDir: '.gemini/agents',
|
|
100
|
+
skillsDir: '.gemini/skills',
|
|
101
|
+
commandsDir: '.gemini/commands',
|
|
102
|
+
memoryDir: '.gemini/memory',
|
|
103
|
+
settingsFile: '.gemini/settings.json',
|
|
104
|
+
mcpFile: '.gemini/mcp.json',
|
|
105
|
+
commandFormat: 'toml',
|
|
106
|
+
argSyntax: '{{args}}',
|
|
107
|
+
detect: () => {
|
|
108
|
+
try {
|
|
109
|
+
require('child_process').execSync('gemini --version', { stdio: 'pipe' });
|
|
110
|
+
return true;
|
|
111
|
+
} catch { return fs.existsSync(path.join(os.homedir(), '.gemini')); }
|
|
112
|
+
},
|
|
113
|
+
installHint: 'npm install -g @anthropic-ai/gemini-cli',
|
|
114
|
+
},
|
|
115
|
+
cursor: {
|
|
116
|
+
name: 'Cursor',
|
|
117
|
+
globalDir: path.join(os.homedir(), '.cursor'),
|
|
118
|
+
rulesFile: '.cursor/rules/project.mdc',
|
|
119
|
+
configDir: '.cursor',
|
|
120
|
+
agentsDir: '.cursor/agents',
|
|
121
|
+
skillsDir: '.cursor/skills',
|
|
122
|
+
commandsDir: '.cursor/commands',
|
|
123
|
+
memoryDir: '.cursor/memory',
|
|
124
|
+
settingsFile: '.cursor/settings.json',
|
|
125
|
+
mcpFile: '.cursor/mcp.json',
|
|
126
|
+
commandFormat: 'md',
|
|
127
|
+
argSyntax: '$ARGUMENTS',
|
|
128
|
+
detect: () => fs.existsSync(path.join(os.homedir(), '.cursor')),
|
|
129
|
+
installHint: 'Download from https://cursor.com',
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
// ─── Helpers ───
|
|
134
|
+
|
|
135
|
+
function banner() {
|
|
136
|
+
log('');
|
|
137
|
+
log(`${C.bold}${C.cyan} ╔══════════════════════════════════════╗${C.reset}`);
|
|
138
|
+
log(`${C.bold}${C.cyan} ║ AZROLE v${PKG.version} ║${C.reset}`);
|
|
139
|
+
log(`${C.bold}${C.cyan} ║ Self-Evolving AI Infrastructure ║${C.reset}`);
|
|
140
|
+
log(`${C.bold}${C.cyan} ║ Works with ANY AI coding CLI ║${C.reset}`);
|
|
141
|
+
log(`${C.bold}${C.cyan} ╚══════════════════════════════════════╝${C.reset}`);
|
|
142
|
+
log('');
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function copyFile(src, dest, overwrite = false) {
|
|
146
|
+
const destDir = path.dirname(dest);
|
|
147
|
+
if (!fs.existsSync(destDir)) {
|
|
148
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
149
|
+
}
|
|
150
|
+
if (fs.existsSync(dest) && !overwrite) {
|
|
151
|
+
const srcContent = fs.readFileSync(src, 'utf8');
|
|
152
|
+
const destContent = fs.readFileSync(dest, 'utf8');
|
|
153
|
+
if (srcContent !== destContent) {
|
|
154
|
+
warn(`${path.basename(dest)} exists and was modified — skipped (use --force)`);
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
fs.copyFileSync(src, dest);
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function detectCLIs() {
|
|
163
|
+
const detected = [];
|
|
164
|
+
for (const [id, cli] of Object.entries(CLIS)) {
|
|
165
|
+
if (cli.detect()) {
|
|
166
|
+
detected.push(id);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return detected;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Convert markdown command to Gemini TOML format
|
|
173
|
+
function mdToToml(mdContent, description) {
|
|
174
|
+
const prompt = mdContent.replace(/\$ARGUMENTS/g, '{{args}}');
|
|
175
|
+
const escapedDesc = description.replace(/"/g, '\\"');
|
|
176
|
+
return `description = "${escapedDesc}"\nprompt = """\n${prompt}\n"""\n`;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Adapt orchestrator for a specific CLI
|
|
180
|
+
function adaptOrchestrator(content, cliId) {
|
|
181
|
+
const cli = CLIS[cliId];
|
|
182
|
+
|
|
183
|
+
const adapterSection = `
|
|
184
|
+
## CLI Runtime — Path Configuration
|
|
185
|
+
|
|
186
|
+
**IMPORTANT**: You are running inside **${cli.name}**. Use these paths for ALL file operations:
|
|
187
|
+
|
|
188
|
+
| What | Path |
|
|
189
|
+
|------|------|
|
|
190
|
+
| Project rules | \`${cli.rulesFile}\` (in project root) |
|
|
191
|
+
| Config directory | \`${cli.configDir}/\` |
|
|
192
|
+
| Agents | \`${cli.agentsDir}/\` |
|
|
193
|
+
| Skills | \`${cli.skillsDir}/\` |
|
|
194
|
+
| Commands | \`${cli.commandsDir}/\` |
|
|
195
|
+
| Memory | \`${cli.memoryDir}/\` |
|
|
196
|
+
| Settings | \`${cli.settingsFile}\` |
|
|
197
|
+
| MCP config | \`${cli.mcpFile}\` |
|
|
198
|
+
|
|
199
|
+
**Override rule**: When examples below reference \`.claude/\` paths, substitute with the paths
|
|
200
|
+
from this table. For example, \`.claude/agents/dev-writer.md\` becomes \`${cli.agentsDir}/dev-writer.md\`.
|
|
201
|
+
When examples reference \`CLAUDE.md\`, use \`${cli.rulesFile}\` instead.
|
|
202
|
+
|
|
203
|
+
`;
|
|
204
|
+
|
|
205
|
+
// Insert adapter section after the frontmatter closing ---
|
|
206
|
+
const frontmatterEnd = content.indexOf('---', content.indexOf('---') + 3);
|
|
207
|
+
if (frontmatterEnd === -1) return adapterSection + content;
|
|
208
|
+
|
|
209
|
+
const insertPoint = frontmatterEnd + 3;
|
|
210
|
+
return content.slice(0, insertPoint) + '\n' + adapterSection + content.slice(insertPoint);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Adapt command content for target CLI
|
|
214
|
+
function adaptCommand(content, cliId) {
|
|
215
|
+
const cli = CLIS[cliId];
|
|
216
|
+
if (cli.argSyntax !== '$ARGUMENTS') {
|
|
217
|
+
return content.replace(/\$ARGUMENTS/g, cli.argSyntax);
|
|
218
|
+
}
|
|
219
|
+
return content;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// ─── Install ───
|
|
223
|
+
|
|
224
|
+
function install(mode, force = false, targetClis = null) {
|
|
225
|
+
const isGlobal = mode === 'global';
|
|
226
|
+
|
|
227
|
+
// Detect CLIs if not specified
|
|
228
|
+
if (!targetClis || targetClis.length === 0) {
|
|
229
|
+
targetClis = detectCLIs();
|
|
230
|
+
if (targetClis.length === 0) {
|
|
231
|
+
warn('No AI coding CLI detected. Installing for Claude Code by default.');
|
|
232
|
+
log(`${C.dim} Supported CLIs: Claude Code, Codex, OpenCode, Gemini CLI, Cursor${C.reset}`);
|
|
233
|
+
log('');
|
|
234
|
+
targetClis = ['claude'];
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
log(`${C.dim} Detected CLIs: ${targetClis.map(id => CLIS[id].name).join(', ')}${C.reset}`);
|
|
239
|
+
log('');
|
|
240
|
+
|
|
241
|
+
const commands = [
|
|
242
|
+
{ file: 'dream.md', desc: '/dream — build a project from an idea', tomlDesc: 'Build entire project from a description' },
|
|
243
|
+
{ file: 'level-up.md', desc: '/level-up — check your level, build the next one', tomlDesc: 'Check environment level and build the next one' },
|
|
244
|
+
{ file: 'evolve.md', desc: '/evolve — auto-improve your environment', tomlDesc: 'Auto-detect and fix gaps in your environment' },
|
|
245
|
+
{ file: 'fix.md', desc: '/fix — describe a bug, get it fixed', tomlDesc: 'Describe a bug and get it fixed' },
|
|
246
|
+
{ file: 'ship.md', desc: '/ship — commit and push your changes', tomlDesc: 'Commit and push changes to git' },
|
|
247
|
+
{ file: 'explain.md', desc: '/explain — explain code/errors in plain English', tomlDesc: 'Explain code or errors in plain English' },
|
|
248
|
+
{ file: 'status.md', desc: '/status — quick project health check', tomlDesc: 'Quick project health check' },
|
|
249
|
+
{ file: 'setup.md', desc: '/setup — scan existing project, build environment', tomlDesc: 'Scan existing project and build AI environment' },
|
|
250
|
+
];
|
|
251
|
+
|
|
252
|
+
for (const cliId of targetClis) {
|
|
253
|
+
const cli = CLIS[cliId];
|
|
254
|
+
const targetBase = isGlobal ? cli.globalDir : path.join(process.cwd(), cli.configDir);
|
|
255
|
+
|
|
256
|
+
log(`${C.bold}${C.magenta} ┌─ ${cli.name}${C.reset}`);
|
|
257
|
+
log(`${C.magenta} │${C.reset}`);
|
|
258
|
+
|
|
259
|
+
// Copy orchestrator agent (adapted for this CLI)
|
|
260
|
+
const orchSrc = path.join(TEMPLATES_DIR, 'agents', 'orchestrator.md');
|
|
261
|
+
const orchDest = path.join(targetBase, 'agents', 'orchestrator.md');
|
|
262
|
+
const orchDir = path.dirname(orchDest);
|
|
263
|
+
if (!fs.existsSync(orchDir)) fs.mkdirSync(orchDir, { recursive: true });
|
|
264
|
+
|
|
265
|
+
let orchCopied = false;
|
|
266
|
+
if (fs.existsSync(orchDest) && !force) {
|
|
267
|
+
const srcContent = adaptOrchestrator(fs.readFileSync(orchSrc, 'utf8'), cliId);
|
|
268
|
+
const destContent = fs.readFileSync(orchDest, 'utf8');
|
|
269
|
+
if (srcContent !== destContent) {
|
|
270
|
+
warn(`orchestrator.md exists and was modified — skipped (use --force)`);
|
|
271
|
+
}
|
|
272
|
+
} else {
|
|
273
|
+
const adapted = adaptOrchestrator(fs.readFileSync(orchSrc, 'utf8'), cliId);
|
|
274
|
+
fs.writeFileSync(orchDest, adapted, 'utf8');
|
|
275
|
+
orchCopied = true;
|
|
276
|
+
}
|
|
277
|
+
if (orchCopied) {
|
|
278
|
+
log(`${C.magenta} │${C.reset} ${C.green}✓${C.reset} orchestrator agent — the brain that builds everything`);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Copy commands (adapted for CLI format)
|
|
282
|
+
const cmdDir = path.join(targetBase, 'commands');
|
|
283
|
+
if (!fs.existsSync(cmdDir)) fs.mkdirSync(cmdDir, { recursive: true });
|
|
284
|
+
|
|
285
|
+
for (const cmd of commands) {
|
|
286
|
+
const srcPath = path.join(TEMPLATES_DIR, 'commands', cmd.file);
|
|
287
|
+
const srcContent = fs.readFileSync(srcPath, 'utf8');
|
|
288
|
+
|
|
289
|
+
let destFile, destContent;
|
|
290
|
+
if (cli.commandFormat === 'toml') {
|
|
291
|
+
// Convert to TOML for Gemini CLI
|
|
292
|
+
destFile = cmd.file.replace('.md', '.toml');
|
|
293
|
+
destContent = mdToToml(srcContent, cmd.tomlDesc);
|
|
294
|
+
} else {
|
|
295
|
+
destFile = cmd.file;
|
|
296
|
+
destContent = adaptCommand(srcContent, cliId);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const destPath = path.join(cmdDir, destFile);
|
|
300
|
+
|
|
301
|
+
if (fs.existsSync(destPath) && !force) {
|
|
302
|
+
const existingContent = fs.readFileSync(destPath, 'utf8');
|
|
303
|
+
if (existingContent !== destContent) {
|
|
304
|
+
warn(`${destFile} exists and was modified — skipped`);
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
fs.writeFileSync(destPath, destContent, 'utf8');
|
|
310
|
+
log(`${C.magenta} │${C.reset} ${C.green}✓${C.reset} ${cmd.desc}`);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
log(`${C.magenta} │${C.reset}`);
|
|
314
|
+
log(`${C.magenta} └─${C.reset} ${C.dim}${isGlobal ? targetBase : 'project-local'}${C.reset}`);
|
|
315
|
+
log('');
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
log(`${C.bold} ✅ Installed for ${targetClis.length} CLI${targetClis.length > 1 ? 's' : ''}!${C.reset}`);
|
|
319
|
+
log('');
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function showUsage(detectedClis) {
|
|
323
|
+
log('');
|
|
324
|
+
log(`${C.bold} Get started:${C.reset}`);
|
|
325
|
+
log('');
|
|
326
|
+
|
|
327
|
+
const cliName = detectedClis && detectedClis[0] ? detectedClis[0] : 'claude';
|
|
328
|
+
const runCmd = { claude: 'claude', codex: 'codex', opencode: 'opencode', gemini: 'gemini', cursor: 'cursor' }[cliName] || 'claude';
|
|
329
|
+
|
|
330
|
+
log(` ${C.white}mkdir my-app && cd my-app${C.reset}`);
|
|
331
|
+
log(` ${C.white}${runCmd}${C.reset}`);
|
|
332
|
+
log(` ${C.cyan}/dream food delivery app with React and Node${C.reset}`);
|
|
333
|
+
log('');
|
|
334
|
+
log(`${C.bold} Commands available inside your CLI:${C.reset}`);
|
|
335
|
+
log('');
|
|
336
|
+
log(` ${C.cyan}/dream${C.reset} ${C.dim}"your idea"${C.reset} Build entire project from description`);
|
|
337
|
+
log(` ${C.cyan}/setup${C.reset} Scan existing project, build environment`);
|
|
338
|
+
log(` ${C.cyan}/level-up${C.reset} See your level, build the next one`);
|
|
339
|
+
log(` ${C.cyan}/evolve${C.reset} Auto-improve your environment`);
|
|
340
|
+
log(` ${C.cyan}/fix${C.reset} ${C.dim}"what's broken"${C.reset} Describe a bug, get it fixed`);
|
|
341
|
+
log(` ${C.cyan}/ship${C.reset} Commit and push your changes`);
|
|
342
|
+
log(` ${C.cyan}/explain${C.reset} ${C.dim}"anything"${C.reset} Explain code/errors in plain English`);
|
|
343
|
+
log(` ${C.cyan}/status${C.reset} Quick project health check`);
|
|
344
|
+
log('');
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function uninstall(targetClis = null) {
|
|
348
|
+
if (!targetClis || targetClis.length === 0) {
|
|
349
|
+
targetClis = Object.keys(CLIS);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
log(`${C.dim} Removing AZROLE files...${C.reset}`);
|
|
353
|
+
log('');
|
|
354
|
+
|
|
355
|
+
let totalRemoved = 0;
|
|
356
|
+
|
|
357
|
+
for (const cliId of targetClis) {
|
|
358
|
+
const cli = CLIS[cliId];
|
|
359
|
+
const ext = cli.commandFormat === 'toml' ? '.toml' : '.md';
|
|
360
|
+
|
|
361
|
+
const locations = [
|
|
362
|
+
path.join(cli.globalDir, 'agents', 'orchestrator.md'),
|
|
363
|
+
path.join(cli.globalDir, 'commands', `dream${ext}`),
|
|
364
|
+
path.join(cli.globalDir, 'commands', `level-up${ext}`),
|
|
365
|
+
path.join(cli.globalDir, 'commands', `evolve${ext}`),
|
|
366
|
+
path.join(cli.globalDir, 'commands', `fix${ext}`),
|
|
367
|
+
path.join(cli.globalDir, 'commands', `ship${ext}`),
|
|
368
|
+
path.join(cli.globalDir, 'commands', `explain${ext}`),
|
|
369
|
+
path.join(cli.globalDir, 'commands', `status${ext}`),
|
|
370
|
+
path.join(cli.globalDir, 'commands', `setup${ext}`),
|
|
371
|
+
];
|
|
372
|
+
|
|
373
|
+
// Also check project-local
|
|
374
|
+
const localLocations = [
|
|
375
|
+
path.join(process.cwd(), cli.configDir, 'agents', 'orchestrator.md'),
|
|
376
|
+
path.join(process.cwd(), cli.configDir, 'commands', `dream${ext}`),
|
|
377
|
+
path.join(process.cwd(), cli.configDir, 'commands', `level-up${ext}`),
|
|
378
|
+
path.join(process.cwd(), cli.configDir, 'commands', `evolve${ext}`),
|
|
379
|
+
path.join(process.cwd(), cli.configDir, 'commands', `fix${ext}`),
|
|
380
|
+
path.join(process.cwd(), cli.configDir, 'commands', `ship${ext}`),
|
|
381
|
+
path.join(process.cwd(), cli.configDir, 'commands', `explain${ext}`),
|
|
382
|
+
path.join(process.cwd(), cli.configDir, 'commands', `status${ext}`),
|
|
383
|
+
path.join(process.cwd(), cli.configDir, 'commands', `setup${ext}`),
|
|
384
|
+
];
|
|
385
|
+
|
|
386
|
+
let removed = 0;
|
|
387
|
+
for (const file of [...locations, ...localLocations]) {
|
|
388
|
+
if (fs.existsSync(file)) {
|
|
389
|
+
fs.unlinkSync(file);
|
|
390
|
+
removed++;
|
|
391
|
+
totalRemoved++;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if (removed > 0) {
|
|
396
|
+
success(`${cli.name} — removed ${removed} files`);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
if (totalRemoved === 0) {
|
|
401
|
+
warn('Nothing to remove — AZROLE is not installed');
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
log('');
|
|
405
|
+
log(`${C.bold} ✅ Uninstalled.${C.reset}`);
|
|
406
|
+
log('');
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function status() {
|
|
410
|
+
log(`${C.bold} Installation status:${C.reset}`);
|
|
411
|
+
log('');
|
|
412
|
+
|
|
413
|
+
const detected = detectCLIs();
|
|
414
|
+
|
|
415
|
+
for (const cliId of Object.keys(CLIS)) {
|
|
416
|
+
const cli = CLIS[cliId];
|
|
417
|
+
const ext = cli.commandFormat === 'toml' ? '.toml' : '.md';
|
|
418
|
+
const isDetected = detected.includes(cliId);
|
|
419
|
+
|
|
420
|
+
const files = [
|
|
421
|
+
{ path: path.join(cli.globalDir, 'agents', 'orchestrator.md'), label: 'orchestrator' },
|
|
422
|
+
{ path: path.join(cli.globalDir, 'commands', `dream${ext}`), label: '/dream' },
|
|
423
|
+
{ path: path.join(cli.globalDir, 'commands', `setup${ext}`), label: '/setup' },
|
|
424
|
+
{ path: path.join(cli.globalDir, 'commands', `level-up${ext}`), label: '/level-up' },
|
|
425
|
+
{ path: path.join(cli.globalDir, 'commands', `evolve${ext}`), label: '/evolve' },
|
|
426
|
+
{ path: path.join(cli.globalDir, 'commands', `fix${ext}`), label: '/fix' },
|
|
427
|
+
{ path: path.join(cli.globalDir, 'commands', `ship${ext}`), label: '/ship' },
|
|
428
|
+
{ path: path.join(cli.globalDir, 'commands', `explain${ext}`), label: '/explain' },
|
|
429
|
+
{ path: path.join(cli.globalDir, 'commands', `status${ext}`), label: '/status' },
|
|
430
|
+
];
|
|
431
|
+
|
|
432
|
+
const installed = files.filter(f => fs.existsSync(f.path)).length;
|
|
433
|
+
|
|
434
|
+
if (installed > 0 || isDetected) {
|
|
435
|
+
const statusIcon = installed === files.length
|
|
436
|
+
? `${C.green}✓${C.reset}`
|
|
437
|
+
: installed > 0
|
|
438
|
+
? `${C.yellow}~${C.reset}`
|
|
439
|
+
: `${C.red}✗${C.reset}`;
|
|
440
|
+
|
|
441
|
+
const cliStatus = isDetected ? '' : `${C.dim} (CLI not detected)${C.reset}`;
|
|
442
|
+
log(` ${statusIcon} ${C.bold}${cli.name}${C.reset}${cliStatus} — ${installed}/${files.length} files`);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
log('');
|
|
447
|
+
|
|
448
|
+
// Check local project
|
|
449
|
+
for (const cliId of Object.keys(CLIS)) {
|
|
450
|
+
const cli = CLIS[cliId];
|
|
451
|
+
const localOrch = path.join(process.cwd(), cli.configDir, 'agents', 'orchestrator.md');
|
|
452
|
+
if (fs.existsSync(localOrch)) {
|
|
453
|
+
info(`${cli.name} also installed locally in this project`);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
log('');
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// ─── Parse CLI flags ───
|
|
461
|
+
|
|
462
|
+
function parseFlags(args) {
|
|
463
|
+
const flags = {
|
|
464
|
+
force: false,
|
|
465
|
+
clis: [],
|
|
466
|
+
all: false,
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
for (const arg of args) {
|
|
470
|
+
if (arg === '--force' || arg === '-f') flags.force = true;
|
|
471
|
+
else if (arg === '--all') flags.all = true;
|
|
472
|
+
else if (arg.startsWith('--')) {
|
|
473
|
+
const cliId = arg.slice(2);
|
|
474
|
+
if (CLIS[cliId]) flags.clis.push(cliId);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
if (flags.all) {
|
|
479
|
+
flags.clis = Object.keys(CLIS);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
return flags;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// ─── Main ───
|
|
486
|
+
|
|
487
|
+
const args = process.argv.slice(2);
|
|
488
|
+
const command = args[0] || 'help';
|
|
489
|
+
const flags = parseFlags(args.slice(1));
|
|
490
|
+
|
|
491
|
+
// Handle version flags before banner
|
|
492
|
+
if (command === '--version' || command === '-v') {
|
|
493
|
+
console.log(PKG.version);
|
|
494
|
+
process.exit(0);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
banner();
|
|
498
|
+
|
|
499
|
+
switch (command) {
|
|
500
|
+
case 'init':
|
|
501
|
+
case 'install':
|
|
502
|
+
install('global', flags.force, flags.clis.length > 0 ? flags.clis : null);
|
|
503
|
+
showUsage(flags.clis.length > 0 ? flags.clis : detectCLIs());
|
|
504
|
+
break;
|
|
505
|
+
|
|
506
|
+
case 'init-local':
|
|
507
|
+
case 'install-local':
|
|
508
|
+
install('local', flags.force, flags.clis.length > 0 ? flags.clis : null);
|
|
509
|
+
showUsage(flags.clis.length > 0 ? flags.clis : detectCLIs());
|
|
510
|
+
break;
|
|
511
|
+
|
|
512
|
+
case 'uninstall':
|
|
513
|
+
case 'remove':
|
|
514
|
+
uninstall(flags.clis.length > 0 ? flags.clis : null);
|
|
515
|
+
break;
|
|
516
|
+
|
|
517
|
+
case 'status':
|
|
518
|
+
status();
|
|
519
|
+
break;
|
|
520
|
+
|
|
521
|
+
case 'detect':
|
|
522
|
+
log(`${C.bold} Detected AI CLIs:${C.reset}`);
|
|
523
|
+
log('');
|
|
524
|
+
const detected = detectCLIs();
|
|
525
|
+
if (detected.length === 0) {
|
|
526
|
+
warn('No AI coding CLIs detected');
|
|
527
|
+
log('');
|
|
528
|
+
log(`${C.dim} Supported: Claude Code, Codex CLI, OpenCode, Gemini CLI, Cursor${C.reset}`);
|
|
529
|
+
} else {
|
|
530
|
+
for (const id of detected) {
|
|
531
|
+
success(`${CLIS[id].name} — ${CLIS[id].globalDir}`);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
log('');
|
|
535
|
+
break;
|
|
536
|
+
|
|
537
|
+
case 'help':
|
|
538
|
+
case '--help':
|
|
539
|
+
case '-h':
|
|
540
|
+
default:
|
|
541
|
+
log(`${C.bold} Usage:${C.reset}`);
|
|
542
|
+
log('');
|
|
543
|
+
log(` ${C.white}npx azrole init${C.reset} Auto-detect CLIs, install globally`);
|
|
544
|
+
log(` ${C.white}npx azrole init --all${C.reset} Install for ALL supported CLIs`);
|
|
545
|
+
log(` ${C.white}npx azrole init --claude${C.reset} Install for Claude Code only`);
|
|
546
|
+
log(` ${C.white}npx azrole init --gemini${C.reset} Install for Gemini CLI only`);
|
|
547
|
+
log(` ${C.white}npx azrole init --codex${C.reset} Install for Codex CLI only`);
|
|
548
|
+
log(` ${C.white}npx azrole init --opencode${C.reset} Install for OpenCode only`);
|
|
549
|
+
log(` ${C.white}npx azrole init --cursor${C.reset} Install for Cursor only`);
|
|
550
|
+
log(` ${C.white}npx azrole init --force${C.reset} Overwrite modified files`);
|
|
551
|
+
log(` ${C.white}npx azrole init-local${C.reset} Install in current project only`);
|
|
552
|
+
log(` ${C.white}npx azrole detect${C.reset} Show which CLIs are installed`);
|
|
553
|
+
log(` ${C.white}npx azrole status${C.reset} Check installation status`);
|
|
554
|
+
log(` ${C.white}npx azrole uninstall${C.reset} Remove everything`);
|
|
555
|
+
log(` ${C.white}npx azrole --version${C.reset} Show version`);
|
|
556
|
+
log('');
|
|
557
|
+
log(`${C.bold} Supported CLIs:${C.reset} Claude Code, Codex, OpenCode, Gemini CLI, Cursor`);
|
|
558
|
+
log('');
|
|
559
|
+
showUsage();
|
|
560
|
+
break;
|
|
561
|
+
}
|