@amirhosseinnateghi/vibed-mcp 0.1.6 → 0.1.8

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.
Files changed (2) hide show
  1. package/dist/index.js +141 -21
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -6009,6 +6009,25 @@ var CATEGORIES = [
6009
6009
  "Art",
6010
6010
  "Other"
6011
6011
  ];
6012
+ var TAGS = [
6013
+ "game",
6014
+ "puzzle",
6015
+ "quiz",
6016
+ "simulation",
6017
+ "music",
6018
+ "art",
6019
+ "animation",
6020
+ "story",
6021
+ "invitation",
6022
+ "love",
6023
+ "education",
6024
+ "science",
6025
+ "news",
6026
+ "tool",
6027
+ "productivity"
6028
+ ];
6029
+ var MAX_TAGS = 5;
6030
+ var FEED_TAG_CHIPS = ["All", ...TAGS];
6012
6031
  var VISIBILITY = ["public", "unlisted"];
6013
6032
  var COVER_MODES = ["image", "gif"];
6014
6033
  var EXPERIENCE_SOURCE = ["builder", "import", "remix"];
@@ -10285,15 +10304,22 @@ var categorySchema = external_exports.enum(CATEGORIES);
10285
10304
  var visibilitySchema = external_exports.enum(VISIBILITY);
10286
10305
  var coverModeSchema = external_exports.enum(COVER_MODES);
10287
10306
  var sourceSchema = external_exports.enum(EXPERIENCE_SOURCE);
10288
- var tagsSchema = external_exports.array(external_exports.string().min(1).max(40)).max(10).transform(
10307
+ var KNOWN_TAGS = new Set(TAGS);
10308
+ var tagsSchema = external_exports.array(external_exports.string().max(40)).transform(
10289
10309
  (tags) => Array.from(
10290
- new Set(tags.map((t) => t.replace(/^#/, "").trim().toLowerCase()).filter(Boolean))
10291
- )
10310
+ new Set(
10311
+ tags.map((t) => t.replace(/^#/, "").trim().toLowerCase()).filter((t) => KNOWN_TAGS.has(t))
10312
+ )
10313
+ ).slice(0, MAX_TAGS)
10292
10314
  ).default([]);
10293
10315
  var publishSchema = external_exports.object({
10294
10316
  title: external_exports.string().min(1).max(120),
10317
+ // Caption was removed from the publish UI; kept optional so old clients / the
10318
+ // CLI don't break and existing captions stay searchable.
10295
10319
  caption: external_exports.string().max(2e3).default(""),
10296
- category: categorySchema,
10320
+ // Legacy: category is no longer chosen in the UI. The connector CLI may still
10321
+ // send it; publishExperience folds it into `tags` and leaves the column null.
10322
+ category: categorySchema.optional(),
10297
10323
  tags: tagsSchema,
10298
10324
  remixable: external_exports.boolean().default(true),
10299
10325
  visibility: visibilitySchema.default("public"),
@@ -10406,7 +10432,8 @@ var aiConnectionSchema = external_exports.object({
10406
10432
  });
10407
10433
  var feedQuerySchema = external_exports.object({
10408
10434
  tab: external_exports.enum(["explore", "following"]).default("explore"),
10409
- category: external_exports.string().optional(),
10435
+ // Filter the explore feed to a single tag (replaces the old `category` param).
10436
+ tag: external_exports.string().optional(),
10410
10437
  cursor: external_exports.string().optional(),
10411
10438
  limit: external_exports.coerce.number().int().min(1).max(30).default(10)
10412
10439
  });
@@ -11191,9 +11218,94 @@ function formatReport(result) {
11191
11218
  out.push(` \u2022 \u2026and ${result.inlineWarnings.length - 12} more`);
11192
11219
  }
11193
11220
  }
11221
+ const usesBrowserStorage = [...result.warnings.map((w) => w.message), ...result.inlineWarnings].some(
11222
+ (m) => /BROWSER_STORAGE|localStorage|sessionStorage|indexedDB/i.test(m)
11223
+ );
11224
+ if (usesBrowserStorage) {
11225
+ out.push(
11226
+ "",
11227
+ "\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."
11228
+ );
11229
+ }
11194
11230
  return out.join("\n");
11195
11231
  }
11196
11232
 
11233
+ // ../cli/src/storageGuide.ts
11234
+ var STORAGE_GUIDE = `# vibed storage \u2014 add persistence to a vibe (\`window.vibed.storage\`)
11235
+
11236
+ A vibe is ONE static sandboxed HTML file with no backend. \`localStorage\`,
11237
+ \`sessionStorage\`, \`indexedDB\`, and cookies are BLOCKED \u2014 they throw in the
11238
+ sandbox (\`vibed check\` warns \`BROWSER_STORAGE\`). For anything you'd persist,
11239
+ use the injected \`window.vibed.storage\` SDK instead. The platform is the
11240
+ backend; you write zero server code.
11241
+
11242
+ ## Two rules that apply to EVERY call
11243
+ 1. Every call returns a Promise that can reject \u2014 always guard it. When the vibe
11244
+ runs OUTSIDE vibed (e.g. a local file, your \`vibed draft\`/\`preview\`), calls
11245
+ reject with \`{ code: "no_host" }\`. The vibe must stay playable standalone, so
11246
+ treat missing storage as "no saved data yet", never a crash.
11247
+ 2. WRITES require the viewer to be signed in. If not, they reject with
11248
+ \`{ code: "unauthenticated" }\` \u2014 catch it and show a gentle "sign in to save"
11249
+ nudge. READS of shared data work for everyone.
11250
+
11251
+ Publishing auto-detects SDK usage and turns storage on for the post \u2014 no flags.
11252
+
11253
+ ## Tier 1 \u2014 \`vibed.storage.local\` (LOCAL: private, per-viewer, per-post)
11254
+ Each viewer gets their own private bucket for THIS post. Nobody else can read it.
11255
+ Use for: this player's high score, their settings, "resume where I left off".
11256
+ \`\`\`js
11257
+ await vibed.storage.local.set("best", 8200);
11258
+ const best = await vibed.storage.local.get("best"); // null if never set
11259
+ await vibed.storage.local.delete("best");
11260
+ \`\`\`
11261
+
11262
+ ## Tier 2 \u2014 \`vibed.storage.shared\` (GLOBAL: one collective store for the whole post)
11263
+ Data every viewer of the post shares \u2014 leaderboards, votes, guestbooks, shared
11264
+ state. Organized into named "collections" (like tables) you invent.
11265
+ \`\`\`js
11266
+ // key/value doc with optimistic concurrency
11267
+ await vibed.storage.shared.set("state", "doc", { phase: "open" }); // \u2192 { value, version, ... }
11268
+ await vibed.storage.shared.set("state", "doc", next, { ifVersion: 3 }); // rejects { code: "conflict" }
11269
+ const doc = await vibed.storage.shared.get("state", "doc"); // null if missing
11270
+ await vibed.storage.shared.delete("state", "doc");
11271
+
11272
+ // append-only list + query (guestbook, bets, submissions)
11273
+ await vibed.storage.shared.append("bets", { pick: "Argentina", stake: 50 });
11274
+ const mine = await vibed.storage.shared.query("bets", { mine: true });
11275
+ const feed = await vibed.storage.shared.query("bets", { sort: "-created", limit: 30 });
11276
+
11277
+ // counter (poll / reactions)
11278
+ const { num } = await vibed.storage.shared.increment("votes", "argentina", 1);
11279
+
11280
+ // leaderboard: submit a score (keep the best), read the top, get MY rank
11281
+ await vibed.storage.shared.submit("leaderboard", { score, keep: "max", meta: { emoji: "\u{1F427}" } });
11282
+ const top = await vibed.storage.shared.query("leaderboard", { sort: "-num", limit: 10 });
11283
+ const rank = await vibed.storage.shared.rank("leaderboard"); // { rank, of, score } | null
11284
+
11285
+ // live updates (polling now): re-renders when the collection changes; returns an unsubscribe fn
11286
+ const off = vibed.storage.shared.subscribe("leaderboard", (r) => render(r.rows), { intervalMs: 4000 });
11287
+ \`\`\`
11288
+
11289
+ ## Who is watching
11290
+ \`\`\`js
11291
+ const me = await vibed.storage.identity(); // { id, signedIn, name? } \u2014 stable per viewer; identify entries
11292
+ const m = await vibed.storage.meta(); // { postId, ... } \u2014 this post's context
11293
+ \`\`\`
11294
+
11295
+ ## Limits (free tier \u2014 design within these)
11296
+ - Each value must be JSON-serializable and under the per-value byte cap
11297
+ (rejects \`{ code: "too_large" }\`); \`invalid\` if it can't be JSON-stringified.
11298
+ - shared collections are for structured game/app state, NOT open chat/DM/messaging.
11299
+
11300
+ ## Porting a project that used browser storage (the \`BROWSER_STORAGE\` warning)
11301
+ 1. Find every \`localStorage\`/\`sessionStorage\`/\`indexedDB\`/cookie use.
11302
+ 2. Per-viewer state (settings, progress, personal best) \u2192 \`vibed.storage.local\`.
11303
+ 3. Anything others should see (scores to compare, shared counters, submissions)
11304
+ \u2192 the matching \`vibed.storage.shared\` primitive above.
11305
+ 4. Make every call \`await\` + \`try/catch\`, with a standalone fallback (no_host).
11306
+ 5. Re-run \`vibed check\` \u2014 the \`BROWSER_STORAGE\` warning should be gone.
11307
+ `;
11308
+
11197
11309
  // ../cli/src/login.ts
11198
11310
  import { spawn } from "node:child_process";
11199
11311
  import { platform } from "node:os";
@@ -11350,10 +11462,14 @@ function inferTitle(html, projectDir) {
11350
11462
  const dir2 = basename(projectDir).replace(/[-_]+/g, " ").trim();
11351
11463
  return (dir2 || "Untitled").slice(0, 120);
11352
11464
  }
11353
- function normalizeCategory(input) {
11354
- if (!input) return "Other";
11355
- const match = CATEGORIES.find((c) => c.toLowerCase() === input.toLowerCase());
11356
- return match ?? "Other";
11465
+ function normalizeTags(tags, category) {
11466
+ const known = new Set(TAGS);
11467
+ const out = [];
11468
+ for (const t of [...tags ?? [], ...category ? [category] : []]) {
11469
+ const s = t.replace(/^#/, "").trim().toLowerCase();
11470
+ if (known.has(s) && !out.includes(s)) out.push(s);
11471
+ }
11472
+ return out.slice(0, MAX_TAGS);
11357
11473
  }
11358
11474
  async function importDraft(client, html) {
11359
11475
  const r = await apiFetch(client, "POST", "/imports", { html });
@@ -11362,9 +11478,7 @@ async function importDraft(client, html) {
11362
11478
  async function publishDraft(client, draftArtifactKey, meta, html, projectDir) {
11363
11479
  const exp = await apiFetch(client, "POST", "/experiences", {
11364
11480
  title: meta.title?.trim() || (html ? inferTitle(html, projectDir ?? ".") : "Untitled"),
11365
- caption: meta.caption ?? "",
11366
- category: normalizeCategory(meta.category),
11367
- tags: meta.tags ?? [],
11481
+ tags: normalizeTags(meta.tags, meta.category),
11368
11482
  remixable: meta.remixable ?? true,
11369
11483
  visibility: meta.visibility ?? "public",
11370
11484
  draftArtifactKey
@@ -11386,16 +11500,21 @@ async function deleteExperience(client, idOrUrl) {
11386
11500
  }
11387
11501
 
11388
11502
  // src/index.ts
11389
- var SERVER_INFO = { name: "vibed", version: "0.1.6" };
11503
+ var SERVER_INFO = { name: "vibed", version: "0.1.8" };
11390
11504
  var PROTOCOL_VERSION = "2024-11-05";
11391
- var CATEGORIES2 = ["Game", "Invitation", "Story", "News", "Quiz", "Tool", "Art", "Other"];
11392
11505
  var pendingLogin = null;
11393
11506
  var str = (v, d = "") => typeof v === "string" ? v : d;
11394
11507
  var bool = (v, d) => typeof v === "boolean" ? v : d;
11395
11508
  var TOOLS = [
11509
+ {
11510
+ name: "vibed_storage_guide",
11511
+ description: "How to add PERSISTENCE to a vibe: leaderboards, votes/polls, saved progress/high scores, guestbooks, or any shared state. A vibe is one static sandboxed file \u2014 localStorage/sessionStorage/indexedDB/cookies and backend calls are BLOCKED \u2014 so use the injected window.vibed.storage SDK instead. Returns the full primer for both tiers: `local` (private, per-viewer) and `shared` (collective, per-post). Call this BEFORE writing any save/leaderboard/multiplayer feature, or when vibed_check warns BROWSER_STORAGE.",
11512
+ inputSchema: { type: "object", properties: {} },
11513
+ run: async () => STORAGE_GUIDE
11514
+ },
11396
11515
  {
11397
11516
  name: "vibed_check",
11398
- description: "Bundle the project into a single HTML file and validate it against vibed's sandbox rules (no network/auth). Returns whether it's publishable and, if not, the blockers to fix. Run this before vibed_publish.",
11517
+ description: "Bundle the project into a single HTML file and validate it against vibed's sandbox rules (no network/auth). Returns whether it's publishable and, if not, the blockers to fix. Run this before vibed_publish. If it warns BROWSER_STORAGE (localStorage etc. throw in the sandbox), call vibed_storage_guide and port to window.vibed.storage.",
11399
11518
  inputSchema: {
11400
11519
  type: "object",
11401
11520
  properties: {
@@ -11494,7 +11613,7 @@ Show it to the user. To publish it, call vibed_publish with draftKey: ${d.draftA
11494
11613
  },
11495
11614
  {
11496
11615
  name: "vibed_publish",
11497
- 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.",
11616
+ 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 tags with the user first \u2014 publishing is public. Returns the published URL, or the blockers if it isn't vibeable.",
11498
11617
  inputSchema: {
11499
11618
  type: "object",
11500
11619
  properties: {
@@ -11504,9 +11623,12 @@ Show it to the user. To publish it, call vibed_publish with draftKey: ${d.draftA
11504
11623
  },
11505
11624
  path: { type: "string", description: "Project directory (default: current directory)" },
11506
11625
  title: { type: "string", description: "Title (default: page <title>/<h1>/folder name)" },
11507
- category: { type: "string", enum: CATEGORIES2, description: "Experience category" },
11508
- tags: { type: "array", items: { type: "string" } },
11509
- caption: { type: "string" },
11626
+ tags: {
11627
+ type: "array",
11628
+ items: { type: "string", enum: TAGS },
11629
+ maxItems: MAX_TAGS,
11630
+ description: `Up to ${MAX_TAGS} tags from: ${TAGS.join(", ")}`
11631
+ },
11510
11632
  unlisted: { type: "boolean", description: "Publish unlisted instead of public" },
11511
11633
  remixable: { type: "boolean", description: "Allow others to remix (default true)" },
11512
11634
  entry: { type: "string" },
@@ -11520,9 +11642,7 @@ Show it to the user. To publish it, call vibed_publish with draftKey: ${d.draftA
11520
11642
  const client = { apiBase: cfg.apiBase, token: cfg.token };
11521
11643
  const meta = {
11522
11644
  title: str(a.title) || void 0,
11523
- category: str(a.category) || void 0,
11524
11645
  tags: Array.isArray(a.tags) ? a.tags : void 0,
11525
- caption: str(a.caption) || void 0,
11526
11646
  remixable: bool(a.remixable, true),
11527
11647
  visibility: bool(a.unlisted, false) ? "unlisted" : "public"
11528
11648
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amirhosseinnateghi/vibed-mcp",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "vibed MCP server — make it vibed from any MCP client (Codex, Cursor, Gemini, …): check, login, and publish tools",
5
5
  "license": "MIT",
6
6
  "type": "module",