@amirhosseinnateghi/vibed-cli 0.1.2 → 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/dist/index.js +604 -58
- package/dist/vibed.cjs +11703 -0
- package/package.json +2 -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
|
};
|
|
@@ -5973,7 +5973,10 @@ var require_dist = __commonJS({
|
|
|
5973
5973
|
});
|
|
5974
5974
|
|
|
5975
5975
|
// src/index.ts
|
|
5976
|
-
import { resolve as resolve2 } from "node:path";
|
|
5976
|
+
import { resolve as resolve2, join as join3 } from "node:path";
|
|
5977
|
+
import { writeFile as writeFile2 } from "node:fs/promises";
|
|
5978
|
+
import { tmpdir } from "node:os";
|
|
5979
|
+
import { pathToFileURL } from "node:url";
|
|
5977
5980
|
|
|
5978
5981
|
// src/config.ts
|
|
5979
5982
|
import { homedir } from "node:os";
|
|
@@ -6019,6 +6022,18 @@ import { dirname, extname, isAbsolute, join as join2, resolve } from "node:path"
|
|
|
6019
6022
|
// ../sandbox/src/index.ts
|
|
6020
6023
|
var import_node_html_parser = __toESM(require_dist(), 1);
|
|
6021
6024
|
|
|
6025
|
+
// ../shared/src/terms.ts
|
|
6026
|
+
var ITEM = {
|
|
6027
|
+
/** lowercase singular — "vibe" */
|
|
6028
|
+
one: "vibe",
|
|
6029
|
+
/** lowercase plural — "vibes" */
|
|
6030
|
+
many: "vibes",
|
|
6031
|
+
/** Capitalized singular — "Vibe" */
|
|
6032
|
+
One: "Vibe",
|
|
6033
|
+
/** Capitalized plural — "Vibes" */
|
|
6034
|
+
Many: "Vibes"
|
|
6035
|
+
};
|
|
6036
|
+
|
|
6022
6037
|
// ../shared/src/constants.ts
|
|
6023
6038
|
var CATEGORIES = [
|
|
6024
6039
|
"Game",
|
|
@@ -6031,10 +6046,13 @@ var CATEGORIES = [
|
|
|
6031
6046
|
"Other"
|
|
6032
6047
|
];
|
|
6033
6048
|
var VISIBILITY = ["public", "unlisted"];
|
|
6049
|
+
var COVER_MODES = ["image", "gif"];
|
|
6034
6050
|
var EXPERIENCE_SOURCE = ["builder", "import", "remix"];
|
|
6051
|
+
var PUSH_PROVIDERS = ["webpush", "fcm", "apns"];
|
|
6052
|
+
var DEVICE_PLATFORMS = ["web", "ios", "android"];
|
|
6035
6053
|
var config = {
|
|
6036
|
-
ARTIFACT_MAX_BYTES: intEnv("ARTIFACT_MAX_BYTES",
|
|
6037
|
-
//
|
|
6054
|
+
ARTIFACT_MAX_BYTES: intEnv("ARTIFACT_MAX_BYTES", 8388608),
|
|
6055
|
+
// 8 MB — headroom for an embedded audio track
|
|
6038
6056
|
ARTIFACT_ALLOWED_ORIGINS: strEnv(
|
|
6039
6057
|
"ARTIFACT_ALLOWED_ORIGINS",
|
|
6040
6058
|
"fonts.googleapis.com,fonts.gstatic.com,cdnjs.cloudflare.com,cdn.jsdelivr.net,unpkg.com"
|
|
@@ -6055,8 +6073,33 @@ var config = {
|
|
|
6055
6073
|
// Light model that auto-tags the experience category. Falls back to a keyword
|
|
6056
6074
|
// heuristic if the call fails.
|
|
6057
6075
|
BUILDER_CLASSIFIER_MODEL: strEnv("BUILDER_CLASSIFIER_MODEL", "gemini-2.5-flash"),
|
|
6058
|
-
|
|
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),
|
|
6059
6089
|
BUILDER_MAX_MESSAGES: intEnv("BUILDER_MAX_MESSAGES", 30),
|
|
6090
|
+
// Hard cap for a single build turn (across the model + one repair). The build
|
|
6091
|
+
// runs detached from the request, so this — not the browser — is what stops a
|
|
6092
|
+
// runaway/abandoned turn. The provider's own 90s per-stall timeout still applies.
|
|
6093
|
+
// Never cut a LIVE generation — these two guards only catch dead work:
|
|
6094
|
+
// stall = total silence (not even a keep-alive byte) long enough to mean the
|
|
6095
|
+
// connection is dead (TCP half-open never errors on its own); the turn cap is
|
|
6096
|
+
// a platform-cost safety ceiling for the whole turn incl. one repair pass.
|
|
6097
|
+
BUILDER_STALL_TIMEOUT_MS: intEnv("BUILDER_STALL_TIMEOUT_MS", 3e5),
|
|
6098
|
+
BUILDER_TURN_TIMEOUT_MS: intEnv("BUILDER_TURN_TIMEOUT_MS", 9e5),
|
|
6099
|
+
// Once a build has run this long, the UI nudges the user to go explore, and on
|
|
6100
|
+
// completion the server drops a notification (§7.1) — so a slow build never
|
|
6101
|
+
// traps them staring at a spinner.
|
|
6102
|
+
BUILDER_SLOW_MS: intEnv("BUILDER_SLOW_MS", 12e3),
|
|
6060
6103
|
DEFAULT_LOCALE: strEnv("DEFAULT_LOCALE", "en")
|
|
6061
6104
|
};
|
|
6062
6105
|
function strEnv(key, fallback) {
|
|
@@ -6079,6 +6122,31 @@ function boolEnv(key, fallback) {
|
|
|
6079
6122
|
return v === "true" || v === "1";
|
|
6080
6123
|
}
|
|
6081
6124
|
|
|
6125
|
+
// ../shared/src/notifications.ts
|
|
6126
|
+
var NOTIF_VERB = {
|
|
6127
|
+
like_milestone: `liked your ${ITEM.one}`,
|
|
6128
|
+
comment: `commented on your ${ITEM.one}`,
|
|
6129
|
+
reply: "replied to your comment",
|
|
6130
|
+
follow: "started following you",
|
|
6131
|
+
mention: "mentioned you",
|
|
6132
|
+
remix: `remixed your ${ITEM.one}`
|
|
6133
|
+
};
|
|
6134
|
+
var SYSTEM_NOTIF = {
|
|
6135
|
+
build_ready: { icon: "\u2728", text: "Your build is ready \u2014 tap to see it", tone: "info" },
|
|
6136
|
+
build_failed: { icon: "\u26A0\uFE0F", text: "A build didn't finish \u2014 tap to try again", tone: "error" },
|
|
6137
|
+
// Moderation reject (spec §9): the vibe never went live (or was pulled), so
|
|
6138
|
+
// the inbox row must carry everything — title + reason arrive in the payload.
|
|
6139
|
+
post_rejected: { icon: "\u{1F6AB}", text: `Your ${ITEM.one} didn't pass moderation`, tone: "error" }
|
|
6140
|
+
};
|
|
6141
|
+
var NOTIF_PREF_GROUPS = ["follow", "comments", "likes", "remixes", "builder"];
|
|
6142
|
+
var NOTIF_GROUP_LABEL = {
|
|
6143
|
+
follow: { title: "New followers", hint: "when someone follows you" },
|
|
6144
|
+
comments: { title: "Comments", hint: "comments, replies & mentions" },
|
|
6145
|
+
likes: { title: "Likes", hint: `when your ${ITEM.many} hit like milestones` },
|
|
6146
|
+
remixes: { title: "Remixes", hint: `when someone remixes your ${ITEM.one}` },
|
|
6147
|
+
builder: { title: "Builder", hint: "when a build finishes or fails" }
|
|
6148
|
+
};
|
|
6149
|
+
|
|
6082
6150
|
// ../shared/src/policy.ts
|
|
6083
6151
|
function boolEnv2(key, fallback) {
|
|
6084
6152
|
const v = typeof process !== "undefined" ? process.env[key] : void 0;
|
|
@@ -9759,23 +9827,23 @@ var ZodEffects = class extends ZodType {
|
|
|
9759
9827
|
}
|
|
9760
9828
|
if (effect.type === "transform") {
|
|
9761
9829
|
if (ctx.common.async === false) {
|
|
9762
|
-
const
|
|
9830
|
+
const base2 = this._def.schema._parseSync({
|
|
9763
9831
|
data: ctx.data,
|
|
9764
9832
|
path: ctx.path,
|
|
9765
9833
|
parent: ctx
|
|
9766
9834
|
});
|
|
9767
|
-
if (!isValid(
|
|
9835
|
+
if (!isValid(base2))
|
|
9768
9836
|
return INVALID;
|
|
9769
|
-
const result = effect.transform(
|
|
9837
|
+
const result = effect.transform(base2.value, checkCtx);
|
|
9770
9838
|
if (result instanceof Promise) {
|
|
9771
9839
|
throw new Error(`Asynchronous transform encountered during synchronous parse operation. Use .parseAsync instead.`);
|
|
9772
9840
|
}
|
|
9773
9841
|
return { status: status.value, value: result };
|
|
9774
9842
|
} else {
|
|
9775
|
-
return this._def.schema._parseAsync({ data: ctx.data, path: ctx.path, parent: ctx }).then((
|
|
9776
|
-
if (!isValid(
|
|
9843
|
+
return this._def.schema._parseAsync({ data: ctx.data, path: ctx.path, parent: ctx }).then((base2) => {
|
|
9844
|
+
if (!isValid(base2))
|
|
9777
9845
|
return INVALID;
|
|
9778
|
-
return Promise.resolve(effect.transform(
|
|
9846
|
+
return Promise.resolve(effect.transform(base2.value, checkCtx)).then((result) => ({
|
|
9779
9847
|
status: status.value,
|
|
9780
9848
|
value: result
|
|
9781
9849
|
}));
|
|
@@ -10151,10 +10219,107 @@ var coerce = {
|
|
|
10151
10219
|
};
|
|
10152
10220
|
var NEVER = INVALID;
|
|
10153
10221
|
|
|
10222
|
+
// ../shared/src/storage.ts
|
|
10223
|
+
var STORAGE = {
|
|
10224
|
+
/** Total shared bytes per post (values, serialized). */
|
|
10225
|
+
SHARED_MAX_BYTES: intEnv("STORE_SHARED_MAX_BYTES", 1048576),
|
|
10226
|
+
// 1 MB
|
|
10227
|
+
/** Total shared records (rows) per post. */
|
|
10228
|
+
SHARED_MAX_RECORDS: intEnv("STORE_SHARED_MAX_RECORDS", 5e3),
|
|
10229
|
+
/** Serialized size cap for a single value. */
|
|
10230
|
+
VALUE_MAX_BYTES: intEnv("STORE_VALUE_MAX_BYTES", 16384),
|
|
10231
|
+
// 16 KB
|
|
10232
|
+
/** Records a single author may hold in one post (anti-flood / anti-grief). */
|
|
10233
|
+
AUTHOR_MAX_RECORDS: intEnv("STORE_AUTHOR_MAX_RECORDS", 100),
|
|
10234
|
+
/** Distinct collections per post. */
|
|
10235
|
+
MAX_COLLECTIONS: intEnv("STORE_MAX_COLLECTIONS", 32),
|
|
10236
|
+
/** Local-tier budget per (post, identity) — enforced by the host broker. */
|
|
10237
|
+
LOCAL_MAX_BYTES: intEnv("STORE_LOCAL_MAX_BYTES", 262144),
|
|
10238
|
+
// 256 KB
|
|
10239
|
+
/** Max records returnable in one query page. */
|
|
10240
|
+
QUERY_MAX_LIMIT: intEnv("STORE_QUERY_MAX_LIMIT", 100),
|
|
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)
|
|
10248
|
+
};
|
|
10249
|
+
var COLLECTION_RE = /^[a-z0-9][a-z0-9:_-]{0,63}$/;
|
|
10250
|
+
var collectionSchemaStore = external_exports.string().regex(COLLECTION_RE, "invalid collection name");
|
|
10251
|
+
var STORE_KEY_RE = /^[a-zA-Z0-9][a-zA-Z0-9:._-]{0,127}$/;
|
|
10252
|
+
var RESERVED_STORE_KEYS = ["submit", "rank", "increment"];
|
|
10253
|
+
var storeKeySchema = external_exports.string().regex(STORE_KEY_RE, "invalid key").refine((k) => !RESERVED_STORE_KEYS.includes(k), {
|
|
10254
|
+
message: "reserved key"
|
|
10255
|
+
});
|
|
10256
|
+
var STORE_WRITE_POLICIES = ["anyone", "logged_in", "owner_only"];
|
|
10257
|
+
var STORE_SORTS = ["num", "-num", "created", "-created"];
|
|
10258
|
+
var storeValueSchema = external_exports.unknown().refine(
|
|
10259
|
+
(v) => {
|
|
10260
|
+
try {
|
|
10261
|
+
const s = JSON.stringify(v);
|
|
10262
|
+
return s !== void 0 && byteLength(s) <= STORAGE.VALUE_MAX_BYTES;
|
|
10263
|
+
} catch {
|
|
10264
|
+
return false;
|
|
10265
|
+
}
|
|
10266
|
+
},
|
|
10267
|
+
{ message: `value must be JSON-serializable and <= ${STORAGE.VALUE_MAX_BYTES} bytes` }
|
|
10268
|
+
);
|
|
10269
|
+
var storePutSchema = external_exports.object({
|
|
10270
|
+
value: storeValueSchema,
|
|
10271
|
+
/** Optimistic concurrency: fail with `conflict` unless current version matches. */
|
|
10272
|
+
ifVersion: external_exports.number().int().positive().optional()
|
|
10273
|
+
});
|
|
10274
|
+
var storeAppendSchema = external_exports.object({
|
|
10275
|
+
value: storeValueSchema,
|
|
10276
|
+
/** Optional numeric field to index (sorting/leaderboards). */
|
|
10277
|
+
num: external_exports.number().finite().optional()
|
|
10278
|
+
});
|
|
10279
|
+
var storeIncrementSchema = external_exports.object({
|
|
10280
|
+
delta: external_exports.number().int().min(-1e6).max(1e6).default(1)
|
|
10281
|
+
});
|
|
10282
|
+
var storeSubmitSchema = external_exports.object({
|
|
10283
|
+
/** The number that ranks this author (mapped into `num`). */
|
|
10284
|
+
score: external_exports.number().finite(),
|
|
10285
|
+
/** Conflict rule when the author already has a row. */
|
|
10286
|
+
keep: external_exports.enum(["max", "min", "last"]).default("max"),
|
|
10287
|
+
/** Small display payload stored alongside (same value-size gate). */
|
|
10288
|
+
meta: storeValueSchema.optional()
|
|
10289
|
+
});
|
|
10290
|
+
var storeQuerySchema = external_exports.object({
|
|
10291
|
+
sort: external_exports.enum(STORE_SORTS).default("-created"),
|
|
10292
|
+
limit: external_exports.coerce.number().int().min(1).max(STORAGE.QUERY_MAX_LIMIT).default(30),
|
|
10293
|
+
cursor: external_exports.string().max(200).optional(),
|
|
10294
|
+
/** Only the caller's records. */
|
|
10295
|
+
mine: external_exports.coerce.boolean().default(false)
|
|
10296
|
+
});
|
|
10297
|
+
var storeAdminSchema = external_exports.discriminatedUnion("action", [
|
|
10298
|
+
external_exports.object({ action: external_exports.literal("freeze") }),
|
|
10299
|
+
external_exports.object({ action: external_exports.literal("unfreeze") }),
|
|
10300
|
+
external_exports.object({ action: external_exports.literal("set_policy"), writePolicy: external_exports.enum(STORE_WRITE_POLICIES) }),
|
|
10301
|
+
external_exports.object({ action: external_exports.literal("set_anon_writes"), anonWrites: external_exports.boolean() }),
|
|
10302
|
+
external_exports.object({ action: external_exports.literal("reset"), collection: collectionSchemaStore.optional() })
|
|
10303
|
+
]);
|
|
10304
|
+
var storageOptInSchema = external_exports.object({
|
|
10305
|
+
local: external_exports.boolean().default(false),
|
|
10306
|
+
shared: external_exports.boolean().default(false)
|
|
10307
|
+
});
|
|
10308
|
+
function byteLength(s) {
|
|
10309
|
+
if (typeof TextEncoder !== "undefined") return new TextEncoder().encode(s).length;
|
|
10310
|
+
let n = 0;
|
|
10311
|
+
for (let i = 0; i < s.length; i++) {
|
|
10312
|
+
const c = s.codePointAt(i);
|
|
10313
|
+
n += c <= 127 ? 1 : c <= 2047 ? 2 : c <= 65535 ? 3 : (i++, 4);
|
|
10314
|
+
}
|
|
10315
|
+
return n;
|
|
10316
|
+
}
|
|
10317
|
+
|
|
10154
10318
|
// ../shared/src/schemas.ts
|
|
10155
10319
|
var handleSchema = external_exports.string().regex(/^[a-z0-9_.]{3,24}$/, "3\u201324 chars: a\u2013z, 0\u20139, _ or .");
|
|
10156
10320
|
var categorySchema = external_exports.enum(CATEGORIES);
|
|
10157
10321
|
var visibilitySchema = external_exports.enum(VISIBILITY);
|
|
10322
|
+
var coverModeSchema = external_exports.enum(COVER_MODES);
|
|
10158
10323
|
var sourceSchema = external_exports.enum(EXPERIENCE_SOURCE);
|
|
10159
10324
|
var tagsSchema = external_exports.array(external_exports.string().min(1).max(40)).max(10).transform(
|
|
10160
10325
|
(tags) => Array.from(
|
|
@@ -10168,8 +10333,12 @@ var publishSchema = external_exports.object({
|
|
|
10168
10333
|
tags: tagsSchema,
|
|
10169
10334
|
remixable: external_exports.boolean().default(true),
|
|
10170
10335
|
visibility: visibilitySchema.default("public"),
|
|
10336
|
+
coverMode: coverModeSchema.default("gif"),
|
|
10171
10337
|
draftArtifactKey: external_exports.string().min(1),
|
|
10172
|
-
buildSessionId: external_exports.string().uuid().optional()
|
|
10338
|
+
buildSessionId: external_exports.string().uuid().optional(),
|
|
10339
|
+
/** Storage opt-in override; when omitted, publish auto-detects from the source
|
|
10340
|
+
* (docs/post-storage.md §9 — default off, enabled only when actually used). */
|
|
10341
|
+
storage: storageOptInSchema.optional()
|
|
10173
10342
|
});
|
|
10174
10343
|
var patchExperienceSchema = external_exports.object({
|
|
10175
10344
|
title: external_exports.string().min(1).max(120).optional(),
|
|
@@ -10177,25 +10346,44 @@ var patchExperienceSchema = external_exports.object({
|
|
|
10177
10346
|
tags: tagsSchema.optional(),
|
|
10178
10347
|
category: categorySchema.optional(),
|
|
10179
10348
|
remixable: external_exports.boolean().optional(),
|
|
10180
|
-
visibility: visibilitySchema.optional()
|
|
10349
|
+
visibility: visibilitySchema.optional(),
|
|
10350
|
+
coverMode: coverModeSchema.optional()
|
|
10181
10351
|
});
|
|
10182
10352
|
var commentSchema = external_exports.object({
|
|
10183
10353
|
body: external_exports.string().min(1).max(500),
|
|
10184
10354
|
parentId: external_exports.string().uuid().optional()
|
|
10185
10355
|
});
|
|
10186
10356
|
var eventSchema = external_exports.object({
|
|
10187
|
-
|
|
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"]),
|
|
10188
10361
|
ms: external_exports.number().int().nonnegative().optional()
|
|
10189
10362
|
});
|
|
10190
10363
|
var createBuildSchema = external_exports.object({
|
|
10191
10364
|
mode: external_exports.enum(["ai", "remix"]),
|
|
10192
10365
|
remixParentId: external_exports.string().uuid().optional()
|
|
10193
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
|
+
});
|
|
10194
10382
|
var builderAttachmentSchema = external_exports.object({
|
|
10195
|
-
kind: external_exports.enum(["image", "file"]),
|
|
10383
|
+
kind: external_exports.enum(["image", "file", "audio"]),
|
|
10196
10384
|
name: external_exports.string().max(200),
|
|
10197
|
-
|
|
10198
|
-
|
|
10385
|
+
// base64 data URL for image/audio (~8.7MB raw fits, covering a short song).
|
|
10386
|
+
dataUrl: external_exports.string().max(12e6).optional(),
|
|
10199
10387
|
text: external_exports.string().max(5e4).optional()
|
|
10200
10388
|
});
|
|
10201
10389
|
var builderMessageSchema = external_exports.object({
|
|
@@ -10227,10 +10415,21 @@ var reportSchema = external_exports.object({
|
|
|
10227
10415
|
var patchMeSchema = external_exports.object({
|
|
10228
10416
|
displayName: external_exports.string().min(1).max(60).optional(),
|
|
10229
10417
|
bio: external_exports.string().max(300).optional(),
|
|
10230
|
-
avatar
|
|
10418
|
+
// A stored image URL, or "" to remove the photo and fall back to the color avatar.
|
|
10419
|
+
avatar: external_exports.string().url().or(external_exports.literal("")).optional(),
|
|
10420
|
+
// Fallback avatar colour (used when there's no photo). Hex like #6C4CF1.
|
|
10421
|
+
avatarBg: external_exports.string().regex(/^#[0-9a-fA-F]{6}$/, "hex colour, e.g. #6C4CF1").optional(),
|
|
10231
10422
|
handle: handleSchema.optional()
|
|
10232
10423
|
// once
|
|
10233
10424
|
});
|
|
10425
|
+
var waitlistSchema = external_exports.object({
|
|
10426
|
+
email: external_exports.string().trim().email().max(200),
|
|
10427
|
+
note: external_exports.string().trim().max(500).optional()
|
|
10428
|
+
});
|
|
10429
|
+
var waitlistReviewSchema = external_exports.object({
|
|
10430
|
+
requestId: external_exports.string().uuid(),
|
|
10431
|
+
action: external_exports.enum(["approve", "reject"])
|
|
10432
|
+
});
|
|
10234
10433
|
var aiConnectionSchema = external_exports.object({
|
|
10235
10434
|
provider: external_exports.enum(AI_PROVIDERS),
|
|
10236
10435
|
apiKey: external_exports.string().trim().min(12).max(300)
|
|
@@ -10244,28 +10443,225 @@ var feedQuerySchema = external_exports.object({
|
|
|
10244
10443
|
var searchQuerySchema = external_exports.object({
|
|
10245
10444
|
q: external_exports.string().default(""),
|
|
10246
10445
|
tab: external_exports.enum(["experiences", "tags", "creators"]).default("experiences"),
|
|
10247
|
-
|
|
10446
|
+
// Query params arrive as strings; z.coerce.boolean() is just Boolean(x), so
|
|
10447
|
+
// "false"/"0" would (wrongly) become true and the filter could never be turned
|
|
10448
|
+
// off. Parse the string explicitly: only "true"/"1" (or a real boolean) is true.
|
|
10449
|
+
remixableOnly: external_exports.union([external_exports.boolean(), external_exports.string()]).optional().transform((v) => v === true || v === "true" || v === "1"),
|
|
10248
10450
|
cursor: external_exports.string().optional(),
|
|
10249
10451
|
limit: external_exports.coerce.number().int().min(1).max(30).default(15)
|
|
10250
10452
|
});
|
|
10453
|
+
var registerPushDeviceSchema = external_exports.object({
|
|
10454
|
+
provider: external_exports.enum(PUSH_PROVIDERS),
|
|
10455
|
+
token: external_exports.string().trim().min(16).max(4096),
|
|
10456
|
+
keys: external_exports.record(external_exports.string().max(512)).default({}),
|
|
10457
|
+
platform: external_exports.enum(DEVICE_PLATFORMS).default("web"),
|
|
10458
|
+
userAgent: external_exports.string().trim().max(400).default("")
|
|
10459
|
+
}).superRefine((v, ctx) => {
|
|
10460
|
+
if (v.provider === "webpush" && (!v.keys.p256dh || !v.keys.auth)) {
|
|
10461
|
+
ctx.addIssue({
|
|
10462
|
+
code: external_exports.ZodIssueCode.custom,
|
|
10463
|
+
path: ["keys"],
|
|
10464
|
+
message: "webpush requires keys.p256dh and keys.auth"
|
|
10465
|
+
});
|
|
10466
|
+
}
|
|
10467
|
+
});
|
|
10468
|
+
var unregisterPushDeviceSchema = external_exports.object({
|
|
10469
|
+
token: external_exports.string().trim().min(16).max(4096)
|
|
10470
|
+
});
|
|
10471
|
+
var notificationsQuerySchema = external_exports.object({
|
|
10472
|
+
filter: external_exports.enum(["all", "mentions", "remixes"]).default("all"),
|
|
10473
|
+
// Keyset cursor: the `createdAt` ISO timestamp of the last item seen.
|
|
10474
|
+
cursor: external_exports.string().datetime({ offset: true }).optional(),
|
|
10475
|
+
limit: external_exports.coerce.number().int().min(1).max(80).default(30)
|
|
10476
|
+
});
|
|
10477
|
+
var notifPrefsPatchSchema = external_exports.object({
|
|
10478
|
+
enabled: external_exports.boolean().optional(),
|
|
10479
|
+
groups: external_exports.record(external_exports.enum(NOTIF_PREF_GROUPS), external_exports.boolean()).optional()
|
|
10480
|
+
});
|
|
10251
10481
|
|
|
10252
|
-
// ../shared/src/
|
|
10253
|
-
var
|
|
10254
|
-
|
|
10255
|
-
|
|
10256
|
-
|
|
10257
|
-
|
|
10258
|
-
|
|
10259
|
-
|
|
10260
|
-
|
|
10261
|
-
|
|
10262
|
-
|
|
10263
|
-
|
|
10264
|
-
|
|
10265
|
-
{ id: "gpt-5.2-pro", label: "GPT-5.2 Pro", tier: "subscription", note: "Premium" },
|
|
10266
|
-
{ 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
|
|
10267
10495
|
];
|
|
10268
|
-
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
|
+
]);
|
|
10528
|
+
|
|
10529
|
+
// ../shared/src/i18n.ts
|
|
10530
|
+
var en = {
|
|
10531
|
+
"nav.home": "Home",
|
|
10532
|
+
"nav.search": "Search",
|
|
10533
|
+
"nav.create": "Create",
|
|
10534
|
+
"nav.notifications": "Notifications",
|
|
10535
|
+
"nav.profile": "Profile",
|
|
10536
|
+
"feed.explore": "Explore",
|
|
10537
|
+
"feed.following": "Following",
|
|
10538
|
+
"feed.tapToInteract": "Tap to interact",
|
|
10539
|
+
"feed.loading": `Loading ${ITEM.one}`,
|
|
10540
|
+
"feed.exit": "Exit",
|
|
10541
|
+
"feed.remixedFrom": "Remixed from @{handle}",
|
|
10542
|
+
"feed.remixedFromRemoved": `Remixed from a removed ${ITEM.one}`,
|
|
10543
|
+
"feed.newVersionOf": "New version of {title}",
|
|
10544
|
+
"feed.end.title": "You're all caught up",
|
|
10545
|
+
"feed.end.body": `No new ${ITEM.many} right now \u2014 remix one you loved, or check back soon.`,
|
|
10546
|
+
"feed.end.remix": "Find something to remix",
|
|
10547
|
+
"feed.end.top": "Back to top",
|
|
10548
|
+
"rail.like": "Like",
|
|
10549
|
+
"rail.comment": "Comment",
|
|
10550
|
+
"rail.remix": "Remix",
|
|
10551
|
+
"rail.save": "Save",
|
|
10552
|
+
"rail.share": "Share",
|
|
10553
|
+
"comments.title": "Comments",
|
|
10554
|
+
"comments.placeholder": "Add a comment\u2026",
|
|
10555
|
+
"comments.reply": "Reply",
|
|
10556
|
+
"comments.empty": "No comments yet \u2014 be the first.",
|
|
10557
|
+
"comments.post": "Post",
|
|
10558
|
+
"share.title": "Share",
|
|
10559
|
+
"share.copyLink": "Copy link",
|
|
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.`,
|
|
10576
|
+
"create.title": "Create",
|
|
10577
|
+
"create.buildWithAI": "Build with AI",
|
|
10578
|
+
"create.import": "Import",
|
|
10579
|
+
"create.remix": "Remix something",
|
|
10580
|
+
"create.prompt.placeholder": "Describe what you want to build\u2026",
|
|
10581
|
+
"create.prompt.starters": "Start from a template",
|
|
10582
|
+
"builder.publish": "Publish",
|
|
10583
|
+
"builder.thinking": "Building\u2026",
|
|
10584
|
+
"builder.updated": "Updated \u2713",
|
|
10585
|
+
"builder.placeholder": "Ask for a change\u2026",
|
|
10586
|
+
"builder.mockNotice": "Preview builder \u2014 AI generation is mocked for now.",
|
|
10587
|
+
"import.title": "Import",
|
|
10588
|
+
"import.paste": "Paste HTML, or drop an .html file",
|
|
10589
|
+
"import.validate": "Validate",
|
|
10590
|
+
"import.warnings": "Warnings",
|
|
10591
|
+
"publish.title": "Publish",
|
|
10592
|
+
"publish.postTitle": "Title",
|
|
10593
|
+
"publish.caption": "Caption",
|
|
10594
|
+
"publish.category": "Category",
|
|
10595
|
+
"publish.remixable": "Allow remixes",
|
|
10596
|
+
"publish.public": "Public",
|
|
10597
|
+
"publish.unlisted": "Unlisted",
|
|
10598
|
+
"publish.cta": "Publish",
|
|
10599
|
+
"publish.remixCredit": "Remixing @{handle} \u2014 {title}",
|
|
10600
|
+
"publish.toast.published": "Published! \u{1F389}",
|
|
10601
|
+
"publish.toast.remix": "Remix published \u{1F501} @{handle} was credited",
|
|
10602
|
+
"remix.confirmTitle": "Remixing @{handle}'s {title}",
|
|
10603
|
+
"remix.confirmBody": `They'll be credited on your ${ITEM.one}. Attribution can't be removed.`,
|
|
10604
|
+
"remix.start": "Start remixing",
|
|
10605
|
+
"remix.galleryTitle": "Remixes",
|
|
10606
|
+
"remix.sortPopular": "Popular",
|
|
10607
|
+
"remix.sortRecent": "Recent",
|
|
10608
|
+
"remix.count": "{n} remixes",
|
|
10609
|
+
"remix.identicalWarn": "This looks identical to the original \u2014 publish anyway?",
|
|
10610
|
+
"search.placeholder": `Search ${ITEM.many}, tags, creators`,
|
|
10611
|
+
"search.tab.experiences": ITEM.Many,
|
|
10612
|
+
"search.tab.tags": "Hashtags",
|
|
10613
|
+
"search.tab.creators": "Creators",
|
|
10614
|
+
"search.remixableOnly": "Remixable only",
|
|
10615
|
+
"search.trending": "Trending",
|
|
10616
|
+
"profile.tab.experiences": ITEM.Many,
|
|
10617
|
+
"profile.tab.remixes": "Remixes",
|
|
10618
|
+
"profile.tab.saved": "Saved",
|
|
10619
|
+
"profile.followers": "followers",
|
|
10620
|
+
"profile.following": "following",
|
|
10621
|
+
"profile.likes": "likes",
|
|
10622
|
+
"profile.follow": "Follow",
|
|
10623
|
+
"profile.unfollow": "Following",
|
|
10624
|
+
"profile.edit": "Edit profile",
|
|
10625
|
+
"notifs.all": "All",
|
|
10626
|
+
"notifs.mentions": "Mentions",
|
|
10627
|
+
"notifs.remixes": "Remixes",
|
|
10628
|
+
"notifs.remixedYou": `@{handle} remixed your ${ITEM.one}`,
|
|
10629
|
+
"notifs.liked": `@{handle} liked your ${ITEM.one}`,
|
|
10630
|
+
"notifs.commented": "@{handle} commented",
|
|
10631
|
+
"notifs.followed": "@{handle} started following you",
|
|
10632
|
+
"notifs.empty": "Nothing here yet.",
|
|
10633
|
+
"collections.title": "Saved",
|
|
10634
|
+
"collections.new": "New collection",
|
|
10635
|
+
"analytics.title": "Analytics",
|
|
10636
|
+
"analytics.views": "Views",
|
|
10637
|
+
"analytics.plays": "Plays",
|
|
10638
|
+
"analytics.playRate": "Play rate",
|
|
10639
|
+
"analytics.avgTime": "Avg. time",
|
|
10640
|
+
"analytics.likes": "Likes",
|
|
10641
|
+
"analytics.comments": "Comments",
|
|
10642
|
+
"analytics.shares": "Shares",
|
|
10643
|
+
"analytics.saves": "Saves",
|
|
10644
|
+
"analytics.remixes": "Remixes",
|
|
10645
|
+
"post.options": `${ITEM.One} options`,
|
|
10646
|
+
"post.analytics": "View analytics",
|
|
10647
|
+
"post.edit": "Edit caption",
|
|
10648
|
+
"post.delete": `Delete ${ITEM.one}`,
|
|
10649
|
+
"post.deleteConfirm": `Delete this ${ITEM.one}? This can't be undone.`,
|
|
10650
|
+
"post.captionPlaceholder": "Write a caption\u2026",
|
|
10651
|
+
"auth.signIn": "Sign in",
|
|
10652
|
+
"auth.google": "Continue with Google",
|
|
10653
|
+
"auth.github": "Continue with GitHub",
|
|
10654
|
+
"auth.dev": "Dev login",
|
|
10655
|
+
"auth.needAccount": "Sign in to like, comment, and remix.",
|
|
10656
|
+
"common.cancel": "Cancel",
|
|
10657
|
+
"common.save": "Save",
|
|
10658
|
+
"common.back": "Back",
|
|
10659
|
+
"common.retry": "Retry",
|
|
10660
|
+
"common.loading": "Loading\u2026",
|
|
10661
|
+
"common.report": "Report",
|
|
10662
|
+
"common.delete": "Delete",
|
|
10663
|
+
"common.views": "{n} views"
|
|
10664
|
+
};
|
|
10269
10665
|
|
|
10270
10666
|
// ../shared/src/tokens.ts
|
|
10271
10667
|
var colors = {
|
|
@@ -10368,6 +10764,15 @@ function validate(html, opts) {
|
|
|
10368
10764
|
errors.push({ code: "NOT_HTML", message: "Content is not parseable as HTML." });
|
|
10369
10765
|
return { ok: false, errors, warnings };
|
|
10370
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
|
+
}
|
|
10371
10776
|
if (on("frames") && root.querySelector("iframe, frame, object, embed")) {
|
|
10372
10777
|
errors.push({
|
|
10373
10778
|
code: "FRAME_TAG",
|
|
@@ -10452,6 +10857,12 @@ function validate(html, opts) {
|
|
|
10452
10857
|
}
|
|
10453
10858
|
}
|
|
10454
10859
|
}
|
|
10860
|
+
if (/(localStorage|sessionStorage|indexedDB|document\s*\.\s*cookie)/.test(html)) {
|
|
10861
|
+
warnings.push({
|
|
10862
|
+
code: "BROWSER_STORAGE",
|
|
10863
|
+
message: "Uses browser storage (localStorage/sessionStorage/indexedDB/cookies), which throws inside the vibed sandbox. Port persistence to window.vibed.storage (local per-viewer, shared collective)."
|
|
10864
|
+
});
|
|
10865
|
+
}
|
|
10455
10866
|
const lower = html.toLowerCase();
|
|
10456
10867
|
const hasCredField = root.querySelector('input[type="email"], input[type="text"], input[type="tel"]') != null && /(password|passcode|otp|verify|sign\s*in|log\s*in|account)/i.test(html);
|
|
10457
10868
|
if (hasCredField) {
|
|
@@ -10950,19 +11361,35 @@ function normalizeCategory(input) {
|
|
|
10950
11361
|
const match = CATEGORIES.find((c) => c.toLowerCase() === input.toLowerCase());
|
|
10951
11362
|
return match ?? "Other";
|
|
10952
11363
|
}
|
|
10953
|
-
async function
|
|
10954
|
-
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) {
|
|
10955
11369
|
const exp = await apiFetch(client, "POST", "/experiences", {
|
|
10956
|
-
title: meta.title?.trim() || inferTitle(html, projectDir),
|
|
11370
|
+
title: meta.title?.trim() || (html ? inferTitle(html, projectDir ?? ".") : "Untitled"),
|
|
10957
11371
|
caption: meta.caption ?? "",
|
|
10958
11372
|
category: normalizeCategory(meta.category),
|
|
10959
11373
|
tags: meta.tags ?? [],
|
|
10960
11374
|
remixable: meta.remixable ?? true,
|
|
10961
11375
|
visibility: meta.visibility ?? "public",
|
|
10962
|
-
draftArtifactKey
|
|
11376
|
+
draftArtifactKey
|
|
10963
11377
|
});
|
|
10964
11378
|
return { id: exp.id, status: exp.status, url: `${client.apiBase}/e/${exp.id}` };
|
|
10965
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
|
+
}
|
|
10966
11393
|
|
|
10967
11394
|
// src/index.ts
|
|
10968
11395
|
function parseArgs(argv) {
|
|
@@ -10996,7 +11423,11 @@ var USAGE = `vibed \u2014 make it vibed
|
|
|
10996
11423
|
Usage:
|
|
10997
11424
|
vibed login Authenticate this machine (opens the browser)
|
|
10998
11425
|
vibed check [path] Check if a project can be published (no network)
|
|
11426
|
+
vibed preview [path] Bundle + open the artifact locally (no publish)
|
|
11427
|
+
vibed draft [path] Upload a private, hosted preview link (no publish)
|
|
10999
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
|
|
11000
11431
|
vibed whoami Show the signed-in account
|
|
11001
11432
|
vibed logout Remove the saved token
|
|
11002
11433
|
|
|
@@ -11032,7 +11463,58 @@ async function cmdCheck(args) {
|
|
|
11032
11463
|
});
|
|
11033
11464
|
return result.ok ? 0 : 1;
|
|
11034
11465
|
}
|
|
11035
|
-
async function
|
|
11466
|
+
async function cmdPreview(args) {
|
|
11467
|
+
const json = isTrue(args.flags.json);
|
|
11468
|
+
const dir2 = resolve2(args._[1] ?? ".");
|
|
11469
|
+
const cfg = await loadConfig({ api: str(args.flags.api) });
|
|
11470
|
+
const policy = isTrue(args.flags["local-policy"]) ? void 0 : await fetchPolicy(cfg.apiBase);
|
|
11471
|
+
const result = await bundleProject(dir2, {
|
|
11472
|
+
entry: str(args.flags.entry),
|
|
11473
|
+
build: !isTrue(args.flags["no-build"]),
|
|
11474
|
+
policy,
|
|
11475
|
+
onLog: (m) => !json && console.error(m)
|
|
11476
|
+
});
|
|
11477
|
+
if (!json) console.log(formatReport(result));
|
|
11478
|
+
if (!result.html) {
|
|
11479
|
+
if (json) out(json, "", { ok: false, fatal: result.fatal });
|
|
11480
|
+
return 1;
|
|
11481
|
+
}
|
|
11482
|
+
const file2 = join3(tmpdir(), `vibed-preview-${result.sizeBytes ?? 0}.html`);
|
|
11483
|
+
await writeFile2(file2, result.html, "utf8");
|
|
11484
|
+
const url = pathToFileURL(file2).href;
|
|
11485
|
+
if (json) {
|
|
11486
|
+
out(json, "", { ok: result.ok, previewFile: file2, previewUrl: url });
|
|
11487
|
+
} else {
|
|
11488
|
+
console.log(`
|
|
11489
|
+
Preview \u2192 ${file2}`);
|
|
11490
|
+
openBrowser(url);
|
|
11491
|
+
}
|
|
11492
|
+
return result.ok ? 0 : 1;
|
|
11493
|
+
}
|
|
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) {
|
|
11036
11518
|
const json = isTrue(args.flags.json);
|
|
11037
11519
|
const dir2 = resolve2(args._[1] ?? ".");
|
|
11038
11520
|
const cfg = await loadConfig({ api: str(args.flags.api) });
|
|
@@ -11047,37 +11529,94 @@ async function cmdPublish(args) {
|
|
|
11047
11529
|
policy,
|
|
11048
11530
|
onLog: (m) => !json && console.error(m)
|
|
11049
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
|
+
});
|
|
11050
11579
|
if (!result.ok || !result.html) {
|
|
11051
11580
|
out(json, formatReport(result), { ...result, html: void 0 });
|
|
11052
11581
|
console.error("\nFix the blockers above, then run `vibed publish` again.");
|
|
11053
11582
|
return 1;
|
|
11054
11583
|
}
|
|
11055
11584
|
if (!json) console.log(formatReport(result));
|
|
11056
|
-
const meta = {
|
|
11057
|
-
title: str(args.flags.title),
|
|
11058
|
-
caption: str(args.flags.caption),
|
|
11059
|
-
category: str(args.flags.category),
|
|
11060
|
-
tags: typeof args.flags.tags === "string" ? args.flags.tags.split(",").map((t) => t.trim()).filter(Boolean) : void 0,
|
|
11061
|
-
remixable: !isTrue(args.flags["no-remix"]),
|
|
11062
|
-
visibility: isTrue(args.flags.unlisted) ? "unlisted" : "public"
|
|
11063
|
-
};
|
|
11064
11585
|
try {
|
|
11065
|
-
const res = await publishArtifact(
|
|
11586
|
+
const res = await publishArtifact(client, result.html, meta, dir2);
|
|
11066
11587
|
out(json, `
|
|
11067
11588
|
\u2713 Published (${res.status}): ${res.url}`, res);
|
|
11068
11589
|
return 0;
|
|
11069
11590
|
} catch (e) {
|
|
11070
|
-
|
|
11071
|
-
|
|
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.");
|
|
11072
11613
|
return 1;
|
|
11073
11614
|
}
|
|
11074
|
-
|
|
11075
|
-
|
|
11076
|
-
|
|
11077
|
-
for (const err of d.errors ?? []) console.error(` \u2717 ${err.code}: ${err.message}`);
|
|
11078
|
-
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;
|
|
11079
11618
|
}
|
|
11080
|
-
return
|
|
11619
|
+
return reportPublishError(e);
|
|
11081
11620
|
}
|
|
11082
11621
|
}
|
|
11083
11622
|
async function cmdLogin(args) {
|
|
@@ -11131,8 +11670,15 @@ async function main() {
|
|
|
11131
11670
|
return cmdWhoami(args);
|
|
11132
11671
|
case "check":
|
|
11133
11672
|
return cmdCheck(args);
|
|
11673
|
+
case "preview":
|
|
11674
|
+
return cmdPreview(args);
|
|
11675
|
+
case "draft":
|
|
11676
|
+
return cmdDraft(args);
|
|
11134
11677
|
case "publish":
|
|
11135
11678
|
return cmdPublish(args);
|
|
11679
|
+
case "delete":
|
|
11680
|
+
case "rm":
|
|
11681
|
+
return cmdDelete(args);
|
|
11136
11682
|
case void 0:
|
|
11137
11683
|
case "help":
|
|
11138
11684
|
case "--help":
|