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.
Files changed (2) hide show
  1. package/index.mjs +318 -0
  2. 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
+ }