guildex 0.1.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 +55 -0
- package/package.json +34 -0
- package/src/index.js +369 -0
package/README.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# guildex CLI
|
|
2
|
+
|
|
3
|
+
Install AI talent from [Guildex](https://guildex.net) directly to your OpenClaw workspace.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Install a persona
|
|
9
|
+
npx guildex install elon-musk
|
|
10
|
+
|
|
11
|
+
# List all available AI talent
|
|
12
|
+
npx guildex list
|
|
13
|
+
|
|
14
|
+
# Search
|
|
15
|
+
npx guildex search marketing
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Requirements
|
|
19
|
+
|
|
20
|
+
- [OpenClaw](https://openclaw.ai) installed
|
|
21
|
+
- `~/.openclaw/workspace/` directory exists
|
|
22
|
+
|
|
23
|
+
## Where are files installed?
|
|
24
|
+
|
|
25
|
+
`~/.openclaw/workspace/AI-talent/[Name]/`
|
|
26
|
+
|
|
27
|
+
Each persona includes: SOUL.md, README.md, SKILLS.md, EXAMPLES.md, TESTS.md
|
|
28
|
+
|
|
29
|
+
## Available Categories
|
|
30
|
+
|
|
31
|
+
- `famous-figures` — Real-world iconic figures
|
|
32
|
+
- `engineering` — Software & systems engineers
|
|
33
|
+
- `marketing` — Growth & marketing experts
|
|
34
|
+
- `design` — UI/UX & product designers
|
|
35
|
+
- `product-strategy` — PMs & strategists
|
|
36
|
+
- `sales-support` — Sales & customer success
|
|
37
|
+
- `game-development` — Game dev specialists
|
|
38
|
+
- `academic-specialized` — Academic & research personas
|
|
39
|
+
- `philosophy-wisdom` — Philosophers & thinkers
|
|
40
|
+
- `health-mindset` — Coaches & wellness experts
|
|
41
|
+
|
|
42
|
+
## Options
|
|
43
|
+
|
|
44
|
+
Set `GITHUB_TOKEN` env var to avoid GitHub rate limits:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
export GITHUB_TOKEN=your_token_here
|
|
48
|
+
npx guildex list
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Links
|
|
52
|
+
|
|
53
|
+
- [Guildex](https://guildex.net) — Browse AI talent online
|
|
54
|
+
- [OpenClaw](https://openclaw.ai) — AI agent platform
|
|
55
|
+
- [GitHub Repo](https://github.com/joe-qiao-ai/guildex-ai-talent) — Talent source files
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "guildex",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI for Guildex AI Talent Network — download and deploy AI talent to your OpenClaw workspace",
|
|
5
|
+
"bin": {
|
|
6
|
+
"guildex": "./src/index.js"
|
|
7
|
+
},
|
|
8
|
+
"main": "./src/index.js",
|
|
9
|
+
"type": "commonjs",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "node src/index.js"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"chalk": "^4.1.2",
|
|
15
|
+
"node-fetch": "^2.7.0"
|
|
16
|
+
},
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": ">=14"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"guildex",
|
|
22
|
+
"ai",
|
|
23
|
+
"talent",
|
|
24
|
+
"openclaw",
|
|
25
|
+
"persona",
|
|
26
|
+
"cli"
|
|
27
|
+
],
|
|
28
|
+
"author": "Guildex",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "https://github.com/joe-qiao-ai/guildex-ai-talent"
|
|
33
|
+
}
|
|
34
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fetch = require('node-fetch');
|
|
5
|
+
const chalk = require('chalk');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const readline = require('readline');
|
|
9
|
+
const os = require('os');
|
|
10
|
+
|
|
11
|
+
// Guildex API config
|
|
12
|
+
// Override with GUILDEX_API_URL env var or set in ~/.guildex/config.json
|
|
13
|
+
const GUILDEX_API_URL =
|
|
14
|
+
process.env.GUILDEX_API_URL ||
|
|
15
|
+
(() => {
|
|
16
|
+
try {
|
|
17
|
+
const cfgPath = require('path').join(require('os').homedir(), '.guildex', 'config.json');
|
|
18
|
+
if (require('fs').existsSync(cfgPath)) {
|
|
19
|
+
return JSON.parse(require('fs').readFileSync(cfgPath, 'utf8')).apiUrl;
|
|
20
|
+
}
|
|
21
|
+
} catch {}
|
|
22
|
+
return null;
|
|
23
|
+
})() ||
|
|
24
|
+
'https://anonymous-guildex.convex.site';
|
|
25
|
+
|
|
26
|
+
const GUILDEX_DOWNLOAD_ENDPOINT = `${GUILDEX_API_URL}/api/download`;
|
|
27
|
+
|
|
28
|
+
// GitHub repo config (used for listing — list still reads GitHub directly)
|
|
29
|
+
const GITHUB_OWNER = 'joe-qiao-ai';
|
|
30
|
+
const GITHUB_REPO = 'guildex-ai-talent';
|
|
31
|
+
const GITHUB_BRANCH = 'main';
|
|
32
|
+
const GITHUB_API_BASE = 'https://api.github.com';
|
|
33
|
+
const GITHUB_RAW_BASE = `https://raw.githubusercontent.com/${GITHUB_OWNER}/${GITHUB_REPO}/${GITHUB_BRANCH}`;
|
|
34
|
+
|
|
35
|
+
// Categories in the repo
|
|
36
|
+
const CATEGORIES = [
|
|
37
|
+
'famous-figures',
|
|
38
|
+
'engineering',
|
|
39
|
+
'marketing',
|
|
40
|
+
'design',
|
|
41
|
+
'product-strategy',
|
|
42
|
+
'sales-support',
|
|
43
|
+
'game-development',
|
|
44
|
+
'academic-specialized',
|
|
45
|
+
'philosophy-wisdom',
|
|
46
|
+
'health-mindset',
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
// Files to download for each persona
|
|
50
|
+
const PERSONA_FILES = ['SOUL.md', 'README.md', 'SKILLS.md', 'EXAMPLES.md', 'TESTS.md'];
|
|
51
|
+
|
|
52
|
+
// Install base path
|
|
53
|
+
const INSTALL_BASE = path.join(os.homedir(), '.openclaw', 'workspace', 'AI-talent');
|
|
54
|
+
|
|
55
|
+
// ─── Helpers ────────────────────────────────────────────────────────────────
|
|
56
|
+
|
|
57
|
+
function toSlug(name) {
|
|
58
|
+
return name.toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, '');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function slugMatches(slug, query) {
|
|
62
|
+
const q = query.toLowerCase().replace(/\s+/g, '-');
|
|
63
|
+
return (
|
|
64
|
+
slug === q ||
|
|
65
|
+
slug.includes(q) ||
|
|
66
|
+
slug.replace(/-/g, ' ').includes(query.toLowerCase())
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function githubGet(endpoint) {
|
|
71
|
+
const headers = { 'User-Agent': 'guildex-cli/0.1.0', Accept: 'application/vnd.github.v3+json' };
|
|
72
|
+
if (process.env.GITHUB_TOKEN) {
|
|
73
|
+
headers['Authorization'] = `token ${process.env.GITHUB_TOKEN}`;
|
|
74
|
+
}
|
|
75
|
+
const res = await fetch(`${GITHUB_API_BASE}${endpoint}`, { headers });
|
|
76
|
+
if (!res.ok) {
|
|
77
|
+
throw new Error(`GitHub API error ${res.status}: ${res.statusText} (${endpoint})`);
|
|
78
|
+
}
|
|
79
|
+
return res.json();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function fetchRawFile(filePath) {
|
|
83
|
+
const url = `${GITHUB_RAW_BASE}/${filePath}`;
|
|
84
|
+
const res = await fetch(url);
|
|
85
|
+
if (!res.ok) return null;
|
|
86
|
+
return res.text();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* List all personas across all categories.
|
|
91
|
+
* Returns: [{ name, slug, category }]
|
|
92
|
+
*/
|
|
93
|
+
async function fetchAllPersonas() {
|
|
94
|
+
const personas = [];
|
|
95
|
+
for (const category of CATEGORIES) {
|
|
96
|
+
let entries;
|
|
97
|
+
try {
|
|
98
|
+
entries = await githubGet(`/repos/${GITHUB_OWNER}/${GITHUB_REPO}/contents/${category}`);
|
|
99
|
+
} catch {
|
|
100
|
+
// Category might not exist yet; skip silently
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (!Array.isArray(entries)) continue;
|
|
104
|
+
for (const entry of entries) {
|
|
105
|
+
if (entry.type === 'dir') {
|
|
106
|
+
personas.push({
|
|
107
|
+
slug: entry.name,
|
|
108
|
+
name: entry.name.replace(/-/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase()),
|
|
109
|
+
category,
|
|
110
|
+
path: entry.path,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return personas;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function prompt(question) {
|
|
119
|
+
return new Promise((resolve) => {
|
|
120
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
121
|
+
rl.question(question, (answer) => {
|
|
122
|
+
rl.close();
|
|
123
|
+
resolve(answer.trim());
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ─── Commands ───────────────────────────────────────────────────────────────
|
|
129
|
+
|
|
130
|
+
async function cmdList() {
|
|
131
|
+
console.log(chalk.bold.cyan('\n🎭 Guildex AI Talent Network\n'));
|
|
132
|
+
console.log(chalk.gray('Fetching available talent...\n'));
|
|
133
|
+
|
|
134
|
+
let personas;
|
|
135
|
+
try {
|
|
136
|
+
personas = await fetchAllPersonas();
|
|
137
|
+
} catch (err) {
|
|
138
|
+
console.error(chalk.red('✗ Failed to fetch talent list:'), err.message);
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (personas.length === 0) {
|
|
143
|
+
console.log(chalk.yellow('No personas found in the repo yet.'));
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Group by category
|
|
148
|
+
const byCategory = {};
|
|
149
|
+
for (const p of personas) {
|
|
150
|
+
if (!byCategory[p.category]) byCategory[p.category] = [];
|
|
151
|
+
byCategory[p.category].push(p);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
for (const [cat, list] of Object.entries(byCategory)) {
|
|
155
|
+
const catLabel = cat.replace(/-/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase());
|
|
156
|
+
console.log(chalk.bold.yellow(`📂 ${catLabel}`));
|
|
157
|
+
for (const p of list) {
|
|
158
|
+
console.log(` ${chalk.green(p.slug.padEnd(32))} ${chalk.gray(p.name)}`);
|
|
159
|
+
}
|
|
160
|
+
console.log();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
console.log(chalk.gray(`Total: ${chalk.white(personas.length)} AI talent available`));
|
|
164
|
+
console.log(chalk.gray(`\nInstall with: ${chalk.white('npx guildex install <slug>')}\n`));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function cmdSearch(keyword) {
|
|
168
|
+
if (!keyword) {
|
|
169
|
+
console.error(chalk.red('Usage: guildex search <keyword>'));
|
|
170
|
+
process.exit(1);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
console.log(chalk.bold.cyan('\n🔍 Searching Guildex AI Talent...\n'));
|
|
174
|
+
console.log(chalk.gray(`Fetching talent list...\n`));
|
|
175
|
+
|
|
176
|
+
let personas;
|
|
177
|
+
try {
|
|
178
|
+
personas = await fetchAllPersonas();
|
|
179
|
+
} catch (err) {
|
|
180
|
+
console.error(chalk.red('✗ Failed to fetch talent list:'), err.message);
|
|
181
|
+
process.exit(1);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const kw = keyword.toLowerCase();
|
|
185
|
+
const results = personas.filter(
|
|
186
|
+
(p) =>
|
|
187
|
+
p.slug.includes(kw) ||
|
|
188
|
+
p.name.toLowerCase().includes(kw) ||
|
|
189
|
+
p.category.includes(kw)
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
if (results.length === 0) {
|
|
193
|
+
console.log(chalk.yellow(`No results for "${keyword}"`));
|
|
194
|
+
console.log(chalk.gray('Try: npx guildex list'));
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
console.log(chalk.bold(`Found ${results.length} match${results.length !== 1 ? 'es' : ''}:\n`));
|
|
199
|
+
for (const p of results) {
|
|
200
|
+
const catLabel = p.category.replace(/-/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase());
|
|
201
|
+
console.log(` ${chalk.green(p.slug.padEnd(32))} ${chalk.white(p.name)} ${chalk.gray(`[${catLabel}]`)}`);
|
|
202
|
+
}
|
|
203
|
+
console.log(chalk.gray(`\nInstall with: ${chalk.white('npx guildex install <slug>')}\n`));
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async function cmdInstall(nameArg) {
|
|
207
|
+
if (!nameArg) {
|
|
208
|
+
console.error(chalk.red('Usage: guildex install <name-or-slug>'));
|
|
209
|
+
process.exit(1);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
console.log(chalk.bold.cyan('\n🎭 Guildex AI Talent Installer\n'));
|
|
213
|
+
console.log(chalk.gray(`Searching for "${nameArg}"...\n`));
|
|
214
|
+
|
|
215
|
+
let personas;
|
|
216
|
+
try {
|
|
217
|
+
personas = await fetchAllPersonas();
|
|
218
|
+
} catch (err) {
|
|
219
|
+
console.error(chalk.red('✗ Failed to fetch talent list:'), err.message);
|
|
220
|
+
process.exit(1);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Find best match
|
|
224
|
+
const slug = toSlug(nameArg);
|
|
225
|
+
let match = personas.find((p) => p.slug === slug);
|
|
226
|
+
|
|
227
|
+
if (!match) {
|
|
228
|
+
// Fuzzy: partial slug match or name match
|
|
229
|
+
const candidates = personas.filter((p) => slugMatches(p.slug, nameArg));
|
|
230
|
+
if (candidates.length === 1) {
|
|
231
|
+
match = candidates[0];
|
|
232
|
+
} else if (candidates.length > 1) {
|
|
233
|
+
console.log(chalk.yellow(`Multiple matches found for "${nameArg}":\n`));
|
|
234
|
+
candidates.forEach((p, i) => {
|
|
235
|
+
console.log(` ${chalk.cyan(`[${i + 1}]`)} ${chalk.green(p.slug)} — ${p.name} ${chalk.gray(`[${p.category}]`)}`);
|
|
236
|
+
});
|
|
237
|
+
const answer = await prompt(chalk.bold('\nEnter number to install (or q to quit): '));
|
|
238
|
+
if (answer.toLowerCase() === 'q' || answer === '') process.exit(0);
|
|
239
|
+
const idx = parseInt(answer, 10) - 1;
|
|
240
|
+
if (isNaN(idx) || idx < 0 || idx >= candidates.length) {
|
|
241
|
+
console.error(chalk.red('Invalid selection.'));
|
|
242
|
+
process.exit(1);
|
|
243
|
+
}
|
|
244
|
+
match = candidates[idx];
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (!match) {
|
|
249
|
+
console.error(chalk.red(`✗ No AI talent found matching "${nameArg}"`));
|
|
250
|
+
console.log(chalk.gray('Run `npx guildex list` to see all available talent.'));
|
|
251
|
+
process.exit(1);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
console.log(chalk.bold(`Found: ${chalk.green(match.name)} ${chalk.gray(`(${match.slug})`)}`));
|
|
255
|
+
console.log(chalk.gray(`Category: ${match.category}\n`));
|
|
256
|
+
|
|
257
|
+
// Check destination
|
|
258
|
+
const destDir = path.join(INSTALL_BASE, match.name);
|
|
259
|
+
|
|
260
|
+
if (fs.existsSync(destDir)) {
|
|
261
|
+
const answer = await prompt(
|
|
262
|
+
chalk.yellow(`⚠️ "${match.name}" already exists at:\n ${destDir}\n\nOverwrite? [y/N] `)
|
|
263
|
+
);
|
|
264
|
+
if (answer.toLowerCase() !== 'y') {
|
|
265
|
+
console.log(chalk.gray('\nInstallation cancelled.'));
|
|
266
|
+
process.exit(0);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Create directory
|
|
271
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
272
|
+
|
|
273
|
+
console.log(chalk.bold(`\nDownloading files to:\n ${chalk.cyan(destDir)}\n`));
|
|
274
|
+
|
|
275
|
+
let successCount = 0;
|
|
276
|
+
let failCount = 0;
|
|
277
|
+
|
|
278
|
+
for (const file of PERSONA_FILES) {
|
|
279
|
+
const remotePath = `${match.path}/${file}`;
|
|
280
|
+
process.stdout.write(` ${chalk.gray('→')} ${file.padEnd(16)}`);
|
|
281
|
+
try {
|
|
282
|
+
const content = await fetchRawFile(remotePath);
|
|
283
|
+
if (content === null) {
|
|
284
|
+
console.log(chalk.yellow('(not found, skipping)'));
|
|
285
|
+
failCount++;
|
|
286
|
+
} else {
|
|
287
|
+
fs.writeFileSync(path.join(destDir, file), content, 'utf8');
|
|
288
|
+
console.log(chalk.green('✓'));
|
|
289
|
+
successCount++;
|
|
290
|
+
}
|
|
291
|
+
} catch (err) {
|
|
292
|
+
console.log(chalk.red(`✗ ${err.message}`));
|
|
293
|
+
failCount++;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
console.log();
|
|
298
|
+
|
|
299
|
+
if (successCount === 0) {
|
|
300
|
+
console.error(chalk.red('✗ Installation failed — no files downloaded.'));
|
|
301
|
+
process.exit(1);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
console.log(chalk.bold.green(`✅ ${match.name} installed successfully!`));
|
|
305
|
+
console.log(chalk.gray(`\n📁 Location: ${chalk.white(destDir)}`));
|
|
306
|
+
if (failCount > 0) {
|
|
307
|
+
console.log(chalk.yellow(`⚠️ ${failCount} file(s) were not found in the repo and were skipped.`));
|
|
308
|
+
}
|
|
309
|
+
console.log(chalk.gray(`\nFiles: ${PERSONA_FILES.join(', ')}`));
|
|
310
|
+
console.log(chalk.bold.cyan('\n🎉 Ready to deploy in OpenClaw!\n'));
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function cmdHelp() {
|
|
314
|
+
console.log(`
|
|
315
|
+
${chalk.bold.cyan('guildex')} — Guildex AI Talent Network CLI
|
|
316
|
+
|
|
317
|
+
${chalk.bold('Usage:')}
|
|
318
|
+
${chalk.green('npx guildex list')} List all available AI talent
|
|
319
|
+
${chalk.green('npx guildex search <keyword>')} Search AI talent by name, category, or keyword
|
|
320
|
+
${chalk.green('npx guildex install <name>')} Install a persona to your OpenClaw workspace
|
|
321
|
+
|
|
322
|
+
${chalk.bold('Examples:')}
|
|
323
|
+
npx guildex install elon-musk
|
|
324
|
+
npx guildex install "Elon Musk"
|
|
325
|
+
npx guildex search marketing
|
|
326
|
+
npx guildex list
|
|
327
|
+
|
|
328
|
+
${chalk.bold('Install location:')}
|
|
329
|
+
${chalk.cyan('~/.openclaw/workspace/AI-talent/[Name]/')}
|
|
330
|
+
|
|
331
|
+
${chalk.bold('Files per persona:')}
|
|
332
|
+
${PERSONA_FILES.join(', ')}
|
|
333
|
+
|
|
334
|
+
${chalk.gray('More info: https://guildex.net')}
|
|
335
|
+
`);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// ─── Main ───────────────────────────────────────────────────────────────────
|
|
339
|
+
|
|
340
|
+
async function main() {
|
|
341
|
+
const [,, command, ...args] = process.argv;
|
|
342
|
+
|
|
343
|
+
switch (command) {
|
|
344
|
+
case 'list':
|
|
345
|
+
await cmdList();
|
|
346
|
+
break;
|
|
347
|
+
case 'search':
|
|
348
|
+
await cmdSearch(args.join(' '));
|
|
349
|
+
break;
|
|
350
|
+
case 'install':
|
|
351
|
+
await cmdInstall(args.join(' '));
|
|
352
|
+
break;
|
|
353
|
+
case 'help':
|
|
354
|
+
case '--help':
|
|
355
|
+
case '-h':
|
|
356
|
+
case undefined:
|
|
357
|
+
cmdHelp();
|
|
358
|
+
break;
|
|
359
|
+
default:
|
|
360
|
+
console.error(chalk.red(`Unknown command: ${command}`));
|
|
361
|
+
cmdHelp();
|
|
362
|
+
process.exit(1);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
main().catch((err) => {
|
|
367
|
+
console.error(chalk.red('Fatal error:'), err.message);
|
|
368
|
+
process.exit(1);
|
|
369
|
+
});
|