@amirhosseinnateghi/vibed-mcp 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 +536 -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
|
};
|
|
@@ -5986,6 +5986,18 @@ import { dirname, extname, isAbsolute, join, resolve } from "node:path";
|
|
|
5986
5986
|
// ../sandbox/src/index.ts
|
|
5987
5987
|
var import_node_html_parser = __toESM(require_dist(), 1);
|
|
5988
5988
|
|
|
5989
|
+
// ../shared/src/terms.ts
|
|
5990
|
+
var ITEM = {
|
|
5991
|
+
/** lowercase singular — "vibe" */
|
|
5992
|
+
one: "vibe",
|
|
5993
|
+
/** lowercase plural — "vibes" */
|
|
5994
|
+
many: "vibes",
|
|
5995
|
+
/** Capitalized singular — "Vibe" */
|
|
5996
|
+
One: "Vibe",
|
|
5997
|
+
/** Capitalized plural — "Vibes" */
|
|
5998
|
+
Many: "Vibes"
|
|
5999
|
+
};
|
|
6000
|
+
|
|
5989
6001
|
// ../shared/src/constants.ts
|
|
5990
6002
|
var CATEGORIES = [
|
|
5991
6003
|
"Game",
|
|
@@ -5998,10 +6010,13 @@ var CATEGORIES = [
|
|
|
5998
6010
|
"Other"
|
|
5999
6011
|
];
|
|
6000
6012
|
var VISIBILITY = ["public", "unlisted"];
|
|
6013
|
+
var COVER_MODES = ["image", "gif"];
|
|
6001
6014
|
var EXPERIENCE_SOURCE = ["builder", "import", "remix"];
|
|
6015
|
+
var PUSH_PROVIDERS = ["webpush", "fcm", "apns"];
|
|
6016
|
+
var DEVICE_PLATFORMS = ["web", "ios", "android"];
|
|
6002
6017
|
var config = {
|
|
6003
|
-
ARTIFACT_MAX_BYTES: intEnv("ARTIFACT_MAX_BYTES",
|
|
6004
|
-
//
|
|
6018
|
+
ARTIFACT_MAX_BYTES: intEnv("ARTIFACT_MAX_BYTES", 8388608),
|
|
6019
|
+
// 8 MB — headroom for an embedded audio track
|
|
6005
6020
|
ARTIFACT_ALLOWED_ORIGINS: strEnv(
|
|
6006
6021
|
"ARTIFACT_ALLOWED_ORIGINS",
|
|
6007
6022
|
"fonts.googleapis.com,fonts.gstatic.com,cdnjs.cloudflare.com,cdn.jsdelivr.net,unpkg.com"
|
|
@@ -6022,8 +6037,33 @@ var config = {
|
|
|
6022
6037
|
// Light model that auto-tags the experience category. Falls back to a keyword
|
|
6023
6038
|
// heuristic if the call fails.
|
|
6024
6039
|
BUILDER_CLASSIFIER_MODEL: strEnv("BUILDER_CLASSIFIER_MODEL", "gemini-2.5-flash"),
|
|
6025
|
-
|
|
6040
|
+
// Speech-to-text model for the builder's voice input (docs/builder-architecture.md).
|
|
6041
|
+
// Gemini 2.5 Flash transcribes the recorded clip via GapGPT's OpenAI-compatible
|
|
6042
|
+
// chat API (an `input_audio` content part) — verified to accept the exact
|
|
6043
|
+
// formats browsers produce (Chrome webm/opus, Safari mp4). It's multilingual,
|
|
6044
|
+
// so Persian and English speech both work with no per-language config.
|
|
6045
|
+
BUILDER_TRANSCRIBE_MODEL: strEnv("BUILDER_TRANSCRIBE_MODEL", "gemini-2.5-flash"),
|
|
6046
|
+
// Upload cap for a voice clip. A minute of webm/opus is ~250KB; 8MB is ample
|
|
6047
|
+
// headroom while still bounding what one request can hand the model.
|
|
6048
|
+
BUILDER_TRANSCRIBE_MAX_BYTES: intEnv("BUILDER_TRANSCRIBE_MAX_BYTES", 8388608),
|
|
6049
|
+
// Output budget per model call. 8k proved too small in prod — a rich game is
|
|
6050
|
+
// ~7-8k tokens of file alone, and a truncated file used to slip through as a
|
|
6051
|
+
// text dump. 24576 was probed OK against every catalog model on GapGPT.
|
|
6052
|
+
BUILDER_MAX_TOKENS: intEnv("BUILDER_MAX_TOKENS", 24576),
|
|
6026
6053
|
BUILDER_MAX_MESSAGES: intEnv("BUILDER_MAX_MESSAGES", 30),
|
|
6054
|
+
// Hard cap for a single build turn (across the model + one repair). The build
|
|
6055
|
+
// runs detached from the request, so this — not the browser — is what stops a
|
|
6056
|
+
// runaway/abandoned turn. The provider's own 90s per-stall timeout still applies.
|
|
6057
|
+
// Never cut a LIVE generation — these two guards only catch dead work:
|
|
6058
|
+
// stall = total silence (not even a keep-alive byte) long enough to mean the
|
|
6059
|
+
// connection is dead (TCP half-open never errors on its own); the turn cap is
|
|
6060
|
+
// a platform-cost safety ceiling for the whole turn incl. one repair pass.
|
|
6061
|
+
BUILDER_STALL_TIMEOUT_MS: intEnv("BUILDER_STALL_TIMEOUT_MS", 3e5),
|
|
6062
|
+
BUILDER_TURN_TIMEOUT_MS: intEnv("BUILDER_TURN_TIMEOUT_MS", 9e5),
|
|
6063
|
+
// Once a build has run this long, the UI nudges the user to go explore, and on
|
|
6064
|
+
// completion the server drops a notification (§7.1) — so a slow build never
|
|
6065
|
+
// traps them staring at a spinner.
|
|
6066
|
+
BUILDER_SLOW_MS: intEnv("BUILDER_SLOW_MS", 12e3),
|
|
6027
6067
|
DEFAULT_LOCALE: strEnv("DEFAULT_LOCALE", "en")
|
|
6028
6068
|
};
|
|
6029
6069
|
function strEnv(key, fallback) {
|
|
@@ -6046,6 +6086,31 @@ function boolEnv(key, fallback) {
|
|
|
6046
6086
|
return v === "true" || v === "1";
|
|
6047
6087
|
}
|
|
6048
6088
|
|
|
6089
|
+
// ../shared/src/notifications.ts
|
|
6090
|
+
var NOTIF_VERB = {
|
|
6091
|
+
like_milestone: `liked your ${ITEM.one}`,
|
|
6092
|
+
comment: `commented on your ${ITEM.one}`,
|
|
6093
|
+
reply: "replied to your comment",
|
|
6094
|
+
follow: "started following you",
|
|
6095
|
+
mention: "mentioned you",
|
|
6096
|
+
remix: `remixed your ${ITEM.one}`
|
|
6097
|
+
};
|
|
6098
|
+
var SYSTEM_NOTIF = {
|
|
6099
|
+
build_ready: { icon: "\u2728", text: "Your build is ready \u2014 tap to see it", tone: "info" },
|
|
6100
|
+
build_failed: { icon: "\u26A0\uFE0F", text: "A build didn't finish \u2014 tap to try again", tone: "error" },
|
|
6101
|
+
// Moderation reject (spec §9): the vibe never went live (or was pulled), so
|
|
6102
|
+
// the inbox row must carry everything — title + reason arrive in the payload.
|
|
6103
|
+
post_rejected: { icon: "\u{1F6AB}", text: `Your ${ITEM.one} didn't pass moderation`, tone: "error" }
|
|
6104
|
+
};
|
|
6105
|
+
var NOTIF_PREF_GROUPS = ["follow", "comments", "likes", "remixes", "builder"];
|
|
6106
|
+
var NOTIF_GROUP_LABEL = {
|
|
6107
|
+
follow: { title: "New followers", hint: "when someone follows you" },
|
|
6108
|
+
comments: { title: "Comments", hint: "comments, replies & mentions" },
|
|
6109
|
+
likes: { title: "Likes", hint: `when your ${ITEM.many} hit like milestones` },
|
|
6110
|
+
remixes: { title: "Remixes", hint: `when someone remixes your ${ITEM.one}` },
|
|
6111
|
+
builder: { title: "Builder", hint: "when a build finishes or fails" }
|
|
6112
|
+
};
|
|
6113
|
+
|
|
6049
6114
|
// ../shared/src/policy.ts
|
|
6050
6115
|
function boolEnv2(key, fallback) {
|
|
6051
6116
|
const v = typeof process !== "undefined" ? process.env[key] : void 0;
|
|
@@ -9726,23 +9791,23 @@ var ZodEffects = class extends ZodType {
|
|
|
9726
9791
|
}
|
|
9727
9792
|
if (effect.type === "transform") {
|
|
9728
9793
|
if (ctx.common.async === false) {
|
|
9729
|
-
const
|
|
9794
|
+
const base2 = this._def.schema._parseSync({
|
|
9730
9795
|
data: ctx.data,
|
|
9731
9796
|
path: ctx.path,
|
|
9732
9797
|
parent: ctx
|
|
9733
9798
|
});
|
|
9734
|
-
if (!isValid(
|
|
9799
|
+
if (!isValid(base2))
|
|
9735
9800
|
return INVALID;
|
|
9736
|
-
const result = effect.transform(
|
|
9801
|
+
const result = effect.transform(base2.value, checkCtx);
|
|
9737
9802
|
if (result instanceof Promise) {
|
|
9738
9803
|
throw new Error(`Asynchronous transform encountered during synchronous parse operation. Use .parseAsync instead.`);
|
|
9739
9804
|
}
|
|
9740
9805
|
return { status: status.value, value: result };
|
|
9741
9806
|
} else {
|
|
9742
|
-
return this._def.schema._parseAsync({ data: ctx.data, path: ctx.path, parent: ctx }).then((
|
|
9743
|
-
if (!isValid(
|
|
9807
|
+
return this._def.schema._parseAsync({ data: ctx.data, path: ctx.path, parent: ctx }).then((base2) => {
|
|
9808
|
+
if (!isValid(base2))
|
|
9744
9809
|
return INVALID;
|
|
9745
|
-
return Promise.resolve(effect.transform(
|
|
9810
|
+
return Promise.resolve(effect.transform(base2.value, checkCtx)).then((result) => ({
|
|
9746
9811
|
status: status.value,
|
|
9747
9812
|
value: result
|
|
9748
9813
|
}));
|
|
@@ -10118,10 +10183,107 @@ var coerce = {
|
|
|
10118
10183
|
};
|
|
10119
10184
|
var NEVER = INVALID;
|
|
10120
10185
|
|
|
10186
|
+
// ../shared/src/storage.ts
|
|
10187
|
+
var STORAGE = {
|
|
10188
|
+
/** Total shared bytes per post (values, serialized). */
|
|
10189
|
+
SHARED_MAX_BYTES: intEnv("STORE_SHARED_MAX_BYTES", 1048576),
|
|
10190
|
+
// 1 MB
|
|
10191
|
+
/** Total shared records (rows) per post. */
|
|
10192
|
+
SHARED_MAX_RECORDS: intEnv("STORE_SHARED_MAX_RECORDS", 5e3),
|
|
10193
|
+
/** Serialized size cap for a single value. */
|
|
10194
|
+
VALUE_MAX_BYTES: intEnv("STORE_VALUE_MAX_BYTES", 16384),
|
|
10195
|
+
// 16 KB
|
|
10196
|
+
/** Records a single author may hold in one post (anti-flood / anti-grief). */
|
|
10197
|
+
AUTHOR_MAX_RECORDS: intEnv("STORE_AUTHOR_MAX_RECORDS", 100),
|
|
10198
|
+
/** Distinct collections per post. */
|
|
10199
|
+
MAX_COLLECTIONS: intEnv("STORE_MAX_COLLECTIONS", 32),
|
|
10200
|
+
/** Local-tier budget per (post, identity) — enforced by the host broker. */
|
|
10201
|
+
LOCAL_MAX_BYTES: intEnv("STORE_LOCAL_MAX_BYTES", 262144),
|
|
10202
|
+
// 256 KB
|
|
10203
|
+
/** Max records returnable in one query page. */
|
|
10204
|
+
QUERY_MAX_LIMIT: intEnv("STORE_QUERY_MAX_LIMIT", 100),
|
|
10205
|
+
/**
|
|
10206
|
+
* Platform kill-switch for anonymous shared writes. ON by default: the real
|
|
10207
|
+
* gate is per-post owner opt-in (`anon_writes` + writePolicy `anyone`), so a
|
|
10208
|
+
* post only accepts guest writes when its owner turns them on. Set the env to
|
|
10209
|
+
* `false` to disable anon writes platform-wide regardless of per-post config.
|
|
10210
|
+
*/
|
|
10211
|
+
ANON_WRITES_AVAILABLE: boolEnv("STORE_ANON_WRITES_AVAILABLE", true)
|
|
10212
|
+
};
|
|
10213
|
+
var COLLECTION_RE = /^[a-z0-9][a-z0-9:_-]{0,63}$/;
|
|
10214
|
+
var collectionSchemaStore = external_exports.string().regex(COLLECTION_RE, "invalid collection name");
|
|
10215
|
+
var STORE_KEY_RE = /^[a-zA-Z0-9][a-zA-Z0-9:._-]{0,127}$/;
|
|
10216
|
+
var RESERVED_STORE_KEYS = ["submit", "rank", "increment"];
|
|
10217
|
+
var storeKeySchema = external_exports.string().regex(STORE_KEY_RE, "invalid key").refine((k) => !RESERVED_STORE_KEYS.includes(k), {
|
|
10218
|
+
message: "reserved key"
|
|
10219
|
+
});
|
|
10220
|
+
var STORE_WRITE_POLICIES = ["anyone", "logged_in", "owner_only"];
|
|
10221
|
+
var STORE_SORTS = ["num", "-num", "created", "-created"];
|
|
10222
|
+
var storeValueSchema = external_exports.unknown().refine(
|
|
10223
|
+
(v) => {
|
|
10224
|
+
try {
|
|
10225
|
+
const s = JSON.stringify(v);
|
|
10226
|
+
return s !== void 0 && byteLength(s) <= STORAGE.VALUE_MAX_BYTES;
|
|
10227
|
+
} catch {
|
|
10228
|
+
return false;
|
|
10229
|
+
}
|
|
10230
|
+
},
|
|
10231
|
+
{ message: `value must be JSON-serializable and <= ${STORAGE.VALUE_MAX_BYTES} bytes` }
|
|
10232
|
+
);
|
|
10233
|
+
var storePutSchema = external_exports.object({
|
|
10234
|
+
value: storeValueSchema,
|
|
10235
|
+
/** Optimistic concurrency: fail with `conflict` unless current version matches. */
|
|
10236
|
+
ifVersion: external_exports.number().int().positive().optional()
|
|
10237
|
+
});
|
|
10238
|
+
var storeAppendSchema = external_exports.object({
|
|
10239
|
+
value: storeValueSchema,
|
|
10240
|
+
/** Optional numeric field to index (sorting/leaderboards). */
|
|
10241
|
+
num: external_exports.number().finite().optional()
|
|
10242
|
+
});
|
|
10243
|
+
var storeIncrementSchema = external_exports.object({
|
|
10244
|
+
delta: external_exports.number().int().min(-1e6).max(1e6).default(1)
|
|
10245
|
+
});
|
|
10246
|
+
var storeSubmitSchema = external_exports.object({
|
|
10247
|
+
/** The number that ranks this author (mapped into `num`). */
|
|
10248
|
+
score: external_exports.number().finite(),
|
|
10249
|
+
/** Conflict rule when the author already has a row. */
|
|
10250
|
+
keep: external_exports.enum(["max", "min", "last"]).default("max"),
|
|
10251
|
+
/** Small display payload stored alongside (same value-size gate). */
|
|
10252
|
+
meta: storeValueSchema.optional()
|
|
10253
|
+
});
|
|
10254
|
+
var storeQuerySchema = external_exports.object({
|
|
10255
|
+
sort: external_exports.enum(STORE_SORTS).default("-created"),
|
|
10256
|
+
limit: external_exports.coerce.number().int().min(1).max(STORAGE.QUERY_MAX_LIMIT).default(30),
|
|
10257
|
+
cursor: external_exports.string().max(200).optional(),
|
|
10258
|
+
/** Only the caller's records. */
|
|
10259
|
+
mine: external_exports.coerce.boolean().default(false)
|
|
10260
|
+
});
|
|
10261
|
+
var storeAdminSchema = external_exports.discriminatedUnion("action", [
|
|
10262
|
+
external_exports.object({ action: external_exports.literal("freeze") }),
|
|
10263
|
+
external_exports.object({ action: external_exports.literal("unfreeze") }),
|
|
10264
|
+
external_exports.object({ action: external_exports.literal("set_policy"), writePolicy: external_exports.enum(STORE_WRITE_POLICIES) }),
|
|
10265
|
+
external_exports.object({ action: external_exports.literal("set_anon_writes"), anonWrites: external_exports.boolean() }),
|
|
10266
|
+
external_exports.object({ action: external_exports.literal("reset"), collection: collectionSchemaStore.optional() })
|
|
10267
|
+
]);
|
|
10268
|
+
var storageOptInSchema = external_exports.object({
|
|
10269
|
+
local: external_exports.boolean().default(false),
|
|
10270
|
+
shared: external_exports.boolean().default(false)
|
|
10271
|
+
});
|
|
10272
|
+
function byteLength(s) {
|
|
10273
|
+
if (typeof TextEncoder !== "undefined") return new TextEncoder().encode(s).length;
|
|
10274
|
+
let n = 0;
|
|
10275
|
+
for (let i = 0; i < s.length; i++) {
|
|
10276
|
+
const c = s.codePointAt(i);
|
|
10277
|
+
n += c <= 127 ? 1 : c <= 2047 ? 2 : c <= 65535 ? 3 : (i++, 4);
|
|
10278
|
+
}
|
|
10279
|
+
return n;
|
|
10280
|
+
}
|
|
10281
|
+
|
|
10121
10282
|
// ../shared/src/schemas.ts
|
|
10122
10283
|
var handleSchema = external_exports.string().regex(/^[a-z0-9_.]{3,24}$/, "3\u201324 chars: a\u2013z, 0\u20139, _ or .");
|
|
10123
10284
|
var categorySchema = external_exports.enum(CATEGORIES);
|
|
10124
10285
|
var visibilitySchema = external_exports.enum(VISIBILITY);
|
|
10286
|
+
var coverModeSchema = external_exports.enum(COVER_MODES);
|
|
10125
10287
|
var sourceSchema = external_exports.enum(EXPERIENCE_SOURCE);
|
|
10126
10288
|
var tagsSchema = external_exports.array(external_exports.string().min(1).max(40)).max(10).transform(
|
|
10127
10289
|
(tags) => Array.from(
|
|
@@ -10135,8 +10297,12 @@ var publishSchema = external_exports.object({
|
|
|
10135
10297
|
tags: tagsSchema,
|
|
10136
10298
|
remixable: external_exports.boolean().default(true),
|
|
10137
10299
|
visibility: visibilitySchema.default("public"),
|
|
10300
|
+
coverMode: coverModeSchema.default("gif"),
|
|
10138
10301
|
draftArtifactKey: external_exports.string().min(1),
|
|
10139
|
-
buildSessionId: external_exports.string().uuid().optional()
|
|
10302
|
+
buildSessionId: external_exports.string().uuid().optional(),
|
|
10303
|
+
/** Storage opt-in override; when omitted, publish auto-detects from the source
|
|
10304
|
+
* (docs/post-storage.md §9 — default off, enabled only when actually used). */
|
|
10305
|
+
storage: storageOptInSchema.optional()
|
|
10140
10306
|
});
|
|
10141
10307
|
var patchExperienceSchema = external_exports.object({
|
|
10142
10308
|
title: external_exports.string().min(1).max(120).optional(),
|
|
@@ -10144,25 +10310,44 @@ var patchExperienceSchema = external_exports.object({
|
|
|
10144
10310
|
tags: tagsSchema.optional(),
|
|
10145
10311
|
category: categorySchema.optional(),
|
|
10146
10312
|
remixable: external_exports.boolean().optional(),
|
|
10147
|
-
visibility: visibilitySchema.optional()
|
|
10313
|
+
visibility: visibilitySchema.optional(),
|
|
10314
|
+
coverMode: coverModeSchema.optional()
|
|
10148
10315
|
});
|
|
10149
10316
|
var commentSchema = external_exports.object({
|
|
10150
10317
|
body: external_exports.string().min(1).max(500),
|
|
10151
10318
|
parentId: external_exports.string().uuid().optional()
|
|
10152
10319
|
});
|
|
10153
10320
|
var eventSchema = external_exports.object({
|
|
10154
|
-
|
|
10321
|
+
// "record"/"screenshot" fire when a viewer captures the vibe (see the in-iframe
|
|
10322
|
+
// bridge capture flow, docs/architecture.md §5.4). Plain-text `viewEvents.kind`
|
|
10323
|
+
// column, so new kinds need no migration.
|
|
10324
|
+
kind: external_exports.enum(["impression", "play", "play_time", "share", "record", "screenshot"]),
|
|
10155
10325
|
ms: external_exports.number().int().nonnegative().optional()
|
|
10156
10326
|
});
|
|
10157
10327
|
var createBuildSchema = external_exports.object({
|
|
10158
10328
|
mode: external_exports.enum(["ai", "remix"]),
|
|
10159
10329
|
remixParentId: external_exports.string().uuid().optional()
|
|
10160
10330
|
});
|
|
10331
|
+
var turnAttachmentSchema = external_exports.object({
|
|
10332
|
+
kind: external_exports.enum(["image", "audio", "file", "sketch"]),
|
|
10333
|
+
key: external_exports.string().max(300).default(""),
|
|
10334
|
+
name: external_exports.string().max(200),
|
|
10335
|
+
// NUL bytes break Postgres jsonb (and mean binary-not-text) — strip defensively.
|
|
10336
|
+
text: external_exports.string().max(5e4).transform((t) => t.replace(/\u0000/g, "")).optional()
|
|
10337
|
+
});
|
|
10338
|
+
var createTurnRequestSchema = external_exports.object({
|
|
10339
|
+
clientTurnId: external_exports.string().uuid(),
|
|
10340
|
+
content: external_exports.string().max(4e3).default(""),
|
|
10341
|
+
attachments: external_exports.array(turnAttachmentSchema).max(6).optional(),
|
|
10342
|
+
model: external_exports.string().max(100).optional()
|
|
10343
|
+
}).refine((v) => v.content.trim().length > 0 || (v.attachments?.length ?? 0) > 0, {
|
|
10344
|
+
message: "Send a message or an attachment."
|
|
10345
|
+
});
|
|
10161
10346
|
var builderAttachmentSchema = external_exports.object({
|
|
10162
|
-
kind: external_exports.enum(["image", "file"]),
|
|
10347
|
+
kind: external_exports.enum(["image", "file", "audio"]),
|
|
10163
10348
|
name: external_exports.string().max(200),
|
|
10164
|
-
|
|
10165
|
-
|
|
10349
|
+
// base64 data URL for image/audio (~8.7MB raw fits, covering a short song).
|
|
10350
|
+
dataUrl: external_exports.string().max(12e6).optional(),
|
|
10166
10351
|
text: external_exports.string().max(5e4).optional()
|
|
10167
10352
|
});
|
|
10168
10353
|
var builderMessageSchema = external_exports.object({
|
|
@@ -10194,10 +10379,21 @@ var reportSchema = external_exports.object({
|
|
|
10194
10379
|
var patchMeSchema = external_exports.object({
|
|
10195
10380
|
displayName: external_exports.string().min(1).max(60).optional(),
|
|
10196
10381
|
bio: external_exports.string().max(300).optional(),
|
|
10197
|
-
avatar
|
|
10382
|
+
// A stored image URL, or "" to remove the photo and fall back to the color avatar.
|
|
10383
|
+
avatar: external_exports.string().url().or(external_exports.literal("")).optional(),
|
|
10384
|
+
// Fallback avatar colour (used when there's no photo). Hex like #6C4CF1.
|
|
10385
|
+
avatarBg: external_exports.string().regex(/^#[0-9a-fA-F]{6}$/, "hex colour, e.g. #6C4CF1").optional(),
|
|
10198
10386
|
handle: handleSchema.optional()
|
|
10199
10387
|
// once
|
|
10200
10388
|
});
|
|
10389
|
+
var waitlistSchema = external_exports.object({
|
|
10390
|
+
email: external_exports.string().trim().email().max(200),
|
|
10391
|
+
note: external_exports.string().trim().max(500).optional()
|
|
10392
|
+
});
|
|
10393
|
+
var waitlistReviewSchema = external_exports.object({
|
|
10394
|
+
requestId: external_exports.string().uuid(),
|
|
10395
|
+
action: external_exports.enum(["approve", "reject"])
|
|
10396
|
+
});
|
|
10201
10397
|
var aiConnectionSchema = external_exports.object({
|
|
10202
10398
|
provider: external_exports.enum(AI_PROVIDERS),
|
|
10203
10399
|
apiKey: external_exports.string().trim().min(12).max(300)
|
|
@@ -10211,28 +10407,225 @@ var feedQuerySchema = external_exports.object({
|
|
|
10211
10407
|
var searchQuerySchema = external_exports.object({
|
|
10212
10408
|
q: external_exports.string().default(""),
|
|
10213
10409
|
tab: external_exports.enum(["experiences", "tags", "creators"]).default("experiences"),
|
|
10214
|
-
|
|
10410
|
+
// Query params arrive as strings; z.coerce.boolean() is just Boolean(x), so
|
|
10411
|
+
// "false"/"0" would (wrongly) become true and the filter could never be turned
|
|
10412
|
+
// off. Parse the string explicitly: only "true"/"1" (or a real boolean) is true.
|
|
10413
|
+
remixableOnly: external_exports.union([external_exports.boolean(), external_exports.string()]).optional().transform((v) => v === true || v === "true" || v === "1"),
|
|
10215
10414
|
cursor: external_exports.string().optional(),
|
|
10216
10415
|
limit: external_exports.coerce.number().int().min(1).max(30).default(15)
|
|
10217
10416
|
});
|
|
10417
|
+
var registerPushDeviceSchema = external_exports.object({
|
|
10418
|
+
provider: external_exports.enum(PUSH_PROVIDERS),
|
|
10419
|
+
token: external_exports.string().trim().min(16).max(4096),
|
|
10420
|
+
keys: external_exports.record(external_exports.string().max(512)).default({}),
|
|
10421
|
+
platform: external_exports.enum(DEVICE_PLATFORMS).default("web"),
|
|
10422
|
+
userAgent: external_exports.string().trim().max(400).default("")
|
|
10423
|
+
}).superRefine((v, ctx) => {
|
|
10424
|
+
if (v.provider === "webpush" && (!v.keys.p256dh || !v.keys.auth)) {
|
|
10425
|
+
ctx.addIssue({
|
|
10426
|
+
code: external_exports.ZodIssueCode.custom,
|
|
10427
|
+
path: ["keys"],
|
|
10428
|
+
message: "webpush requires keys.p256dh and keys.auth"
|
|
10429
|
+
});
|
|
10430
|
+
}
|
|
10431
|
+
});
|
|
10432
|
+
var unregisterPushDeviceSchema = external_exports.object({
|
|
10433
|
+
token: external_exports.string().trim().min(16).max(4096)
|
|
10434
|
+
});
|
|
10435
|
+
var notificationsQuerySchema = external_exports.object({
|
|
10436
|
+
filter: external_exports.enum(["all", "mentions", "remixes"]).default("all"),
|
|
10437
|
+
// Keyset cursor: the `createdAt` ISO timestamp of the last item seen.
|
|
10438
|
+
cursor: external_exports.string().datetime({ offset: true }).optional(),
|
|
10439
|
+
limit: external_exports.coerce.number().int().min(1).max(80).default(30)
|
|
10440
|
+
});
|
|
10441
|
+
var notifPrefsPatchSchema = external_exports.object({
|
|
10442
|
+
enabled: external_exports.boolean().optional(),
|
|
10443
|
+
groups: external_exports.record(external_exports.enum(NOTIF_PREF_GROUPS), external_exports.boolean()).optional()
|
|
10444
|
+
});
|
|
10218
10445
|
|
|
10219
|
-
// ../shared/src/
|
|
10220
|
-
var
|
|
10221
|
-
|
|
10222
|
-
|
|
10223
|
-
|
|
10224
|
-
|
|
10225
|
-
|
|
10226
|
-
|
|
10227
|
-
|
|
10228
|
-
|
|
10229
|
-
|
|
10230
|
-
|
|
10231
|
-
|
|
10232
|
-
{ id: "gpt-5.2-pro", label: "GPT-5.2 Pro", tier: "subscription", note: "Premium" },
|
|
10233
|
-
{ id: "gemini-3-pro-preview", label: "Gemini 3 Pro", tier: "subscription", note: "Premium", vision: true }
|
|
10446
|
+
// ../shared/src/builderEvents.ts
|
|
10447
|
+
var BUILD_EVENT_VERSION = 1;
|
|
10448
|
+
var BUILD_PHASES = [
|
|
10449
|
+
"queued",
|
|
10450
|
+
// accepted, waiting for a worker slot
|
|
10451
|
+
"thinking",
|
|
10452
|
+
// model connected, no visible output yet (keep-alives flowing)
|
|
10453
|
+
"writing",
|
|
10454
|
+
// streaming the vibe's code/prose
|
|
10455
|
+
"validating",
|
|
10456
|
+
// sandbox pass on the produced artifact
|
|
10457
|
+
"repairing"
|
|
10458
|
+
// one automatic fix round after a validation failure
|
|
10234
10459
|
];
|
|
10235
|
-
var
|
|
10460
|
+
var base = { v: external_exports.literal(BUILD_EVENT_VERSION), turnId: external_exports.string() };
|
|
10461
|
+
var buildEventSchema = external_exports.discriminatedUnion("type", [
|
|
10462
|
+
/** Assistant prose delta (never contains the HTML code block). */
|
|
10463
|
+
external_exports.object({ ...base, type: external_exports.literal("token"), text: external_exports.string() }),
|
|
10464
|
+
/**
|
|
10465
|
+
* Honest heartbeat, ~every 2s while the turn runs: real elapsed ms, real
|
|
10466
|
+
* output-token count, and the executor's actual phase. The UI renders this
|
|
10467
|
+
* as a live counter — a number that moves because work is happening.
|
|
10468
|
+
*/
|
|
10469
|
+
external_exports.object({
|
|
10470
|
+
...base,
|
|
10471
|
+
type: external_exports.literal("progress"),
|
|
10472
|
+
phase: external_exports.enum(BUILD_PHASES),
|
|
10473
|
+
elapsedMs: external_exports.number(),
|
|
10474
|
+
tokensOut: external_exports.number()
|
|
10475
|
+
}),
|
|
10476
|
+
/** Friendly status chip ("index.html", "Fixing a couple of things…"). */
|
|
10477
|
+
external_exports.object({ ...base, type: external_exports.literal("status"), summary: external_exports.string() }),
|
|
10478
|
+
/** First-message category classification. */
|
|
10479
|
+
external_exports.object({ ...base, type: external_exports.literal("category"), category: external_exports.string() }),
|
|
10480
|
+
/** The validated draft landed. Preview loads it from the draft URL. */
|
|
10481
|
+
external_exports.object({ ...base, type: external_exports.literal("artifact"), artifactKey: external_exports.string() }),
|
|
10482
|
+
/** Terminal: failure with taxonomy code + human message. */
|
|
10483
|
+
external_exports.object({
|
|
10484
|
+
...base,
|
|
10485
|
+
type: external_exports.literal("error"),
|
|
10486
|
+
code: external_exports.string(),
|
|
10487
|
+
message: external_exports.string()
|
|
10488
|
+
}),
|
|
10489
|
+
/** Terminal: success. */
|
|
10490
|
+
external_exports.object({ ...base, type: external_exports.literal("done") })
|
|
10491
|
+
]);
|
|
10492
|
+
|
|
10493
|
+
// ../shared/src/i18n.ts
|
|
10494
|
+
var en = {
|
|
10495
|
+
"nav.home": "Home",
|
|
10496
|
+
"nav.search": "Search",
|
|
10497
|
+
"nav.create": "Create",
|
|
10498
|
+
"nav.notifications": "Notifications",
|
|
10499
|
+
"nav.profile": "Profile",
|
|
10500
|
+
"feed.explore": "Explore",
|
|
10501
|
+
"feed.following": "Following",
|
|
10502
|
+
"feed.tapToInteract": "Tap to interact",
|
|
10503
|
+
"feed.loading": `Loading ${ITEM.one}`,
|
|
10504
|
+
"feed.exit": "Exit",
|
|
10505
|
+
"feed.remixedFrom": "Remixed from @{handle}",
|
|
10506
|
+
"feed.remixedFromRemoved": `Remixed from a removed ${ITEM.one}`,
|
|
10507
|
+
"feed.newVersionOf": "New version of {title}",
|
|
10508
|
+
"feed.end.title": "You're all caught up",
|
|
10509
|
+
"feed.end.body": `No new ${ITEM.many} right now \u2014 remix one you loved, or check back soon.`,
|
|
10510
|
+
"feed.end.remix": "Find something to remix",
|
|
10511
|
+
"feed.end.top": "Back to top",
|
|
10512
|
+
"rail.like": "Like",
|
|
10513
|
+
"rail.comment": "Comment",
|
|
10514
|
+
"rail.remix": "Remix",
|
|
10515
|
+
"rail.save": "Save",
|
|
10516
|
+
"rail.share": "Share",
|
|
10517
|
+
"comments.title": "Comments",
|
|
10518
|
+
"comments.placeholder": "Add a comment\u2026",
|
|
10519
|
+
"comments.reply": "Reply",
|
|
10520
|
+
"comments.empty": "No comments yet \u2014 be the first.",
|
|
10521
|
+
"comments.post": "Post",
|
|
10522
|
+
"share.title": "Share",
|
|
10523
|
+
"share.copyLink": "Copy link",
|
|
10524
|
+
"share.copied": "Link copied",
|
|
10525
|
+
"capture.record": "Record",
|
|
10526
|
+
"capture.screenshot": "Screenshot",
|
|
10527
|
+
"capture.recording": "Recording",
|
|
10528
|
+
"capture.stop": "Stop",
|
|
10529
|
+
"capture.preparing": "Preparing\u2026",
|
|
10530
|
+
"capture.processing": "Finishing\u2026",
|
|
10531
|
+
"capture.preview.videoTitle": "Your clip",
|
|
10532
|
+
"capture.preview.imageTitle": "Your screenshot",
|
|
10533
|
+
"capture.save": "Save",
|
|
10534
|
+
"capture.shareVia": "Share via\u2026",
|
|
10535
|
+
"capture.retry": "Try again",
|
|
10536
|
+
"capture.error": "Capture failed \u2014 try again.",
|
|
10537
|
+
"capture.allowShare": "Allow \u201CShare this tab\u201D to record \u2014 it captures just the vibe.",
|
|
10538
|
+
"capture.pickTab": "Choose \u201CThis Tab\u201D so it records only the vibe.",
|
|
10539
|
+
"capture.unsupported": `This ${ITEM.one} can't be captured yet. Try again in a moment.`,
|
|
10540
|
+
"create.title": "Create",
|
|
10541
|
+
"create.buildWithAI": "Build with AI",
|
|
10542
|
+
"create.import": "Import",
|
|
10543
|
+
"create.remix": "Remix something",
|
|
10544
|
+
"create.prompt.placeholder": "Describe what you want to build\u2026",
|
|
10545
|
+
"create.prompt.starters": "Start from a template",
|
|
10546
|
+
"builder.publish": "Publish",
|
|
10547
|
+
"builder.thinking": "Building\u2026",
|
|
10548
|
+
"builder.updated": "Updated \u2713",
|
|
10549
|
+
"builder.placeholder": "Ask for a change\u2026",
|
|
10550
|
+
"builder.mockNotice": "Preview builder \u2014 AI generation is mocked for now.",
|
|
10551
|
+
"import.title": "Import",
|
|
10552
|
+
"import.paste": "Paste HTML, or drop an .html file",
|
|
10553
|
+
"import.validate": "Validate",
|
|
10554
|
+
"import.warnings": "Warnings",
|
|
10555
|
+
"publish.title": "Publish",
|
|
10556
|
+
"publish.postTitle": "Title",
|
|
10557
|
+
"publish.caption": "Caption",
|
|
10558
|
+
"publish.category": "Category",
|
|
10559
|
+
"publish.remixable": "Allow remixes",
|
|
10560
|
+
"publish.public": "Public",
|
|
10561
|
+
"publish.unlisted": "Unlisted",
|
|
10562
|
+
"publish.cta": "Publish",
|
|
10563
|
+
"publish.remixCredit": "Remixing @{handle} \u2014 {title}",
|
|
10564
|
+
"publish.toast.published": "Published! \u{1F389}",
|
|
10565
|
+
"publish.toast.remix": "Remix published \u{1F501} @{handle} was credited",
|
|
10566
|
+
"remix.confirmTitle": "Remixing @{handle}'s {title}",
|
|
10567
|
+
"remix.confirmBody": `They'll be credited on your ${ITEM.one}. Attribution can't be removed.`,
|
|
10568
|
+
"remix.start": "Start remixing",
|
|
10569
|
+
"remix.galleryTitle": "Remixes",
|
|
10570
|
+
"remix.sortPopular": "Popular",
|
|
10571
|
+
"remix.sortRecent": "Recent",
|
|
10572
|
+
"remix.count": "{n} remixes",
|
|
10573
|
+
"remix.identicalWarn": "This looks identical to the original \u2014 publish anyway?",
|
|
10574
|
+
"search.placeholder": `Search ${ITEM.many}, tags, creators`,
|
|
10575
|
+
"search.tab.experiences": ITEM.Many,
|
|
10576
|
+
"search.tab.tags": "Hashtags",
|
|
10577
|
+
"search.tab.creators": "Creators",
|
|
10578
|
+
"search.remixableOnly": "Remixable only",
|
|
10579
|
+
"search.trending": "Trending",
|
|
10580
|
+
"profile.tab.experiences": ITEM.Many,
|
|
10581
|
+
"profile.tab.remixes": "Remixes",
|
|
10582
|
+
"profile.tab.saved": "Saved",
|
|
10583
|
+
"profile.followers": "followers",
|
|
10584
|
+
"profile.following": "following",
|
|
10585
|
+
"profile.likes": "likes",
|
|
10586
|
+
"profile.follow": "Follow",
|
|
10587
|
+
"profile.unfollow": "Following",
|
|
10588
|
+
"profile.edit": "Edit profile",
|
|
10589
|
+
"notifs.all": "All",
|
|
10590
|
+
"notifs.mentions": "Mentions",
|
|
10591
|
+
"notifs.remixes": "Remixes",
|
|
10592
|
+
"notifs.remixedYou": `@{handle} remixed your ${ITEM.one}`,
|
|
10593
|
+
"notifs.liked": `@{handle} liked your ${ITEM.one}`,
|
|
10594
|
+
"notifs.commented": "@{handle} commented",
|
|
10595
|
+
"notifs.followed": "@{handle} started following you",
|
|
10596
|
+
"notifs.empty": "Nothing here yet.",
|
|
10597
|
+
"collections.title": "Saved",
|
|
10598
|
+
"collections.new": "New collection",
|
|
10599
|
+
"analytics.title": "Analytics",
|
|
10600
|
+
"analytics.views": "Views",
|
|
10601
|
+
"analytics.plays": "Plays",
|
|
10602
|
+
"analytics.playRate": "Play rate",
|
|
10603
|
+
"analytics.avgTime": "Avg. time",
|
|
10604
|
+
"analytics.likes": "Likes",
|
|
10605
|
+
"analytics.comments": "Comments",
|
|
10606
|
+
"analytics.shares": "Shares",
|
|
10607
|
+
"analytics.saves": "Saves",
|
|
10608
|
+
"analytics.remixes": "Remixes",
|
|
10609
|
+
"post.options": `${ITEM.One} options`,
|
|
10610
|
+
"post.analytics": "View analytics",
|
|
10611
|
+
"post.edit": "Edit caption",
|
|
10612
|
+
"post.delete": `Delete ${ITEM.one}`,
|
|
10613
|
+
"post.deleteConfirm": `Delete this ${ITEM.one}? This can't be undone.`,
|
|
10614
|
+
"post.captionPlaceholder": "Write a caption\u2026",
|
|
10615
|
+
"auth.signIn": "Sign in",
|
|
10616
|
+
"auth.google": "Continue with Google",
|
|
10617
|
+
"auth.github": "Continue with GitHub",
|
|
10618
|
+
"auth.dev": "Dev login",
|
|
10619
|
+
"auth.needAccount": "Sign in to like, comment, and remix.",
|
|
10620
|
+
"common.cancel": "Cancel",
|
|
10621
|
+
"common.save": "Save",
|
|
10622
|
+
"common.back": "Back",
|
|
10623
|
+
"common.retry": "Retry",
|
|
10624
|
+
"common.loading": "Loading\u2026",
|
|
10625
|
+
"common.report": "Report",
|
|
10626
|
+
"common.delete": "Delete",
|
|
10627
|
+
"common.views": "{n} views"
|
|
10628
|
+
};
|
|
10236
10629
|
|
|
10237
10630
|
// ../shared/src/tokens.ts
|
|
10238
10631
|
var colors = {
|
|
@@ -10335,6 +10728,15 @@ function validate(html, opts) {
|
|
|
10335
10728
|
errors.push({ code: "NOT_HTML", message: "Content is not parseable as HTML." });
|
|
10336
10729
|
return { ok: false, errors, warnings };
|
|
10337
10730
|
}
|
|
10731
|
+
const scriptOpens = (html.match(/<script\b/gi) ?? []).length;
|
|
10732
|
+
const scriptCloses = (html.match(/<\/script\s*>/gi) ?? []).length;
|
|
10733
|
+
if (scriptOpens > scriptCloses) {
|
|
10734
|
+
errors.push({
|
|
10735
|
+
code: "TRUNCATED_DOC",
|
|
10736
|
+
message: "The file ends mid-<script> \u2014 it looks cut off. Regenerate or paste the complete file."
|
|
10737
|
+
});
|
|
10738
|
+
return { ok: false, errors, warnings };
|
|
10739
|
+
}
|
|
10338
10740
|
if (on("frames") && root.querySelector("iframe, frame, object, embed")) {
|
|
10339
10741
|
errors.push({
|
|
10340
10742
|
code: "FRAME_TAG",
|
|
@@ -10419,6 +10821,12 @@ function validate(html, opts) {
|
|
|
10419
10821
|
}
|
|
10420
10822
|
}
|
|
10421
10823
|
}
|
|
10824
|
+
if (/(localStorage|sessionStorage|indexedDB|document\s*\.\s*cookie)/.test(html)) {
|
|
10825
|
+
warnings.push({
|
|
10826
|
+
code: "BROWSER_STORAGE",
|
|
10827
|
+
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)."
|
|
10828
|
+
});
|
|
10829
|
+
}
|
|
10422
10830
|
const lower = html.toLowerCase();
|
|
10423
10831
|
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);
|
|
10424
10832
|
if (hasCredField) {
|
|
@@ -10932,22 +11340,38 @@ function normalizeCategory(input) {
|
|
|
10932
11340
|
const match = CATEGORIES.find((c) => c.toLowerCase() === input.toLowerCase());
|
|
10933
11341
|
return match ?? "Other";
|
|
10934
11342
|
}
|
|
10935
|
-
async function
|
|
10936
|
-
const
|
|
11343
|
+
async function importDraft(client, html) {
|
|
11344
|
+
const r = await apiFetch(client, "POST", "/imports", { html });
|
|
11345
|
+
return { draftArtifactKey: r.draftArtifactKey, previewUrl: r.previewUrl };
|
|
11346
|
+
}
|
|
11347
|
+
async function publishDraft(client, draftArtifactKey, meta, html, projectDir) {
|
|
10937
11348
|
const exp = await apiFetch(client, "POST", "/experiences", {
|
|
10938
|
-
title: meta.title?.trim() || inferTitle(html, projectDir),
|
|
11349
|
+
title: meta.title?.trim() || (html ? inferTitle(html, projectDir ?? ".") : "Untitled"),
|
|
10939
11350
|
caption: meta.caption ?? "",
|
|
10940
11351
|
category: normalizeCategory(meta.category),
|
|
10941
11352
|
tags: meta.tags ?? [],
|
|
10942
11353
|
remixable: meta.remixable ?? true,
|
|
10943
11354
|
visibility: meta.visibility ?? "public",
|
|
10944
|
-
draftArtifactKey
|
|
11355
|
+
draftArtifactKey
|
|
10945
11356
|
});
|
|
10946
11357
|
return { id: exp.id, status: exp.status, url: `${client.apiBase}/e/${exp.id}` };
|
|
10947
11358
|
}
|
|
11359
|
+
async function publishArtifact(client, html, meta, projectDir) {
|
|
11360
|
+
const { draftArtifactKey } = await importDraft(client, html);
|
|
11361
|
+
return publishDraft(client, draftArtifactKey, meta, html, projectDir);
|
|
11362
|
+
}
|
|
11363
|
+
function experienceIdFrom(input) {
|
|
11364
|
+
const m = input.match(/\/e\/([0-9a-f-]{36})/i);
|
|
11365
|
+
return (m?.[1] ?? input.trim()).toLowerCase();
|
|
11366
|
+
}
|
|
11367
|
+
async function deleteExperience(client, idOrUrl) {
|
|
11368
|
+
const id = experienceIdFrom(idOrUrl);
|
|
11369
|
+
await apiFetch(client, "DELETE", `/experiences/${id}`);
|
|
11370
|
+
return id;
|
|
11371
|
+
}
|
|
10948
11372
|
|
|
10949
11373
|
// src/index.ts
|
|
10950
|
-
var SERVER_INFO = { name: "vibed", version: "0.1.
|
|
11374
|
+
var SERVER_INFO = { name: "vibed", version: "0.1.4" };
|
|
10951
11375
|
var PROTOCOL_VERSION = "2024-11-05";
|
|
10952
11376
|
var CATEGORIES2 = ["Game", "Invitation", "Story", "News", "Quiz", "Tool", "Art", "Other"];
|
|
10953
11377
|
var pendingLogin = null;
|
|
@@ -11022,12 +11446,47 @@ After the user approves, call vibed_login_wait.`;
|
|
|
11022
11446
|
}
|
|
11023
11447
|
}
|
|
11024
11448
|
},
|
|
11449
|
+
{
|
|
11450
|
+
name: "vibed_draft",
|
|
11451
|
+
description: "Bundle the project and upload it as a PRIVATE draft, returning a hosted, sandboxed PREVIEW link (\u2026/p/<id>/) and a draftKey. Publishes nothing \u2014 the link is in no feed, profile, or search; only someone with it can open it. Show the preview to the user, then call vibed_publish with the returned draftKey once they approve. Requires sign-in.",
|
|
11452
|
+
inputSchema: {
|
|
11453
|
+
type: "object",
|
|
11454
|
+
properties: {
|
|
11455
|
+
path: { type: "string", description: "Project directory (default: current directory)" },
|
|
11456
|
+
entry: { type: "string" },
|
|
11457
|
+
build: { type: "boolean" },
|
|
11458
|
+
api: { type: "string", description: "API base URL (default: production)" }
|
|
11459
|
+
}
|
|
11460
|
+
},
|
|
11461
|
+
run: async (a) => {
|
|
11462
|
+
const dir2 = resolve2(str(a.path, "."));
|
|
11463
|
+
const cfg = await loadConfig({ api: str(a.api) || void 0 });
|
|
11464
|
+
if (!cfg.token) return "Not signed in. Use vibed_login first.";
|
|
11465
|
+
const result = await bundleProject(dir2, {
|
|
11466
|
+
entry: str(a.entry) || void 0,
|
|
11467
|
+
build: bool(a.build, true),
|
|
11468
|
+
onLog: (m) => process.stderr.write(m + "\n")
|
|
11469
|
+
});
|
|
11470
|
+
if (!result.ok || !result.html) {
|
|
11471
|
+
return `No preview \u2014 the project isn't vibeable yet.
|
|
11472
|
+
|
|
11473
|
+
${formatReport(result)}`;
|
|
11474
|
+
}
|
|
11475
|
+
const d = await importDraft({ apiBase: cfg.apiBase, token: cfg.token }, result.html);
|
|
11476
|
+
return `\u2713 Private preview (not published): ${d.previewUrl ?? "(no preview URL)"}
|
|
11477
|
+
Show it to the user. To publish it, call vibed_publish with draftKey: ${d.draftArtifactKey}`;
|
|
11478
|
+
}
|
|
11479
|
+
},
|
|
11025
11480
|
{
|
|
11026
11481
|
name: "vibed_publish",
|
|
11027
|
-
description: "
|
|
11482
|
+
description: "Publish a vibed Experience. Pass draftKey (from vibed_draft) to promote an already-previewed draft \u2014 the recommended flow \u2014 or pass path to bundle + publish in one shot. Requires sign-in (vibed_whoami / vibed_login). Confirm the title and category with the user first \u2014 publishing is public. Returns the published URL, or the blockers if it isn't vibeable.",
|
|
11028
11483
|
inputSchema: {
|
|
11029
11484
|
type: "object",
|
|
11030
11485
|
properties: {
|
|
11486
|
+
draftKey: {
|
|
11487
|
+
type: "string",
|
|
11488
|
+
description: "Promote this draft (from vibed_draft) instead of bundling path"
|
|
11489
|
+
},
|
|
11031
11490
|
path: { type: "string", description: "Project directory (default: current directory)" },
|
|
11032
11491
|
title: { type: "string", description: "Title (default: page <title>/<h1>/folder name)" },
|
|
11033
11492
|
category: { type: "string", enum: CATEGORIES2, description: "Experience category" },
|
|
@@ -11041,9 +11500,23 @@ After the user approves, call vibed_login_wait.`;
|
|
|
11041
11500
|
}
|
|
11042
11501
|
},
|
|
11043
11502
|
run: async (a) => {
|
|
11044
|
-
const dir2 = resolve2(str(a.path, "."));
|
|
11045
11503
|
const cfg = await loadConfig({ api: str(a.api) || void 0 });
|
|
11046
11504
|
if (!cfg.token) return "Not signed in. Use vibed_login first.";
|
|
11505
|
+
const client = { apiBase: cfg.apiBase, token: cfg.token };
|
|
11506
|
+
const meta = {
|
|
11507
|
+
title: str(a.title) || void 0,
|
|
11508
|
+
category: str(a.category) || void 0,
|
|
11509
|
+
tags: Array.isArray(a.tags) ? a.tags : void 0,
|
|
11510
|
+
caption: str(a.caption) || void 0,
|
|
11511
|
+
remixable: bool(a.remixable, true),
|
|
11512
|
+
visibility: bool(a.unlisted, false) ? "unlisted" : "public"
|
|
11513
|
+
};
|
|
11514
|
+
const draftKey = str(a.draftKey);
|
|
11515
|
+
if (draftKey) {
|
|
11516
|
+
const outcome2 = await publishDraft(client, draftKey, meta);
|
|
11517
|
+
return `\u2713 Published (${outcome2.status}): ${outcome2.url}`;
|
|
11518
|
+
}
|
|
11519
|
+
const dir2 = resolve2(str(a.path, "."));
|
|
11047
11520
|
const result = await bundleProject(dir2, {
|
|
11048
11521
|
entry: str(a.entry) || void 0,
|
|
11049
11522
|
build: bool(a.build, true),
|
|
@@ -11054,21 +11527,29 @@ After the user approves, call vibed_login_wait.`;
|
|
|
11054
11527
|
|
|
11055
11528
|
${formatReport(result)}`;
|
|
11056
11529
|
}
|
|
11057
|
-
const outcome = await publishArtifact(
|
|
11058
|
-
{ apiBase: cfg.apiBase, token: cfg.token },
|
|
11059
|
-
result.html,
|
|
11060
|
-
{
|
|
11061
|
-
title: str(a.title) || void 0,
|
|
11062
|
-
category: str(a.category) || void 0,
|
|
11063
|
-
tags: Array.isArray(a.tags) ? a.tags : void 0,
|
|
11064
|
-
caption: str(a.caption) || void 0,
|
|
11065
|
-
remixable: bool(a.remixable, true),
|
|
11066
|
-
visibility: bool(a.unlisted, false) ? "unlisted" : "public"
|
|
11067
|
-
},
|
|
11068
|
-
dir2
|
|
11069
|
-
);
|
|
11530
|
+
const outcome = await publishArtifact(client, result.html, meta, dir2);
|
|
11070
11531
|
return `\u2713 Published (${outcome.status}): ${outcome.url}`;
|
|
11071
11532
|
}
|
|
11533
|
+
},
|
|
11534
|
+
{
|
|
11535
|
+
name: "vibed_delete",
|
|
11536
|
+
description: "Delete one of your vibed posts, by experience id or a \u2026/e/<id> share URL. Owner-only (the server rejects deleting someone else's post). Destructive \u2014 confirm the specific post with the user first. Requires sign-in.",
|
|
11537
|
+
inputSchema: {
|
|
11538
|
+
type: "object",
|
|
11539
|
+
properties: {
|
|
11540
|
+
target: { type: "string", description: "Experience id or https://\u2026/e/<id> URL" },
|
|
11541
|
+
api: { type: "string", description: "API base URL (default: production)" }
|
|
11542
|
+
},
|
|
11543
|
+
required: ["target"]
|
|
11544
|
+
},
|
|
11545
|
+
run: async (a) => {
|
|
11546
|
+
const target = str(a.target);
|
|
11547
|
+
if (!target) return "Provide the post's id or its \u2026/e/<id> URL.";
|
|
11548
|
+
const cfg = await loadConfig({ api: str(a.api) || void 0 });
|
|
11549
|
+
if (!cfg.token) return "Not signed in. Use vibed_login first.";
|
|
11550
|
+
const id = await deleteExperience({ apiBase: cfg.apiBase, token: cfg.token }, target);
|
|
11551
|
+
return `\u2713 Deleted post ${id}.`;
|
|
11552
|
+
}
|
|
11072
11553
|
}
|
|
11073
11554
|
];
|
|
11074
11555
|
function write(msg) {
|
package/package.json
CHANGED