@vibingview/cli 0.1.3 → 0.1.4
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/package.json +1 -1
- package/src/cli.mjs +42 -10
- package/src/mcp.mjs +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vibingview/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "VibingView command-line interface and stdio MCP bridge: pair with browser approval, deploy apps, read logs, manage env vars and databases, and connect AI editors.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
package/src/cli.mjs
CHANGED
|
@@ -10,7 +10,7 @@ import { fileURLToPath } from "node:url";
|
|
|
10
10
|
import { ensureGitignore, inferAppName, isIdenticalDeploy, packageProject, readProjectConfig, writeProjectConfig } from "./lib/project.mjs";
|
|
11
11
|
import { defaultTransport, installMcpConfig, maskToken, maskedEntry, mcpConfigPath, MCP_CLIENTS, NPM_PACKAGE } from "./lib/mcp-install.mjs";
|
|
12
12
|
|
|
13
|
-
export const VERSION = "0.1.
|
|
13
|
+
export const VERSION = "0.1.4";
|
|
14
14
|
/** Production control plane used when no --url, VIBINGVIEW_URL or saved login selects another. */
|
|
15
15
|
export const DEFAULT_URL = "https://vibingview.com";
|
|
16
16
|
const USAGE = `VibingView CLI ${VERSION}
|
|
@@ -122,11 +122,17 @@ function createClient(base, token) {
|
|
|
122
122
|
response = await fetch(`${base}${route}`, { ...init, redirect: "error", signal: AbortSignal.timeout(init.timeout ?? 120000), headers: { ...(token ? { Authorization: `Bearer ${token}` } : {}), ...init.headers } });
|
|
123
123
|
} catch (error) { throw new CliError(redact(`Could not reach ${base}: ${error.cause?.message ?? error.message}`)); }
|
|
124
124
|
if (response.status === 204) return {};
|
|
125
|
-
|
|
125
|
+
let body = {}, unreadable = false;
|
|
126
|
+
const text = await response.text().catch(() => { unreadable = true; return ""; });
|
|
127
|
+
if (text) { try { body = JSON.parse(text); } catch { unreadable = true; } }
|
|
128
|
+
// A success whose body was cut off (e.g. the connection dropped) must not read as "empty": callers would treat an
|
|
129
|
+
// unread app list as "no apps" and create a duplicate.
|
|
130
|
+
if (response.ok && unreadable)
|
|
131
|
+
throw new CliError(`Could not read the server's answer to ${route.split("?")[0]}. Nothing was assumed; try again (after a deploy, check vibingview status first).`);
|
|
126
132
|
if (!response.ok) {
|
|
127
133
|
let message = typeof body.error === "string" ? body.error : body.error?.message ?? `HTTP ${response.status}`;
|
|
128
134
|
if (response.status === 401 && token) message += " — the token may be expired or revoked, or lacks the permission this command needs (database create/reveal need a token with the 'manage' permission). Run vibingview login or use an appropriately scoped VIBINGVIEW_TOKEN.";
|
|
129
|
-
throw Object.assign(new CliError(redact(message)), { status: response.status });
|
|
135
|
+
throw Object.assign(new CliError(redact(message)), { status: response.status, code: typeof body.code === "string" ? body.code : undefined });
|
|
130
136
|
}
|
|
131
137
|
return body;
|
|
132
138
|
};
|
|
@@ -315,7 +321,7 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
315
321
|
// ----- lookups -------------------------------------------------------------
|
|
316
322
|
const cwd = process.cwd();
|
|
317
323
|
const project = await readProjectConfig(cwd);
|
|
318
|
-
const workspacesList = async () =>
|
|
324
|
+
const workspacesList = async () => { const { workspaces } = await call("/api/workspaces"); if (!Array.isArray(workspaces)) throw new CliError("The server did not send the list of workspaces. Nothing was changed; try again."); return workspaces; };
|
|
319
325
|
const isRunning = workspace => String(workspace.status).toUpperCase() === "RUNNING";
|
|
320
326
|
async function resolveWorkspace(reference, { running = true } = {}) {
|
|
321
327
|
const all = await workspacesList();
|
|
@@ -371,9 +377,22 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
371
377
|
reportNotableSkips(pkg.notable ?? []);
|
|
372
378
|
if (isIdenticalDeploy(linked, pkg.sha256, flags.force)) { out("No changes since the last deploy. Use --force to redeploy anyway."); return 0; }
|
|
373
379
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
if (linked.applicationId &&
|
|
380
|
+
if (linked.controlPlane && linked.controlPlane !== base) warn(`This folder was deployed to ${linked.controlPlane}; you are now using ${base}.`);
|
|
381
|
+
const appList = async () => { const { applications } = await call("/api/apps"); if (!Array.isArray(applications)) throw new CliError("The server did not send the list of applications. Nothing was changed; try again."); return applications; };
|
|
382
|
+
if (linked.applicationId && flags.name && flags.name !== linked.name && !flags.force) throw new CliError(`This folder is linked to the application "${linked.name}", but --name says "${flags.name}". Drop --name to redeploy it, use --force to create another app, or remove .vibingview/config.json.`);
|
|
383
|
+
const useLinked = linked.applicationId && (!flags.name || flags.name === linked.name);
|
|
384
|
+
let app = useLinked ? (await appList()).find(item => item.id === linked.applicationId) : undefined;
|
|
385
|
+
if (useLinked && !app) {
|
|
386
|
+
// One answer without the app must never create a duplicate: ask for the app itself before assuming it was deleted.
|
|
387
|
+
const deleted = await call(`/api/apps/${encodeURIComponent(linked.applicationId)}/settings`).then(() => false, error => {
|
|
388
|
+
if (error.status === 404 || error.code === "apps.not_found") return true;
|
|
389
|
+
throw new CliError(`Could not confirm that the linked application ${linked.name ?? linked.applicationId} was deleted (${error.message}). Nothing was created; try again.`);
|
|
390
|
+
});
|
|
391
|
+
if (!deleted) {
|
|
392
|
+
app = (await appList()).find(item => item.id === linked.applicationId);
|
|
393
|
+
if (!app) throw new CliError(`The linked application ${linked.name ?? linked.applicationId} exists but was missing from the server's list. Nothing was created; try again in a moment.`);
|
|
394
|
+
} else warn("The linked application was deleted; creating a new one.");
|
|
395
|
+
}
|
|
377
396
|
const catalog = await call("/api/catalog");
|
|
378
397
|
const suffixes = catalog.domains ?? [];
|
|
379
398
|
let suffix = linked.domainSuffix;
|
|
@@ -416,7 +435,8 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
416
435
|
if (port !== undefined) form.set("port", String(port));
|
|
417
436
|
if (builder) form.set("builder", builder);
|
|
418
437
|
form.set("file", new Blob([pkg.archive], { type: "application/zip" }), `${name}.zip`);
|
|
419
|
-
|
|
438
|
+
// A large upload must not time out client-side while the server finishes creating the app (that made a second app on retry).
|
|
439
|
+
app = (await call("/api/apps", { method: "POST", body: form, timeout: 120000 + Math.ceil(pkg.archive.length / 1_000_000) * 4000 })).application;
|
|
420
440
|
deploymentId = app.lastDeploymentId;
|
|
421
441
|
}
|
|
422
442
|
const state = { controlPlane: base, workspaceId, applicationId: app.id, name: app.name, url: app.url, domainSuffix: suffix, customDomain: customDomain ?? linked.customDomain, lastHash: linked.lastHash, deployedAt: new Date().toISOString() };
|
|
@@ -515,8 +535,20 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
515
535
|
const environment = {};
|
|
516
536
|
const pairs = args.slice(1);
|
|
517
537
|
if (flags.stdin) {
|
|
518
|
-
|
|
519
|
-
|
|
538
|
+
// One name: the whole input is its value. No name: one NAME=value per line (.env style), so secrets never sit in argv.
|
|
539
|
+
if (pairs.length > 1) throw new CliError("Usage: vibingview env set NAME --stdin, or env set --stdin with NAME=value lines on stdin");
|
|
540
|
+
const input = await readStdin();
|
|
541
|
+
if (pairs.length === 1) environment[pairs[0]] = input;
|
|
542
|
+
else for (const line of input.split(/\r?\n/)) {
|
|
543
|
+
const text = line.trim();
|
|
544
|
+
if (!text || text.startsWith("#")) continue;
|
|
545
|
+
const match = /^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)=(.*)$/.exec(text);
|
|
546
|
+
if (!match) throw new CliError(`Not a NAME=value line: ${text.slice(0, 40)}`);
|
|
547
|
+
let value = match[2];
|
|
548
|
+
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) value = value.slice(1, -1);
|
|
549
|
+
environment[match[1]] = value;
|
|
550
|
+
}
|
|
551
|
+
if (!Object.keys(environment).length) throw new CliError("No NAME=value lines were read from stdin.");
|
|
520
552
|
} else {
|
|
521
553
|
if (!pairs.length) throw new CliError("Usage: vibingview env set NAME=value [NAME=value…] or NAME --stdin");
|
|
522
554
|
for (const pair of pairs) {
|
package/src/mcp.mjs
CHANGED
|
@@ -46,7 +46,7 @@ async function handle(message) {
|
|
|
46
46
|
const params = message.params && typeof message.params === "object" && !Array.isArray(message.params) ? message.params : {};
|
|
47
47
|
if (message.method === "initialize") {
|
|
48
48
|
const protocolVersion = SUPPORTED_VERSIONS.includes(params.protocolVersion) ? params.protocolVersion : SUPPORTED_VERSIONS[0];
|
|
49
|
-
return { protocolVersion, capabilities: { tools: { listChanged: false } }, serverInfo: { name: "vibingview", version: "0.1.
|
|
49
|
+
return { protocolVersion, capabilities: { tools: { listChanged: false } }, serverInfo: { name: "vibingview", version: "0.1.4" }, instructions: INSTRUCTIONS };
|
|
50
50
|
}
|
|
51
51
|
if (message.method === "ping") return {};
|
|
52
52
|
if (message.method === "tools/list") {
|