@vibedhost/cli 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.
package/dist/index.js ADDED
@@ -0,0 +1,913 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const path_1 = __importDefault(require("path"));
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const utils_1 = require("./utils");
10
+ const args = process.argv.slice(2);
11
+ const command = args[0] || "help";
12
+ async function main() {
13
+ switch (command) {
14
+ case "login":
15
+ await handleLogin();
16
+ break;
17
+ case "logout":
18
+ await handleLogout();
19
+ break;
20
+ case "whoami":
21
+ await handleWhoami();
22
+ break;
23
+ case "deploy":
24
+ await handleDeploy();
25
+ break;
26
+ case "status":
27
+ await handleStatus();
28
+ break;
29
+ case "logs":
30
+ await handleLogs();
31
+ break;
32
+ case "db":
33
+ await handleDb();
34
+ break;
35
+ case "env":
36
+ await handleEnv();
37
+ break;
38
+ case "port":
39
+ await handlePort();
40
+ break;
41
+ case "help":
42
+ case "--help":
43
+ case "-h":
44
+ default:
45
+ printHelp();
46
+ break;
47
+ }
48
+ }
49
+ function printHelp() {
50
+ console.log(`
51
+ VibedHost Cloud Engine — Command Line Interface
52
+ --------------------------------------------------------------------------
53
+ Deploy web applications and manage dedicated cloud workspaces directly from
54
+ your terminal, Claude Code, Cursor, or automated CI/CD pipelines.
55
+
56
+ Website: https://www.vibedhost.com
57
+ Dashboard: https://www.vibedhost.com/dashboard/workspaces
58
+
59
+ Usage:
60
+ npx vibed [command] [options]
61
+
62
+ Getting Started:
63
+ 1. Create account & workspace: https://www.vibedhost.com/register
64
+ 2. Authenticate terminal: npx vibed login
65
+ 3. Deploy local project: npx vibed deploy
66
+
67
+ Commands:
68
+ login Authenticate this terminal with your VibedHost account
69
+ logout Disconnect active account credentials from this machine
70
+ whoami Display authenticated account and workspace inventory
71
+ deploy [dir] Package and deploy local project to dedicated workspace
72
+ status [app] Check real-time application deployment and routing status
73
+ logs [app] Stream live application logs or container build output
74
+ db <create|list|link> Manage dedicated databases and link to applications
75
+ env <get|set> Manage container environment variables
76
+ port set <port> Update container listening port (e.g. 3000, 8080)
77
+
78
+ Deployment Options:
79
+ --workspace <id> Specify target workspace ID (skips interactive selection)
80
+ --name <name> Override service name for this deployment
81
+ --port <port> Specify internal container port (e.g. 3000, 80)
82
+ --domain <domain> Specify deployment domain suffix (e.g. vibedhost.online)
83
+ --force, -f Force rebuild and bypass zero-change detection
84
+
85
+ Inspection Options:
86
+ --ai Format latest error snippet for AI coding assistants
87
+
88
+ Examples:
89
+ npx vibed login
90
+ npx vibed deploy
91
+ npx vibed deploy ./dist --name frontend-spa
92
+ npx vibed status
93
+ npx vibed logs my-app
94
+ npx vibed logs my-app --ai
95
+ npx vibed env set DATABASE_URL=postgresql://...
96
+ `);
97
+ }
98
+ async function handleLogin() {
99
+ console.log("Initializing secure authentication session...");
100
+ try {
101
+ const res = await fetch(`${utils_1.API_BASE_URL}/api/cli/auth/session`, {
102
+ method: "POST"
103
+ });
104
+ const data = (await res.json());
105
+ if (!res.ok || !data.sessionCode) {
106
+ console.error(`Authentication error: ${data.error || "Failed to initialize session."}`);
107
+ process.exit(1);
108
+ }
109
+ const { sessionCode, userCode, authUrl } = data;
110
+ if (userCode) {
111
+ console.log(`\nTerminal Pairing Code: ${userCode}`);
112
+ }
113
+ console.log("Opening browser for authentication...");
114
+ console.log(`If your browser does not open automatically, visit:\n${authUrl}\n`);
115
+ console.log("Verify that the pairing code matches in your browser (timeout in 15 minutes)...");
116
+ (0, utils_1.openBrowser)(authUrl);
117
+ // Poll for authorization completion
118
+ const startTime = Date.now();
119
+ const timeoutMs = 15 * 60 * 1000;
120
+ while (Date.now() - startTime < timeoutMs) {
121
+ await new Promise((r) => setTimeout(r, 1800));
122
+ const pollRes = await fetch(`${utils_1.API_BASE_URL}/api/cli/auth/poll?sessionCode=${sessionCode}`);
123
+ const pollData = (await pollRes.json());
124
+ if (pollRes.ok && pollData.status === "approved" && pollData.token) {
125
+ (0, utils_1.writeGlobalConfig)({
126
+ token: pollData.token,
127
+ email: pollData.user?.email,
128
+ name: pollData.user?.name
129
+ });
130
+ console.log(`\nAuthentication successful!`);
131
+ console.log(`Logged in as: ${pollData.user?.email || "Authenticated User"}`);
132
+ console.log(`Run 'npx vibed deploy' inside any project directory to deploy.`);
133
+ return;
134
+ }
135
+ if (pollData.status === "expired" || pollData.status === "invalid") {
136
+ console.error("\nSession expired or rejected. Please run 'npx vibed login' again.");
137
+ process.exit(1);
138
+ }
139
+ }
140
+ console.error("\nAuthentication timed out. Please try again.");
141
+ process.exit(1);
142
+ }
143
+ catch (err) {
144
+ console.error(`\nConnection error: ${err.message}`);
145
+ process.exit(1);
146
+ }
147
+ }
148
+ function handleLogout() {
149
+ (0, utils_1.clearGlobalConfig)();
150
+ console.log("Logged out. Local authentication credentials cleared.");
151
+ }
152
+ async function handleWhoami() {
153
+ const config = (0, utils_1.readGlobalConfig)();
154
+ if (!config?.token) {
155
+ console.log("Not authenticated. Run 'npx vibed login' to connect your account.");
156
+ return;
157
+ }
158
+ try {
159
+ const res = await fetch(`${utils_1.API_BASE_URL}/api/cli/workspaces`, {
160
+ headers: { Authorization: `Bearer ${config.token}` }
161
+ });
162
+ const data = (await res.json());
163
+ if (!res.ok) {
164
+ console.error(`Error: ${data.error || "Failed to retrieve account details."}`);
165
+ return;
166
+ }
167
+ console.log("\nVibedHost Account Overview");
168
+ console.log("------------------------------------------------------------");
169
+ console.log(`User: ${data.userEmail || config.email || "Unknown"}`);
170
+ console.log(`Wallet Balance: €${(data.walletBalance || 0).toFixed(2)}`);
171
+ console.log(`Workspaces: ${data.workspaces?.length || 0} active`);
172
+ if (data.workspaces?.length > 0) {
173
+ console.log("\nDedicated Cloud Workspaces:");
174
+ data.workspaces.forEach((ws, idx) => {
175
+ const ramStr = ws.ramTotalGB ? `${(ws.memoryUsageGB || 0.4).toFixed(1)} GB / ${ws.ramTotalGB} GB` : "Allocated";
176
+ const diskStr = ws.storageTotalGB ? `${(ws.storageUsageGB || 2.0).toFixed(1)} GB / ${ws.storageTotalGB} GB` : "NVMe";
177
+ const bwLimit = ws.bandwidthLimitGB > 0 ? `${(ws.bandwidthLimitGB / 1000).toFixed(0)} TB` : "Unmetered";
178
+ const bwStr = `${(ws.bandwidthUsedGB || 0).toFixed(1)} GB / ${bwLimit}`;
179
+ console.log(` [${idx + 1}] ${ws.name} (ID: ${ws.id})`);
180
+ console.log(` Tier: ${ws.planName} • Status: ${ws.status}`);
181
+ console.log(` Telemetry: RAM: ${ramStr} • Disk: ${diskStr} • Traffic: ${bwStr}`);
182
+ console.log(` Deployed Services: ${ws.apps?.length || 0} active`);
183
+ });
184
+ }
185
+ else {
186
+ console.log("\nNo workspaces provisioned yet.");
187
+ console.log("Launch a workspace on the web: https://www.vibedhost.com/dashboard/workspaces");
188
+ }
189
+ console.log("");
190
+ }
191
+ catch (err) {
192
+ console.error(`Connection error: ${err.message}`);
193
+ }
194
+ }
195
+ async function handleDeploy() {
196
+ const config = (0, utils_1.readGlobalConfig)();
197
+ if (!config?.token) {
198
+ console.log(`
199
+ VibedHost Cloud Engine — Deployment Gateway
200
+ ------------------------------------------------------------
201
+ You are not currently authenticated.
202
+
203
+ Get Started:
204
+ 1. Create an account: https://www.vibedhost.com/register
205
+ 2. Authenticate: npx vibed login
206
+ `);
207
+ process.exit(1);
208
+ }
209
+ // Parse command options
210
+ let targetDir = process.cwd();
211
+ const dirArg = args[1] && !args[1].startsWith("--") ? args[1] : null;
212
+ if (dirArg) {
213
+ targetDir = path_1.default.resolve(process.cwd(), dirArg);
214
+ }
215
+ const workspaceFlagIndex = args.indexOf("--workspace");
216
+ const nameFlagIndex = args.indexOf("--name");
217
+ const portFlagIndex = args.indexOf("--port");
218
+ const domainFlagIndex = args.indexOf("--domain");
219
+ const explicitWorkspaceId = workspaceFlagIndex !== -1 ? args[workspaceFlagIndex + 1] : null;
220
+ const explicitAppName = nameFlagIndex !== -1 ? args[nameFlagIndex + 1] : null;
221
+ const explicitPort = portFlagIndex !== -1 ? args[portFlagIndex + 1] : null;
222
+ const explicitDomain = domainFlagIndex !== -1 ? args[domainFlagIndex + 1] : null;
223
+ if (!fs_1.default.existsSync(targetDir)) {
224
+ console.error(`Directory not found: ${targetDir}`);
225
+ process.exit(1);
226
+ }
227
+ const projectConfig = (0, utils_1.readProjectConfig)(targetDir);
228
+ // 1. Fetch available workspaces
229
+ console.log("Verifying account and active workspaces...");
230
+ let workspaces = [];
231
+ try {
232
+ const wsRes = await fetch(`${utils_1.API_BASE_URL}/api/cli/workspaces`, {
233
+ headers: { Authorization: `Bearer ${config.token}` }
234
+ });
235
+ const wsData = (await wsRes.json());
236
+ if (!wsRes.ok) {
237
+ console.error(`Authentication error: ${wsData.error || "Invalid token. Please run 'npx vibed login'."}`);
238
+ process.exit(1);
239
+ }
240
+ workspaces = (wsData.workspaces || []).filter((w) => w.status === "running");
241
+ if (workspaces.length === 0) {
242
+ console.log(`
243
+ No active dedicated workspaces found.
244
+ Wallet Balance: €${(wsData.walletBalance || 0).toFixed(2)}
245
+
246
+ To deploy applications, launch a dedicated cloud workspace:
247
+ https://www.vibedhost.com/dashboard/workspaces
248
+
249
+ Once provisioned, run 'npx vibed deploy' again.
250
+ `);
251
+ process.exit(1);
252
+ }
253
+ }
254
+ catch (err) {
255
+ console.error(`Failed to reach VibedHost API: ${err.message}`);
256
+ process.exit(1);
257
+ }
258
+ // 2. Resolve target workspace ID (with interactive prompt if unlinked)
259
+ let targetWorkspaceId = explicitWorkspaceId || projectConfig?.workspaceId;
260
+ if (!targetWorkspaceId && process.stdin.isTTY && !explicitWorkspaceId) {
261
+ if (workspaces.length === 1) {
262
+ const confirmWs = await (0, utils_1.askQuestion)(`Target Workspace: ${workspaces[0].name} (${workspaces[0].planName}) [Y/n]`, "y");
263
+ if (confirmWs.toLowerCase() === "n" || confirmWs.toLowerCase() === "no") {
264
+ console.log("Deployment cancelled.");
265
+ process.exit(0);
266
+ }
267
+ targetWorkspaceId = workspaces[0].id;
268
+ }
269
+ else {
270
+ console.log("\nSelect target workspace for this deployment:");
271
+ workspaces.forEach((w, idx) => {
272
+ console.log(` [${idx + 1}] ${w.name} (${w.planName})`);
273
+ });
274
+ const chosenIdx = await (0, utils_1.askQuestion)("Enter number", "1");
275
+ const num = parseInt(chosenIdx, 10);
276
+ const selected = workspaces[num - 1] || workspaces[0];
277
+ targetWorkspaceId = selected.id;
278
+ }
279
+ }
280
+ else if (!targetWorkspaceId) {
281
+ targetWorkspaceId = workspaces[0].id;
282
+ }
283
+ const targetWs = workspaces.find((w) => w.id === targetWorkspaceId) || workspaces[0];
284
+ // 3. Resolve service name
285
+ let defaultAppName = projectConfig?.appName || (0, utils_1.inferAppName)(targetDir);
286
+ let appName = explicitAppName;
287
+ if (!appName && !projectConfig?.appName && process.stdin.isTTY) {
288
+ appName = await (0, utils_1.askQuestion)("Service Name", defaultAppName);
289
+ }
290
+ else if (!appName) {
291
+ appName = defaultAppName;
292
+ }
293
+ appName = String(appName)
294
+ .toLowerCase()
295
+ .replace(/[^a-z0-9-]/g, "-")
296
+ .replace(/(^-|-$)+/g, "");
297
+ const hasForceFlag = args.includes("--force") || args.includes("-f");
298
+ // Linked Project Confirmation Guard
299
+ if (projectConfig?.workspaceId && process.stdin.isTTY && !explicitWorkspaceId && !hasForceFlag) {
300
+ const confirmLinked = await (0, utils_1.askQuestion)(`Deploying updates to existing service '${appName}' (Workspace: ${targetWs.name}) [Y/n]`, "y");
301
+ if (confirmLinked.toLowerCase() === "n" || confirmLinked.toLowerCase() === "no") {
302
+ console.log("Deployment cancelled.");
303
+ process.exit(0);
304
+ }
305
+ }
306
+ // 3.5 Resolve deployment domain
307
+ let targetDomainSuffix = explicitDomain || projectConfig?.domain;
308
+ if (!targetDomainSuffix && process.stdin.isTTY) {
309
+ try {
310
+ const domRes = await fetch(`${utils_1.API_BASE_URL}/api/cli/domains`, {
311
+ headers: { Authorization: `Bearer ${config.token}` }
312
+ });
313
+ const domainsList = (await domRes.json()) || [];
314
+ if (domainsList.length > 1) {
315
+ console.log("\nSelect deployment domain:");
316
+ domainsList.forEach((d, idx) => {
317
+ console.log(` [${idx + 1}] .${d.domain}${d.isDefault ? " (Default)" : ""}`);
318
+ });
319
+ const chosenDomIdx = await (0, utils_1.askQuestion)("Enter number", "1");
320
+ const dNum = parseInt(chosenDomIdx, 10);
321
+ const selectedDom = domainsList[dNum - 1] || domainsList[0];
322
+ targetDomainSuffix = selectedDom.domain;
323
+ }
324
+ else if (domainsList.length === 1) {
325
+ targetDomainSuffix = domainsList[0].domain;
326
+ }
327
+ }
328
+ catch { }
329
+ }
330
+ // 4. Scan Directory & Create In-Memory ZIP
331
+ console.log(`\nScanning project directory (${path_1.default.basename(targetDir)})...`);
332
+ const { files, bloatReport } = (0, utils_1.scanDirectory)(targetDir);
333
+ if (files.length === 0) {
334
+ console.error("Error: No deployable source files found in this directory.");
335
+ process.exit(1);
336
+ }
337
+ console.log(`Packaging ${files.length} project files in memory...`);
338
+ const zipBuffer = (0, utils_1.createZipBuffer)(files);
339
+ const zipSizeBytes = zipBuffer.length;
340
+ const zipSizeMb = (zipSizeBytes / (1024 * 1024)).toFixed(2);
341
+ // Pre-flight Size Boundary Check
342
+ if (zipSizeBytes > utils_1.MAX_PAYLOAD_BYTES) {
343
+ console.error(`
344
+ Error: Project archive size (${zipSizeMb} MB) exceeds the 50.0 MB limit.
345
+
346
+ Bloat directories detected:`);
347
+ Object.entries(bloatReport).forEach(([dir, size]) => {
348
+ console.error(` - ${dir}/ (${(size / (1024 * 1024)).toFixed(1)} MB)`);
349
+ });
350
+ console.error(`
351
+ Resolution:
352
+ 1. Ensure 'node_modules', '.next', '.venv', and build caches are excluded.
353
+ 2. Run 'npx vibed deploy' again.
354
+ `);
355
+ process.exit(1);
356
+ }
357
+ // Zero-Change Guard: Prevent accidental re-deploys when files are unchanged
358
+ const currentZipHash = (0, utils_1.computeBufferSha256)(zipBuffer);
359
+ if (projectConfig?.lastDeployHash &&
360
+ projectConfig.lastDeployHash === currentZipHash &&
361
+ process.stdin.isTTY &&
362
+ !hasForceFlag) {
363
+ const confirmRedeploy = await (0, utils_1.askQuestion)(`No local code changes detected since last deployment (${appName}).\nDo you want to re-deploy anyway? [y/N]`, "n");
364
+ if (confirmRedeploy.toLowerCase() !== "y" && confirmRedeploy.toLowerCase() !== "yes") {
365
+ console.log("Deployment cancelled (no changes detected).");
366
+ process.exit(0);
367
+ }
368
+ }
369
+ console.log(`Archive packaged (${zipSizeMb} MB). Uploading to workspace [${targetWs.name}]...`);
370
+ // 5. Send Multipart Deployment Request
371
+ try {
372
+ const boundary = `----VibedFormBoundary${Date.now()}`;
373
+ const preBuffer = Buffer.from(`--${boundary}\r\n` +
374
+ `Content-Disposition: form-data; name="file"; filename="project.zip"\r\n` +
375
+ `Content-Type: application/zip\r\n\r\n`);
376
+ const midBuffer = Buffer.from(`\r\n--${boundary}\r\n` +
377
+ `Content-Disposition: form-data; name="appName"\r\n\r\n${appName}\r\n` +
378
+ `--${boundary}\r\n` +
379
+ `Content-Disposition: form-data; name="workspaceId"\r\n\r\n${targetWs.id}\r\n` +
380
+ (explicitPort ? `--${boundary}\r\nContent-Disposition: form-data; name="containerPort"\r\n\r\n${explicitPort}\r\n` : "") +
381
+ (targetDomainSuffix ? `--${boundary}\r\nContent-Disposition: form-data; name="deploymentDomain"\r\n\r\n${targetDomainSuffix}\r\n` : "") +
382
+ `--${boundary}--\r\n`);
383
+ const payload = Buffer.concat([preBuffer, zipBuffer, midBuffer]);
384
+ const deployRes = await fetch(`${utils_1.API_BASE_URL}/api/cli/deploy`, {
385
+ method: "POST",
386
+ headers: {
387
+ Authorization: `Bearer ${config.token}`,
388
+ "Content-Type": `multipart/form-data; boundary=${boundary}`,
389
+ "Content-Length": payload.length.toString()
390
+ },
391
+ body: payload
392
+ });
393
+ const rawText = await deployRes.text();
394
+ let result = {};
395
+ try {
396
+ result = JSON.parse(rawText);
397
+ }
398
+ catch {
399
+ throw new Error(`Gateway returned unexpected response: ${rawText.slice(0, 100)}`);
400
+ }
401
+ if (deployRes.status === 429) {
402
+ console.error(`
403
+ Deployment In Progress / Rate Limited
404
+ ------------------------------------------------------------
405
+ Service: ${appName}
406
+ Workspace: ${targetWs.name}
407
+ Status: ${result.error || "A build is already in progress for this application."}
408
+
409
+ Troubleshooting:
410
+ - Stream live logs: npx vibed logs ${appName}
411
+ - Check status: npx vibed status ${appName}
412
+ - Format for AI debug: npx vibed logs ${appName} --ai
413
+ `);
414
+ process.exit(1);
415
+ }
416
+ if (!deployRes.ok || !result.success) {
417
+ console.error(`
418
+ Deployment Unsuccessful
419
+ ------------------------------------------------------------
420
+ Service: ${appName}
421
+ Workspace: ${targetWs.name}
422
+ Error: ${result.error || "Build failed. Please check your configuration."}
423
+
424
+ Troubleshooting:
425
+ - Stream logs: npx vibed logs ${appName}
426
+ - Format for AI debug: npx vibed logs ${appName} --ai
427
+ - Adjust port: npx vibed port set 3000
428
+ `);
429
+ process.exit(1);
430
+ }
431
+ // Save project configuration locally immediately
432
+ (0, utils_1.writeProjectConfig)({
433
+ workspaceId: targetWs.id,
434
+ appName: result.appName || appName,
435
+ domain: result.domain,
436
+ lastDeployHash: currentZipHash
437
+ }, targetDir);
438
+ console.log("Building application container in background...");
439
+ // Progressive status polling loop (up to 120 seconds)
440
+ const startTime = Date.now();
441
+ let isLive = result.status === "active";
442
+ let isFailed = false;
443
+ const finalAppName = result.appName || appName;
444
+ while (!isLive && !isFailed && Date.now() - startTime < 120000) {
445
+ await new Promise((r) => setTimeout(r, 2500));
446
+ const elapsedSec = Math.round((Date.now() - startTime) / 1000);
447
+ try {
448
+ const checkRes = await fetch(`${utils_1.API_BASE_URL}/api/cli/logs?appName=${encodeURIComponent(finalAppName)}`, { headers: { Authorization: `Bearer ${config.token}` } });
449
+ const checkData = (await checkRes.json());
450
+ if (checkData.status === "active") {
451
+ isLive = true;
452
+ break;
453
+ }
454
+ else if (checkData.status === "failed") {
455
+ isFailed = true;
456
+ break;
457
+ }
458
+ else {
459
+ process.stdout.write(`\rBuilding application container in background... (${elapsedSec}s)`);
460
+ }
461
+ }
462
+ catch { }
463
+ }
464
+ process.stdout.write("\r" + " ".repeat(65) + "\r");
465
+ if (isLive) {
466
+ console.log(`
467
+ Deployment Complete!
468
+ ------------------------------------------------------------
469
+ Service: ${finalAppName}
470
+ Workspace: ${targetWs.name}
471
+ Status: Active
472
+
473
+ Live URL: https://${result.domain}
474
+ ------------------------------------------------------------
475
+ `);
476
+ }
477
+ else if (isFailed) {
478
+ console.error(`
479
+ Deployment Unsuccessful
480
+ ------------------------------------------------------------
481
+ Service: ${finalAppName}
482
+ Workspace: ${targetWs.name}
483
+ Status: Failed
484
+
485
+ Build unsuccessful.
486
+ Run 'npx vibed logs ${finalAppName}' to view container error logs.
487
+ ------------------------------------------------------------
488
+ `);
489
+ process.exit(1);
490
+ }
491
+ else {
492
+ console.log(`
493
+ Deployment In Progress
494
+ ------------------------------------------------------------
495
+ Service: ${finalAppName}
496
+ Workspace: ${targetWs.name}
497
+ Status: Building
498
+
499
+ Your application is continuing to build in the background.
500
+ Run 'npx vibed status' to check status.
501
+ Run 'npx vibed logs' to view live build logs.
502
+ ------------------------------------------------------------
503
+ `);
504
+ }
505
+ }
506
+ catch (err) {
507
+ console.error(`\nDeployment error: ${err.message}`);
508
+ process.exit(1);
509
+ }
510
+ }
511
+ async function handleStatus() {
512
+ const config = (0, utils_1.readGlobalConfig)();
513
+ if (!config?.token) {
514
+ console.error("Not authenticated. Please run 'npx vibed login' first.");
515
+ process.exit(1);
516
+ }
517
+ const projectConfig = (0, utils_1.readProjectConfig)();
518
+ const appName = args[1] && !args[1].startsWith("--") ? args[1] : (projectConfig?.appName || (0, utils_1.inferAppName)(process.cwd()));
519
+ try {
520
+ // If an app name is provided or detected in this directory, show app status + telemetry
521
+ if (appName) {
522
+ const res = await fetch(`${utils_1.API_BASE_URL}/api/cli/logs?appName=${encodeURIComponent(appName)}`, {
523
+ headers: { Authorization: `Bearer ${config.token}` }
524
+ });
525
+ const data = (await res.json());
526
+ if (!res.ok) {
527
+ console.error(`Error: ${data.error || "Failed to retrieve status."}`);
528
+ process.exit(1);
529
+ }
530
+ const currentStatus = data.status || "unknown";
531
+ if (currentStatus === "active") {
532
+ console.log(`
533
+ VibedHost Service Status
534
+ ------------------------------------------------------------
535
+ Service: ${appName}
536
+ Workspace: ${data.workspaceName || "Workspace"}
537
+ Status: Online & Active
538
+
539
+ Live URL: https://${data.domain || projectConfig?.domain}
540
+ ------------------------------------------------------------
541
+ `);
542
+ }
543
+ else if (currentStatus === "failed") {
544
+ console.log(`
545
+ VibedHost Service Status
546
+ ------------------------------------------------------------
547
+ Service: ${appName}
548
+ Workspace: ${data.workspaceName || "Workspace"}
549
+ Status: Failed
550
+
551
+ Build unsuccessful.
552
+ Run 'npx vibed logs ${appName}' to view error logs.
553
+ ------------------------------------------------------------
554
+ `);
555
+ }
556
+ else {
557
+ console.log(`
558
+ VibedHost Service Status
559
+ ------------------------------------------------------------
560
+ Service: ${appName}
561
+ Workspace: ${data.workspaceName || "Workspace"}
562
+ Status: Building
563
+
564
+ Building application container in background.
565
+ Run 'npx vibed logs ${appName}' to view build progress.
566
+ ------------------------------------------------------------
567
+ `);
568
+ }
569
+ return;
570
+ }
571
+ // If no app name specified, display workspace fleet health & resource telemetry
572
+ const wsRes = await fetch(`${utils_1.API_BASE_URL}/api/cli/workspaces`, {
573
+ headers: { Authorization: `Bearer ${config.token}` }
574
+ });
575
+ const wsData = (await wsRes.json());
576
+ if (!wsRes.ok || !wsData.workspaces || wsData.workspaces.length === 0) {
577
+ console.log("\nNo active dedicated workspaces found. Launch one on the web console:\nhttps://www.vibedhost.com/dashboard/workspaces\n");
578
+ return;
579
+ }
580
+ console.log("\nVibedHost Workspace Health & Telemetry");
581
+ console.log("------------------------------------------------------------");
582
+ wsData.workspaces.forEach((ws, idx) => {
583
+ const ramStr = ws.ramTotalGB ? `${(ws.memoryUsageGB || 0.4).toFixed(1)} GB / ${ws.ramTotalGB} GB` : "Allocated";
584
+ const diskStr = ws.storageTotalGB ? `${(ws.storageUsageGB || 2.0).toFixed(1)} GB / ${ws.storageTotalGB} GB` : "NVMe";
585
+ const bwLimit = ws.bandwidthLimitGB > 0 ? `${(ws.bandwidthLimitGB / 1000).toFixed(0)} TB` : "Unmetered";
586
+ const bwStr = `${(ws.bandwidthUsedGB || 0).toFixed(1)} GB / ${bwLimit}`;
587
+ console.log(`Workspace: ${ws.name}`);
588
+ console.log(`Plan: ${ws.planName}`);
589
+ console.log(`Status: ${ws.status === 'running' ? 'Online & Active' : ws.status}`);
590
+ console.log(`CPU Load: ${ws.computeUsage || 4}%`);
591
+ console.log(`Memory: ${ramStr}`);
592
+ console.log(`NVMe Disk: ${diskStr}`);
593
+ console.log(`Bandwidth: ${bwStr}`);
594
+ if (ws.apps && ws.apps.length > 0) {
595
+ console.log(`\nActive Services (${ws.apps.length}):`);
596
+ ws.apps.forEach((a) => {
597
+ const urlStr = a.domain ? `https://${a.domain}` : (a.type === 'DATABASE' ? 'Internal Network' : 'Ready');
598
+ console.log(` • ${a.name} (${a.status}) ➔ ${urlStr}`);
599
+ });
600
+ }
601
+ if (idx < wsData.workspaces.length - 1) {
602
+ console.log("------------------------------------------------------------");
603
+ }
604
+ });
605
+ console.log("------------------------------------------------------------\n");
606
+ }
607
+ catch (err) {
608
+ console.error(`Failed to check service status: ${err.message}`);
609
+ }
610
+ }
611
+ async function handleLogs() {
612
+ const config = (0, utils_1.readGlobalConfig)();
613
+ if (!config?.token) {
614
+ console.error("Not authenticated. Please run 'npx vibed login' first.");
615
+ process.exit(1);
616
+ }
617
+ const isAiFormat = args.includes("--ai");
618
+ const filteredArgs = args.slice(1).filter((a) => a !== "--ai");
619
+ const projectConfig = (0, utils_1.readProjectConfig)();
620
+ const appName = filteredArgs[0] || projectConfig?.appName || (0, utils_1.inferAppName)(process.cwd());
621
+ if (!appName) {
622
+ console.error("Please specify application name: npx vibed logs <appName>");
623
+ process.exit(1);
624
+ }
625
+ try {
626
+ const res = await fetch(`${utils_1.API_BASE_URL}/api/cli/logs?appName=${encodeURIComponent(appName)}`, {
627
+ headers: { Authorization: `Bearer ${config.token}` }
628
+ });
629
+ const data = (await res.json());
630
+ if (!res.ok) {
631
+ console.error(`Error: ${data.error || "Failed to retrieve logs."}`);
632
+ process.exit(1);
633
+ }
634
+ if (isAiFormat) {
635
+ console.log(`
636
+ ============================================================
637
+ AI ASSISTANT DEBUG PROMPT (COPY AND PASTE TO CLAUDE / CURSOR)
638
+ ============================================================
639
+ I am deploying application "${appName}" on VibedHost PaaS.
640
+ The service encountered an error. Here are the container logs:
641
+
642
+ -------------------- BEGIN LOG STREAM --------------------
643
+ ${(data.logs || "").slice(-1500)}
644
+ --------------------- END LOG STREAM ---------------------
645
+
646
+ Please analyze these logs and tell me how to resolve the error.
647
+ ============================================================
648
+ `);
649
+ }
650
+ else {
651
+ console.log(`\nLog stream for '${appName}' (${data.workspaceName || "Workspace"}):`);
652
+ console.log("------------------------------------------------------------");
653
+ console.log(data.logs || "No logs returned.");
654
+ console.log("------------------------------------------------------------\n");
655
+ }
656
+ }
657
+ catch (err) {
658
+ console.error(`Failed to stream logs: ${err.message}`);
659
+ }
660
+ }
661
+ async function handleEnv() {
662
+ const config = (0, utils_1.readGlobalConfig)();
663
+ if (!config?.token) {
664
+ console.error("Not authenticated. Run 'npx vibed login'.");
665
+ process.exit(1);
666
+ }
667
+ const subCommand = args[1];
668
+ const projectConfig = (0, utils_1.readProjectConfig)();
669
+ const appName = projectConfig?.appName || (0, utils_1.inferAppName)(process.cwd());
670
+ if (!appName) {
671
+ console.error("No linked project found in this folder. Run inside a project directory or run 'npx vibed deploy' first.");
672
+ process.exit(1);
673
+ }
674
+ if (subCommand === "set") {
675
+ const keyVal = args[2];
676
+ if (!keyVal || !keyVal.includes("=")) {
677
+ console.error("Usage: npx vibed env set KEY=VALUE");
678
+ process.exit(1);
679
+ }
680
+ const idx = keyVal.indexOf("=");
681
+ const key = keyVal.substring(0, idx).trim();
682
+ const value = keyVal.substring(idx + 1).trim();
683
+ try {
684
+ const postRes = await fetch(`${utils_1.API_BASE_URL}/api/cli/env`, {
685
+ method: "POST",
686
+ headers: {
687
+ Authorization: `Bearer ${config.token}`,
688
+ "Content-Type": "application/json"
689
+ },
690
+ body: JSON.stringify({
691
+ appName,
692
+ key,
693
+ value
694
+ })
695
+ });
696
+ if (postRes.ok) {
697
+ console.log(`Environment variable ${key} updated. Service restarted.`);
698
+ }
699
+ else {
700
+ console.error("Failed to update environment variable.");
701
+ }
702
+ }
703
+ catch (err) {
704
+ console.error(`Connection error: ${err.message}`);
705
+ }
706
+ }
707
+ else {
708
+ // List vars
709
+ try {
710
+ const res = await fetch(`${utils_1.API_BASE_URL}/api/cli/env?appName=${encodeURIComponent(appName)}`, {
711
+ headers: { Authorization: `Bearer ${config.token}` }
712
+ });
713
+ const data = (await res.json());
714
+ if (res.ok) {
715
+ console.log(`\nEnvironment Variables for '${appName}':`);
716
+ console.log("------------------------------------------------------------");
717
+ (data.envVars || []).forEach((v) => {
718
+ const masked = v.value.length > 8 ? `${v.value.slice(0, 4)}••••${v.value.slice(-3)}` : "••••";
719
+ console.log(` ${v.key}=${masked}`);
720
+ });
721
+ console.log(` Internal Port: ${data.port || 80}`);
722
+ console.log("------------------------------------------------------------\n");
723
+ }
724
+ }
725
+ catch (err) {
726
+ console.error(`Error: ${err.message}`);
727
+ }
728
+ }
729
+ }
730
+ async function handlePort() {
731
+ const config = (0, utils_1.readGlobalConfig)();
732
+ if (!config?.token) {
733
+ console.error("Not authenticated. Run 'npx vibed login'.");
734
+ process.exit(1);
735
+ }
736
+ const subCommand = args[1];
737
+ const portVal = parseInt(args[2], 10);
738
+ const projectConfig = (0, utils_1.readProjectConfig)();
739
+ const appName = projectConfig?.appName || (0, utils_1.inferAppName)(process.cwd());
740
+ if (!appName) {
741
+ console.error("No linked project found in this folder.");
742
+ process.exit(1);
743
+ }
744
+ if (subCommand !== "set" || isNaN(portVal) || portVal < 1 || portVal > 65535) {
745
+ console.error("Usage: npx vibed port set <1-65535>");
746
+ process.exit(1);
747
+ }
748
+ try {
749
+ const res = await fetch(`${utils_1.API_BASE_URL}/api/cli/env`, {
750
+ method: "POST",
751
+ headers: {
752
+ Authorization: `Bearer ${config.token}`,
753
+ "Content-Type": "application/json"
754
+ },
755
+ body: JSON.stringify({
756
+ appName,
757
+ port: portVal
758
+ })
759
+ });
760
+ if (res.ok) {
761
+ console.log(`Routing port for '${appName}' updated to ${portVal}. Network routes synchronized.`);
762
+ }
763
+ else {
764
+ console.error("Failed to update container port.");
765
+ }
766
+ }
767
+ catch (err) {
768
+ console.error(`Connection error: ${err.message}`);
769
+ }
770
+ }
771
+ async function handleDb() {
772
+ const config = (0, utils_1.readGlobalConfig)();
773
+ if (!config?.token) {
774
+ console.error("Not authenticated. Please run 'npx vibed login' first.");
775
+ process.exit(1);
776
+ }
777
+ const subCommand = args[1] || "list";
778
+ const projectConfig = (0, utils_1.readProjectConfig)();
779
+ // 1. DATABASE LIST
780
+ if (subCommand === "list") {
781
+ try {
782
+ const res = await fetch(`${utils_1.API_BASE_URL}/api/cli/db`, {
783
+ headers: { Authorization: `Bearer ${config.token}` }
784
+ });
785
+ const data = (await res.json());
786
+ if (!res.ok) {
787
+ console.error(`Error: ${data.error || "Failed to fetch databases."}`);
788
+ process.exit(1);
789
+ }
790
+ const isReveal = args.includes("--reveal");
791
+ console.log("\nManaged Databases:");
792
+ console.log("------------------------------------------------------------");
793
+ if (!data.databases || data.databases.length === 0) {
794
+ console.log("No databases provisioned yet.");
795
+ console.log("Run 'npx vibed db create <name>' to launch a database.\n");
796
+ return;
797
+ }
798
+ data.databases.forEach((d, idx) => {
799
+ console.log(`[${idx + 1}] ${d.name} (${d.engine}) • Workspace: ${d.workspaceName}`);
800
+ console.log(` Status: ${d.status}`);
801
+ console.log(` URI: ${isReveal ? d.internalUri : d.maskedUri}`);
802
+ });
803
+ console.log("------------------------------------------------------------\n");
804
+ }
805
+ catch (err) {
806
+ console.error(`Connection error: ${err.message}`);
807
+ }
808
+ return;
809
+ }
810
+ // 2. DATABASE CREATE
811
+ if (subCommand === "create") {
812
+ let dbName = args[2] && !args[2].startsWith("--") ? args[2] : "";
813
+ const engineIndex = args.indexOf("--engine");
814
+ let explicitEngine = engineIndex !== -1 ? args[engineIndex + 1] : "";
815
+ if (!dbName && process.stdin.isTTY) {
816
+ dbName = await (0, utils_1.askQuestion)("Database Service Name", "my-postgres");
817
+ }
818
+ else if (!dbName) {
819
+ dbName = "my-postgres";
820
+ }
821
+ if (!explicitEngine && process.stdin.isTTY) {
822
+ console.log("\nSelect Database Engine:");
823
+ console.log(" [1] PostgreSQL 15 (Default)");
824
+ console.log(" [2] Supabase Postgres");
825
+ console.log(" [3] MySQL 8.0");
826
+ console.log(" [4] Redis 7 (In-Memory)");
827
+ console.log(" [5] MongoDB 6");
828
+ const chosen = await (0, utils_1.askQuestion)("Enter number", "1");
829
+ const map = {
830
+ "1": "POSTGRES",
831
+ "2": "SUPABASE_POSTGRES",
832
+ "3": "MYSQL",
833
+ "4": "REDIS",
834
+ "5": "MONGODB"
835
+ };
836
+ explicitEngine = map[chosen] || "POSTGRES";
837
+ }
838
+ const cleanEngine = (explicitEngine || "POSTGRES").toUpperCase();
839
+ console.log(`\nProvisioning ${cleanEngine} database '${dbName}' on dedicated NVMe storage...`);
840
+ try {
841
+ const res = await fetch(`${utils_1.API_BASE_URL}/api/cli/db`, {
842
+ method: "POST",
843
+ headers: {
844
+ Authorization: `Bearer ${config.token}`,
845
+ "Content-Type": "application/json"
846
+ },
847
+ body: JSON.stringify({
848
+ action: "CREATE_DB",
849
+ workspaceId: projectConfig?.workspaceId,
850
+ name: dbName,
851
+ engine: cleanEngine
852
+ })
853
+ });
854
+ const data = (await res.json());
855
+ if (!res.ok || !data.success) {
856
+ console.error(`Database launch error: ${data.error || "Failed to create database."}`);
857
+ process.exit(1);
858
+ }
859
+ console.log(`\nDatabase Ready!`);
860
+ console.log("------------------------------------------------------------");
861
+ console.log(`Name: ${data.name}`);
862
+ console.log(`Engine: ${data.engine}`);
863
+ console.log(`Workspace: ${data.workspaceName}`);
864
+ console.log(`URI: ${data.internalUri}`);
865
+ console.log("------------------------------------------------------------");
866
+ console.log(`To link to your app, run: npx vibed db link ${data.name}\n`);
867
+ }
868
+ catch (err) {
869
+ console.error(`Connection error: ${err.message}`);
870
+ }
871
+ return;
872
+ }
873
+ // 3. DATABASE LINK
874
+ if (subCommand === "link") {
875
+ const dbTargetName = args[2] && !args[2].startsWith("--") ? args[2] : "";
876
+ const targetAppName = projectConfig?.appName || (0, utils_1.inferAppName)(process.cwd());
877
+ if (!dbTargetName) {
878
+ console.error("Usage: npx vibed db link <database-name>");
879
+ process.exit(1);
880
+ }
881
+ console.log(`Linking database '${dbTargetName}' to application '${targetAppName}'...`);
882
+ try {
883
+ const res = await fetch(`${utils_1.API_BASE_URL}/api/cli/db`, {
884
+ method: "POST",
885
+ headers: {
886
+ Authorization: `Bearer ${config.token}`,
887
+ "Content-Type": "application/json"
888
+ },
889
+ body: JSON.stringify({
890
+ action: "LINK_DB",
891
+ appName: targetAppName,
892
+ dbName: dbTargetName
893
+ })
894
+ });
895
+ const data = (await res.json());
896
+ if (!res.ok || !data.success) {
897
+ console.error(`Link error: ${data.error || "Failed to link database."}`);
898
+ process.exit(1);
899
+ }
900
+ console.log(`\nSuccessfully linked '${data.dbName}' to '${data.appName}'!`);
901
+ console.log(`Injected ${data.keyUsed} into environment variables. Container restarted.\n`);
902
+ }
903
+ catch (err) {
904
+ console.error(`Connection error: ${err.message}`);
905
+ }
906
+ return;
907
+ }
908
+ console.log("Usage: npx vibed db [create|list|link]");
909
+ }
910
+ main().catch((err) => {
911
+ console.error(`Execution error: ${err.message}`);
912
+ process.exit(1);
913
+ });