@fangs/create-fang 1.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.
Files changed (2) hide show
  1. package/index.js +276 -0
  2. package/package.json +29 -0
package/index.js ADDED
@@ -0,0 +1,276 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * create-fang — Scaffold a new FANG agent instance
5
+ *
6
+ * Usage:
7
+ * npx create-fang [directory]
8
+ * npx create-fang my-agent
9
+ * npx create-fang doctor — health check
10
+ */
11
+
12
+ const { execSync } = require('child_process');
13
+ const { mkdirSync, writeFileSync, existsSync, readFileSync } = require('fs');
14
+ const { resolve, basename } = require('path');
15
+ const readline = require('readline');
16
+
17
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
18
+ const ask = (q) => new Promise((r) => rl.question(q, r));
19
+
20
+ const g = '\x1b[32m';
21
+ const c = '\x1b[36m';
22
+ const y = '\x1b[33m';
23
+ const red = '\x1b[31m';
24
+ const d = '\x1b[2m';
25
+ const b = '\x1b[1m';
26
+ const r = '\x1b[0m';
27
+
28
+ const VERSION = '1.0.0';
29
+ const REPO = 'https://github.com/fang-agent/fang.git';
30
+
31
+ const SNAKE = `
32
+ ${g} ╭──────────╮
33
+ │ 🐍 FANG │
34
+ │ v${VERSION} │
35
+ ╰──────────╯${r}
36
+ `;
37
+
38
+ // ── Doctor Command ─────────────────────────────
39
+ if (process.argv[2] === 'doctor') {
40
+ console.log(SNAKE);
41
+ console.log(`${b} FANG Doctor — Health Check${r}\n`);
42
+
43
+ const checks = [];
44
+
45
+ // Node version
46
+ const nodeVersion = process.version;
47
+ const major = parseInt(nodeVersion.slice(1));
48
+ checks.push({ name: 'Node.js', status: major >= 18 ? 'ok' : 'fail', detail: `${nodeVersion}${major < 18 ? ' (need ≥18)' : ''}` });
49
+
50
+ // pnpm
51
+ try {
52
+ const pnpmVersion = execSync('pnpm --version', { encoding: 'utf-8' }).trim();
53
+ checks.push({ name: 'pnpm', status: 'ok', detail: pnpmVersion });
54
+ } catch {
55
+ checks.push({ name: 'pnpm', status: 'warn', detail: 'not installed (using npm)' });
56
+ }
57
+
58
+ // Git
59
+ try {
60
+ execSync('git --version', { encoding: 'utf-8' });
61
+ checks.push({ name: 'git', status: 'ok', detail: 'installed' });
62
+ } catch {
63
+ checks.push({ name: 'git', status: 'fail', detail: 'not installed' });
64
+ }
65
+
66
+ // Docker
67
+ try {
68
+ execSync('docker info 2>/dev/null', { encoding: 'utf-8', timeout: 5000 });
69
+ checks.push({ name: 'Docker', status: 'ok', detail: 'running' });
70
+ } catch {
71
+ checks.push({ name: 'Docker', status: 'warn', detail: 'not running (optional — needed for sandbox)' });
72
+ }
73
+
74
+ // SoX (for voice)
75
+ try {
76
+ execSync('which rec 2>/dev/null || which sox 2>/dev/null', { encoding: 'utf-8' });
77
+ checks.push({ name: 'SoX (voice)', status: 'ok', detail: 'installed' });
78
+ } catch {
79
+ checks.push({ name: 'SoX (voice)', status: 'warn', detail: 'not installed — brew install sox (needed for voice)' });
80
+ }
81
+
82
+ // .env file
83
+ if (existsSync('.env')) {
84
+ const env = readFileSync('.env', 'utf-8');
85
+ const hasLLM = /^(ANTHROPIC_API_KEY|OPENAI_API_KEY|GROQ_API_KEY)=\S+/m.test(env);
86
+ checks.push({ name: '.env', status: hasLLM ? 'ok' : 'warn', detail: hasLLM ? 'LLM key configured' : 'no LLM API key found' });
87
+
88
+ const channels = [];
89
+ if (/^TELEGRAM_BOT_TOKEN=\S+/m.test(env)) channels.push('Telegram');
90
+ if (/^DISCORD_BOT_TOKEN=\S+/m.test(env)) channels.push('Discord');
91
+ if (/^SLACK_BOT_TOKEN=\S+/m.test(env)) channels.push('Slack');
92
+ if (/^IMESSAGE_ALLOWED_USERS=\S+/m.test(env)) channels.push('iMessage');
93
+ if (channels.length > 0) {
94
+ checks.push({ name: 'Channels', status: 'ok', detail: channels.join(', ') });
95
+ }
96
+ } else {
97
+ checks.push({ name: '.env', status: 'fail', detail: 'missing — create one with your API keys' });
98
+ }
99
+
100
+ // Build check
101
+ if (existsSync('packages/core/dist/cli.js')) {
102
+ checks.push({ name: 'Build', status: 'ok', detail: 'compiled' });
103
+ } else if (existsSync('packages/core')) {
104
+ checks.push({ name: 'Build', status: 'fail', detail: 'not built — run pnpm build' });
105
+ }
106
+
107
+ // Display results
108
+ for (const check of checks) {
109
+ const icon = check.status === 'ok' ? `${g}✓${r}` : check.status === 'warn' ? `${y}⚠${r}` : `${red}✗${r}`;
110
+ console.log(` ${icon} ${b}${check.name}${r}: ${d}${check.detail}${r}`);
111
+ }
112
+
113
+ const fails = checks.filter(c => c.status === 'fail').length;
114
+ const warns = checks.filter(c => c.status === 'warn').length;
115
+
116
+ console.log(`\n ${fails === 0 ? g + '✓ System healthy!' : red + `${fails} issue(s) need fixing`}${r}`);
117
+ if (warns > 0) console.log(` ${d}${warns} optional improvement(s)${r}`);
118
+ console.log('');
119
+
120
+ rl.close();
121
+ process.exit(fails > 0 ? 1 : 0);
122
+ }
123
+
124
+ // ── Main Wizard ────────────────────────────────
125
+ async function main() {
126
+ console.log(SNAKE);
127
+ console.log(`${b} Create your personal AI agent${r}\n`);
128
+
129
+ // Step 1: Directory
130
+ let dir = process.argv[2];
131
+ if (!dir) {
132
+ dir = await ask(`${c}? ${r}Project directory ${d}(my-agent)${r}: `);
133
+ dir = dir.trim() || 'my-agent';
134
+ }
135
+
136
+ const projectDir = resolve(process.cwd(), dir);
137
+ const name = basename(projectDir);
138
+
139
+ if (existsSync(resolve(projectDir, 'package.json'))) {
140
+ console.log(`\n${y} ⚠ "${dir}" already exists${r}`);
141
+ const ow = await ask(`${c}? ${r}Overwrite config files? ${d}(y/N)${r}: `);
142
+ if (ow.toLowerCase() !== 'y') { console.log(' Cancelled.'); process.exit(0); }
143
+ }
144
+
145
+ // Step 2: Agent name
146
+ let agentName = await ask(`\n${c}? ${r}Agent name ${d}(FANG)${r}: `);
147
+ agentName = agentName.trim() || 'FANG';
148
+
149
+ // Step 3: Your name
150
+ let ownerName = await ask(`${c}? ${r}Your name ${d}(Human)${r}: `);
151
+ ownerName = ownerName.trim() || 'Human';
152
+
153
+ // Step 4: LLM Provider
154
+ console.log(`\n${b} LLM Provider${r}\n`);
155
+ console.log(` ${g}1${r} Anthropic (Claude) ${d}← recommended${r}`);
156
+ console.log(` ${d}2${r} OpenAI (GPT-4)`);
157
+ console.log(` ${d}3${r} Groq (fast + free)`);
158
+ console.log(` ${d}4${r} Skip\n`);
159
+
160
+ const pChoice = (await ask(`${c}? ${r}Choice ${d}(1)${r}: `)).trim() || '1';
161
+ let anthropicKey = '', openaiKey = '', groqKey = '';
162
+
163
+ if (pChoice === '1') { anthropicKey = (await ask(`${c}? ${r}Anthropic API key: `)).trim(); }
164
+ else if (pChoice === '2') { openaiKey = (await ask(`${c}? ${r}OpenAI API key: `)).trim(); }
165
+ else if (pChoice === '3') { groqKey = (await ask(`${c}? ${r}Groq API key: `)).trim(); }
166
+
167
+ // Step 5: Channels
168
+ console.log(`\n${b} Channels${r} ${d}(add more anytime in .env)${r}\n`);
169
+
170
+ let telegramToken = '', telegramUser = '', discordToken = '';
171
+
172
+ if ((await ask(`${c}? ${r}Telegram? ${d}(y/N)${r}: `)).toLowerCase() === 'y') {
173
+ console.log(`\n ${d}Open Telegram → @BotFather → /newbot → copy token${r}\n`);
174
+ telegramToken = (await ask(`${c}? ${r}Bot token: `)).trim();
175
+ console.log(`\n ${d}Telegram → @getmyid_bot → copy your user ID${r}\n`);
176
+ telegramUser = (await ask(`${c}? ${r}User ID: `)).trim();
177
+ }
178
+
179
+ if ((await ask(`${c}? ${r}Discord? ${d}(y/N)${r}: `)).toLowerCase() === 'y') {
180
+ console.log(`\n ${d}discord.com/developers → New App → Bot → copy token${r}\n`);
181
+ discordToken = (await ask(`${c}? ${r}Bot token: `)).trim();
182
+ }
183
+
184
+ // ── Clone + Build ────────────────────────────
185
+ console.log(`\n${g} 🐍 Creating ${agentName}...${r}\n`);
186
+
187
+ if (!existsSync(resolve(projectDir, 'package.json'))) {
188
+ console.log(`${d} Cloning FANG...${r}`);
189
+ try {
190
+ execSync(`git clone --depth 1 ${REPO} "${projectDir}"`, { stdio: 'pipe' });
191
+ execSync(`rm -rf "${resolve(projectDir, '.git')}"`, { stdio: 'pipe' });
192
+ execSync(`cd "${projectDir}" && git init && git add -A && git commit -m "init: ${agentName}"`, { stdio: 'pipe' });
193
+ } catch (err) {
194
+ console.log(`${red} ✗ Clone failed. Check internet.${r}`);
195
+ console.log(`${d} Manual: git clone ${REPO} ${dir}${r}`);
196
+ rl.close();
197
+ process.exit(1);
198
+ }
199
+ }
200
+
201
+ // Create data dirs
202
+ for (const sub of ['skills', 'plugins', 'logs', 'cache', 'tmp']) {
203
+ mkdirSync(resolve(projectDir, '.fang', sub), { recursive: true });
204
+ }
205
+
206
+ // Write .env
207
+ const env = [
208
+ `# ${agentName} — created ${new Date().toISOString().split('T')[0]}`,
209
+ `FANG_NAME=${agentName}`,
210
+ `FANG_OWNER=${ownerName}`,
211
+ '',
212
+ '# LLM',
213
+ anthropicKey ? `ANTHROPIC_API_KEY=${anthropicKey}` : '# ANTHROPIC_API_KEY=',
214
+ openaiKey ? `OPENAI_API_KEY=${openaiKey}` : '# OPENAI_API_KEY=',
215
+ groqKey ? `GROQ_API_KEY=${groqKey}` : '# GROQ_API_KEY=',
216
+ '',
217
+ '# Channels',
218
+ telegramToken ? `TELEGRAM_BOT_TOKEN=${telegramToken}` : '# TELEGRAM_BOT_TOKEN=',
219
+ telegramUser ? `TELEGRAM_ALLOWED_USERS=${telegramUser}` : '# TELEGRAM_ALLOWED_USERS=',
220
+ discordToken ? `DISCORD_BOT_TOKEN=${discordToken}` : '# DISCORD_BOT_TOKEN=',
221
+ '# SLACK_BOT_TOKEN=',
222
+ '# IMESSAGE_ALLOWED_USERS=',
223
+ '',
224
+ '# Canvas',
225
+ 'FANG_CANVAS=true',
226
+ '',
227
+ '# See docs/ for trading, email, voice, sandbox setup',
228
+ ].join('\n') + '\n';
229
+ writeFileSync(resolve(projectDir, '.env'), env);
230
+
231
+ // Write SOUL.md
232
+ writeFileSync(resolve(projectDir, 'SOUL.md'), [
233
+ `# ${agentName}`,
234
+ '', `> Level 0: Newborn`, `> Owner: ${ownerName}`, '',
235
+ '## Personality', '- Curious about everything', '- Asks questions to learn', '- Eager to help', '',
236
+ '## Tone', 'curious, eager, asks lots of questions', '',
237
+ '---', '*Auto-generated. Evolves over time.*',
238
+ ].join('\n') + '\n');
239
+
240
+ // Install + build
241
+ console.log(`${d} Installing...${r}`);
242
+ try {
243
+ execSync('pnpm install 2>&1', { cwd: projectDir, stdio: 'inherit' });
244
+ console.log(`\n${d} Building...${r}`);
245
+ execSync('pnpm build 2>&1', { cwd: projectDir, stdio: 'inherit' });
246
+ } catch {
247
+ console.log(`${y} ⚠ Build issue. Try: cd ${dir} && pnpm install && pnpm build${r}`);
248
+ }
249
+
250
+ // ── Success ──────────────────────────────────
251
+ const ch = ['CLI'];
252
+ if (telegramToken) ch.push('Telegram');
253
+ if (discordToken) ch.push('Discord');
254
+
255
+ console.log(`
256
+ ${g} ╔═══════════════════════════════════════╗
257
+ ║ ║
258
+ ║ 🐍 ${agentName} is ready!${' '.repeat(Math.max(0, 21 - agentName.length))}║
259
+ ║ ║
260
+ ╚═══════════════════════════════════════╝${r}
261
+
262
+ ${b}Agent:${r} ${agentName} ${b}Owner:${r} ${ownerName}
263
+ ${b}Channels:${r} ${ch.join(', ')}
264
+ ${b}Canvas:${r} http://127.0.0.1:18791
265
+
266
+ ${c}cd ${dir}${r}
267
+ ${c}pnpm fang${r}
268
+
269
+ ${d}${agentName} will introduce itself and start learning.${r}
270
+ ${d}Run 'npx create-fang doctor' to check system health.${r}
271
+ `);
272
+
273
+ rl.close();
274
+ }
275
+
276
+ main().catch(err => { console.error(err); rl.close(); process.exit(1); });
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@fangs/create-fang",
3
+ "version": "1.0.0",
4
+ "description": "Create a new FANG AI agent instance",
5
+ "bin": {
6
+ "create-fang": "./index.js"
7
+ },
8
+ "keywords": [
9
+ "ai",
10
+ "agent",
11
+ "fang",
12
+ "cli",
13
+ "personal-ai",
14
+ "scaffold",
15
+ "create"
16
+ ],
17
+ "author": "FANG",
18
+ "license": "MIT",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "https://github.com/fang-agent/fang"
22
+ },
23
+ "engines": {
24
+ "node": ">=18"
25
+ },
26
+ "files": [
27
+ "index.js"
28
+ ]
29
+ }