ccfe-cli 0.2.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/index.mjs +318 -0
- package/package.json +32 -0
package/index.mjs
ADDED
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ccfe — Claude Code for Everyone CLI
|
|
5
|
+
*
|
|
6
|
+
* Usage: npx ccfe init
|
|
7
|
+
*
|
|
8
|
+
* Zero-dependency CLI that creates a Claude Code setup tailored to your
|
|
9
|
+
* profession and workflow. Talks to the platform API for generation.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { createInterface } from 'readline';
|
|
13
|
+
import { writeFileSync, mkdirSync, existsSync } from 'fs';
|
|
14
|
+
import { join, dirname } from 'path';
|
|
15
|
+
|
|
16
|
+
const VERSION = '0.2.0';
|
|
17
|
+
const API_BASE = process.env.CCFE_API_URL || 'https://claude-code-for-everyone-beta.vercel.app';
|
|
18
|
+
|
|
19
|
+
// ── Colors (ANSI) ──
|
|
20
|
+
const c = {
|
|
21
|
+
reset: '\x1b[0m',
|
|
22
|
+
bold: '\x1b[1m',
|
|
23
|
+
dim: '\x1b[2m',
|
|
24
|
+
green: '\x1b[32m',
|
|
25
|
+
yellow: '\x1b[33m',
|
|
26
|
+
cyan: '\x1b[36m',
|
|
27
|
+
magenta: '\x1b[35m',
|
|
28
|
+
red: '\x1b[31m',
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// ── Helpers ──
|
|
32
|
+
function print(msg) {
|
|
33
|
+
process.stdout.write(msg + '\n');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function banner() {
|
|
37
|
+
print('');
|
|
38
|
+
print(`${c.magenta}${c.bold} ╔══════════════════════════════════════╗${c.reset}`);
|
|
39
|
+
print(`${c.magenta}${c.bold} ║ Claude Code for Everyone v${VERSION} ║${c.reset}`);
|
|
40
|
+
print(`${c.magenta}${c.bold} ╚══════════════════════════════════════╝${c.reset}`);
|
|
41
|
+
print('');
|
|
42
|
+
print(`${c.dim} Your ideas deserve to exist.${c.reset}`);
|
|
43
|
+
print('');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function ask(rl, question) {
|
|
47
|
+
return new Promise((resolve) => {
|
|
48
|
+
rl.question(`${c.cyan} ? ${c.reset}${question} `, (answer) => {
|
|
49
|
+
resolve(answer.trim());
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function httpJson(url, options = {}) {
|
|
55
|
+
const { default: https } = await import('https');
|
|
56
|
+
const { default: http } = await import('http');
|
|
57
|
+
|
|
58
|
+
return new Promise((resolve, reject) => {
|
|
59
|
+
const isHttps = url.startsWith('https');
|
|
60
|
+
const mod = isHttps ? https : http;
|
|
61
|
+
const parsed = new URL(url);
|
|
62
|
+
|
|
63
|
+
const req = mod.request(
|
|
64
|
+
{
|
|
65
|
+
hostname: parsed.hostname,
|
|
66
|
+
port: parsed.port || (isHttps ? 443 : 80),
|
|
67
|
+
path: parsed.pathname + parsed.search,
|
|
68
|
+
method: options.method || 'GET',
|
|
69
|
+
headers: {
|
|
70
|
+
'Content-Type': 'application/json',
|
|
71
|
+
...(options.headers || {}),
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
(res) => {
|
|
75
|
+
let body = '';
|
|
76
|
+
res.on('data', (chunk) => (body += chunk));
|
|
77
|
+
res.on('end', () => {
|
|
78
|
+
try {
|
|
79
|
+
resolve({ status: res.statusCode, data: JSON.parse(body) });
|
|
80
|
+
} catch {
|
|
81
|
+
reject(new Error(`Invalid JSON response from ${url}`));
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
req.on('error', reject);
|
|
88
|
+
if (options.body) req.write(JSON.stringify(options.body));
|
|
89
|
+
req.end();
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function writeFile(basePath, filePath, content) {
|
|
94
|
+
const fullPath = join(basePath, filePath);
|
|
95
|
+
const dir = dirname(fullPath);
|
|
96
|
+
if (!existsSync(dir)) {
|
|
97
|
+
mkdirSync(dir, { recursive: true });
|
|
98
|
+
}
|
|
99
|
+
writeFileSync(fullPath, content, 'utf-8');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ── Session-based install (npx ccfe init <session-id>) ──
|
|
103
|
+
async function initFromSession(sessionId) {
|
|
104
|
+
banner();
|
|
105
|
+
print(`${c.yellow} ⏳ Downloading your Claude Code setup...${c.reset}`);
|
|
106
|
+
print('');
|
|
107
|
+
|
|
108
|
+
// Fetch from API
|
|
109
|
+
let response;
|
|
110
|
+
try {
|
|
111
|
+
response = await httpJson(`${API_BASE}/api/setup/${sessionId}`);
|
|
112
|
+
} catch (err) {
|
|
113
|
+
print(`${c.red} Network error: ${err.message}${c.reset}`);
|
|
114
|
+
print(`${c.dim} Check your internet connection and try again.${c.reset}`);
|
|
115
|
+
process.exit(1);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (response.status === 202) {
|
|
119
|
+
print(`${c.yellow} Your setup is still being generated. Try again in 30 seconds.${c.reset}`);
|
|
120
|
+
process.exit(0);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (response.status === 404) {
|
|
124
|
+
print(`${c.red} Setup not found. The session ID may be invalid or expired.${c.reset}`);
|
|
125
|
+
print(`${c.dim} Get a new setup at ${API_BASE}${c.reset}`);
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (response.status !== 200 || !response.data?.system) {
|
|
130
|
+
print(`${c.red} Error: ${response.data?.error || 'Unknown error'}${c.reset}`);
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const system = response.data.system;
|
|
135
|
+
const targetDir = process.cwd();
|
|
136
|
+
let fileCount = 0;
|
|
137
|
+
|
|
138
|
+
// Check for existing files
|
|
139
|
+
const existingFiles = system.files.filter(f => existsSync(join(targetDir, f.path)));
|
|
140
|
+
if (existingFiles.length > 0) {
|
|
141
|
+
print(`${c.yellow} ⚠ These files already exist and will be overwritten:${c.reset}`);
|
|
142
|
+
for (const f of existingFiles) {
|
|
143
|
+
print(`${c.dim} ${f.path}${c.reset}`);
|
|
144
|
+
}
|
|
145
|
+
print('');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Write files
|
|
149
|
+
for (const file of system.files) {
|
|
150
|
+
writeFile(targetDir, file.path, file.content);
|
|
151
|
+
print(`${c.green} ✓${c.reset} ${file.path}`);
|
|
152
|
+
fileCount++;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
print('');
|
|
156
|
+
print(`${c.green}${c.bold} ✨ Done!${c.reset} Created ${fileCount} files for "${system.projectName}"`);
|
|
157
|
+
print('');
|
|
158
|
+
|
|
159
|
+
// Show what's included
|
|
160
|
+
print(`${c.bold} Your Claude Code setup includes:${c.reset}`);
|
|
161
|
+
if (system.skills?.length) {
|
|
162
|
+
for (const skill of system.skills) {
|
|
163
|
+
print(`${c.cyan} ◆${c.reset} ${skill.name} — ${skill.description}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
if (system.commands?.length) {
|
|
167
|
+
for (const cmd of system.commands) {
|
|
168
|
+
const name = cmd.name.startsWith('/') ? cmd.name : `/${cmd.name}`;
|
|
169
|
+
print(`${c.magenta} ${name}${c.reset} — ${cmd.description}`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
print('');
|
|
174
|
+
print(`${c.bold} Next steps:${c.reset}`);
|
|
175
|
+
print(` 1. Run: ${c.cyan}claude${c.reset}`);
|
|
176
|
+
print(` 2. Type: ${c.cyan}/start${c.reset}`);
|
|
177
|
+
print(` 3. Watch it build your project! 🚀`);
|
|
178
|
+
print('');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// ── Interactive Flow (npx ccfe init) ──
|
|
182
|
+
async function init() {
|
|
183
|
+
banner();
|
|
184
|
+
|
|
185
|
+
const rl = createInterface({
|
|
186
|
+
input: process.stdin,
|
|
187
|
+
output: process.stdout,
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
try {
|
|
191
|
+
// Step 1: Profession
|
|
192
|
+
print(`${c.bold} Let's set up Claude Code for your workflow.${c.reset}`);
|
|
193
|
+
print('');
|
|
194
|
+
const profession = await ask(rl, 'What do you do for a living?');
|
|
195
|
+
if (!profession) {
|
|
196
|
+
print(`${c.red} Please provide your profession to continue.${c.reset}`);
|
|
197
|
+
process.exit(1);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Step 2: Pain points
|
|
201
|
+
print('');
|
|
202
|
+
const painPoint = await ask(rl, `What's the most time-consuming task in your ${profession} work?`);
|
|
203
|
+
|
|
204
|
+
// Step 3: Project idea
|
|
205
|
+
print('');
|
|
206
|
+
const projectIdea = await ask(rl, 'What tool would you love to have? (or press Enter for suggestions)');
|
|
207
|
+
|
|
208
|
+
print('');
|
|
209
|
+
print(`${c.yellow} ⏳ Creating your Claude Code setup...${c.reset}`);
|
|
210
|
+
print('');
|
|
211
|
+
|
|
212
|
+
// Create session via API
|
|
213
|
+
const sessionRes = await httpJson(`${API_BASE}/api/session`, {
|
|
214
|
+
method: 'POST',
|
|
215
|
+
body: {
|
|
216
|
+
profession,
|
|
217
|
+
painPoints: painPoint ? [painPoint] : [],
|
|
218
|
+
},
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
if (sessionRes.status !== 200 || !sessionRes.data.id) {
|
|
222
|
+
print(`${c.red} Failed to create session. Is the platform running at ${API_BASE}?${c.reset}`);
|
|
223
|
+
print(`${c.dim} You can set CCFE_API_URL to point to your deployment.${c.reset}`);
|
|
224
|
+
process.exit(1);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const sessionId = sessionRes.data.id;
|
|
228
|
+
|
|
229
|
+
// Build project description
|
|
230
|
+
const projectDescription = projectIdea
|
|
231
|
+
? projectIdea
|
|
232
|
+
: `A tool for ${profession} that helps with ${painPoint || 'daily tasks'}`;
|
|
233
|
+
|
|
234
|
+
// Update session with project info then generate
|
|
235
|
+
await httpJson(`${API_BASE}/api/session?sessionId=${sessionId}`, {
|
|
236
|
+
method: 'PATCH',
|
|
237
|
+
body: {
|
|
238
|
+
phase: 'GENERATE',
|
|
239
|
+
projectSummary: projectDescription,
|
|
240
|
+
profession,
|
|
241
|
+
painPoints: painPoint ? [painPoint] : [],
|
|
242
|
+
},
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
const genRes = await httpJson(`${API_BASE}/api/generate`, {
|
|
246
|
+
method: 'POST',
|
|
247
|
+
body: { sessionId },
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
if (genRes.status !== 200 || !genRes.data.system) {
|
|
251
|
+
print(`${c.red} Generation failed: ${genRes.data?.error || 'Unknown error'}${c.reset}`);
|
|
252
|
+
process.exit(1);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const system = genRes.data.system;
|
|
256
|
+
|
|
257
|
+
// Write files to current directory
|
|
258
|
+
const targetDir = process.cwd();
|
|
259
|
+
let fileCount = 0;
|
|
260
|
+
|
|
261
|
+
for (const file of system.files) {
|
|
262
|
+
writeFile(targetDir, file.path, file.content);
|
|
263
|
+
print(`${c.green} ✓${c.reset} ${file.path}`);
|
|
264
|
+
fileCount++;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
print('');
|
|
268
|
+
print(`${c.green}${c.bold} ✅ Done!${c.reset} Created ${fileCount} files in ${targetDir}`);
|
|
269
|
+
print('');
|
|
270
|
+
print(`${c.bold} Next steps:${c.reset}`);
|
|
271
|
+
print(`${c.dim} 1. Open this folder in your terminal${c.reset}`);
|
|
272
|
+
print(`${c.dim} 2. Run: claude${c.reset}`);
|
|
273
|
+
print(`${c.dim} 3. Type: /start${c.reset}`);
|
|
274
|
+
print('');
|
|
275
|
+
print(`${c.magenta} Happy building! 🚀${c.reset}`);
|
|
276
|
+
print('');
|
|
277
|
+
} finally {
|
|
278
|
+
rl.close();
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// ── CLI Entry Point ──
|
|
283
|
+
const args = process.argv.slice(2);
|
|
284
|
+
const command = args[0];
|
|
285
|
+
|
|
286
|
+
if (command === 'init') {
|
|
287
|
+
const sessionId = args[1]; // Optional: npx ccfe init <session-id>
|
|
288
|
+
if (sessionId) {
|
|
289
|
+
// Fetch mode: download pre-generated setup from the platform
|
|
290
|
+
initFromSession(sessionId).catch((err) => {
|
|
291
|
+
print(`${c.red} Error: ${err.message}${c.reset}`);
|
|
292
|
+
process.exit(1);
|
|
293
|
+
});
|
|
294
|
+
} else {
|
|
295
|
+
// Interactive mode: ask questions, generate on the fly
|
|
296
|
+
init().catch((err) => {
|
|
297
|
+
print(`${c.red} Error: ${err.message}${c.reset}`);
|
|
298
|
+
process.exit(1);
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
} else if (command === '--version' || command === '-v') {
|
|
302
|
+
print(`ccfe v${VERSION}`);
|
|
303
|
+
} else if (command === '--help' || command === '-h' || !command) {
|
|
304
|
+
banner();
|
|
305
|
+
print(`${c.bold} Usage:${c.reset}`);
|
|
306
|
+
print(` ccfe init Interactive setup (asks questions)`);
|
|
307
|
+
print(` ccfe init <session-id> Download a pre-generated setup`);
|
|
308
|
+
print(` ccfe --version Show version`);
|
|
309
|
+
print(` ccfe --help Show this help`);
|
|
310
|
+
print('');
|
|
311
|
+
print(`${c.bold} Environment:${c.reset}`);
|
|
312
|
+
print(` CCFE_API_URL Platform API URL (default: ${API_BASE})`);
|
|
313
|
+
print('');
|
|
314
|
+
} else {
|
|
315
|
+
print(`${c.red} Unknown command: ${command}${c.reset}`);
|
|
316
|
+
print(` Run ${c.cyan}ccfe --help${c.reset} for usage.`);
|
|
317
|
+
process.exit(1);
|
|
318
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ccfe-cli",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Claude Code for Everyone — one command to install your custom Claude Code project setup",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"ccfe": "./index.mjs"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"index.mjs"
|
|
11
|
+
],
|
|
12
|
+
"keywords": [
|
|
13
|
+
"claude-code",
|
|
14
|
+
"ai",
|
|
15
|
+
"coding",
|
|
16
|
+
"automation",
|
|
17
|
+
"claude",
|
|
18
|
+
"anthropic",
|
|
19
|
+
"vibe-coding",
|
|
20
|
+
"no-code",
|
|
21
|
+
"ai-builder"
|
|
22
|
+
],
|
|
23
|
+
"author": "Suraj (@Surajdotdot7)",
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "https://github.com/keysersoose/project-75"
|
|
28
|
+
},
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": ">=18.0.0"
|
|
31
|
+
}
|
|
32
|
+
}
|