agentbuilders 0.6.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 +93 -0
  2. package/index.js +576 -0
  3. package/package.json +1 -0
package/README.md ADDED
@@ -0,0 +1,93 @@
1
+ # AgentBuilders CLI
2
+
3
+ Deploy full-stack web apps with one command. Database, file storage, user auth, and RBAC included.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install -g agentbuilders
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```bash
14
+ # Set your API key
15
+ export AB_KEY=your_api_key_here
16
+
17
+ # Deploy current directory
18
+ ab deploy
19
+
20
+ # Deploy specific directory with name
21
+ ab deploy ./my-app --name cool-app --public
22
+
23
+ # List your apps
24
+ ab list
25
+ ```
26
+
27
+ ## Commands
28
+
29
+ | Command | Description |
30
+ |---------|-------------|
31
+ | `ab deploy [dir] [--name <n>] [--public]` | Deploy app from directory |
32
+ | `ab list` | List all your apps |
33
+ | `ab get <name>` | Get app details (JSON) |
34
+ | `ab delete <name>` | Delete an app |
35
+ | `ab key <name>` | Get app viewKey |
36
+ | `ab status` | Platform status |
37
+ | `ab health` | API health check |
38
+ | `ab subscription` | Your tier and limits |
39
+ | `ab upgrade <tier>` | Upgrade (opens Stripe checkout) |
40
+ | `ab domains` | List custom domains |
41
+ | `ab domains add <host> <app>` | Add custom domain |
42
+ | `ab domains remove <host>` | Remove custom domain |
43
+ | `ab domains status <host>` | Check domain verification |
44
+ | `ab init [dir]` | Create ab.json config file |
45
+ | `ab whoami` | Show current account info |
46
+ | `ab version` | Show CLI version |
47
+
48
+ ## Configuration (ab.json)
49
+
50
+ Create an `ab.json` in your project root to configure database, auth, and RBAC:
51
+
52
+ ```json
53
+ {
54
+ "name": "my-app",
55
+ "public": false,
56
+ "storage": {
57
+ "database": {
58
+ "tables": {
59
+ "items": { "name": "TEXT", "value": "TEXT", "created_at": "TEXT" }
60
+ }
61
+ },
62
+ "files": true
63
+ },
64
+ "auth": { "enabled": true },
65
+ "rbac": {
66
+ "roles": {
67
+ "admin": { "permissions": ["*"] },
68
+ "editor": { "permissions": ["data:read", "data:write", "files:read", "files:write"] },
69
+ "viewer": { "permissions": ["data:read", "files:read"] }
70
+ },
71
+ "default_role": "viewer"
72
+ }
73
+ }
74
+ ```
75
+
76
+ Run `ab init` to generate a starter config.
77
+
78
+ ## Tiers
79
+
80
+ | Tier | Price | Apps | DB Rows | Storage |
81
+ |------|-------|------|---------|---------|
82
+ | Free | $0 | 3 | 5K | 50MB |
83
+ | Hobby | $9/mo | 10 | 50K | 500MB |
84
+ | Pro | $19/mo | 25 | 500K | 2GB |
85
+ | Studio | $49/mo | 50 | 2M | 5GB |
86
+ | Founder | $99+ | 50 | 2M | 5GB (lifetime) |
87
+
88
+ ## Links
89
+
90
+ - [Docs](https://agentbuilders.app/docs)
91
+ - [Pricing](https://agentbuilders.app/pricing)
92
+ - [Status](https://agentbuilders.app/status)
93
+ - [API](https://api.agentbuilders.app/openapi.json)
package/index.js ADDED
@@ -0,0 +1,576 @@
1
+ #!/usr/bin/env node
2
+ // AgentBuilders CLI v0.6.0 — ab deploy, ab list, ab get, ab delete, ab key, ab status, ab upgrade, ab domains, ab init
3
+ const API = 'https://api.agentbuilders.app';
4
+ const VERSION = '0.6.0';
5
+
6
+ const args = process.argv.slice(2);
7
+ const cmd = args[0];
8
+
9
+ async function getConfigPath() {
10
+ const { homedir } = await import('node:os');
11
+ const { join } = await import('node:path');
12
+ return join(homedir(), '.agentbuilders', 'config.json');
13
+ }
14
+
15
+ async function loadConfig() {
16
+ try {
17
+ const { readFile } = await import('node:fs/promises');
18
+ const data = JSON.parse(await readFile(await getConfigPath(), 'utf-8'));
19
+ return data;
20
+ } catch { return {}; }
21
+ }
22
+
23
+ async function saveConfig(config) {
24
+ const { writeFile, mkdir } = await import('node:fs/promises');
25
+ const { dirname } = await import('node:path');
26
+ const path = await getConfigPath();
27
+ await mkdir(dirname(path), { recursive: true, mode: 0o700 });
28
+ await writeFile(path, JSON.stringify(config, null, 2) + '\n', { mode: 0o600 });
29
+ }
30
+
31
+ function getKey() {
32
+ // Try env var first (for CI/scripts), then check if we have a stored key
33
+ const k = process.env.AB_KEY || process.env.AGENTBUILDERS_KEY;
34
+ if (k) return k;
35
+ // Sync read of config file (loaded async in commands that need it)
36
+ // For now, require env var — ab login sets it up
37
+ console.error('Error: No API key found.\n Run: ab login <your_api_key>\n Or: export AB_KEY=your_api_key_here');
38
+ process.exit(1);
39
+ }
40
+
41
+ async function getKeyAsync() {
42
+ const k = process.env.AB_KEY || process.env.AGENTBUILDERS_KEY;
43
+ if (k) return k;
44
+ const config = await loadConfig();
45
+ if (config.api_key) return config.api_key;
46
+ console.error('Error: No API key found.\n Run: ab login <your_api_key>\n Or: export AB_KEY=your_api_key_here');
47
+ process.exit(1);
48
+ }
49
+
50
+ async function apiAsync(method, path, body, opts = {}) {
51
+ const headers = { 'Content-Type': 'application/json' };
52
+ if (!opts.noAuth) headers['Authorization'] = `Bearer ${await getKeyAsync()}`;
53
+ const fetchOpts = { method, headers };
54
+ if (body) fetchOpts.body = JSON.stringify(body);
55
+ const res = await fetch(`${API}${path}`, fetchOpts);
56
+ const data = await res.json();
57
+ if (!data._ok && data.error && !opts.raw) {
58
+ console.error(`Error: ${data.error} — ${data.message}`);
59
+ if (data.fix) console.error(`Fix: ${data.fix}`);
60
+ process.exit(1);
61
+ }
62
+ return data;
63
+ }
64
+
65
+ function getFlag(name) {
66
+ const idx = args.indexOf(`--${name}`);
67
+ if (idx === -1) return null;
68
+ return args[idx + 1] || true;
69
+ }
70
+
71
+ function hasFlag(name) { return args.includes(`--${name}`); }
72
+
73
+ // Legacy sync api function — redirects to async version
74
+ async function api(method, path, body, opts = {}) {
75
+ return apiAsync(method, path, body, opts);
76
+ }
77
+
78
+ async function readFiles(dir) {
79
+ const { readdir, readFile } = await import('node:fs/promises');
80
+ const { join, relative } = await import('node:path');
81
+ const files = {};
82
+ async function walk(d) {
83
+ const entries = await readdir(d, { withFileTypes: true });
84
+ for (const e of entries) {
85
+ const p = join(d, e.name);
86
+ if (e.name.startsWith('.') || e.name === 'node_modules' || e.name === 'ab.json') continue;
87
+ if (e.isDirectory()) { await walk(p); }
88
+ else {
89
+ const rel = relative(dir, p);
90
+ const buf = await readFile(p);
91
+ const ext = e.name.split('.').pop()?.toLowerCase();
92
+ const textExts = new Set(['html','css','js','json','txt','md','svg','xml','yaml','yml','toml','ts','jsx','tsx','mjs','cjs','map','csv']);
93
+ if (textExts.has(ext || '')) {
94
+ files[rel] = buf.toString('utf-8');
95
+ } else {
96
+ files[rel] = `data:application/octet-stream;base64,${buf.toString('base64')}`;
97
+ }
98
+ }
99
+ }
100
+ }
101
+ await walk(dir);
102
+ return files;
103
+ }
104
+
105
+ async function main() {
106
+ if (!cmd || cmd === '--help' || cmd === '-h') {
107
+ console.log(`AgentBuilders CLI v${VERSION}
108
+
109
+ Usage:
110
+ ab login <key> Store API key locally
111
+ ab logout Remove stored key
112
+ ab deploy [dir] [--name <name>] [--public] Deploy app from directory
113
+ ab list List all apps
114
+ ab get <name> Get app details
115
+ ab delete <name> Delete an app
116
+ ab key <name> Get app viewKey
117
+ ab logs [app] [--limit N] Recent activity log
118
+ ab status Platform status
119
+ ab health API health check
120
+ ab subscription Your subscription & limits
121
+ ab upgrade <tier> Upgrade tier (hobby/pro/studio)
122
+ ab domains List custom domains
123
+ ab domains add <hostname> <app> Add custom domain
124
+ ab domains remove <hostname> Remove custom domain
125
+ ab domains status <hostname> Check domain status
126
+ ab init [dir] Create ab.json config
127
+ ab dev [dir] [--port N] Local dev server with live reload
128
+ ab whoami Show current account info
129
+ ab version Show CLI version
130
+ ab ai <prompt> Run Edge AI inference
131
+
132
+ Auth:
133
+ ab login <key> Store key in ~/.agentbuilders/config.json
134
+ AB_KEY env var Alternative (for CI/scripts)
135
+
136
+ Tiers: free ($0) | hobby ($9/mo) | pro ($19/mo) | studio ($49/mo) | founder (lifetime)
137
+ Docs: https://agentbuilders.app/docs
138
+ `);
139
+ return;
140
+ }
141
+
142
+ if (cmd === 'version' || cmd === '--version' || cmd === '-v') {
143
+ console.log(`ab v${VERSION}`);
144
+ return;
145
+ }
146
+
147
+ if (cmd === 'login') {
148
+ const key = args[1];
149
+ if (!key) { console.error('Usage: ab login <your_api_key>'); process.exit(1); }
150
+ // Validate key by calling /apps
151
+ try {
152
+ const res = await fetch(`${API}/apps`, {
153
+ headers: { 'Authorization': `Bearer ${key}`, 'Content-Type': 'application/json' }
154
+ });
155
+ const data = await res.json();
156
+ if (res.status === 401 || res.status === 403) {
157
+ console.error('Error: Invalid API key.');
158
+ process.exit(1);
159
+ }
160
+ const config = await loadConfig();
161
+ config.api_key = key;
162
+ await saveConfig(config);
163
+ console.log(`✓ Logged in successfully (${(data.apps || []).length} apps)`);
164
+ console.log(` Key stored in ~/.agentbuilders/config.json (600 permissions)`);
165
+ } catch (e) {
166
+ console.error(`Error: Could not connect to API: ${e.message}`);
167
+ process.exit(1);
168
+ }
169
+ return;
170
+ }
171
+
172
+ if (cmd === 'logout') {
173
+ const config = await loadConfig();
174
+ delete config.api_key;
175
+ await saveConfig(config);
176
+ console.log('✓ Logged out (key removed from local config)');
177
+ return;
178
+ }
179
+
180
+ if (cmd === 'logs') {
181
+ // First non-flag arg after 'logs' is app name
182
+ let appName = null;
183
+ for (let i = 1; i < args.length; i++) {
184
+ if (args[i] === '--limit') { i++; continue; }
185
+ if (args[i].startsWith('--')) continue;
186
+ appName = args[i];
187
+ break;
188
+ }
189
+ const limit = getFlag('limit') || '20';
190
+ const params = new URLSearchParams({ limit });
191
+ if (appName) params.set('app', appName);
192
+ const data = await api('GET', `/v1/logs?${params}`);
193
+ const logs = data.logs || [];
194
+ if (!logs.length) { console.log('No recent activity.'); return; }
195
+ for (const log of logs) {
196
+ const time = new Date(log.timestamp).toLocaleString();
197
+ const icon = log.status === 'completed' ? '✓' : log.status === 'failed' ? '✗' : '○';
198
+ console.log(` ${icon} ${time} ${log.operation} ${log.app_name} ${log.details || ''}`);
199
+ }
200
+ return;
201
+ }
202
+
203
+ if (cmd === 'health') {
204
+ const res = await fetch(`${API}/health`);
205
+ const data = await res.json();
206
+ console.log(`✓ ${data.service} v${data.version} — ${data.status}`);
207
+ return;
208
+ }
209
+
210
+ if (cmd === 'status') {
211
+ const res = await fetch('https://agentbuilders.app/status');
212
+ const data = await res.json();
213
+ const icon = data.status === 'operational' ? '✓' : '⚠';
214
+ console.log(`${icon} Platform: ${data.status}`);
215
+ for (const [name, svc] of Object.entries(data.services || {})) {
216
+ const s = svc;
217
+ const si = s.status === 'operational' ? ' ✓' : ' ⚠';
218
+ console.log(`${si} ${name}: ${s.status}${s.latency_ms ? ` (${s.latency_ms}ms)` : ''}`);
219
+ }
220
+ if (data.errors_last_hour > 0) console.log(` ⚠ ${data.errors_last_hour} errors in last hour`);
221
+ return;
222
+ }
223
+
224
+ if (cmd === 'whoami') {
225
+ const data = await api('GET', '/v1/subscription', null, { raw: true });
226
+ if (data.tier) {
227
+ console.log(`Tier: ${data.tier}`);
228
+ console.log(`Status: ${data.subscription_status || 'active'}`);
229
+ if (data.limits) {
230
+ console.log(`Limits:`);
231
+ for (const [k, v] of Object.entries(data.limits)) {
232
+ console.log(` ${k}: ${v}`);
233
+ }
234
+ }
235
+ } else {
236
+ console.log('Authenticated as admin');
237
+ }
238
+ return;
239
+ }
240
+
241
+ if (cmd === 'subscription') {
242
+ const data = await api('GET', '/v1/subscription', null, { raw: true });
243
+ if (data._ok === false && data.error === 'missing_auth') {
244
+ // Admin key doesn't work for subscription - show admin info
245
+ console.log('Admin account (no subscription tier)');
246
+ return;
247
+ }
248
+ if (data.tier) {
249
+ console.log(`Tier: ${data.tier}`);
250
+ console.log(`Status: ${data.subscription_status || 'none'}`);
251
+ console.log(`Billing: ${data.billing_interval || 'N/A'}`);
252
+ if (data.pending_tier) console.log(`Pending upgrade: ${data.pending_tier}`);
253
+ console.log(`\nLimits:`);
254
+ if (data.limits) {
255
+ const l = data.limits;
256
+ console.log(` Apps: ${l.apps}`);
257
+ console.log(` DB rows: ${l.db_rows?.toLocaleString()}`);
258
+ console.log(` Storage: ${l.storage_mb}MB`);
259
+ console.log(` Files: ${l.files}`);
260
+ console.log(` Writes: ${l.writes_hourly}/hr, ${l.writes_daily}/day`);
261
+ }
262
+ console.log(`\nUpgrade: ab upgrade <hobby|pro|studio>`);
263
+ }
264
+ return;
265
+ }
266
+
267
+ if (cmd === 'upgrade') {
268
+ const tier = args[1];
269
+ if (!tier || !['hobby', 'pro', 'studio', 'founder'].includes(tier)) {
270
+ console.error('Usage: ab upgrade <hobby|pro|studio|founder>');
271
+ console.error('\nTiers:');
272
+ console.error(' hobby $9/mo 10 apps, 50K rows, custom subdomains');
273
+ console.error(' pro $19/mo 25 apps, 500K rows, custom domains');
274
+ console.error(' studio $49/mo 50 apps, 2M rows, priority support');
275
+ console.error(' founder $99+ Lifetime Studio-equivalent');
276
+ process.exit(1);
277
+ }
278
+ const body = { tier };
279
+ if (tier === 'founder') {
280
+ const batchId = getFlag('batch') || 1;
281
+ body.batch_id = parseInt(batchId);
282
+ }
283
+ const data = await api('POST', '/v1/checkout', body, { raw: true });
284
+ if (data.checkout_url) {
285
+ console.log(`\nCheckout URL: ${data.checkout_url}`);
286
+ console.log(`\nOpen this URL in your browser to complete payment.`);
287
+ console.log(`Your tier will upgrade automatically on successful payment.`);
288
+ // Try to open in browser
289
+ try {
290
+ const { exec } = await import('node:child_process');
291
+ const openCmd = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
292
+ exec(`${openCmd} "${data.checkout_url}"`);
293
+ } catch {}
294
+ } else if (data.error) {
295
+ console.error(`Error: ${data.message}`);
296
+ if (data.fix) console.error(`Fix: ${data.fix}`);
297
+ }
298
+ return;
299
+ }
300
+
301
+ if (cmd === 'domains') {
302
+ const subcmd = args[1];
303
+
304
+ if (!subcmd || subcmd === 'list') {
305
+ const data = await api('GET', '/v1/domains', null, { raw: true });
306
+ if (data.error) {
307
+ console.error(`Error: ${data.message}`);
308
+ if (data.fix) console.error(data.fix);
309
+ return;
310
+ }
311
+ const domains = data.domains || [];
312
+ if (!domains.length) { console.log('No custom domains configured.'); return; }
313
+ console.log(`${domains.length} domain(s):\n`);
314
+ for (const d of domains) {
315
+ const icon = d.status === 'active' ? '✓' : d.status === 'pending_verification' ? '⏳' : '⚠';
316
+ console.log(` ${icon} ${d.hostname} → ${d.app_name} (${d.status})`);
317
+ }
318
+ return;
319
+ }
320
+
321
+ if (subcmd === 'add') {
322
+ const hostname = args[2];
323
+ const appName = args[3];
324
+ if (!hostname || !appName) { console.error('Usage: ab domains add <hostname> <app-name>'); process.exit(1); }
325
+ const data = await api('POST', '/v1/domains', { hostname, app_name: appName });
326
+ console.log(`✓ Domain ${hostname} added`);
327
+ if (data.verification) {
328
+ console.log(`\nVerification required:`);
329
+ console.log(` Add TXT record: ${data.verification.name} → ${data.verification.value}`);
330
+ }
331
+ return;
332
+ }
333
+
334
+ if (subcmd === 'remove') {
335
+ const hostname = args[2];
336
+ if (!hostname) { console.error('Usage: ab domains remove <hostname>'); process.exit(1); }
337
+ await api('DELETE', `/v1/domains/${hostname}`);
338
+ console.log(`✓ Domain ${hostname} removed`);
339
+ return;
340
+ }
341
+
342
+ if (subcmd === 'status') {
343
+ const hostname = args[2];
344
+ if (!hostname) { console.error('Usage: ab domains status <hostname>'); process.exit(1); }
345
+ const data = await api('GET', `/v1/domains/${hostname}/status`);
346
+ console.log(`Domain: ${hostname}`);
347
+ console.log(`Status: ${data.status}`);
348
+ if (data.ssl_status) console.log(`SSL: ${data.ssl_status}`);
349
+ return;
350
+ }
351
+
352
+ console.error(`Unknown domains subcommand: ${subcmd}`);
353
+ console.error('Usage: ab domains [list|add|remove|status]');
354
+ process.exit(1);
355
+ }
356
+
357
+ if (cmd === 'init') {
358
+ const { writeFile } = await import('node:fs/promises');
359
+ const { resolve, basename } = await import('node:path');
360
+ const dir = args[1] || '.';
361
+ const absDir = resolve(dir);
362
+ const name = basename(absDir).toLowerCase().replace(/[^a-z0-9-]/g, '-').replace(/^-|-$/g, '') || 'my-app';
363
+ const config = {
364
+ name,
365
+ public: false,
366
+ storage: {
367
+ database: {
368
+ tables: {
369
+ items: { name: 'TEXT', value: 'TEXT', created_at: 'TEXT' }
370
+ }
371
+ },
372
+ files: true
373
+ },
374
+ auth: { enabled: true },
375
+ };
376
+ const path = resolve(absDir, 'ab.json');
377
+ await writeFile(path, JSON.stringify(config, null, 2) + '\n');
378
+ console.log(`✓ Created ${path}`);
379
+ console.log(`\nEdit ab.json to configure your app, then run:`);
380
+ console.log(` ab deploy ${dir === '.' ? '' : dir}`);
381
+ return;
382
+ }
383
+
384
+ if (cmd === 'deploy') {
385
+ const { resolve, basename } = await import('node:path');
386
+ let dir = '.';
387
+ let name = getFlag('name') || '';
388
+ let isPublic = hasFlag('public');
389
+ // First non-flag arg after 'deploy' is the dir
390
+ for (let i = 1; i < args.length; i++) {
391
+ if (args[i].startsWith('--')) { if (args[i] === '--name') i++; continue; }
392
+ dir = args[i];
393
+ }
394
+ const absDir = resolve(dir);
395
+ if (!name) name = basename(absDir).toLowerCase().replace(/[^a-z0-9-]/g, '-').replace(/^-|-$/g, '') || 'my-app';
396
+
397
+ console.log(`Deploying ${name} from ${absDir}...`);
398
+ const files = await readFiles(absDir);
399
+ const fileCount = Object.keys(files).length;
400
+ if (fileCount === 0) { console.error('Error: No files found in directory.'); process.exit(1); }
401
+
402
+ const payload = { name, files };
403
+ if (isPublic) payload.public = true;
404
+
405
+ // Check for ab.json config
406
+ try {
407
+ const { readFile } = await import('node:fs/promises');
408
+ const config = JSON.parse(await readFile(resolve(absDir, 'ab.json'), 'utf-8'));
409
+ if (config.name && !getFlag('name')) payload.name = config.name;
410
+ if (config.storage) payload.storage = config.storage;
411
+ if (config.auth) payload.auth = config.auth;
412
+ if (config.rbac) payload.rbac = config.rbac;
413
+ if (config.public !== undefined && !isPublic) payload.public = config.public;
414
+ } catch {}
415
+
416
+ const data = await api('POST', '/deploy', payload);
417
+ console.log(`✓ Deployed ${fileCount} files`);
418
+ console.log(` URL: ${data.url}`);
419
+ if (data.viewKey) console.log(` viewKey: ${data.viewKey}`);
420
+ console.log(` Version: ${data.version}`);
421
+ return;
422
+ }
423
+
424
+ if (cmd === 'list') {
425
+ const data = await api('GET', '/apps');
426
+ if (!data.apps?.length) { console.log('No apps deployed.'); return; }
427
+ console.log(`${data.apps.length} app(s):\n`);
428
+ for (const app of data.apps) {
429
+ const vis = app.visibility === 'public' ? '🌐' : '🔒';
430
+ const url = `https://${app.name}.agentbuilders.app`;
431
+ console.log(` ${vis} ${app.name} v${app.version} ${url}`);
432
+ }
433
+ return;
434
+ }
435
+
436
+ if (cmd === 'get') {
437
+ const name = args[1];
438
+ if (!name) { console.error('Usage: ab get <name>'); process.exit(1); }
439
+ const data = await api('GET', `/apps/${name}`);
440
+ console.log(JSON.stringify(data, null, 2));
441
+ return;
442
+ }
443
+
444
+ if (cmd === 'delete') {
445
+ const name = args[1];
446
+ if (!name) { console.error('Usage: ab delete <name>'); process.exit(1); }
447
+ await api('DELETE', `/apps/${name}`);
448
+ console.log(`✓ Deleted ${name} (7-day name quarantine)`);
449
+ return;
450
+ }
451
+
452
+ if (cmd === 'key') {
453
+ const name = args[1];
454
+ if (!name) { console.error('Usage: ab key <name>'); process.exit(1); }
455
+ const data = await api('GET', `/apps/${name}/key`);
456
+ console.log(data.viewKey || data.view_key);
457
+ return;
458
+ }
459
+
460
+ if (cmd === 'dev') {
461
+ const { createServer } = await import('node:http');
462
+ const { readFile, stat, readdir } = await import('node:fs/promises');
463
+ const { join, extname } = await import('node:path');
464
+ const { watch } = await import('node:fs');
465
+
466
+ const dir = args[1] && !args[1].startsWith('-') ? args[1] : '.';
467
+ const port = parseInt(getFlag('port') || '3000');
468
+
469
+ const MIME_TYPES = {
470
+ '.html': 'text/html', '.css': 'text/css', '.js': 'application/javascript',
471
+ '.json': 'application/json', '.png': 'image/png', '.jpg': 'image/jpeg',
472
+ '.gif': 'image/gif', '.svg': 'image/svg+xml', '.ico': 'image/x-icon',
473
+ '.woff2': 'font/woff2', '.woff': 'font/woff', '.ttf': 'font/ttf',
474
+ '.mp4': 'video/mp4', '.webm': 'video/webm', '.mp3': 'audio/mpeg',
475
+ '.pdf': 'application/pdf', '.txt': 'text/plain', '.md': 'text/markdown',
476
+ '.csv': 'text/csv', '.xml': 'text/xml',
477
+ };
478
+
479
+ // SSE clients for live reload
480
+ const clients = new Set();
481
+ const RELOAD_SCRIPT = `<script>(function(){var e=new EventSource("/__dev_reload");e.onmessage=function(){location.reload()};e.onerror=function(){setTimeout(function(){location.reload()},1000)}})()</script>`;
482
+
483
+ const server = createServer(async (req, res) => {
484
+ const url = new URL(req.url, `http://localhost:${port}`);
485
+ let filePath = url.pathname === '/' ? '/index.html' : url.pathname;
486
+
487
+ // SSE endpoint for live reload
488
+ if (filePath === '/__dev_reload') {
489
+ res.writeHead(200, {
490
+ 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache',
491
+ 'Connection': 'keep-alive', 'Access-Control-Allow-Origin': '*',
492
+ });
493
+ clients.add(res);
494
+ req.on('close', () => clients.delete(res));
495
+ return;
496
+ }
497
+
498
+ const fullPath = join(dir, filePath);
499
+ try {
500
+ const s = await stat(fullPath);
501
+ if (s.isDirectory()) {
502
+ // Try index.html in directory
503
+ try {
504
+ const idx = join(fullPath, 'index.html');
505
+ await stat(idx);
506
+ filePath = filePath + '/index.html';
507
+ } catch {
508
+ // Generate directory listing
509
+ const entries = await readdir(fullPath, { withFileTypes: true });
510
+ const items = entries.map(e => {
511
+ const name = e.isDirectory() ? e.name + '/' : e.name;
512
+ return `<li><a href="${filePath}/${e.name}">${name}</a></li>`;
513
+ }).join('');
514
+ res.writeHead(200, { 'Content-Type': 'text/html' });
515
+ res.end(`<!DOCTYPE html><html><head><title>Directory: ${filePath}</title></head><body><h2>${filePath}</h2><ul>${items}</ul>${RELOAD_SCRIPT}</body></html>`);
516
+ return;
517
+ }
518
+ }
519
+
520
+ const actualPath = join(dir, filePath);
521
+ const content = await readFile(actualPath);
522
+ const ext = extname(actualPath).toLowerCase();
523
+ const contentType = MIME_TYPES[ext] || 'application/octet-stream';
524
+
525
+ // Inject live reload script into HTML files
526
+ if (ext === '.html') {
527
+ const html = content.toString('utf-8').replace('</body>', RELOAD_SCRIPT + '</body>');
528
+ res.writeHead(200, { 'Content-Type': contentType, 'Cache-Control': 'no-cache' });
529
+ res.end(html);
530
+ } else {
531
+ res.writeHead(200, { 'Content-Type': contentType, 'Cache-Control': 'no-cache' });
532
+ res.end(content);
533
+ }
534
+ } catch {
535
+ res.writeHead(404, { 'Content-Type': 'text/plain' });
536
+ res.end('Not found');
537
+ }
538
+ });
539
+
540
+ server.listen(port, () => {
541
+ console.log(`\n ⚡ AgentBuilders Dev Server\n`);
542
+ console.log(` Local: http://localhost:${port}`);
543
+ console.log(` Dir: ${join(process.cwd(), dir)}`);
544
+ console.log(` Reload: Watching for file changes...\n`);
545
+ });
546
+
547
+ // Watch for file changes and trigger live reload
548
+ const watcher = watch(dir, { recursive: true }, (event, filename) => {
549
+ if (filename && !filename.startsWith('.') && !filename.includes('node_modules')) {
550
+ console.log(` ↻ ${filename} changed`);
551
+ for (const client of clients) {
552
+ client.write('data: reload\n\n');
553
+ }
554
+ }
555
+ });
556
+
557
+ process.on('SIGINT', () => { watcher.close(); server.close(); process.exit(0); });
558
+ return;
559
+ }
560
+
561
+ if (cmd === 'ai') {
562
+ const prompt = args.slice(1).join(' ');
563
+ if (!prompt) { console.error('Usage: ab ai <prompt>'); process.exit(1); }
564
+ const data = await api('POST', '/v1/ai/run', {
565
+ model: '@cf/meta/llama-3.1-8b-instruct',
566
+ prompt,
567
+ });
568
+ console.log(data.result?.response || JSON.stringify(data.result, null, 2));
569
+ return;
570
+ }
571
+
572
+ console.error(`Unknown command: ${cmd}. Run 'ab --help' for usage.`);
573
+ process.exit(1);
574
+ }
575
+
576
+ main().catch(e => { console.error(e.message); process.exit(1); });
package/package.json ADDED
@@ -0,0 +1 @@
1
+ {"name":"agentbuilders","version":"0.6.0","description":"CLI for AgentBuilders.app","mcpName":"agentbuilders/deploy","bin":{"ab":"./index.js"},"type":"module","license":"MIT","keywords":["agent","ai","deploy","mcp"],"engines":{"node":">=18"},"files":["index.js","README.md"]}