@amirhosseinnateghi/vibed-cli 0.1.3 → 0.1.5
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 +243 -55
- package/dist/vibed.cjs +243 -55
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1248,10 +1248,10 @@ var require_decode = __commonJS({
|
|
|
1248
1248
|
this.state = EntityDecoderState.NumericDecimal;
|
|
1249
1249
|
return this.stateNumericDecimal(str2, offset);
|
|
1250
1250
|
};
|
|
1251
|
-
EntityDecoder2.prototype.addToNumericResult = function(str2, start, end,
|
|
1251
|
+
EntityDecoder2.prototype.addToNumericResult = function(str2, start, end, base2) {
|
|
1252
1252
|
if (start !== end) {
|
|
1253
1253
|
var digitCount = end - start;
|
|
1254
|
-
this.result = this.result * Math.pow(
|
|
1254
|
+
this.result = this.result * Math.pow(base2, digitCount) + parseInt(str2.substr(start, digitCount), base2);
|
|
1255
1255
|
this.consumed += digitCount;
|
|
1256
1256
|
}
|
|
1257
1257
|
};
|
|
@@ -5982,7 +5982,7 @@ import { pathToFileURL } from "node:url";
|
|
|
5982
5982
|
import { homedir } from "node:os";
|
|
5983
5983
|
import { join } from "node:path";
|
|
5984
5984
|
import { mkdir, readFile, writeFile, chmod } from "node:fs/promises";
|
|
5985
|
-
var DEFAULT_API = "https://
|
|
5985
|
+
var DEFAULT_API = "https://vibed.city";
|
|
5986
5986
|
var dir = () => join(homedir(), ".vibed");
|
|
5987
5987
|
var file = () => join(dir(), "config.json");
|
|
5988
5988
|
async function readFileConfig() {
|
|
@@ -6073,7 +6073,19 @@ var config = {
|
|
|
6073
6073
|
// Light model that auto-tags the experience category. Falls back to a keyword
|
|
6074
6074
|
// heuristic if the call fails.
|
|
6075
6075
|
BUILDER_CLASSIFIER_MODEL: strEnv("BUILDER_CLASSIFIER_MODEL", "gemini-2.5-flash"),
|
|
6076
|
-
|
|
6076
|
+
// Speech-to-text model for the builder's voice input (docs/builder-architecture.md).
|
|
6077
|
+
// Gemini 2.5 Flash transcribes the recorded clip via GapGPT's OpenAI-compatible
|
|
6078
|
+
// chat API (an `input_audio` content part) — verified to accept the exact
|
|
6079
|
+
// formats browsers produce (Chrome webm/opus, Safari mp4). It's multilingual,
|
|
6080
|
+
// so Persian and English speech both work with no per-language config.
|
|
6081
|
+
BUILDER_TRANSCRIBE_MODEL: strEnv("BUILDER_TRANSCRIBE_MODEL", "gemini-2.5-flash"),
|
|
6082
|
+
// Upload cap for a voice clip. A minute of webm/opus is ~250KB; 8MB is ample
|
|
6083
|
+
// headroom while still bounding what one request can hand the model.
|
|
6084
|
+
BUILDER_TRANSCRIBE_MAX_BYTES: intEnv("BUILDER_TRANSCRIBE_MAX_BYTES", 8388608),
|
|
6085
|
+
// Output budget per model call. 8k proved too small in prod — a rich game is
|
|
6086
|
+
// ~7-8k tokens of file alone, and a truncated file used to slip through as a
|
|
6087
|
+
// text dump. 24576 was probed OK against every catalog model on GapGPT.
|
|
6088
|
+
BUILDER_MAX_TOKENS: intEnv("BUILDER_MAX_TOKENS", 24576),
|
|
6077
6089
|
BUILDER_MAX_MESSAGES: intEnv("BUILDER_MAX_MESSAGES", 30),
|
|
6078
6090
|
// Hard cap for a single build turn (across the model + one repair). The build
|
|
6079
6091
|
// runs detached from the request, so this — not the browser — is what stops a
|
|
@@ -9815,23 +9827,23 @@ var ZodEffects = class extends ZodType {
|
|
|
9815
9827
|
}
|
|
9816
9828
|
if (effect.type === "transform") {
|
|
9817
9829
|
if (ctx.common.async === false) {
|
|
9818
|
-
const
|
|
9830
|
+
const base2 = this._def.schema._parseSync({
|
|
9819
9831
|
data: ctx.data,
|
|
9820
9832
|
path: ctx.path,
|
|
9821
9833
|
parent: ctx
|
|
9822
9834
|
});
|
|
9823
|
-
if (!isValid(
|
|
9835
|
+
if (!isValid(base2))
|
|
9824
9836
|
return INVALID;
|
|
9825
|
-
const result = effect.transform(
|
|
9837
|
+
const result = effect.transform(base2.value, checkCtx);
|
|
9826
9838
|
if (result instanceof Promise) {
|
|
9827
9839
|
throw new Error(`Asynchronous transform encountered during synchronous parse operation. Use .parseAsync instead.`);
|
|
9828
9840
|
}
|
|
9829
9841
|
return { status: status.value, value: result };
|
|
9830
9842
|
} else {
|
|
9831
|
-
return this._def.schema._parseAsync({ data: ctx.data, path: ctx.path, parent: ctx }).then((
|
|
9832
|
-
if (!isValid(
|
|
9843
|
+
return this._def.schema._parseAsync({ data: ctx.data, path: ctx.path, parent: ctx }).then((base2) => {
|
|
9844
|
+
if (!isValid(base2))
|
|
9833
9845
|
return INVALID;
|
|
9834
|
-
return Promise.resolve(effect.transform(
|
|
9846
|
+
return Promise.resolve(effect.transform(base2.value, checkCtx)).then((result) => ({
|
|
9835
9847
|
status: status.value,
|
|
9836
9848
|
value: result
|
|
9837
9849
|
}));
|
|
@@ -10226,8 +10238,13 @@ var STORAGE = {
|
|
|
10226
10238
|
// 256 KB
|
|
10227
10239
|
/** Max records returnable in one query page. */
|
|
10228
10240
|
QUERY_MAX_LIMIT: intEnv("STORE_QUERY_MAX_LIMIT", 100),
|
|
10229
|
-
/**
|
|
10230
|
-
|
|
10241
|
+
/**
|
|
10242
|
+
* Platform kill-switch for anonymous shared writes. ON by default: the real
|
|
10243
|
+
* gate is per-post owner opt-in (`anon_writes` + writePolicy `anyone`), so a
|
|
10244
|
+
* post only accepts guest writes when its owner turns them on. Set the env to
|
|
10245
|
+
* `false` to disable anon writes platform-wide regardless of per-post config.
|
|
10246
|
+
*/
|
|
10247
|
+
ANON_WRITES_AVAILABLE: boolEnv("STORE_ANON_WRITES_AVAILABLE", true)
|
|
10231
10248
|
};
|
|
10232
10249
|
var COLLECTION_RE = /^[a-z0-9][a-z0-9:_-]{0,63}$/;
|
|
10233
10250
|
var collectionSchemaStore = external_exports.string().regex(COLLECTION_RE, "invalid collection name");
|
|
@@ -10337,13 +10354,31 @@ var commentSchema = external_exports.object({
|
|
|
10337
10354
|
parentId: external_exports.string().uuid().optional()
|
|
10338
10355
|
});
|
|
10339
10356
|
var eventSchema = external_exports.object({
|
|
10340
|
-
|
|
10357
|
+
// "record"/"screenshot" fire when a viewer captures the vibe (see the in-iframe
|
|
10358
|
+
// bridge capture flow, docs/architecture.md §5.4). Plain-text `viewEvents.kind`
|
|
10359
|
+
// column, so new kinds need no migration.
|
|
10360
|
+
kind: external_exports.enum(["impression", "play", "play_time", "share", "record", "screenshot"]),
|
|
10341
10361
|
ms: external_exports.number().int().nonnegative().optional()
|
|
10342
10362
|
});
|
|
10343
10363
|
var createBuildSchema = external_exports.object({
|
|
10344
10364
|
mode: external_exports.enum(["ai", "remix"]),
|
|
10345
10365
|
remixParentId: external_exports.string().uuid().optional()
|
|
10346
10366
|
});
|
|
10367
|
+
var turnAttachmentSchema = external_exports.object({
|
|
10368
|
+
kind: external_exports.enum(["image", "audio", "file", "sketch"]),
|
|
10369
|
+
key: external_exports.string().max(300).default(""),
|
|
10370
|
+
name: external_exports.string().max(200),
|
|
10371
|
+
// NUL bytes break Postgres jsonb (and mean binary-not-text) — strip defensively.
|
|
10372
|
+
text: external_exports.string().max(5e4).transform((t) => t.replace(/\u0000/g, "")).optional()
|
|
10373
|
+
});
|
|
10374
|
+
var createTurnRequestSchema = external_exports.object({
|
|
10375
|
+
clientTurnId: external_exports.string().uuid(),
|
|
10376
|
+
content: external_exports.string().max(4e3).default(""),
|
|
10377
|
+
attachments: external_exports.array(turnAttachmentSchema).max(6).optional(),
|
|
10378
|
+
model: external_exports.string().max(100).optional()
|
|
10379
|
+
}).refine((v) => v.content.trim().length > 0 || (v.attachments?.length ?? 0) > 0, {
|
|
10380
|
+
message: "Send a message or an attachment."
|
|
10381
|
+
});
|
|
10347
10382
|
var builderAttachmentSchema = external_exports.object({
|
|
10348
10383
|
kind: external_exports.enum(["image", "file", "audio"]),
|
|
10349
10384
|
name: external_exports.string().max(200),
|
|
@@ -10444,27 +10479,52 @@ var notifPrefsPatchSchema = external_exports.object({
|
|
|
10444
10479
|
groups: external_exports.record(external_exports.enum(NOTIF_PREF_GROUPS), external_exports.boolean()).optional()
|
|
10445
10480
|
});
|
|
10446
10481
|
|
|
10447
|
-
// ../shared/src/
|
|
10448
|
-
var
|
|
10449
|
-
|
|
10450
|
-
|
|
10451
|
-
//
|
|
10452
|
-
|
|
10453
|
-
|
|
10454
|
-
|
|
10455
|
-
|
|
10456
|
-
|
|
10457
|
-
|
|
10458
|
-
|
|
10459
|
-
|
|
10460
|
-
// ── Subscription (locked until billing is enabled) — premium ──────
|
|
10461
|
-
// Opus 4.8 is owner-only early access: locked for everyone, live for the owner.
|
|
10462
|
-
{ id: "claude-opus-4-8", label: "Claude Opus 4.8", tier: "subscription", note: "Top quality", vision: true, ownerOnly: true },
|
|
10463
|
-
{ id: "gpt-5.2-pro", label: "GPT-5.2 Pro", tier: "subscription", note: "Premium" },
|
|
10464
|
-
{ id: "gemini-3-pro-preview", label: "Gemini 3 Pro", tier: "subscription", note: "Premium", vision: true }
|
|
10482
|
+
// ../shared/src/builderEvents.ts
|
|
10483
|
+
var BUILD_EVENT_VERSION = 1;
|
|
10484
|
+
var BUILD_PHASES = [
|
|
10485
|
+
"queued",
|
|
10486
|
+
// accepted, waiting for a worker slot
|
|
10487
|
+
"thinking",
|
|
10488
|
+
// model connected, no visible output yet (keep-alives flowing)
|
|
10489
|
+
"writing",
|
|
10490
|
+
// streaming the vibe's code/prose
|
|
10491
|
+
"validating",
|
|
10492
|
+
// sandbox pass on the produced artifact
|
|
10493
|
+
"repairing"
|
|
10494
|
+
// one automatic fix round after a validation failure
|
|
10465
10495
|
];
|
|
10466
|
-
var
|
|
10467
|
-
var
|
|
10496
|
+
var base = { v: external_exports.literal(BUILD_EVENT_VERSION), turnId: external_exports.string() };
|
|
10497
|
+
var buildEventSchema = external_exports.discriminatedUnion("type", [
|
|
10498
|
+
/** Assistant prose delta (never contains the HTML code block). */
|
|
10499
|
+
external_exports.object({ ...base, type: external_exports.literal("token"), text: external_exports.string() }),
|
|
10500
|
+
/**
|
|
10501
|
+
* Honest heartbeat, ~every 2s while the turn runs: real elapsed ms, real
|
|
10502
|
+
* output-token count, and the executor's actual phase. The UI renders this
|
|
10503
|
+
* as a live counter — a number that moves because work is happening.
|
|
10504
|
+
*/
|
|
10505
|
+
external_exports.object({
|
|
10506
|
+
...base,
|
|
10507
|
+
type: external_exports.literal("progress"),
|
|
10508
|
+
phase: external_exports.enum(BUILD_PHASES),
|
|
10509
|
+
elapsedMs: external_exports.number(),
|
|
10510
|
+
tokensOut: external_exports.number()
|
|
10511
|
+
}),
|
|
10512
|
+
/** Friendly status chip ("index.html", "Fixing a couple of things…"). */
|
|
10513
|
+
external_exports.object({ ...base, type: external_exports.literal("status"), summary: external_exports.string() }),
|
|
10514
|
+
/** First-message category classification. */
|
|
10515
|
+
external_exports.object({ ...base, type: external_exports.literal("category"), category: external_exports.string() }),
|
|
10516
|
+
/** The validated draft landed. Preview loads it from the draft URL. */
|
|
10517
|
+
external_exports.object({ ...base, type: external_exports.literal("artifact"), artifactKey: external_exports.string() }),
|
|
10518
|
+
/** Terminal: failure with taxonomy code + human message. */
|
|
10519
|
+
external_exports.object({
|
|
10520
|
+
...base,
|
|
10521
|
+
type: external_exports.literal("error"),
|
|
10522
|
+
code: external_exports.string(),
|
|
10523
|
+
message: external_exports.string()
|
|
10524
|
+
}),
|
|
10525
|
+
/** Terminal: success. */
|
|
10526
|
+
external_exports.object({ ...base, type: external_exports.literal("done") })
|
|
10527
|
+
]);
|
|
10468
10528
|
|
|
10469
10529
|
// ../shared/src/i18n.ts
|
|
10470
10530
|
var en = {
|
|
@@ -10498,6 +10558,21 @@ var en = {
|
|
|
10498
10558
|
"share.title": "Share",
|
|
10499
10559
|
"share.copyLink": "Copy link",
|
|
10500
10560
|
"share.copied": "Link copied",
|
|
10561
|
+
"capture.record": "Record",
|
|
10562
|
+
"capture.screenshot": "Screenshot",
|
|
10563
|
+
"capture.recording": "Recording",
|
|
10564
|
+
"capture.stop": "Stop",
|
|
10565
|
+
"capture.preparing": "Preparing\u2026",
|
|
10566
|
+
"capture.processing": "Finishing\u2026",
|
|
10567
|
+
"capture.preview.videoTitle": "Your clip",
|
|
10568
|
+
"capture.preview.imageTitle": "Your screenshot",
|
|
10569
|
+
"capture.save": "Save",
|
|
10570
|
+
"capture.shareVia": "Share via\u2026",
|
|
10571
|
+
"capture.retry": "Try again",
|
|
10572
|
+
"capture.error": "Capture failed \u2014 try again.",
|
|
10573
|
+
"capture.allowShare": "Allow \u201CShare this tab\u201D to record \u2014 it captures just the vibe.",
|
|
10574
|
+
"capture.pickTab": "Choose \u201CThis Tab\u201D so it records only the vibe.",
|
|
10575
|
+
"capture.unsupported": `This ${ITEM.one} can't be captured yet. Try again in a moment.`,
|
|
10501
10576
|
"create.title": "Create",
|
|
10502
10577
|
"create.buildWithAI": "Build with AI",
|
|
10503
10578
|
"create.import": "Import",
|
|
@@ -10689,6 +10764,15 @@ function validate(html, opts) {
|
|
|
10689
10764
|
errors.push({ code: "NOT_HTML", message: "Content is not parseable as HTML." });
|
|
10690
10765
|
return { ok: false, errors, warnings };
|
|
10691
10766
|
}
|
|
10767
|
+
const scriptOpens = (html.match(/<script\b/gi) ?? []).length;
|
|
10768
|
+
const scriptCloses = (html.match(/<\/script\s*>/gi) ?? []).length;
|
|
10769
|
+
if (scriptOpens > scriptCloses) {
|
|
10770
|
+
errors.push({
|
|
10771
|
+
code: "TRUNCATED_DOC",
|
|
10772
|
+
message: "The file ends mid-<script> \u2014 it looks cut off. Regenerate or paste the complete file."
|
|
10773
|
+
});
|
|
10774
|
+
return { ok: false, errors, warnings };
|
|
10775
|
+
}
|
|
10692
10776
|
if (on("frames") && root.querySelector("iframe, frame, object, embed")) {
|
|
10693
10777
|
errors.push({
|
|
10694
10778
|
code: "FRAME_TAG",
|
|
@@ -11277,19 +11361,35 @@ function normalizeCategory(input) {
|
|
|
11277
11361
|
const match = CATEGORIES.find((c) => c.toLowerCase() === input.toLowerCase());
|
|
11278
11362
|
return match ?? "Other";
|
|
11279
11363
|
}
|
|
11280
|
-
async function
|
|
11281
|
-
const
|
|
11364
|
+
async function importDraft(client, html) {
|
|
11365
|
+
const r = await apiFetch(client, "POST", "/imports", { html });
|
|
11366
|
+
return { draftArtifactKey: r.draftArtifactKey, previewUrl: r.previewUrl };
|
|
11367
|
+
}
|
|
11368
|
+
async function publishDraft(client, draftArtifactKey, meta, html, projectDir) {
|
|
11282
11369
|
const exp = await apiFetch(client, "POST", "/experiences", {
|
|
11283
|
-
title: meta.title?.trim() || inferTitle(html, projectDir),
|
|
11370
|
+
title: meta.title?.trim() || (html ? inferTitle(html, projectDir ?? ".") : "Untitled"),
|
|
11284
11371
|
caption: meta.caption ?? "",
|
|
11285
11372
|
category: normalizeCategory(meta.category),
|
|
11286
11373
|
tags: meta.tags ?? [],
|
|
11287
11374
|
remixable: meta.remixable ?? true,
|
|
11288
11375
|
visibility: meta.visibility ?? "public",
|
|
11289
|
-
draftArtifactKey
|
|
11376
|
+
draftArtifactKey
|
|
11290
11377
|
});
|
|
11291
11378
|
return { id: exp.id, status: exp.status, url: `${client.apiBase}/e/${exp.id}` };
|
|
11292
11379
|
}
|
|
11380
|
+
async function publishArtifact(client, html, meta, projectDir) {
|
|
11381
|
+
const { draftArtifactKey } = await importDraft(client, html);
|
|
11382
|
+
return publishDraft(client, draftArtifactKey, meta, html, projectDir);
|
|
11383
|
+
}
|
|
11384
|
+
function experienceIdFrom(input) {
|
|
11385
|
+
const m = input.match(/\/e\/([0-9a-f-]{36})/i);
|
|
11386
|
+
return (m?.[1] ?? input.trim()).toLowerCase();
|
|
11387
|
+
}
|
|
11388
|
+
async function deleteExperience(client, idOrUrl) {
|
|
11389
|
+
const id = experienceIdFrom(idOrUrl);
|
|
11390
|
+
await apiFetch(client, "DELETE", `/experiences/${id}`);
|
|
11391
|
+
return id;
|
|
11392
|
+
}
|
|
11293
11393
|
|
|
11294
11394
|
// src/index.ts
|
|
11295
11395
|
function parseArgs(argv) {
|
|
@@ -11324,7 +11424,10 @@ Usage:
|
|
|
11324
11424
|
vibed login Authenticate this machine (opens the browser)
|
|
11325
11425
|
vibed check [path] Check if a project can be published (no network)
|
|
11326
11426
|
vibed preview [path] Bundle + open the artifact locally (no publish)
|
|
11427
|
+
vibed draft [path] Upload a private, hosted preview link (no publish)
|
|
11327
11428
|
vibed publish [path] Bundle the project and publish it to vibed
|
|
11429
|
+
vibed publish --draft-key <k> Promote a draft preview to a public post
|
|
11430
|
+
vibed delete <id|url> Delete one of your posts
|
|
11328
11431
|
vibed whoami Show the signed-in account
|
|
11329
11432
|
vibed logout Remove the saved token
|
|
11330
11433
|
|
|
@@ -11388,7 +11491,30 @@ Preview \u2192 ${file2}`);
|
|
|
11388
11491
|
}
|
|
11389
11492
|
return result.ok ? 0 : 1;
|
|
11390
11493
|
}
|
|
11391
|
-
|
|
11494
|
+
function metaFromArgs(args) {
|
|
11495
|
+
return {
|
|
11496
|
+
title: str(args.flags.title),
|
|
11497
|
+
caption: str(args.flags.caption),
|
|
11498
|
+
category: str(args.flags.category),
|
|
11499
|
+
tags: typeof args.flags.tags === "string" ? args.flags.tags.split(",").map((t) => t.trim()).filter(Boolean) : void 0,
|
|
11500
|
+
remixable: !isTrue(args.flags["no-remix"]),
|
|
11501
|
+
visibility: isTrue(args.flags.unlisted) ? "unlisted" : "public"
|
|
11502
|
+
};
|
|
11503
|
+
}
|
|
11504
|
+
function reportPublishError(e) {
|
|
11505
|
+
if (e instanceof ApiError && e.code === "unauthorized") {
|
|
11506
|
+
console.error("Your token is no longer valid. Run `vibed login` again.");
|
|
11507
|
+
return 1;
|
|
11508
|
+
}
|
|
11509
|
+
console.error(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
11510
|
+
if (e instanceof ApiError && e.details) {
|
|
11511
|
+
const d = e.details;
|
|
11512
|
+
for (const err of d.errors ?? []) console.error(` \u2717 ${err.code}: ${err.message}`);
|
|
11513
|
+
if (!d.errors) console.error(` ${JSON.stringify(e.details)}`);
|
|
11514
|
+
}
|
|
11515
|
+
return 1;
|
|
11516
|
+
}
|
|
11517
|
+
async function cmdDraft(args) {
|
|
11392
11518
|
const json = isTrue(args.flags.json);
|
|
11393
11519
|
const dir2 = resolve2(args._[1] ?? ".");
|
|
11394
11520
|
const cfg = await loadConfig({ api: str(args.flags.api) });
|
|
@@ -11403,37 +11529,94 @@ async function cmdPublish(args) {
|
|
|
11403
11529
|
policy,
|
|
11404
11530
|
onLog: (m) => !json && console.error(m)
|
|
11405
11531
|
});
|
|
11532
|
+
if (!result.ok || !result.html) {
|
|
11533
|
+
out(json, formatReport(result), { ...result, html: void 0 });
|
|
11534
|
+
return 1;
|
|
11535
|
+
}
|
|
11536
|
+
if (!json) console.log(formatReport(result));
|
|
11537
|
+
try {
|
|
11538
|
+
const d = await importDraft({ apiBase: cfg.apiBase, token: cfg.token }, result.html);
|
|
11539
|
+
out(
|
|
11540
|
+
json,
|
|
11541
|
+
`
|
|
11542
|
+
\u2713 Preview (not published): ${d.previewUrl ?? "(no preview URL)"}
|
|
11543
|
+
Publish it with: vibed publish --draft-key ${d.draftArtifactKey} --title "\u2026" --category <Cat>`,
|
|
11544
|
+
{ ok: true, ...d }
|
|
11545
|
+
);
|
|
11546
|
+
return 0;
|
|
11547
|
+
} catch (e) {
|
|
11548
|
+
return reportPublishError(e);
|
|
11549
|
+
}
|
|
11550
|
+
}
|
|
11551
|
+
async function cmdPublish(args) {
|
|
11552
|
+
const json = isTrue(args.flags.json);
|
|
11553
|
+
const cfg = await loadConfig({ api: str(args.flags.api) });
|
|
11554
|
+
if (!cfg.token) {
|
|
11555
|
+
console.error("Not signed in. Run `vibed login` first.");
|
|
11556
|
+
return 1;
|
|
11557
|
+
}
|
|
11558
|
+
const client = { apiBase: cfg.apiBase, token: cfg.token };
|
|
11559
|
+
const meta = metaFromArgs(args);
|
|
11560
|
+
const draftKey = str(args.flags["draft-key"]);
|
|
11561
|
+
if (draftKey) {
|
|
11562
|
+
try {
|
|
11563
|
+
const res = await publishDraft(client, draftKey, meta);
|
|
11564
|
+
out(json, `
|
|
11565
|
+
\u2713 Published (${res.status}): ${res.url}`, res);
|
|
11566
|
+
return 0;
|
|
11567
|
+
} catch (e) {
|
|
11568
|
+
return reportPublishError(e);
|
|
11569
|
+
}
|
|
11570
|
+
}
|
|
11571
|
+
const dir2 = resolve2(args._[1] ?? ".");
|
|
11572
|
+
const policy = await fetchPolicy(cfg.apiBase);
|
|
11573
|
+
const result = await bundleProject(dir2, {
|
|
11574
|
+
entry: str(args.flags.entry),
|
|
11575
|
+
build: !isTrue(args.flags["no-build"]),
|
|
11576
|
+
policy,
|
|
11577
|
+
onLog: (m) => !json && console.error(m)
|
|
11578
|
+
});
|
|
11406
11579
|
if (!result.ok || !result.html) {
|
|
11407
11580
|
out(json, formatReport(result), { ...result, html: void 0 });
|
|
11408
11581
|
console.error("\nFix the blockers above, then run `vibed publish` again.");
|
|
11409
11582
|
return 1;
|
|
11410
11583
|
}
|
|
11411
11584
|
if (!json) console.log(formatReport(result));
|
|
11412
|
-
const meta = {
|
|
11413
|
-
title: str(args.flags.title),
|
|
11414
|
-
caption: str(args.flags.caption),
|
|
11415
|
-
category: str(args.flags.category),
|
|
11416
|
-
tags: typeof args.flags.tags === "string" ? args.flags.tags.split(",").map((t) => t.trim()).filter(Boolean) : void 0,
|
|
11417
|
-
remixable: !isTrue(args.flags["no-remix"]),
|
|
11418
|
-
visibility: isTrue(args.flags.unlisted) ? "unlisted" : "public"
|
|
11419
|
-
};
|
|
11420
11585
|
try {
|
|
11421
|
-
const res = await publishArtifact(
|
|
11586
|
+
const res = await publishArtifact(client, result.html, meta, dir2);
|
|
11422
11587
|
out(json, `
|
|
11423
11588
|
\u2713 Published (${res.status}): ${res.url}`, res);
|
|
11424
11589
|
return 0;
|
|
11425
11590
|
} catch (e) {
|
|
11426
|
-
|
|
11427
|
-
|
|
11591
|
+
return reportPublishError(e);
|
|
11592
|
+
}
|
|
11593
|
+
}
|
|
11594
|
+
async function cmdDelete(args) {
|
|
11595
|
+
const json = isTrue(args.flags.json);
|
|
11596
|
+
const target = args._[1];
|
|
11597
|
+
if (!target) {
|
|
11598
|
+
console.error("Usage: vibed delete <experience-id | https://\u2026/e/<id>>");
|
|
11599
|
+
return 1;
|
|
11600
|
+
}
|
|
11601
|
+
const cfg = await loadConfig({ api: str(args.flags.api) });
|
|
11602
|
+
if (!cfg.token) {
|
|
11603
|
+
console.error("Not signed in. Run `vibed login` first.");
|
|
11604
|
+
return 1;
|
|
11605
|
+
}
|
|
11606
|
+
try {
|
|
11607
|
+
const id = await deleteExperience({ apiBase: cfg.apiBase, token: cfg.token }, target);
|
|
11608
|
+
out(json, `\u2713 Deleted post ${id}.`, { ok: true, id });
|
|
11609
|
+
return 0;
|
|
11610
|
+
} catch (e) {
|
|
11611
|
+
if (e instanceof ApiError && (e.code === "forbidden" || e.status === 403)) {
|
|
11612
|
+
console.error("That post isn't yours to delete.");
|
|
11428
11613
|
return 1;
|
|
11429
11614
|
}
|
|
11430
|
-
|
|
11431
|
-
|
|
11432
|
-
|
|
11433
|
-
for (const err of d.errors ?? []) console.error(` \u2717 ${err.code}: ${err.message}`);
|
|
11434
|
-
if (!d.errors) console.error(` ${JSON.stringify(e.details)}`);
|
|
11615
|
+
if (e instanceof ApiError && (e.code === "not_found" || e.status === 404)) {
|
|
11616
|
+
console.error("No such post (or already deleted).");
|
|
11617
|
+
return 1;
|
|
11435
11618
|
}
|
|
11436
|
-
return
|
|
11619
|
+
return reportPublishError(e);
|
|
11437
11620
|
}
|
|
11438
11621
|
}
|
|
11439
11622
|
async function cmdLogin(args) {
|
|
@@ -11489,8 +11672,13 @@ async function main() {
|
|
|
11489
11672
|
return cmdCheck(args);
|
|
11490
11673
|
case "preview":
|
|
11491
11674
|
return cmdPreview(args);
|
|
11675
|
+
case "draft":
|
|
11676
|
+
return cmdDraft(args);
|
|
11492
11677
|
case "publish":
|
|
11493
11678
|
return cmdPublish(args);
|
|
11679
|
+
case "delete":
|
|
11680
|
+
case "rm":
|
|
11681
|
+
return cmdDelete(args);
|
|
11494
11682
|
case void 0:
|
|
11495
11683
|
case "help":
|
|
11496
11684
|
case "--help":
|
package/dist/vibed.cjs
CHANGED
|
@@ -1249,10 +1249,10 @@ var require_decode = __commonJS({
|
|
|
1249
1249
|
this.state = EntityDecoderState.NumericDecimal;
|
|
1250
1250
|
return this.stateNumericDecimal(str2, offset);
|
|
1251
1251
|
};
|
|
1252
|
-
EntityDecoder2.prototype.addToNumericResult = function(str2, start, end,
|
|
1252
|
+
EntityDecoder2.prototype.addToNumericResult = function(str2, start, end, base2) {
|
|
1253
1253
|
if (start !== end) {
|
|
1254
1254
|
var digitCount = end - start;
|
|
1255
|
-
this.result = this.result * Math.pow(
|
|
1255
|
+
this.result = this.result * Math.pow(base2, digitCount) + parseInt(str2.substr(start, digitCount), base2);
|
|
1256
1256
|
this.consumed += digitCount;
|
|
1257
1257
|
}
|
|
1258
1258
|
};
|
|
@@ -5983,7 +5983,7 @@ var import_node_url = require("node:url");
|
|
|
5983
5983
|
var import_node_os = require("node:os");
|
|
5984
5984
|
var import_node_path = require("node:path");
|
|
5985
5985
|
var import_promises = require("node:fs/promises");
|
|
5986
|
-
var DEFAULT_API = "https://
|
|
5986
|
+
var DEFAULT_API = "https://vibed.city";
|
|
5987
5987
|
var dir = () => (0, import_node_path.join)((0, import_node_os.homedir)(), ".vibed");
|
|
5988
5988
|
var file = () => (0, import_node_path.join)(dir(), "config.json");
|
|
5989
5989
|
async function readFileConfig() {
|
|
@@ -6074,7 +6074,19 @@ var config = {
|
|
|
6074
6074
|
// Light model that auto-tags the experience category. Falls back to a keyword
|
|
6075
6075
|
// heuristic if the call fails.
|
|
6076
6076
|
BUILDER_CLASSIFIER_MODEL: strEnv("BUILDER_CLASSIFIER_MODEL", "gemini-2.5-flash"),
|
|
6077
|
-
|
|
6077
|
+
// Speech-to-text model for the builder's voice input (docs/builder-architecture.md).
|
|
6078
|
+
// Gemini 2.5 Flash transcribes the recorded clip via GapGPT's OpenAI-compatible
|
|
6079
|
+
// chat API (an `input_audio` content part) — verified to accept the exact
|
|
6080
|
+
// formats browsers produce (Chrome webm/opus, Safari mp4). It's multilingual,
|
|
6081
|
+
// so Persian and English speech both work with no per-language config.
|
|
6082
|
+
BUILDER_TRANSCRIBE_MODEL: strEnv("BUILDER_TRANSCRIBE_MODEL", "gemini-2.5-flash"),
|
|
6083
|
+
// Upload cap for a voice clip. A minute of webm/opus is ~250KB; 8MB is ample
|
|
6084
|
+
// headroom while still bounding what one request can hand the model.
|
|
6085
|
+
BUILDER_TRANSCRIBE_MAX_BYTES: intEnv("BUILDER_TRANSCRIBE_MAX_BYTES", 8388608),
|
|
6086
|
+
// Output budget per model call. 8k proved too small in prod — a rich game is
|
|
6087
|
+
// ~7-8k tokens of file alone, and a truncated file used to slip through as a
|
|
6088
|
+
// text dump. 24576 was probed OK against every catalog model on GapGPT.
|
|
6089
|
+
BUILDER_MAX_TOKENS: intEnv("BUILDER_MAX_TOKENS", 24576),
|
|
6078
6090
|
BUILDER_MAX_MESSAGES: intEnv("BUILDER_MAX_MESSAGES", 30),
|
|
6079
6091
|
// Hard cap for a single build turn (across the model + one repair). The build
|
|
6080
6092
|
// runs detached from the request, so this — not the browser — is what stops a
|
|
@@ -9816,23 +9828,23 @@ var ZodEffects = class extends ZodType {
|
|
|
9816
9828
|
}
|
|
9817
9829
|
if (effect.type === "transform") {
|
|
9818
9830
|
if (ctx.common.async === false) {
|
|
9819
|
-
const
|
|
9831
|
+
const base2 = this._def.schema._parseSync({
|
|
9820
9832
|
data: ctx.data,
|
|
9821
9833
|
path: ctx.path,
|
|
9822
9834
|
parent: ctx
|
|
9823
9835
|
});
|
|
9824
|
-
if (!isValid(
|
|
9836
|
+
if (!isValid(base2))
|
|
9825
9837
|
return INVALID;
|
|
9826
|
-
const result = effect.transform(
|
|
9838
|
+
const result = effect.transform(base2.value, checkCtx);
|
|
9827
9839
|
if (result instanceof Promise) {
|
|
9828
9840
|
throw new Error(`Asynchronous transform encountered during synchronous parse operation. Use .parseAsync instead.`);
|
|
9829
9841
|
}
|
|
9830
9842
|
return { status: status.value, value: result };
|
|
9831
9843
|
} else {
|
|
9832
|
-
return this._def.schema._parseAsync({ data: ctx.data, path: ctx.path, parent: ctx }).then((
|
|
9833
|
-
if (!isValid(
|
|
9844
|
+
return this._def.schema._parseAsync({ data: ctx.data, path: ctx.path, parent: ctx }).then((base2) => {
|
|
9845
|
+
if (!isValid(base2))
|
|
9834
9846
|
return INVALID;
|
|
9835
|
-
return Promise.resolve(effect.transform(
|
|
9847
|
+
return Promise.resolve(effect.transform(base2.value, checkCtx)).then((result) => ({
|
|
9836
9848
|
status: status.value,
|
|
9837
9849
|
value: result
|
|
9838
9850
|
}));
|
|
@@ -10227,8 +10239,13 @@ var STORAGE = {
|
|
|
10227
10239
|
// 256 KB
|
|
10228
10240
|
/** Max records returnable in one query page. */
|
|
10229
10241
|
QUERY_MAX_LIMIT: intEnv("STORE_QUERY_MAX_LIMIT", 100),
|
|
10230
|
-
/**
|
|
10231
|
-
|
|
10242
|
+
/**
|
|
10243
|
+
* Platform kill-switch for anonymous shared writes. ON by default: the real
|
|
10244
|
+
* gate is per-post owner opt-in (`anon_writes` + writePolicy `anyone`), so a
|
|
10245
|
+
* post only accepts guest writes when its owner turns them on. Set the env to
|
|
10246
|
+
* `false` to disable anon writes platform-wide regardless of per-post config.
|
|
10247
|
+
*/
|
|
10248
|
+
ANON_WRITES_AVAILABLE: boolEnv("STORE_ANON_WRITES_AVAILABLE", true)
|
|
10232
10249
|
};
|
|
10233
10250
|
var COLLECTION_RE = /^[a-z0-9][a-z0-9:_-]{0,63}$/;
|
|
10234
10251
|
var collectionSchemaStore = external_exports.string().regex(COLLECTION_RE, "invalid collection name");
|
|
@@ -10338,13 +10355,31 @@ var commentSchema = external_exports.object({
|
|
|
10338
10355
|
parentId: external_exports.string().uuid().optional()
|
|
10339
10356
|
});
|
|
10340
10357
|
var eventSchema = external_exports.object({
|
|
10341
|
-
|
|
10358
|
+
// "record"/"screenshot" fire when a viewer captures the vibe (see the in-iframe
|
|
10359
|
+
// bridge capture flow, docs/architecture.md §5.4). Plain-text `viewEvents.kind`
|
|
10360
|
+
// column, so new kinds need no migration.
|
|
10361
|
+
kind: external_exports.enum(["impression", "play", "play_time", "share", "record", "screenshot"]),
|
|
10342
10362
|
ms: external_exports.number().int().nonnegative().optional()
|
|
10343
10363
|
});
|
|
10344
10364
|
var createBuildSchema = external_exports.object({
|
|
10345
10365
|
mode: external_exports.enum(["ai", "remix"]),
|
|
10346
10366
|
remixParentId: external_exports.string().uuid().optional()
|
|
10347
10367
|
});
|
|
10368
|
+
var turnAttachmentSchema = external_exports.object({
|
|
10369
|
+
kind: external_exports.enum(["image", "audio", "file", "sketch"]),
|
|
10370
|
+
key: external_exports.string().max(300).default(""),
|
|
10371
|
+
name: external_exports.string().max(200),
|
|
10372
|
+
// NUL bytes break Postgres jsonb (and mean binary-not-text) — strip defensively.
|
|
10373
|
+
text: external_exports.string().max(5e4).transform((t) => t.replace(/\u0000/g, "")).optional()
|
|
10374
|
+
});
|
|
10375
|
+
var createTurnRequestSchema = external_exports.object({
|
|
10376
|
+
clientTurnId: external_exports.string().uuid(),
|
|
10377
|
+
content: external_exports.string().max(4e3).default(""),
|
|
10378
|
+
attachments: external_exports.array(turnAttachmentSchema).max(6).optional(),
|
|
10379
|
+
model: external_exports.string().max(100).optional()
|
|
10380
|
+
}).refine((v) => v.content.trim().length > 0 || (v.attachments?.length ?? 0) > 0, {
|
|
10381
|
+
message: "Send a message or an attachment."
|
|
10382
|
+
});
|
|
10348
10383
|
var builderAttachmentSchema = external_exports.object({
|
|
10349
10384
|
kind: external_exports.enum(["image", "file", "audio"]),
|
|
10350
10385
|
name: external_exports.string().max(200),
|
|
@@ -10445,27 +10480,52 @@ var notifPrefsPatchSchema = external_exports.object({
|
|
|
10445
10480
|
groups: external_exports.record(external_exports.enum(NOTIF_PREF_GROUPS), external_exports.boolean()).optional()
|
|
10446
10481
|
});
|
|
10447
10482
|
|
|
10448
|
-
// ../shared/src/
|
|
10449
|
-
var
|
|
10450
|
-
|
|
10451
|
-
|
|
10452
|
-
//
|
|
10453
|
-
|
|
10454
|
-
|
|
10455
|
-
|
|
10456
|
-
|
|
10457
|
-
|
|
10458
|
-
|
|
10459
|
-
|
|
10460
|
-
|
|
10461
|
-
// ── Subscription (locked until billing is enabled) — premium ──────
|
|
10462
|
-
// Opus 4.8 is owner-only early access: locked for everyone, live for the owner.
|
|
10463
|
-
{ id: "claude-opus-4-8", label: "Claude Opus 4.8", tier: "subscription", note: "Top quality", vision: true, ownerOnly: true },
|
|
10464
|
-
{ id: "gpt-5.2-pro", label: "GPT-5.2 Pro", tier: "subscription", note: "Premium" },
|
|
10465
|
-
{ id: "gemini-3-pro-preview", label: "Gemini 3 Pro", tier: "subscription", note: "Premium", vision: true }
|
|
10483
|
+
// ../shared/src/builderEvents.ts
|
|
10484
|
+
var BUILD_EVENT_VERSION = 1;
|
|
10485
|
+
var BUILD_PHASES = [
|
|
10486
|
+
"queued",
|
|
10487
|
+
// accepted, waiting for a worker slot
|
|
10488
|
+
"thinking",
|
|
10489
|
+
// model connected, no visible output yet (keep-alives flowing)
|
|
10490
|
+
"writing",
|
|
10491
|
+
// streaming the vibe's code/prose
|
|
10492
|
+
"validating",
|
|
10493
|
+
// sandbox pass on the produced artifact
|
|
10494
|
+
"repairing"
|
|
10495
|
+
// one automatic fix round after a validation failure
|
|
10466
10496
|
];
|
|
10467
|
-
var
|
|
10468
|
-
var
|
|
10497
|
+
var base = { v: external_exports.literal(BUILD_EVENT_VERSION), turnId: external_exports.string() };
|
|
10498
|
+
var buildEventSchema = external_exports.discriminatedUnion("type", [
|
|
10499
|
+
/** Assistant prose delta (never contains the HTML code block). */
|
|
10500
|
+
external_exports.object({ ...base, type: external_exports.literal("token"), text: external_exports.string() }),
|
|
10501
|
+
/**
|
|
10502
|
+
* Honest heartbeat, ~every 2s while the turn runs: real elapsed ms, real
|
|
10503
|
+
* output-token count, and the executor's actual phase. The UI renders this
|
|
10504
|
+
* as a live counter — a number that moves because work is happening.
|
|
10505
|
+
*/
|
|
10506
|
+
external_exports.object({
|
|
10507
|
+
...base,
|
|
10508
|
+
type: external_exports.literal("progress"),
|
|
10509
|
+
phase: external_exports.enum(BUILD_PHASES),
|
|
10510
|
+
elapsedMs: external_exports.number(),
|
|
10511
|
+
tokensOut: external_exports.number()
|
|
10512
|
+
}),
|
|
10513
|
+
/** Friendly status chip ("index.html", "Fixing a couple of things…"). */
|
|
10514
|
+
external_exports.object({ ...base, type: external_exports.literal("status"), summary: external_exports.string() }),
|
|
10515
|
+
/** First-message category classification. */
|
|
10516
|
+
external_exports.object({ ...base, type: external_exports.literal("category"), category: external_exports.string() }),
|
|
10517
|
+
/** The validated draft landed. Preview loads it from the draft URL. */
|
|
10518
|
+
external_exports.object({ ...base, type: external_exports.literal("artifact"), artifactKey: external_exports.string() }),
|
|
10519
|
+
/** Terminal: failure with taxonomy code + human message. */
|
|
10520
|
+
external_exports.object({
|
|
10521
|
+
...base,
|
|
10522
|
+
type: external_exports.literal("error"),
|
|
10523
|
+
code: external_exports.string(),
|
|
10524
|
+
message: external_exports.string()
|
|
10525
|
+
}),
|
|
10526
|
+
/** Terminal: success. */
|
|
10527
|
+
external_exports.object({ ...base, type: external_exports.literal("done") })
|
|
10528
|
+
]);
|
|
10469
10529
|
|
|
10470
10530
|
// ../shared/src/i18n.ts
|
|
10471
10531
|
var en = {
|
|
@@ -10499,6 +10559,21 @@ var en = {
|
|
|
10499
10559
|
"share.title": "Share",
|
|
10500
10560
|
"share.copyLink": "Copy link",
|
|
10501
10561
|
"share.copied": "Link copied",
|
|
10562
|
+
"capture.record": "Record",
|
|
10563
|
+
"capture.screenshot": "Screenshot",
|
|
10564
|
+
"capture.recording": "Recording",
|
|
10565
|
+
"capture.stop": "Stop",
|
|
10566
|
+
"capture.preparing": "Preparing\u2026",
|
|
10567
|
+
"capture.processing": "Finishing\u2026",
|
|
10568
|
+
"capture.preview.videoTitle": "Your clip",
|
|
10569
|
+
"capture.preview.imageTitle": "Your screenshot",
|
|
10570
|
+
"capture.save": "Save",
|
|
10571
|
+
"capture.shareVia": "Share via\u2026",
|
|
10572
|
+
"capture.retry": "Try again",
|
|
10573
|
+
"capture.error": "Capture failed \u2014 try again.",
|
|
10574
|
+
"capture.allowShare": "Allow \u201CShare this tab\u201D to record \u2014 it captures just the vibe.",
|
|
10575
|
+
"capture.pickTab": "Choose \u201CThis Tab\u201D so it records only the vibe.",
|
|
10576
|
+
"capture.unsupported": `This ${ITEM.one} can't be captured yet. Try again in a moment.`,
|
|
10502
10577
|
"create.title": "Create",
|
|
10503
10578
|
"create.buildWithAI": "Build with AI",
|
|
10504
10579
|
"create.import": "Import",
|
|
@@ -10690,6 +10765,15 @@ function validate(html, opts) {
|
|
|
10690
10765
|
errors.push({ code: "NOT_HTML", message: "Content is not parseable as HTML." });
|
|
10691
10766
|
return { ok: false, errors, warnings };
|
|
10692
10767
|
}
|
|
10768
|
+
const scriptOpens = (html.match(/<script\b/gi) ?? []).length;
|
|
10769
|
+
const scriptCloses = (html.match(/<\/script\s*>/gi) ?? []).length;
|
|
10770
|
+
if (scriptOpens > scriptCloses) {
|
|
10771
|
+
errors.push({
|
|
10772
|
+
code: "TRUNCATED_DOC",
|
|
10773
|
+
message: "The file ends mid-<script> \u2014 it looks cut off. Regenerate or paste the complete file."
|
|
10774
|
+
});
|
|
10775
|
+
return { ok: false, errors, warnings };
|
|
10776
|
+
}
|
|
10693
10777
|
if (on("frames") && root.querySelector("iframe, frame, object, embed")) {
|
|
10694
10778
|
errors.push({
|
|
10695
10779
|
code: "FRAME_TAG",
|
|
@@ -11278,19 +11362,35 @@ function normalizeCategory(input) {
|
|
|
11278
11362
|
const match = CATEGORIES.find((c) => c.toLowerCase() === input.toLowerCase());
|
|
11279
11363
|
return match ?? "Other";
|
|
11280
11364
|
}
|
|
11281
|
-
async function
|
|
11282
|
-
const
|
|
11365
|
+
async function importDraft(client, html) {
|
|
11366
|
+
const r = await apiFetch(client, "POST", "/imports", { html });
|
|
11367
|
+
return { draftArtifactKey: r.draftArtifactKey, previewUrl: r.previewUrl };
|
|
11368
|
+
}
|
|
11369
|
+
async function publishDraft(client, draftArtifactKey, meta, html, projectDir) {
|
|
11283
11370
|
const exp = await apiFetch(client, "POST", "/experiences", {
|
|
11284
|
-
title: meta.title?.trim() || inferTitle(html, projectDir),
|
|
11371
|
+
title: meta.title?.trim() || (html ? inferTitle(html, projectDir ?? ".") : "Untitled"),
|
|
11285
11372
|
caption: meta.caption ?? "",
|
|
11286
11373
|
category: normalizeCategory(meta.category),
|
|
11287
11374
|
tags: meta.tags ?? [],
|
|
11288
11375
|
remixable: meta.remixable ?? true,
|
|
11289
11376
|
visibility: meta.visibility ?? "public",
|
|
11290
|
-
draftArtifactKey
|
|
11377
|
+
draftArtifactKey
|
|
11291
11378
|
});
|
|
11292
11379
|
return { id: exp.id, status: exp.status, url: `${client.apiBase}/e/${exp.id}` };
|
|
11293
11380
|
}
|
|
11381
|
+
async function publishArtifact(client, html, meta, projectDir) {
|
|
11382
|
+
const { draftArtifactKey } = await importDraft(client, html);
|
|
11383
|
+
return publishDraft(client, draftArtifactKey, meta, html, projectDir);
|
|
11384
|
+
}
|
|
11385
|
+
function experienceIdFrom(input) {
|
|
11386
|
+
const m = input.match(/\/e\/([0-9a-f-]{36})/i);
|
|
11387
|
+
return (m?.[1] ?? input.trim()).toLowerCase();
|
|
11388
|
+
}
|
|
11389
|
+
async function deleteExperience(client, idOrUrl) {
|
|
11390
|
+
const id = experienceIdFrom(idOrUrl);
|
|
11391
|
+
await apiFetch(client, "DELETE", `/experiences/${id}`);
|
|
11392
|
+
return id;
|
|
11393
|
+
}
|
|
11294
11394
|
|
|
11295
11395
|
// src/index.ts
|
|
11296
11396
|
function parseArgs(argv) {
|
|
@@ -11325,7 +11425,10 @@ Usage:
|
|
|
11325
11425
|
vibed login Authenticate this machine (opens the browser)
|
|
11326
11426
|
vibed check [path] Check if a project can be published (no network)
|
|
11327
11427
|
vibed preview [path] Bundle + open the artifact locally (no publish)
|
|
11428
|
+
vibed draft [path] Upload a private, hosted preview link (no publish)
|
|
11328
11429
|
vibed publish [path] Bundle the project and publish it to vibed
|
|
11430
|
+
vibed publish --draft-key <k> Promote a draft preview to a public post
|
|
11431
|
+
vibed delete <id|url> Delete one of your posts
|
|
11329
11432
|
vibed whoami Show the signed-in account
|
|
11330
11433
|
vibed logout Remove the saved token
|
|
11331
11434
|
|
|
@@ -11389,7 +11492,30 @@ Preview \u2192 ${file2}`);
|
|
|
11389
11492
|
}
|
|
11390
11493
|
return result.ok ? 0 : 1;
|
|
11391
11494
|
}
|
|
11392
|
-
|
|
11495
|
+
function metaFromArgs(args) {
|
|
11496
|
+
return {
|
|
11497
|
+
title: str(args.flags.title),
|
|
11498
|
+
caption: str(args.flags.caption),
|
|
11499
|
+
category: str(args.flags.category),
|
|
11500
|
+
tags: typeof args.flags.tags === "string" ? args.flags.tags.split(",").map((t) => t.trim()).filter(Boolean) : void 0,
|
|
11501
|
+
remixable: !isTrue(args.flags["no-remix"]),
|
|
11502
|
+
visibility: isTrue(args.flags.unlisted) ? "unlisted" : "public"
|
|
11503
|
+
};
|
|
11504
|
+
}
|
|
11505
|
+
function reportPublishError(e) {
|
|
11506
|
+
if (e instanceof ApiError && e.code === "unauthorized") {
|
|
11507
|
+
console.error("Your token is no longer valid. Run `vibed login` again.");
|
|
11508
|
+
return 1;
|
|
11509
|
+
}
|
|
11510
|
+
console.error(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
11511
|
+
if (e instanceof ApiError && e.details) {
|
|
11512
|
+
const d = e.details;
|
|
11513
|
+
for (const err of d.errors ?? []) console.error(` \u2717 ${err.code}: ${err.message}`);
|
|
11514
|
+
if (!d.errors) console.error(` ${JSON.stringify(e.details)}`);
|
|
11515
|
+
}
|
|
11516
|
+
return 1;
|
|
11517
|
+
}
|
|
11518
|
+
async function cmdDraft(args) {
|
|
11393
11519
|
const json = isTrue(args.flags.json);
|
|
11394
11520
|
const dir2 = (0, import_node_path4.resolve)(args._[1] ?? ".");
|
|
11395
11521
|
const cfg = await loadConfig({ api: str(args.flags.api) });
|
|
@@ -11404,37 +11530,94 @@ async function cmdPublish(args) {
|
|
|
11404
11530
|
policy,
|
|
11405
11531
|
onLog: (m) => !json && console.error(m)
|
|
11406
11532
|
});
|
|
11533
|
+
if (!result.ok || !result.html) {
|
|
11534
|
+
out(json, formatReport(result), { ...result, html: void 0 });
|
|
11535
|
+
return 1;
|
|
11536
|
+
}
|
|
11537
|
+
if (!json) console.log(formatReport(result));
|
|
11538
|
+
try {
|
|
11539
|
+
const d = await importDraft({ apiBase: cfg.apiBase, token: cfg.token }, result.html);
|
|
11540
|
+
out(
|
|
11541
|
+
json,
|
|
11542
|
+
`
|
|
11543
|
+
\u2713 Preview (not published): ${d.previewUrl ?? "(no preview URL)"}
|
|
11544
|
+
Publish it with: vibed publish --draft-key ${d.draftArtifactKey} --title "\u2026" --category <Cat>`,
|
|
11545
|
+
{ ok: true, ...d }
|
|
11546
|
+
);
|
|
11547
|
+
return 0;
|
|
11548
|
+
} catch (e) {
|
|
11549
|
+
return reportPublishError(e);
|
|
11550
|
+
}
|
|
11551
|
+
}
|
|
11552
|
+
async function cmdPublish(args) {
|
|
11553
|
+
const json = isTrue(args.flags.json);
|
|
11554
|
+
const cfg = await loadConfig({ api: str(args.flags.api) });
|
|
11555
|
+
if (!cfg.token) {
|
|
11556
|
+
console.error("Not signed in. Run `vibed login` first.");
|
|
11557
|
+
return 1;
|
|
11558
|
+
}
|
|
11559
|
+
const client = { apiBase: cfg.apiBase, token: cfg.token };
|
|
11560
|
+
const meta = metaFromArgs(args);
|
|
11561
|
+
const draftKey = str(args.flags["draft-key"]);
|
|
11562
|
+
if (draftKey) {
|
|
11563
|
+
try {
|
|
11564
|
+
const res = await publishDraft(client, draftKey, meta);
|
|
11565
|
+
out(json, `
|
|
11566
|
+
\u2713 Published (${res.status}): ${res.url}`, res);
|
|
11567
|
+
return 0;
|
|
11568
|
+
} catch (e) {
|
|
11569
|
+
return reportPublishError(e);
|
|
11570
|
+
}
|
|
11571
|
+
}
|
|
11572
|
+
const dir2 = (0, import_node_path4.resolve)(args._[1] ?? ".");
|
|
11573
|
+
const policy = await fetchPolicy(cfg.apiBase);
|
|
11574
|
+
const result = await bundleProject(dir2, {
|
|
11575
|
+
entry: str(args.flags.entry),
|
|
11576
|
+
build: !isTrue(args.flags["no-build"]),
|
|
11577
|
+
policy,
|
|
11578
|
+
onLog: (m) => !json && console.error(m)
|
|
11579
|
+
});
|
|
11407
11580
|
if (!result.ok || !result.html) {
|
|
11408
11581
|
out(json, formatReport(result), { ...result, html: void 0 });
|
|
11409
11582
|
console.error("\nFix the blockers above, then run `vibed publish` again.");
|
|
11410
11583
|
return 1;
|
|
11411
11584
|
}
|
|
11412
11585
|
if (!json) console.log(formatReport(result));
|
|
11413
|
-
const meta = {
|
|
11414
|
-
title: str(args.flags.title),
|
|
11415
|
-
caption: str(args.flags.caption),
|
|
11416
|
-
category: str(args.flags.category),
|
|
11417
|
-
tags: typeof args.flags.tags === "string" ? args.flags.tags.split(",").map((t) => t.trim()).filter(Boolean) : void 0,
|
|
11418
|
-
remixable: !isTrue(args.flags["no-remix"]),
|
|
11419
|
-
visibility: isTrue(args.flags.unlisted) ? "unlisted" : "public"
|
|
11420
|
-
};
|
|
11421
11586
|
try {
|
|
11422
|
-
const res = await publishArtifact(
|
|
11587
|
+
const res = await publishArtifact(client, result.html, meta, dir2);
|
|
11423
11588
|
out(json, `
|
|
11424
11589
|
\u2713 Published (${res.status}): ${res.url}`, res);
|
|
11425
11590
|
return 0;
|
|
11426
11591
|
} catch (e) {
|
|
11427
|
-
|
|
11428
|
-
|
|
11592
|
+
return reportPublishError(e);
|
|
11593
|
+
}
|
|
11594
|
+
}
|
|
11595
|
+
async function cmdDelete(args) {
|
|
11596
|
+
const json = isTrue(args.flags.json);
|
|
11597
|
+
const target = args._[1];
|
|
11598
|
+
if (!target) {
|
|
11599
|
+
console.error("Usage: vibed delete <experience-id | https://\u2026/e/<id>>");
|
|
11600
|
+
return 1;
|
|
11601
|
+
}
|
|
11602
|
+
const cfg = await loadConfig({ api: str(args.flags.api) });
|
|
11603
|
+
if (!cfg.token) {
|
|
11604
|
+
console.error("Not signed in. Run `vibed login` first.");
|
|
11605
|
+
return 1;
|
|
11606
|
+
}
|
|
11607
|
+
try {
|
|
11608
|
+
const id = await deleteExperience({ apiBase: cfg.apiBase, token: cfg.token }, target);
|
|
11609
|
+
out(json, `\u2713 Deleted post ${id}.`, { ok: true, id });
|
|
11610
|
+
return 0;
|
|
11611
|
+
} catch (e) {
|
|
11612
|
+
if (e instanceof ApiError && (e.code === "forbidden" || e.status === 403)) {
|
|
11613
|
+
console.error("That post isn't yours to delete.");
|
|
11429
11614
|
return 1;
|
|
11430
11615
|
}
|
|
11431
|
-
|
|
11432
|
-
|
|
11433
|
-
|
|
11434
|
-
for (const err of d.errors ?? []) console.error(` \u2717 ${err.code}: ${err.message}`);
|
|
11435
|
-
if (!d.errors) console.error(` ${JSON.stringify(e.details)}`);
|
|
11616
|
+
if (e instanceof ApiError && (e.code === "not_found" || e.status === 404)) {
|
|
11617
|
+
console.error("No such post (or already deleted).");
|
|
11618
|
+
return 1;
|
|
11436
11619
|
}
|
|
11437
|
-
return
|
|
11620
|
+
return reportPublishError(e);
|
|
11438
11621
|
}
|
|
11439
11622
|
}
|
|
11440
11623
|
async function cmdLogin(args) {
|
|
@@ -11490,8 +11673,13 @@ async function main() {
|
|
|
11490
11673
|
return cmdCheck(args);
|
|
11491
11674
|
case "preview":
|
|
11492
11675
|
return cmdPreview(args);
|
|
11676
|
+
case "draft":
|
|
11677
|
+
return cmdDraft(args);
|
|
11493
11678
|
case "publish":
|
|
11494
11679
|
return cmdPublish(args);
|
|
11680
|
+
case "delete":
|
|
11681
|
+
case "rm":
|
|
11682
|
+
return cmdDelete(args);
|
|
11495
11683
|
case void 0:
|
|
11496
11684
|
case "help":
|
|
11497
11685
|
case "--help":
|