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.
- package/README.md +93 -0
- package/index.js +576 -0
- 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"]}
|