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.
Files changed (3) hide show
  1. package/README.md +55 -0
  2. package/package.json +34 -0
  3. 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
+ });