@amirhosseinnateghi/vibed-cli 0.1.5 → 0.1.7
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 +107 -3
- package/dist/vibed.cjs +107 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5983,6 +5983,7 @@ import { homedir } from "node:os";
|
|
|
5983
5983
|
import { join } from "node:path";
|
|
5984
5984
|
import { mkdir, readFile, writeFile, chmod } from "node:fs/promises";
|
|
5985
5985
|
var DEFAULT_API = "https://vibed.city";
|
|
5986
|
+
var LEGACY_API_DEFAULTS = /* @__PURE__ */ new Set(["https://web-production-834c9.up.railway.app"]);
|
|
5986
5987
|
var dir = () => join(homedir(), ".vibed");
|
|
5987
5988
|
var file = () => join(dir(), "config.json");
|
|
5988
5989
|
async function readFileConfig() {
|
|
@@ -5994,7 +5995,8 @@ async function readFileConfig() {
|
|
|
5994
5995
|
}
|
|
5995
5996
|
async function loadConfig(overrides = {}) {
|
|
5996
5997
|
const fromFile = await readFileConfig();
|
|
5997
|
-
|
|
5998
|
+
let apiBase = (overrides.api || process.env.VIBED_API || fromFile.apiBase || DEFAULT_API).replace(/\/$/, "");
|
|
5999
|
+
if (LEGACY_API_DEFAULTS.has(apiBase)) apiBase = DEFAULT_API;
|
|
5998
6000
|
const token = process.env.VIBED_TOKEN || fromFile.token;
|
|
5999
6001
|
return { apiBase, token };
|
|
6000
6002
|
}
|
|
@@ -10371,13 +10373,19 @@ var turnAttachmentSchema = external_exports.object({
|
|
|
10371
10373
|
// NUL bytes break Postgres jsonb (and mean binary-not-text) — strip defensively.
|
|
10372
10374
|
text: external_exports.string().max(5e4).transform((t) => t.replace(/\u0000/g, "")).optional()
|
|
10373
10375
|
});
|
|
10376
|
+
var voiceInputSchema = external_exports.object({
|
|
10377
|
+
key: external_exports.string().min(1).max(300),
|
|
10378
|
+
mime: external_exports.string().max(100).default("audio/webm")
|
|
10379
|
+
});
|
|
10374
10380
|
var createTurnRequestSchema = external_exports.object({
|
|
10375
10381
|
clientTurnId: external_exports.string().uuid(),
|
|
10376
10382
|
content: external_exports.string().max(4e3).default(""),
|
|
10377
10383
|
attachments: external_exports.array(turnAttachmentSchema).max(6).optional(),
|
|
10384
|
+
// A voice turn carries no text yet — the worker transcribes `voice` first (م۷).
|
|
10385
|
+
voice: voiceInputSchema.optional(),
|
|
10378
10386
|
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
|
|
10387
|
+
}).refine((v) => v.content.trim().length > 0 || (v.attachments?.length ?? 0) > 0 || !!v.voice, {
|
|
10388
|
+
message: "Send a message, an attachment, or a voice recording."
|
|
10381
10389
|
});
|
|
10382
10390
|
var builderAttachmentSchema = external_exports.object({
|
|
10383
10391
|
kind: external_exports.enum(["image", "file", "audio"]),
|
|
@@ -10511,6 +10519,13 @@ var buildEventSchema = external_exports.discriminatedUnion("type", [
|
|
|
10511
10519
|
}),
|
|
10512
10520
|
/** Friendly status chip ("index.html", "Fixing a couple of things…"). */
|
|
10513
10521
|
external_exports.object({ ...base, type: external_exports.literal("status"), summary: external_exports.string() }),
|
|
10522
|
+
/**
|
|
10523
|
+
* The user's spoken words, transcribed server-side (م۷). A voice turn starts
|
|
10524
|
+
* with empty `userText` and a "🎤 Transcribing…" bubble; this event fills the
|
|
10525
|
+
* user bubble with the transcript the moment the worker finishes transcribing,
|
|
10526
|
+
* before the build's prose begins.
|
|
10527
|
+
*/
|
|
10528
|
+
external_exports.object({ ...base, type: external_exports.literal("transcript"), text: external_exports.string() }),
|
|
10514
10529
|
/** First-message category classification. */
|
|
10515
10530
|
external_exports.object({ ...base, type: external_exports.literal("category"), category: external_exports.string() }),
|
|
10516
10531
|
/** The validated draft landed. Preview loads it from the draft URL. */
|
|
@@ -11214,6 +11229,15 @@ function formatReport(result) {
|
|
|
11214
11229
|
out2.push(` \u2022 \u2026and ${result.inlineWarnings.length - 12} more`);
|
|
11215
11230
|
}
|
|
11216
11231
|
}
|
|
11232
|
+
const usesBrowserStorage = [...result.warnings.map((w) => w.message), ...result.inlineWarnings].some(
|
|
11233
|
+
(m) => /BROWSER_STORAGE|localStorage|sessionStorage|indexedDB/i.test(m)
|
|
11234
|
+
);
|
|
11235
|
+
if (usesBrowserStorage) {
|
|
11236
|
+
out2.push(
|
|
11237
|
+
"",
|
|
11238
|
+
"\u2139 Uses browser storage (blocked in the sandbox). Port it to `window.vibed.storage` \u2014 run `vibed storage` (or the vibed_storage_guide MCP tool) for the local + shared SDK."
|
|
11239
|
+
);
|
|
11240
|
+
}
|
|
11217
11241
|
return out2.join("\n");
|
|
11218
11242
|
}
|
|
11219
11243
|
|
|
@@ -11391,6 +11415,82 @@ async function deleteExperience(client, idOrUrl) {
|
|
|
11391
11415
|
return id;
|
|
11392
11416
|
}
|
|
11393
11417
|
|
|
11418
|
+
// src/storageGuide.ts
|
|
11419
|
+
var STORAGE_GUIDE = `# vibed storage \u2014 add persistence to a vibe (\`window.vibed.storage\`)
|
|
11420
|
+
|
|
11421
|
+
A vibe is ONE static sandboxed HTML file with no backend. \`localStorage\`,
|
|
11422
|
+
\`sessionStorage\`, \`indexedDB\`, and cookies are BLOCKED \u2014 they throw in the
|
|
11423
|
+
sandbox (\`vibed check\` warns \`BROWSER_STORAGE\`). For anything you'd persist,
|
|
11424
|
+
use the injected \`window.vibed.storage\` SDK instead. The platform is the
|
|
11425
|
+
backend; you write zero server code.
|
|
11426
|
+
|
|
11427
|
+
## Two rules that apply to EVERY call
|
|
11428
|
+
1. Every call returns a Promise that can reject \u2014 always guard it. When the vibe
|
|
11429
|
+
runs OUTSIDE vibed (e.g. a local file, your \`vibed draft\`/\`preview\`), calls
|
|
11430
|
+
reject with \`{ code: "no_host" }\`. The vibe must stay playable standalone, so
|
|
11431
|
+
treat missing storage as "no saved data yet", never a crash.
|
|
11432
|
+
2. WRITES require the viewer to be signed in. If not, they reject with
|
|
11433
|
+
\`{ code: "unauthenticated" }\` \u2014 catch it and show a gentle "sign in to save"
|
|
11434
|
+
nudge. READS of shared data work for everyone.
|
|
11435
|
+
|
|
11436
|
+
Publishing auto-detects SDK usage and turns storage on for the post \u2014 no flags.
|
|
11437
|
+
|
|
11438
|
+
## Tier 1 \u2014 \`vibed.storage.local\` (LOCAL: private, per-viewer, per-post)
|
|
11439
|
+
Each viewer gets their own private bucket for THIS post. Nobody else can read it.
|
|
11440
|
+
Use for: this player's high score, their settings, "resume where I left off".
|
|
11441
|
+
\`\`\`js
|
|
11442
|
+
await vibed.storage.local.set("best", 8200);
|
|
11443
|
+
const best = await vibed.storage.local.get("best"); // null if never set
|
|
11444
|
+
await vibed.storage.local.delete("best");
|
|
11445
|
+
\`\`\`
|
|
11446
|
+
|
|
11447
|
+
## Tier 2 \u2014 \`vibed.storage.shared\` (GLOBAL: one collective store for the whole post)
|
|
11448
|
+
Data every viewer of the post shares \u2014 leaderboards, votes, guestbooks, shared
|
|
11449
|
+
state. Organized into named "collections" (like tables) you invent.
|
|
11450
|
+
\`\`\`js
|
|
11451
|
+
// key/value doc with optimistic concurrency
|
|
11452
|
+
await vibed.storage.shared.set("state", "doc", { phase: "open" }); // \u2192 { value, version, ... }
|
|
11453
|
+
await vibed.storage.shared.set("state", "doc", next, { ifVersion: 3 }); // rejects { code: "conflict" }
|
|
11454
|
+
const doc = await vibed.storage.shared.get("state", "doc"); // null if missing
|
|
11455
|
+
await vibed.storage.shared.delete("state", "doc");
|
|
11456
|
+
|
|
11457
|
+
// append-only list + query (guestbook, bets, submissions)
|
|
11458
|
+
await vibed.storage.shared.append("bets", { pick: "Argentina", stake: 50 });
|
|
11459
|
+
const mine = await vibed.storage.shared.query("bets", { mine: true });
|
|
11460
|
+
const feed = await vibed.storage.shared.query("bets", { sort: "-created", limit: 30 });
|
|
11461
|
+
|
|
11462
|
+
// counter (poll / reactions)
|
|
11463
|
+
const { num } = await vibed.storage.shared.increment("votes", "argentina", 1);
|
|
11464
|
+
|
|
11465
|
+
// leaderboard: submit a score (keep the best), read the top, get MY rank
|
|
11466
|
+
await vibed.storage.shared.submit("leaderboard", { score, keep: "max", meta: { emoji: "\u{1F427}" } });
|
|
11467
|
+
const top = await vibed.storage.shared.query("leaderboard", { sort: "-num", limit: 10 });
|
|
11468
|
+
const rank = await vibed.storage.shared.rank("leaderboard"); // { rank, of, score } | null
|
|
11469
|
+
|
|
11470
|
+
// live updates (polling now): re-renders when the collection changes; returns an unsubscribe fn
|
|
11471
|
+
const off = vibed.storage.shared.subscribe("leaderboard", (r) => render(r.rows), { intervalMs: 4000 });
|
|
11472
|
+
\`\`\`
|
|
11473
|
+
|
|
11474
|
+
## Who is watching
|
|
11475
|
+
\`\`\`js
|
|
11476
|
+
const me = await vibed.storage.identity(); // { id, signedIn, name? } \u2014 stable per viewer; identify entries
|
|
11477
|
+
const m = await vibed.storage.meta(); // { postId, ... } \u2014 this post's context
|
|
11478
|
+
\`\`\`
|
|
11479
|
+
|
|
11480
|
+
## Limits (free tier \u2014 design within these)
|
|
11481
|
+
- Each value must be JSON-serializable and under the per-value byte cap
|
|
11482
|
+
(rejects \`{ code: "too_large" }\`); \`invalid\` if it can't be JSON-stringified.
|
|
11483
|
+
- shared collections are for structured game/app state, NOT open chat/DM/messaging.
|
|
11484
|
+
|
|
11485
|
+
## Porting a project that used browser storage (the \`BROWSER_STORAGE\` warning)
|
|
11486
|
+
1. Find every \`localStorage\`/\`sessionStorage\`/\`indexedDB\`/cookie use.
|
|
11487
|
+
2. Per-viewer state (settings, progress, personal best) \u2192 \`vibed.storage.local\`.
|
|
11488
|
+
3. Anything others should see (scores to compare, shared counters, submissions)
|
|
11489
|
+
\u2192 the matching \`vibed.storage.shared\` primitive above.
|
|
11490
|
+
4. Make every call \`await\` + \`try/catch\`, with a standalone fallback (no_host).
|
|
11491
|
+
5. Re-run \`vibed check\` \u2014 the \`BROWSER_STORAGE\` warning should be gone.
|
|
11492
|
+
`;
|
|
11493
|
+
|
|
11394
11494
|
// src/index.ts
|
|
11395
11495
|
function parseArgs(argv) {
|
|
11396
11496
|
const _ = [];
|
|
@@ -11423,6 +11523,7 @@ var USAGE = `vibed \u2014 make it vibed
|
|
|
11423
11523
|
Usage:
|
|
11424
11524
|
vibed login Authenticate this machine (opens the browser)
|
|
11425
11525
|
vibed check [path] Check if a project can be published (no network)
|
|
11526
|
+
vibed storage Print the persistence guide (leaderboards, saves, shared state)
|
|
11426
11527
|
vibed preview [path] Bundle + open the artifact locally (no publish)
|
|
11427
11528
|
vibed draft [path] Upload a private, hosted preview link (no publish)
|
|
11428
11529
|
vibed publish [path] Bundle the project and publish it to vibed
|
|
@@ -11670,6 +11771,9 @@ async function main() {
|
|
|
11670
11771
|
return cmdWhoami(args);
|
|
11671
11772
|
case "check":
|
|
11672
11773
|
return cmdCheck(args);
|
|
11774
|
+
case "storage":
|
|
11775
|
+
console.log(STORAGE_GUIDE);
|
|
11776
|
+
return 0;
|
|
11673
11777
|
case "preview":
|
|
11674
11778
|
return cmdPreview(args);
|
|
11675
11779
|
case "draft":
|
package/dist/vibed.cjs
CHANGED
|
@@ -5984,6 +5984,7 @@ 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
5986
|
var DEFAULT_API = "https://vibed.city";
|
|
5987
|
+
var LEGACY_API_DEFAULTS = /* @__PURE__ */ new Set(["https://web-production-834c9.up.railway.app"]);
|
|
5987
5988
|
var dir = () => (0, import_node_path.join)((0, import_node_os.homedir)(), ".vibed");
|
|
5988
5989
|
var file = () => (0, import_node_path.join)(dir(), "config.json");
|
|
5989
5990
|
async function readFileConfig() {
|
|
@@ -5995,7 +5996,8 @@ async function readFileConfig() {
|
|
|
5995
5996
|
}
|
|
5996
5997
|
async function loadConfig(overrides = {}) {
|
|
5997
5998
|
const fromFile = await readFileConfig();
|
|
5998
|
-
|
|
5999
|
+
let apiBase = (overrides.api || process.env.VIBED_API || fromFile.apiBase || DEFAULT_API).replace(/\/$/, "");
|
|
6000
|
+
if (LEGACY_API_DEFAULTS.has(apiBase)) apiBase = DEFAULT_API;
|
|
5999
6001
|
const token = process.env.VIBED_TOKEN || fromFile.token;
|
|
6000
6002
|
return { apiBase, token };
|
|
6001
6003
|
}
|
|
@@ -10372,13 +10374,19 @@ var turnAttachmentSchema = external_exports.object({
|
|
|
10372
10374
|
// NUL bytes break Postgres jsonb (and mean binary-not-text) — strip defensively.
|
|
10373
10375
|
text: external_exports.string().max(5e4).transform((t) => t.replace(/\u0000/g, "")).optional()
|
|
10374
10376
|
});
|
|
10377
|
+
var voiceInputSchema = external_exports.object({
|
|
10378
|
+
key: external_exports.string().min(1).max(300),
|
|
10379
|
+
mime: external_exports.string().max(100).default("audio/webm")
|
|
10380
|
+
});
|
|
10375
10381
|
var createTurnRequestSchema = external_exports.object({
|
|
10376
10382
|
clientTurnId: external_exports.string().uuid(),
|
|
10377
10383
|
content: external_exports.string().max(4e3).default(""),
|
|
10378
10384
|
attachments: external_exports.array(turnAttachmentSchema).max(6).optional(),
|
|
10385
|
+
// A voice turn carries no text yet — the worker transcribes `voice` first (م۷).
|
|
10386
|
+
voice: voiceInputSchema.optional(),
|
|
10379
10387
|
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
|
|
10388
|
+
}).refine((v) => v.content.trim().length > 0 || (v.attachments?.length ?? 0) > 0 || !!v.voice, {
|
|
10389
|
+
message: "Send a message, an attachment, or a voice recording."
|
|
10382
10390
|
});
|
|
10383
10391
|
var builderAttachmentSchema = external_exports.object({
|
|
10384
10392
|
kind: external_exports.enum(["image", "file", "audio"]),
|
|
@@ -10512,6 +10520,13 @@ var buildEventSchema = external_exports.discriminatedUnion("type", [
|
|
|
10512
10520
|
}),
|
|
10513
10521
|
/** Friendly status chip ("index.html", "Fixing a couple of things…"). */
|
|
10514
10522
|
external_exports.object({ ...base, type: external_exports.literal("status"), summary: external_exports.string() }),
|
|
10523
|
+
/**
|
|
10524
|
+
* The user's spoken words, transcribed server-side (م۷). A voice turn starts
|
|
10525
|
+
* with empty `userText` and a "🎤 Transcribing…" bubble; this event fills the
|
|
10526
|
+
* user bubble with the transcript the moment the worker finishes transcribing,
|
|
10527
|
+
* before the build's prose begins.
|
|
10528
|
+
*/
|
|
10529
|
+
external_exports.object({ ...base, type: external_exports.literal("transcript"), text: external_exports.string() }),
|
|
10515
10530
|
/** First-message category classification. */
|
|
10516
10531
|
external_exports.object({ ...base, type: external_exports.literal("category"), category: external_exports.string() }),
|
|
10517
10532
|
/** The validated draft landed. Preview loads it from the draft URL. */
|
|
@@ -11215,6 +11230,15 @@ function formatReport(result) {
|
|
|
11215
11230
|
out2.push(` \u2022 \u2026and ${result.inlineWarnings.length - 12} more`);
|
|
11216
11231
|
}
|
|
11217
11232
|
}
|
|
11233
|
+
const usesBrowserStorage = [...result.warnings.map((w) => w.message), ...result.inlineWarnings].some(
|
|
11234
|
+
(m) => /BROWSER_STORAGE|localStorage|sessionStorage|indexedDB/i.test(m)
|
|
11235
|
+
);
|
|
11236
|
+
if (usesBrowserStorage) {
|
|
11237
|
+
out2.push(
|
|
11238
|
+
"",
|
|
11239
|
+
"\u2139 Uses browser storage (blocked in the sandbox). Port it to `window.vibed.storage` \u2014 run `vibed storage` (or the vibed_storage_guide MCP tool) for the local + shared SDK."
|
|
11240
|
+
);
|
|
11241
|
+
}
|
|
11218
11242
|
return out2.join("\n");
|
|
11219
11243
|
}
|
|
11220
11244
|
|
|
@@ -11392,6 +11416,82 @@ async function deleteExperience(client, idOrUrl) {
|
|
|
11392
11416
|
return id;
|
|
11393
11417
|
}
|
|
11394
11418
|
|
|
11419
|
+
// src/storageGuide.ts
|
|
11420
|
+
var STORAGE_GUIDE = `# vibed storage \u2014 add persistence to a vibe (\`window.vibed.storage\`)
|
|
11421
|
+
|
|
11422
|
+
A vibe is ONE static sandboxed HTML file with no backend. \`localStorage\`,
|
|
11423
|
+
\`sessionStorage\`, \`indexedDB\`, and cookies are BLOCKED \u2014 they throw in the
|
|
11424
|
+
sandbox (\`vibed check\` warns \`BROWSER_STORAGE\`). For anything you'd persist,
|
|
11425
|
+
use the injected \`window.vibed.storage\` SDK instead. The platform is the
|
|
11426
|
+
backend; you write zero server code.
|
|
11427
|
+
|
|
11428
|
+
## Two rules that apply to EVERY call
|
|
11429
|
+
1. Every call returns a Promise that can reject \u2014 always guard it. When the vibe
|
|
11430
|
+
runs OUTSIDE vibed (e.g. a local file, your \`vibed draft\`/\`preview\`), calls
|
|
11431
|
+
reject with \`{ code: "no_host" }\`. The vibe must stay playable standalone, so
|
|
11432
|
+
treat missing storage as "no saved data yet", never a crash.
|
|
11433
|
+
2. WRITES require the viewer to be signed in. If not, they reject with
|
|
11434
|
+
\`{ code: "unauthenticated" }\` \u2014 catch it and show a gentle "sign in to save"
|
|
11435
|
+
nudge. READS of shared data work for everyone.
|
|
11436
|
+
|
|
11437
|
+
Publishing auto-detects SDK usage and turns storage on for the post \u2014 no flags.
|
|
11438
|
+
|
|
11439
|
+
## Tier 1 \u2014 \`vibed.storage.local\` (LOCAL: private, per-viewer, per-post)
|
|
11440
|
+
Each viewer gets their own private bucket for THIS post. Nobody else can read it.
|
|
11441
|
+
Use for: this player's high score, their settings, "resume where I left off".
|
|
11442
|
+
\`\`\`js
|
|
11443
|
+
await vibed.storage.local.set("best", 8200);
|
|
11444
|
+
const best = await vibed.storage.local.get("best"); // null if never set
|
|
11445
|
+
await vibed.storage.local.delete("best");
|
|
11446
|
+
\`\`\`
|
|
11447
|
+
|
|
11448
|
+
## Tier 2 \u2014 \`vibed.storage.shared\` (GLOBAL: one collective store for the whole post)
|
|
11449
|
+
Data every viewer of the post shares \u2014 leaderboards, votes, guestbooks, shared
|
|
11450
|
+
state. Organized into named "collections" (like tables) you invent.
|
|
11451
|
+
\`\`\`js
|
|
11452
|
+
// key/value doc with optimistic concurrency
|
|
11453
|
+
await vibed.storage.shared.set("state", "doc", { phase: "open" }); // \u2192 { value, version, ... }
|
|
11454
|
+
await vibed.storage.shared.set("state", "doc", next, { ifVersion: 3 }); // rejects { code: "conflict" }
|
|
11455
|
+
const doc = await vibed.storage.shared.get("state", "doc"); // null if missing
|
|
11456
|
+
await vibed.storage.shared.delete("state", "doc");
|
|
11457
|
+
|
|
11458
|
+
// append-only list + query (guestbook, bets, submissions)
|
|
11459
|
+
await vibed.storage.shared.append("bets", { pick: "Argentina", stake: 50 });
|
|
11460
|
+
const mine = await vibed.storage.shared.query("bets", { mine: true });
|
|
11461
|
+
const feed = await vibed.storage.shared.query("bets", { sort: "-created", limit: 30 });
|
|
11462
|
+
|
|
11463
|
+
// counter (poll / reactions)
|
|
11464
|
+
const { num } = await vibed.storage.shared.increment("votes", "argentina", 1);
|
|
11465
|
+
|
|
11466
|
+
// leaderboard: submit a score (keep the best), read the top, get MY rank
|
|
11467
|
+
await vibed.storage.shared.submit("leaderboard", { score, keep: "max", meta: { emoji: "\u{1F427}" } });
|
|
11468
|
+
const top = await vibed.storage.shared.query("leaderboard", { sort: "-num", limit: 10 });
|
|
11469
|
+
const rank = await vibed.storage.shared.rank("leaderboard"); // { rank, of, score } | null
|
|
11470
|
+
|
|
11471
|
+
// live updates (polling now): re-renders when the collection changes; returns an unsubscribe fn
|
|
11472
|
+
const off = vibed.storage.shared.subscribe("leaderboard", (r) => render(r.rows), { intervalMs: 4000 });
|
|
11473
|
+
\`\`\`
|
|
11474
|
+
|
|
11475
|
+
## Who is watching
|
|
11476
|
+
\`\`\`js
|
|
11477
|
+
const me = await vibed.storage.identity(); // { id, signedIn, name? } \u2014 stable per viewer; identify entries
|
|
11478
|
+
const m = await vibed.storage.meta(); // { postId, ... } \u2014 this post's context
|
|
11479
|
+
\`\`\`
|
|
11480
|
+
|
|
11481
|
+
## Limits (free tier \u2014 design within these)
|
|
11482
|
+
- Each value must be JSON-serializable and under the per-value byte cap
|
|
11483
|
+
(rejects \`{ code: "too_large" }\`); \`invalid\` if it can't be JSON-stringified.
|
|
11484
|
+
- shared collections are for structured game/app state, NOT open chat/DM/messaging.
|
|
11485
|
+
|
|
11486
|
+
## Porting a project that used browser storage (the \`BROWSER_STORAGE\` warning)
|
|
11487
|
+
1. Find every \`localStorage\`/\`sessionStorage\`/\`indexedDB\`/cookie use.
|
|
11488
|
+
2. Per-viewer state (settings, progress, personal best) \u2192 \`vibed.storage.local\`.
|
|
11489
|
+
3. Anything others should see (scores to compare, shared counters, submissions)
|
|
11490
|
+
\u2192 the matching \`vibed.storage.shared\` primitive above.
|
|
11491
|
+
4. Make every call \`await\` + \`try/catch\`, with a standalone fallback (no_host).
|
|
11492
|
+
5. Re-run \`vibed check\` \u2014 the \`BROWSER_STORAGE\` warning should be gone.
|
|
11493
|
+
`;
|
|
11494
|
+
|
|
11395
11495
|
// src/index.ts
|
|
11396
11496
|
function parseArgs(argv) {
|
|
11397
11497
|
const _ = [];
|
|
@@ -11424,6 +11524,7 @@ var USAGE = `vibed \u2014 make it vibed
|
|
|
11424
11524
|
Usage:
|
|
11425
11525
|
vibed login Authenticate this machine (opens the browser)
|
|
11426
11526
|
vibed check [path] Check if a project can be published (no network)
|
|
11527
|
+
vibed storage Print the persistence guide (leaderboards, saves, shared state)
|
|
11427
11528
|
vibed preview [path] Bundle + open the artifact locally (no publish)
|
|
11428
11529
|
vibed draft [path] Upload a private, hosted preview link (no publish)
|
|
11429
11530
|
vibed publish [path] Bundle the project and publish it to vibed
|
|
@@ -11671,6 +11772,9 @@ async function main() {
|
|
|
11671
11772
|
return cmdWhoami(args);
|
|
11672
11773
|
case "check":
|
|
11673
11774
|
return cmdCheck(args);
|
|
11775
|
+
case "storage":
|
|
11776
|
+
console.log(STORAGE_GUIDE);
|
|
11777
|
+
return 0;
|
|
11674
11778
|
case "preview":
|
|
11675
11779
|
return cmdPreview(args);
|
|
11676
11780
|
case "draft":
|