@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 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
- const apiBase = (overrides.api || process.env.VIBED_API || fromFile.apiBase || DEFAULT_API).replace(/\/$/, "");
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 an attachment."
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
- const apiBase = (overrides.api || process.env.VIBED_API || fromFile.apiBase || DEFAULT_API).replace(/\/$/, "");
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 an attachment."
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":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amirhosseinnateghi/vibed-cli",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "make it vibed — check, bundle, and publish a project to vibed from the terminal",
5
5
  "license": "MIT",
6
6
  "type": "module",