@amirhosseinnateghi/vibed-cli 0.1.8 → 0.2.0
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 +346 -15
- package/dist/vibed.cjs +351 -20
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -5973,8 +5973,8 @@ var require_dist = __commonJS({
|
|
|
5973
5973
|
});
|
|
5974
5974
|
|
|
5975
5975
|
// src/index.ts
|
|
5976
|
-
import { resolve as resolve2, join as
|
|
5977
|
-
import { writeFile as
|
|
5976
|
+
import { resolve as resolve2, join as join4 } from "node:path";
|
|
5977
|
+
import { writeFile as writeFile3 } from "node:fs/promises";
|
|
5978
5978
|
import { tmpdir } from "node:os";
|
|
5979
5979
|
import { pathToFileURL } from "node:url";
|
|
5980
5980
|
|
|
@@ -6121,6 +6121,9 @@ var config = {
|
|
|
6121
6121
|
// completion the server drops a notification (§7.1) — so a slow build never
|
|
6122
6122
|
// traps them staring at a spinner.
|
|
6123
6123
|
BUILDER_SLOW_MS: intEnv("BUILDER_SLOW_MS", 12e3),
|
|
6124
|
+
/** Lite edits (ف۷) kill switch: set to "off" to force every edit turn back to
|
|
6125
|
+
* whole-file regeneration without deploying code. */
|
|
6126
|
+
BUILDER_LITE_EDITS: strEnv("BUILDER_LITE_EDITS", "on"),
|
|
6124
6127
|
DEFAULT_LOCALE: strEnv("DEFAULT_LOCALE", "en")
|
|
6125
6128
|
};
|
|
6126
6129
|
function strEnv(key, fallback) {
|
|
@@ -10260,10 +10263,11 @@ var STORAGE = {
|
|
|
10260
10263
|
/** Max records returnable in one query page. */
|
|
10261
10264
|
QUERY_MAX_LIMIT: intEnv("STORE_QUERY_MAX_LIMIT", 100),
|
|
10262
10265
|
/**
|
|
10263
|
-
* Platform kill-switch for anonymous shared writes. ON by default
|
|
10264
|
-
* gate is per-post
|
|
10265
|
-
*
|
|
10266
|
-
*
|
|
10266
|
+
* Platform kill-switch for anonymous shared writes. ON by default. The real
|
|
10267
|
+
* gate is the per-post config (`anon_writes` + writePolicy `anyone`), which is
|
|
10268
|
+
* guest-writable BY DEFAULT (a shared vibe should work for whoever opens the
|
|
10269
|
+
* link) — an owner can lock it down per post. Set the env to `false` to disable
|
|
10270
|
+
* anon writes platform-wide regardless of per-post config.
|
|
10267
10271
|
*/
|
|
10268
10272
|
ANON_WRITES_AVAILABLE: boolEnv("STORE_ANON_WRITES_AVAILABLE", true)
|
|
10269
10273
|
};
|
|
@@ -10377,9 +10381,39 @@ var patchExperienceSchema = external_exports.object({
|
|
|
10377
10381
|
visibility: visibilitySchema.optional(),
|
|
10378
10382
|
coverMode: coverModeSchema.optional()
|
|
10379
10383
|
});
|
|
10384
|
+
var COMMENT_MEDIA = {
|
|
10385
|
+
quality: 0.82,
|
|
10386
|
+
maxEdge: 1600,
|
|
10387
|
+
maxBytes: 8 * 1024 * 1024,
|
|
10388
|
+
// video/* is the compressed comment clip (client-side MediaRecorder → mp4 on
|
|
10389
|
+
// iOS, webm on Chromium); the client never sends a raw camera video as-is.
|
|
10390
|
+
types: ["image/webp", "image/jpeg", "image/png", "image/gif", "video/mp4", "video/webm"]
|
|
10391
|
+
};
|
|
10392
|
+
var COMMENT_VIDEO = {
|
|
10393
|
+
maxDurationSec: 60,
|
|
10394
|
+
maxEdge: 720,
|
|
10395
|
+
// 1 Mbps → a 60s clip is ~7.5MB, comfortably under the 10MB request-body cap.
|
|
10396
|
+
bitsPerSecond: 1e6,
|
|
10397
|
+
crf: 30,
|
|
10398
|
+
maxUploadBytes: 120 * 1024 * 1024
|
|
10399
|
+
};
|
|
10400
|
+
var commentMediaSchema = external_exports.object({
|
|
10401
|
+
key: external_exports.string().min(1).max(300),
|
|
10402
|
+
type: external_exports.enum(COMMENT_MEDIA.types),
|
|
10403
|
+
w: external_exports.number().int().positive().max(2e4),
|
|
10404
|
+
h: external_exports.number().int().positive().max(2e4)
|
|
10405
|
+
});
|
|
10406
|
+
var commentVideoSchema = external_exports.object({ key: external_exports.string().min(1).max(300) });
|
|
10380
10407
|
var commentSchema = external_exports.object({
|
|
10381
|
-
body: external_exports.string().
|
|
10382
|
-
parentId: external_exports.string().uuid().optional()
|
|
10408
|
+
body: external_exports.string().max(500),
|
|
10409
|
+
parentId: external_exports.string().uuid().optional(),
|
|
10410
|
+
media: commentMediaSchema.optional(),
|
|
10411
|
+
// ready image/GIF (compressed client-side)
|
|
10412
|
+
video: commentVideoSchema.optional()
|
|
10413
|
+
// raw video → transcoded async in the worker
|
|
10414
|
+
}).refine((d) => d.body.trim().length > 0 || !!d.media || !!d.video, {
|
|
10415
|
+
message: "Write something or attach a photo or video.",
|
|
10416
|
+
path: ["body"]
|
|
10383
10417
|
});
|
|
10384
10418
|
var eventSchema = external_exports.object({
|
|
10385
10419
|
// "record"/"screenshot" fire when a viewer captures the vibe (see the in-iframe
|
|
@@ -10410,6 +10444,9 @@ var createTurnRequestSchema = external_exports.object({
|
|
|
10410
10444
|
// A voice turn carries no text yet — the worker transcribes `voice` first (م۷).
|
|
10411
10445
|
voice: voiceInputSchema.optional(),
|
|
10412
10446
|
model: external_exports.string().max(100).optional()
|
|
10447
|
+
// No editMode: lite edits (ف۷) proved themselves, so every edit takes that
|
|
10448
|
+
// path and the server decides — a client can't ask for the slow rewrite.
|
|
10449
|
+
// BUILDER_LITE_EDITS=off remains the fleet-wide kill switch.
|
|
10413
10450
|
}).refine((v) => v.content.trim().length > 0 || (v.attachments?.length ?? 0) > 0 || !!v.voice, {
|
|
10414
10451
|
message: "Send a message, an attachment, or a voice recording."
|
|
10415
10452
|
});
|
|
@@ -10514,6 +10551,183 @@ var notifPrefsPatchSchema = external_exports.object({
|
|
|
10514
10551
|
groups: external_exports.record(external_exports.enum(NOTIF_PREF_GROUPS), external_exports.boolean()).optional()
|
|
10515
10552
|
});
|
|
10516
10553
|
|
|
10554
|
+
// ../shared/src/remix.ts
|
|
10555
|
+
var REMIX_GUIDE = `# Make it remix-friendly
|
|
10556
|
+
|
|
10557
|
+
On vibed every published vibe is **remixable**: anyone can fork it and make it
|
|
10558
|
+
theirs, and the AI builder can be asked to change it. A vibe is only *really*
|
|
10559
|
+
remixable if a stranger \u2014 or a model editing it \u2014 can find the knobs without
|
|
10560
|
+
reading the whole file. Treat this as part of shipping, like mobile-first.
|
|
10561
|
+
|
|
10562
|
+
It pays off twice: edits to a well-structured file are applied as small precise
|
|
10563
|
+
patches instead of regenerating the whole thing, which is faster, cheaper, and
|
|
10564
|
+
far less likely to break what already worked.
|
|
10565
|
+
|
|
10566
|
+
## 1. One CONFIG block, at the top
|
|
10567
|
+
|
|
10568
|
+
Every tunable lives in a single object at the top of the script \u2014 the first
|
|
10569
|
+
thing anyone sees:
|
|
10570
|
+
|
|
10571
|
+
\`\`\`js
|
|
10572
|
+
const CONFIG = {
|
|
10573
|
+
// --- Feel ---
|
|
10574
|
+
gravity: 0.6,
|
|
10575
|
+
jumpForce: -11,
|
|
10576
|
+
pipeGap: 160,
|
|
10577
|
+
pipeSpeed: 2.4,
|
|
10578
|
+
// --- Look ---
|
|
10579
|
+
skyColor: "#7ec8ff",
|
|
10580
|
+
birdColor: "#ffd166",
|
|
10581
|
+
// --- Words (all user-facing text) ---
|
|
10582
|
+
title: "Tap to Fly",
|
|
10583
|
+
gameOverText: "Ouch! Tap to try again",
|
|
10584
|
+
scoreLabel: "Score",
|
|
10585
|
+
};
|
|
10586
|
+
\`\`\`
|
|
10587
|
+
|
|
10588
|
+
Rules that make it useful:
|
|
10589
|
+
- **Literal values only** \u2014 numbers, strings, colors. No computed expressions.
|
|
10590
|
+
- **Everything reads from CONFIG.** If a number appears twice in the logic, it
|
|
10591
|
+
belongs in CONFIG once.
|
|
10592
|
+
- **All user-facing text goes here too**, so rewording or translating a vibe is
|
|
10593
|
+
one edit, not a hunt.
|
|
10594
|
+
|
|
10595
|
+
## 2. Content separate from logic
|
|
10596
|
+
|
|
10597
|
+
Questions, levels, items, cards, prompts \u2014 a labelled array right after CONFIG:
|
|
10598
|
+
|
|
10599
|
+
\`\`\`js
|
|
10600
|
+
const QUESTIONS = [
|
|
10601
|
+
{ q: "Pick a color", options: ["Red", "Blue"], answer: 0 },
|
|
10602
|
+
// add more here \u2014 same shape
|
|
10603
|
+
];
|
|
10604
|
+
\`\`\`
|
|
10605
|
+
|
|
10606
|
+
Now "add five more questions" is a data edit anyone can make.
|
|
10607
|
+
|
|
10608
|
+
## 3. Predictable section order
|
|
10609
|
+
|
|
10610
|
+
Lay the script out in this order, each with a one-line banner comment:
|
|
10611
|
+
|
|
10612
|
+
\`\`\`js
|
|
10613
|
+
// ============ CONFIG ============
|
|
10614
|
+
// ============ CONTENT ===========
|
|
10615
|
+
// ============ STATE =============
|
|
10616
|
+
// ============ HELPERS ===========
|
|
10617
|
+
// ============ RENDER ============
|
|
10618
|
+
// ============ INPUT =============
|
|
10619
|
+
// ============ INIT ==============
|
|
10620
|
+
\`\`\`
|
|
10621
|
+
|
|
10622
|
+
A remixer (and a patching model) then knows where a change belongs before
|
|
10623
|
+
reading a single line.
|
|
10624
|
+
|
|
10625
|
+
## 4. Write it plainly
|
|
10626
|
+
|
|
10627
|
+
- Descriptive names: \`spawnEnemy()\`, not \`se()\`; \`playerSpeed\`, not \`ps\`.
|
|
10628
|
+
- Small functions that do one thing. One \`init()\` entry point, one game loop.
|
|
10629
|
+
- Two-space indent, no minification, no bundler output, no clever one-liners or
|
|
10630
|
+
chained ternaries.
|
|
10631
|
+
- A short comment at each seam someone would want to change:
|
|
10632
|
+
\`// Win condition \u2014 swap this for a timer, a score target, ...\`
|
|
10633
|
+
- Stable \`id\`/\`class\` names on the elements a remixer would target.
|
|
10634
|
+
- No dead code, and no copy-pasted near-identical blocks \u2014 factor them into one
|
|
10635
|
+
function. (Duplicated blocks also make an automated edit ambiguous, so it
|
|
10636
|
+
fails or lands in the wrong place.)
|
|
10637
|
+
|
|
10638
|
+
## 5. Quick self-check before publishing
|
|
10639
|
+
|
|
10640
|
+
- Could someone change the colors, speed, and all the words by editing only
|
|
10641
|
+
CONFIG?
|
|
10642
|
+
- Could they add one more level/question/item by editing only the content array?
|
|
10643
|
+
- Is there any magic number or user-facing string outside those two blocks?
|
|
10644
|
+
- Would a stranger know where to add a feature within ten seconds of scrolling?
|
|
10645
|
+
|
|
10646
|
+
If the answer to any of those is no, fix it before you publish \u2014 that is the
|
|
10647
|
+
difference between a vibe people fork and one they scroll past.`;
|
|
10648
|
+
var SMALL_VIBE_CHARS = 1200;
|
|
10649
|
+
var SMALL_VIBE_LINES = 40;
|
|
10650
|
+
var COMMENT_EXPECTED_LINES = 60;
|
|
10651
|
+
var SECTIONS_EXPECTED_LINES = 120;
|
|
10652
|
+
var MINIFIED_LINE_CHARS = 400;
|
|
10653
|
+
var MINIFIED_AVG_LINE_CHARS = 160;
|
|
10654
|
+
function authorScript(html) {
|
|
10655
|
+
const parts = [];
|
|
10656
|
+
const re = /<script\b([^>]*)>([\s\S]*?)<\/script\s*>/gi;
|
|
10657
|
+
let m;
|
|
10658
|
+
while (m = re.exec(html)) {
|
|
10659
|
+
const attrs = m[1] ?? "";
|
|
10660
|
+
if (/data-vibed-bridge/i.test(attrs)) continue;
|
|
10661
|
+
if (/\bsrc=/i.test(attrs)) continue;
|
|
10662
|
+
parts.push(m[2] ?? "");
|
|
10663
|
+
}
|
|
10664
|
+
return parts.join("\n");
|
|
10665
|
+
}
|
|
10666
|
+
function remixAudit(html) {
|
|
10667
|
+
const script = authorScript(html ?? "");
|
|
10668
|
+
const lines = script.split(/\r?\n/);
|
|
10669
|
+
const codeLines = lines.filter((l) => l.trim() !== "");
|
|
10670
|
+
const scriptLines = codeLines.length;
|
|
10671
|
+
const scriptChars = codeLines.reduce((n, l) => n + l.trim().length, 0);
|
|
10672
|
+
const avgLine = scriptLines > 0 ? scriptChars / scriptLines : 0;
|
|
10673
|
+
const findings = [];
|
|
10674
|
+
const longest = codeLines.reduce((n, l) => Math.max(n, l.trim().length), 0);
|
|
10675
|
+
const denselyPacked = scriptChars > 600 && avgLine > MINIFIED_AVG_LINE_CHARS;
|
|
10676
|
+
if (longest > MINIFIED_LINE_CHARS || denselyPacked) {
|
|
10677
|
+
findings.push({
|
|
10678
|
+
code: "MINIFIED",
|
|
10679
|
+
blocking: true,
|
|
10680
|
+
message: `The script averages ${Math.round(avgLine)} characters per line (longest ${longest}) \u2014 this looks minified or bundled.`,
|
|
10681
|
+
fix: "Publish readable source: turn off minification for the vibe build, or inline the unminified code."
|
|
10682
|
+
});
|
|
10683
|
+
}
|
|
10684
|
+
if (scriptLines >= SMALL_VIBE_LINES || scriptChars >= SMALL_VIBE_CHARS) {
|
|
10685
|
+
if (!/\b(?:const|let|var)\s+CONFIG\s*=/.test(script)) {
|
|
10686
|
+
findings.push({
|
|
10687
|
+
code: "NO_CONFIG",
|
|
10688
|
+
blocking: true,
|
|
10689
|
+
message: "No `const CONFIG = { ... }` block \u2014 a remixer has to hunt through the logic to change anything.",
|
|
10690
|
+
fix: "Move every tunable (speeds, sizes, colors, counts, durations) AND all user-facing text into one CONFIG object at the top, and read from it everywhere."
|
|
10691
|
+
});
|
|
10692
|
+
}
|
|
10693
|
+
const cryptic = /* @__PURE__ */ new Set();
|
|
10694
|
+
const decl = /\b(?:const|let|var)\s+([A-Za-z_$])\b(?!\s*[,)])/g;
|
|
10695
|
+
let d;
|
|
10696
|
+
while (d = decl.exec(script)) {
|
|
10697
|
+
const name = d[1];
|
|
10698
|
+
if (!"ijknxyt_".includes(name)) cryptic.add(name);
|
|
10699
|
+
}
|
|
10700
|
+
if (cryptic.size >= 5) {
|
|
10701
|
+
findings.push({
|
|
10702
|
+
code: "CRYPTIC_NAMES",
|
|
10703
|
+
blocking: true,
|
|
10704
|
+
message: `${cryptic.size} single-letter variables (${[...cryptic].slice(0, 6).join(", ")}) \u2014 the code reads as machine output.`,
|
|
10705
|
+
fix: "Give variables and functions descriptive names (`playerSpeed`, not `p`)."
|
|
10706
|
+
});
|
|
10707
|
+
}
|
|
10708
|
+
if (scriptLines >= COMMENT_EXPECTED_LINES) {
|
|
10709
|
+
const comments = codeLines.filter((l) => /^\s*(\/\/|\/\*|\*)/.test(l)).length;
|
|
10710
|
+
if (comments === 0) {
|
|
10711
|
+
findings.push({
|
|
10712
|
+
code: "NO_COMMENTS",
|
|
10713
|
+
blocking: false,
|
|
10714
|
+
message: "The script has no comments at all.",
|
|
10715
|
+
fix: "Add a short note at each seam a remixer would change (the win condition, the spawn rule, the scoring)."
|
|
10716
|
+
});
|
|
10717
|
+
}
|
|
10718
|
+
}
|
|
10719
|
+
if (scriptLines >= SECTIONS_EXPECTED_LINES && !/CONFIG\s*=+\s*$|=== *CONFIG|-- *CONFIG/im.test(script)) {
|
|
10720
|
+
findings.push({
|
|
10721
|
+
code: "NO_SECTIONS",
|
|
10722
|
+
blocking: false,
|
|
10723
|
+
message: "No section banners in a long script.",
|
|
10724
|
+
fix: "Split it with banner comments: CONFIG \u2192 CONTENT \u2192 STATE \u2192 helpers \u2192 render \u2192 input \u2192 init."
|
|
10725
|
+
});
|
|
10726
|
+
}
|
|
10727
|
+
}
|
|
10728
|
+
return { ok: !findings.some((f) => f.blocking), findings, scriptLines };
|
|
10729
|
+
}
|
|
10730
|
+
|
|
10517
10731
|
// ../shared/src/builderEvents.ts
|
|
10518
10732
|
var BUILD_EVENT_VERSION = 1;
|
|
10519
10733
|
var BUILD_PHASES = [
|
|
@@ -11171,8 +11385,10 @@ async function bundleProject(projectDir, opts = {}) {
|
|
|
11171
11385
|
allowedOrigins
|
|
11172
11386
|
});
|
|
11173
11387
|
const result = validate(html, { maxBytes, allowedOrigins, policy: opts.policy });
|
|
11388
|
+
const remix = remixAudit(html);
|
|
11174
11389
|
return {
|
|
11175
|
-
ok: result.ok,
|
|
11390
|
+
ok: result.ok && remix.ok,
|
|
11391
|
+
remix,
|
|
11176
11392
|
html,
|
|
11177
11393
|
policy: opts.policy,
|
|
11178
11394
|
entry: resolved.entry,
|
|
@@ -11233,12 +11449,28 @@ function formatReport(result) {
|
|
|
11233
11449
|
result.sizeBytes != null ? `size: ${fmtSize(result.sizeBytes)}` : null
|
|
11234
11450
|
].filter(Boolean).join(" \xB7 ");
|
|
11235
11451
|
if (where) out2.push(where);
|
|
11452
|
+
const remixBlockers = (result.remix?.findings ?? []).filter((f) => f.blocking);
|
|
11236
11453
|
if (result.ok) {
|
|
11237
11454
|
out2.push("\u2713 Vibeable \u2014 this project can be published to vibed.");
|
|
11238
|
-
} else {
|
|
11455
|
+
} else if (result.blockers.length > 0) {
|
|
11239
11456
|
out2.push(`\u2717 Not vibeable yet \u2014 ${result.blockers.length} blocker(s):`);
|
|
11240
11457
|
out2.push(...summarize(result).lines);
|
|
11241
11458
|
}
|
|
11459
|
+
if (remixBlockers.length > 0) {
|
|
11460
|
+
out2.push("");
|
|
11461
|
+
out2.push(`\u2717 Not remixable yet \u2014 ${remixBlockers.length} thing(s) to fix before publishing:`);
|
|
11462
|
+
for (const f of remixBlockers) {
|
|
11463
|
+
out2.push(` \u2022 ${f.message}`);
|
|
11464
|
+
out2.push(` \u2192 ${f.fix}`);
|
|
11465
|
+
}
|
|
11466
|
+
out2.push(" Run `vibed remixable` for the full guide.");
|
|
11467
|
+
}
|
|
11468
|
+
const remixAdvice = (result.remix?.findings ?? []).filter((f) => !f.blocking);
|
|
11469
|
+
if (remixAdvice.length > 0) {
|
|
11470
|
+
out2.push("");
|
|
11471
|
+
out2.push("Remix polish (not blocking):");
|
|
11472
|
+
for (const f of remixAdvice) out2.push(` \u2022 ${f.message} \u2192 ${f.fix}`);
|
|
11473
|
+
}
|
|
11242
11474
|
if (result.policy && ruleActive("no_vertical_scroll", result.policy)) {
|
|
11243
11475
|
out2.push(
|
|
11244
11476
|
"",
|
|
@@ -11520,6 +11752,82 @@ const m = await vibed.storage.meta(); // { postId, ... } \u2014 this post'
|
|
|
11520
11752
|
5. Re-run \`vibed check\` \u2014 the \`BROWSER_STORAGE\` warning should be gone.
|
|
11521
11753
|
`;
|
|
11522
11754
|
|
|
11755
|
+
// src/version.ts
|
|
11756
|
+
import { homedir as homedir2 } from "node:os";
|
|
11757
|
+
import { join as join3 } from "node:path";
|
|
11758
|
+
import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile2 } from "node:fs/promises";
|
|
11759
|
+
var VERSION = true ? "0.2.0" : "0.0.0-dev";
|
|
11760
|
+
var PKG = "@amirhosseinnateghi/vibed-cli";
|
|
11761
|
+
var REGISTRY = `https://registry.npmjs.org/${PKG}/latest`;
|
|
11762
|
+
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
11763
|
+
var FETCH_TIMEOUT_MS = 2e3;
|
|
11764
|
+
var stateFile = () => join3(homedir2(), ".vibed", "update-check.json");
|
|
11765
|
+
function isOutdated(current, latest) {
|
|
11766
|
+
const parse4 = (v) => {
|
|
11767
|
+
const core = v.trim().replace(/^v/, "").split("-")[0];
|
|
11768
|
+
if (!core || !/^\d+(\.\d+)*$/.test(core)) return null;
|
|
11769
|
+
return core.split(".").map(Number);
|
|
11770
|
+
};
|
|
11771
|
+
const a = parse4(current);
|
|
11772
|
+
const b = parse4(latest);
|
|
11773
|
+
if (!a || !b) return false;
|
|
11774
|
+
for (let i = 0; i < Math.max(a.length, b.length); i++) {
|
|
11775
|
+
const x = a[i] ?? 0;
|
|
11776
|
+
const y = b[i] ?? 0;
|
|
11777
|
+
if (y > x) return true;
|
|
11778
|
+
if (y < x) return false;
|
|
11779
|
+
}
|
|
11780
|
+
return false;
|
|
11781
|
+
}
|
|
11782
|
+
function updateCheckEnabled(argv, env) {
|
|
11783
|
+
if (argv.includes("--json")) return false;
|
|
11784
|
+
if (env.VIBED_NO_UPDATE_CHECK === "1" || env.NO_UPDATE_NOTIFIER === "1") return false;
|
|
11785
|
+
if (env.CI) return false;
|
|
11786
|
+
if (VERSION === "0.0.0-dev") return false;
|
|
11787
|
+
return true;
|
|
11788
|
+
}
|
|
11789
|
+
function updateHint(latest, env) {
|
|
11790
|
+
const viaPlugin = !!env.CLAUDE_PLUGIN_ROOT;
|
|
11791
|
+
const how = viaPlugin ? "update it with: /plugin update make-it-vibed" : `update it with: npm i -g ${PKG}`;
|
|
11792
|
+
return `vibed ${VERSION} is out of date (latest ${latest}) \u2014 ${how}`;
|
|
11793
|
+
}
|
|
11794
|
+
async function readState() {
|
|
11795
|
+
try {
|
|
11796
|
+
return JSON.parse(await readFile3(stateFile(), "utf8"));
|
|
11797
|
+
} catch {
|
|
11798
|
+
return {};
|
|
11799
|
+
}
|
|
11800
|
+
}
|
|
11801
|
+
async function writeState(latest) {
|
|
11802
|
+
try {
|
|
11803
|
+
await mkdir2(join3(homedir2(), ".vibed"), { recursive: true });
|
|
11804
|
+
await writeFile2(stateFile(), JSON.stringify({ checkedAt: Date.now(), latest }), "utf8");
|
|
11805
|
+
} catch {
|
|
11806
|
+
}
|
|
11807
|
+
}
|
|
11808
|
+
async function checkForUpdate(argv = process.argv, env = process.env) {
|
|
11809
|
+
if (!updateCheckEnabled(argv, env)) return null;
|
|
11810
|
+
const state = await readState();
|
|
11811
|
+
if (state.checkedAt && Date.now() - state.checkedAt < CHECK_INTERVAL_MS) {
|
|
11812
|
+
return state.latest && isOutdated(VERSION, state.latest) ? updateHint(state.latest, env) : null;
|
|
11813
|
+
}
|
|
11814
|
+
let latest = null;
|
|
11815
|
+
try {
|
|
11816
|
+
const res = await fetch(REGISTRY, {
|
|
11817
|
+
headers: { accept: "application/vnd.npm.install-v1+json, application/json" },
|
|
11818
|
+
signal: AbortSignal.timeout?.(FETCH_TIMEOUT_MS)
|
|
11819
|
+
});
|
|
11820
|
+
if (res.ok) {
|
|
11821
|
+
const body = await res.json();
|
|
11822
|
+
if (typeof body.version === "string") latest = body.version;
|
|
11823
|
+
}
|
|
11824
|
+
} catch {
|
|
11825
|
+
}
|
|
11826
|
+
if (!latest) return null;
|
|
11827
|
+
await writeState(latest);
|
|
11828
|
+
return isOutdated(VERSION, latest) ? updateHint(latest, env) : null;
|
|
11829
|
+
}
|
|
11830
|
+
|
|
11523
11831
|
// src/index.ts
|
|
11524
11832
|
function parseArgs(argv) {
|
|
11525
11833
|
const _ = [];
|
|
@@ -11553,6 +11861,7 @@ Usage:
|
|
|
11553
11861
|
vibed login Authenticate this machine (opens the browser)
|
|
11554
11862
|
vibed check [path] Check if a project can be published (no network)
|
|
11555
11863
|
vibed storage Print the persistence guide (leaderboards, saves, shared state)
|
|
11864
|
+
vibed remixable Print the remix-friendly authoring guide (CONFIG block, readable code)
|
|
11556
11865
|
vibed preview [path] Bundle + open the artifact locally (no publish)
|
|
11557
11866
|
vibed draft [path] Upload a private, hosted preview link (no publish)
|
|
11558
11867
|
vibed publish [path] Bundle the project and publish it to vibed
|
|
@@ -11560,6 +11869,7 @@ Usage:
|
|
|
11560
11869
|
vibed delete <id|url> Delete one of your posts
|
|
11561
11870
|
vibed whoami Show the signed-in account
|
|
11562
11871
|
vibed logout Remove the saved token
|
|
11872
|
+
vibed version Print the connector version (--version works too)
|
|
11563
11873
|
|
|
11564
11874
|
Flags:
|
|
11565
11875
|
--api <url> API base (or VIBED_API env). Default: production
|
|
@@ -11609,8 +11919,8 @@ async function cmdPreview(args) {
|
|
|
11609
11919
|
if (json) out(json, "", { ok: false, fatal: result.fatal });
|
|
11610
11920
|
return 1;
|
|
11611
11921
|
}
|
|
11612
|
-
const file2 =
|
|
11613
|
-
await
|
|
11922
|
+
const file2 = join4(tmpdir(), `vibed-preview-${result.sizeBytes ?? 0}.html`);
|
|
11923
|
+
await writeFile3(file2, result.html, "utf8");
|
|
11614
11924
|
const url = pathToFileURL(file2).href;
|
|
11615
11925
|
if (json) {
|
|
11616
11926
|
out(json, "", { ok: result.ok, previewFile: file2, previewUrl: url });
|
|
@@ -11709,7 +12019,7 @@ async function cmdPublish(args) {
|
|
|
11709
12019
|
});
|
|
11710
12020
|
if (!result.ok || !result.html) {
|
|
11711
12021
|
out(json, formatReport(result), { ...result, html: void 0 });
|
|
11712
|
-
console.error("\nFix
|
|
12022
|
+
console.error("\nFix everything above, then run `vibed publish` again.");
|
|
11713
12023
|
return 1;
|
|
11714
12024
|
}
|
|
11715
12025
|
if (!json) console.log(formatReport(result));
|
|
@@ -11790,7 +12100,12 @@ async function cmdLogout() {
|
|
|
11790
12100
|
return 0;
|
|
11791
12101
|
}
|
|
11792
12102
|
async function main() {
|
|
11793
|
-
const
|
|
12103
|
+
const raw = process.argv.slice(2);
|
|
12104
|
+
if (raw.includes("--version") || raw.includes("-v")) {
|
|
12105
|
+
console.log(VERSION);
|
|
12106
|
+
return 0;
|
|
12107
|
+
}
|
|
12108
|
+
const args = parseArgs(raw);
|
|
11794
12109
|
const cmd = args._[0];
|
|
11795
12110
|
switch (cmd) {
|
|
11796
12111
|
case "login":
|
|
@@ -11804,6 +12119,9 @@ async function main() {
|
|
|
11804
12119
|
case "storage":
|
|
11805
12120
|
console.log(STORAGE_GUIDE);
|
|
11806
12121
|
return 0;
|
|
12122
|
+
case "remixable":
|
|
12123
|
+
console.log(REMIX_GUIDE);
|
|
12124
|
+
return 0;
|
|
11807
12125
|
case "preview":
|
|
11808
12126
|
return cmdPreview(args);
|
|
11809
12127
|
case "draft":
|
|
@@ -11813,6 +12131,11 @@ async function main() {
|
|
|
11813
12131
|
case "delete":
|
|
11814
12132
|
case "rm":
|
|
11815
12133
|
return cmdDelete(args);
|
|
12134
|
+
case "version":
|
|
12135
|
+
case "--version":
|
|
12136
|
+
case "-v":
|
|
12137
|
+
console.log(VERSION);
|
|
12138
|
+
return 0;
|
|
11816
12139
|
case void 0:
|
|
11817
12140
|
case "help":
|
|
11818
12141
|
case "--help":
|
|
@@ -11825,7 +12148,15 @@ ${USAGE}`);
|
|
|
11825
12148
|
return 1;
|
|
11826
12149
|
}
|
|
11827
12150
|
}
|
|
11828
|
-
main().then(
|
|
12151
|
+
main().then(async (code) => {
|
|
12152
|
+
const nudge = await Promise.race([
|
|
12153
|
+
checkForUpdate().catch(() => null),
|
|
12154
|
+
new Promise((r) => setTimeout(() => r(null), 2500))
|
|
12155
|
+
]);
|
|
12156
|
+
if (nudge) console.error(`
|
|
12157
|
+
${nudge}`);
|
|
12158
|
+
process.exit(code);
|
|
12159
|
+
}).catch((e) => {
|
|
11829
12160
|
console.error(e instanceof Error ? e.message : String(e));
|
|
11830
12161
|
process.exit(1);
|
|
11831
12162
|
});
|
package/dist/vibed.cjs
CHANGED
|
@@ -5974,9 +5974,9 @@ var require_dist = __commonJS({
|
|
|
5974
5974
|
});
|
|
5975
5975
|
|
|
5976
5976
|
// src/index.ts
|
|
5977
|
-
var
|
|
5978
|
-
var
|
|
5979
|
-
var
|
|
5977
|
+
var import_node_path5 = require("node:path");
|
|
5978
|
+
var import_promises4 = require("node:fs/promises");
|
|
5979
|
+
var import_node_os4 = require("node:os");
|
|
5980
5980
|
var import_node_url = require("node:url");
|
|
5981
5981
|
|
|
5982
5982
|
// src/config.ts
|
|
@@ -6122,6 +6122,9 @@ var config = {
|
|
|
6122
6122
|
// completion the server drops a notification (§7.1) — so a slow build never
|
|
6123
6123
|
// traps them staring at a spinner.
|
|
6124
6124
|
BUILDER_SLOW_MS: intEnv("BUILDER_SLOW_MS", 12e3),
|
|
6125
|
+
/** Lite edits (ف۷) kill switch: set to "off" to force every edit turn back to
|
|
6126
|
+
* whole-file regeneration without deploying code. */
|
|
6127
|
+
BUILDER_LITE_EDITS: strEnv("BUILDER_LITE_EDITS", "on"),
|
|
6125
6128
|
DEFAULT_LOCALE: strEnv("DEFAULT_LOCALE", "en")
|
|
6126
6129
|
};
|
|
6127
6130
|
function strEnv(key, fallback) {
|
|
@@ -10261,10 +10264,11 @@ var STORAGE = {
|
|
|
10261
10264
|
/** Max records returnable in one query page. */
|
|
10262
10265
|
QUERY_MAX_LIMIT: intEnv("STORE_QUERY_MAX_LIMIT", 100),
|
|
10263
10266
|
/**
|
|
10264
|
-
* Platform kill-switch for anonymous shared writes. ON by default
|
|
10265
|
-
* gate is per-post
|
|
10266
|
-
*
|
|
10267
|
-
*
|
|
10267
|
+
* Platform kill-switch for anonymous shared writes. ON by default. The real
|
|
10268
|
+
* gate is the per-post config (`anon_writes` + writePolicy `anyone`), which is
|
|
10269
|
+
* guest-writable BY DEFAULT (a shared vibe should work for whoever opens the
|
|
10270
|
+
* link) — an owner can lock it down per post. Set the env to `false` to disable
|
|
10271
|
+
* anon writes platform-wide regardless of per-post config.
|
|
10268
10272
|
*/
|
|
10269
10273
|
ANON_WRITES_AVAILABLE: boolEnv("STORE_ANON_WRITES_AVAILABLE", true)
|
|
10270
10274
|
};
|
|
@@ -10378,9 +10382,39 @@ var patchExperienceSchema = external_exports.object({
|
|
|
10378
10382
|
visibility: visibilitySchema.optional(),
|
|
10379
10383
|
coverMode: coverModeSchema.optional()
|
|
10380
10384
|
});
|
|
10385
|
+
var COMMENT_MEDIA = {
|
|
10386
|
+
quality: 0.82,
|
|
10387
|
+
maxEdge: 1600,
|
|
10388
|
+
maxBytes: 8 * 1024 * 1024,
|
|
10389
|
+
// video/* is the compressed comment clip (client-side MediaRecorder → mp4 on
|
|
10390
|
+
// iOS, webm on Chromium); the client never sends a raw camera video as-is.
|
|
10391
|
+
types: ["image/webp", "image/jpeg", "image/png", "image/gif", "video/mp4", "video/webm"]
|
|
10392
|
+
};
|
|
10393
|
+
var COMMENT_VIDEO = {
|
|
10394
|
+
maxDurationSec: 60,
|
|
10395
|
+
maxEdge: 720,
|
|
10396
|
+
// 1 Mbps → a 60s clip is ~7.5MB, comfortably under the 10MB request-body cap.
|
|
10397
|
+
bitsPerSecond: 1e6,
|
|
10398
|
+
crf: 30,
|
|
10399
|
+
maxUploadBytes: 120 * 1024 * 1024
|
|
10400
|
+
};
|
|
10401
|
+
var commentMediaSchema = external_exports.object({
|
|
10402
|
+
key: external_exports.string().min(1).max(300),
|
|
10403
|
+
type: external_exports.enum(COMMENT_MEDIA.types),
|
|
10404
|
+
w: external_exports.number().int().positive().max(2e4),
|
|
10405
|
+
h: external_exports.number().int().positive().max(2e4)
|
|
10406
|
+
});
|
|
10407
|
+
var commentVideoSchema = external_exports.object({ key: external_exports.string().min(1).max(300) });
|
|
10381
10408
|
var commentSchema = external_exports.object({
|
|
10382
|
-
body: external_exports.string().
|
|
10383
|
-
parentId: external_exports.string().uuid().optional()
|
|
10409
|
+
body: external_exports.string().max(500),
|
|
10410
|
+
parentId: external_exports.string().uuid().optional(),
|
|
10411
|
+
media: commentMediaSchema.optional(),
|
|
10412
|
+
// ready image/GIF (compressed client-side)
|
|
10413
|
+
video: commentVideoSchema.optional()
|
|
10414
|
+
// raw video → transcoded async in the worker
|
|
10415
|
+
}).refine((d) => d.body.trim().length > 0 || !!d.media || !!d.video, {
|
|
10416
|
+
message: "Write something or attach a photo or video.",
|
|
10417
|
+
path: ["body"]
|
|
10384
10418
|
});
|
|
10385
10419
|
var eventSchema = external_exports.object({
|
|
10386
10420
|
// "record"/"screenshot" fire when a viewer captures the vibe (see the in-iframe
|
|
@@ -10411,6 +10445,9 @@ var createTurnRequestSchema = external_exports.object({
|
|
|
10411
10445
|
// A voice turn carries no text yet — the worker transcribes `voice` first (م۷).
|
|
10412
10446
|
voice: voiceInputSchema.optional(),
|
|
10413
10447
|
model: external_exports.string().max(100).optional()
|
|
10448
|
+
// No editMode: lite edits (ف۷) proved themselves, so every edit takes that
|
|
10449
|
+
// path and the server decides — a client can't ask for the slow rewrite.
|
|
10450
|
+
// BUILDER_LITE_EDITS=off remains the fleet-wide kill switch.
|
|
10414
10451
|
}).refine((v) => v.content.trim().length > 0 || (v.attachments?.length ?? 0) > 0 || !!v.voice, {
|
|
10415
10452
|
message: "Send a message, an attachment, or a voice recording."
|
|
10416
10453
|
});
|
|
@@ -10515,6 +10552,183 @@ var notifPrefsPatchSchema = external_exports.object({
|
|
|
10515
10552
|
groups: external_exports.record(external_exports.enum(NOTIF_PREF_GROUPS), external_exports.boolean()).optional()
|
|
10516
10553
|
});
|
|
10517
10554
|
|
|
10555
|
+
// ../shared/src/remix.ts
|
|
10556
|
+
var REMIX_GUIDE = `# Make it remix-friendly
|
|
10557
|
+
|
|
10558
|
+
On vibed every published vibe is **remixable**: anyone can fork it and make it
|
|
10559
|
+
theirs, and the AI builder can be asked to change it. A vibe is only *really*
|
|
10560
|
+
remixable if a stranger \u2014 or a model editing it \u2014 can find the knobs without
|
|
10561
|
+
reading the whole file. Treat this as part of shipping, like mobile-first.
|
|
10562
|
+
|
|
10563
|
+
It pays off twice: edits to a well-structured file are applied as small precise
|
|
10564
|
+
patches instead of regenerating the whole thing, which is faster, cheaper, and
|
|
10565
|
+
far less likely to break what already worked.
|
|
10566
|
+
|
|
10567
|
+
## 1. One CONFIG block, at the top
|
|
10568
|
+
|
|
10569
|
+
Every tunable lives in a single object at the top of the script \u2014 the first
|
|
10570
|
+
thing anyone sees:
|
|
10571
|
+
|
|
10572
|
+
\`\`\`js
|
|
10573
|
+
const CONFIG = {
|
|
10574
|
+
// --- Feel ---
|
|
10575
|
+
gravity: 0.6,
|
|
10576
|
+
jumpForce: -11,
|
|
10577
|
+
pipeGap: 160,
|
|
10578
|
+
pipeSpeed: 2.4,
|
|
10579
|
+
// --- Look ---
|
|
10580
|
+
skyColor: "#7ec8ff",
|
|
10581
|
+
birdColor: "#ffd166",
|
|
10582
|
+
// --- Words (all user-facing text) ---
|
|
10583
|
+
title: "Tap to Fly",
|
|
10584
|
+
gameOverText: "Ouch! Tap to try again",
|
|
10585
|
+
scoreLabel: "Score",
|
|
10586
|
+
};
|
|
10587
|
+
\`\`\`
|
|
10588
|
+
|
|
10589
|
+
Rules that make it useful:
|
|
10590
|
+
- **Literal values only** \u2014 numbers, strings, colors. No computed expressions.
|
|
10591
|
+
- **Everything reads from CONFIG.** If a number appears twice in the logic, it
|
|
10592
|
+
belongs in CONFIG once.
|
|
10593
|
+
- **All user-facing text goes here too**, so rewording or translating a vibe is
|
|
10594
|
+
one edit, not a hunt.
|
|
10595
|
+
|
|
10596
|
+
## 2. Content separate from logic
|
|
10597
|
+
|
|
10598
|
+
Questions, levels, items, cards, prompts \u2014 a labelled array right after CONFIG:
|
|
10599
|
+
|
|
10600
|
+
\`\`\`js
|
|
10601
|
+
const QUESTIONS = [
|
|
10602
|
+
{ q: "Pick a color", options: ["Red", "Blue"], answer: 0 },
|
|
10603
|
+
// add more here \u2014 same shape
|
|
10604
|
+
];
|
|
10605
|
+
\`\`\`
|
|
10606
|
+
|
|
10607
|
+
Now "add five more questions" is a data edit anyone can make.
|
|
10608
|
+
|
|
10609
|
+
## 3. Predictable section order
|
|
10610
|
+
|
|
10611
|
+
Lay the script out in this order, each with a one-line banner comment:
|
|
10612
|
+
|
|
10613
|
+
\`\`\`js
|
|
10614
|
+
// ============ CONFIG ============
|
|
10615
|
+
// ============ CONTENT ===========
|
|
10616
|
+
// ============ STATE =============
|
|
10617
|
+
// ============ HELPERS ===========
|
|
10618
|
+
// ============ RENDER ============
|
|
10619
|
+
// ============ INPUT =============
|
|
10620
|
+
// ============ INIT ==============
|
|
10621
|
+
\`\`\`
|
|
10622
|
+
|
|
10623
|
+
A remixer (and a patching model) then knows where a change belongs before
|
|
10624
|
+
reading a single line.
|
|
10625
|
+
|
|
10626
|
+
## 4. Write it plainly
|
|
10627
|
+
|
|
10628
|
+
- Descriptive names: \`spawnEnemy()\`, not \`se()\`; \`playerSpeed\`, not \`ps\`.
|
|
10629
|
+
- Small functions that do one thing. One \`init()\` entry point, one game loop.
|
|
10630
|
+
- Two-space indent, no minification, no bundler output, no clever one-liners or
|
|
10631
|
+
chained ternaries.
|
|
10632
|
+
- A short comment at each seam someone would want to change:
|
|
10633
|
+
\`// Win condition \u2014 swap this for a timer, a score target, ...\`
|
|
10634
|
+
- Stable \`id\`/\`class\` names on the elements a remixer would target.
|
|
10635
|
+
- No dead code, and no copy-pasted near-identical blocks \u2014 factor them into one
|
|
10636
|
+
function. (Duplicated blocks also make an automated edit ambiguous, so it
|
|
10637
|
+
fails or lands in the wrong place.)
|
|
10638
|
+
|
|
10639
|
+
## 5. Quick self-check before publishing
|
|
10640
|
+
|
|
10641
|
+
- Could someone change the colors, speed, and all the words by editing only
|
|
10642
|
+
CONFIG?
|
|
10643
|
+
- Could they add one more level/question/item by editing only the content array?
|
|
10644
|
+
- Is there any magic number or user-facing string outside those two blocks?
|
|
10645
|
+
- Would a stranger know where to add a feature within ten seconds of scrolling?
|
|
10646
|
+
|
|
10647
|
+
If the answer to any of those is no, fix it before you publish \u2014 that is the
|
|
10648
|
+
difference between a vibe people fork and one they scroll past.`;
|
|
10649
|
+
var SMALL_VIBE_CHARS = 1200;
|
|
10650
|
+
var SMALL_VIBE_LINES = 40;
|
|
10651
|
+
var COMMENT_EXPECTED_LINES = 60;
|
|
10652
|
+
var SECTIONS_EXPECTED_LINES = 120;
|
|
10653
|
+
var MINIFIED_LINE_CHARS = 400;
|
|
10654
|
+
var MINIFIED_AVG_LINE_CHARS = 160;
|
|
10655
|
+
function authorScript(html) {
|
|
10656
|
+
const parts = [];
|
|
10657
|
+
const re = /<script\b([^>]*)>([\s\S]*?)<\/script\s*>/gi;
|
|
10658
|
+
let m;
|
|
10659
|
+
while (m = re.exec(html)) {
|
|
10660
|
+
const attrs = m[1] ?? "";
|
|
10661
|
+
if (/data-vibed-bridge/i.test(attrs)) continue;
|
|
10662
|
+
if (/\bsrc=/i.test(attrs)) continue;
|
|
10663
|
+
parts.push(m[2] ?? "");
|
|
10664
|
+
}
|
|
10665
|
+
return parts.join("\n");
|
|
10666
|
+
}
|
|
10667
|
+
function remixAudit(html) {
|
|
10668
|
+
const script = authorScript(html ?? "");
|
|
10669
|
+
const lines = script.split(/\r?\n/);
|
|
10670
|
+
const codeLines = lines.filter((l) => l.trim() !== "");
|
|
10671
|
+
const scriptLines = codeLines.length;
|
|
10672
|
+
const scriptChars = codeLines.reduce((n, l) => n + l.trim().length, 0);
|
|
10673
|
+
const avgLine = scriptLines > 0 ? scriptChars / scriptLines : 0;
|
|
10674
|
+
const findings = [];
|
|
10675
|
+
const longest = codeLines.reduce((n, l) => Math.max(n, l.trim().length), 0);
|
|
10676
|
+
const denselyPacked = scriptChars > 600 && avgLine > MINIFIED_AVG_LINE_CHARS;
|
|
10677
|
+
if (longest > MINIFIED_LINE_CHARS || denselyPacked) {
|
|
10678
|
+
findings.push({
|
|
10679
|
+
code: "MINIFIED",
|
|
10680
|
+
blocking: true,
|
|
10681
|
+
message: `The script averages ${Math.round(avgLine)} characters per line (longest ${longest}) \u2014 this looks minified or bundled.`,
|
|
10682
|
+
fix: "Publish readable source: turn off minification for the vibe build, or inline the unminified code."
|
|
10683
|
+
});
|
|
10684
|
+
}
|
|
10685
|
+
if (scriptLines >= SMALL_VIBE_LINES || scriptChars >= SMALL_VIBE_CHARS) {
|
|
10686
|
+
if (!/\b(?:const|let|var)\s+CONFIG\s*=/.test(script)) {
|
|
10687
|
+
findings.push({
|
|
10688
|
+
code: "NO_CONFIG",
|
|
10689
|
+
blocking: true,
|
|
10690
|
+
message: "No `const CONFIG = { ... }` block \u2014 a remixer has to hunt through the logic to change anything.",
|
|
10691
|
+
fix: "Move every tunable (speeds, sizes, colors, counts, durations) AND all user-facing text into one CONFIG object at the top, and read from it everywhere."
|
|
10692
|
+
});
|
|
10693
|
+
}
|
|
10694
|
+
const cryptic = /* @__PURE__ */ new Set();
|
|
10695
|
+
const decl = /\b(?:const|let|var)\s+([A-Za-z_$])\b(?!\s*[,)])/g;
|
|
10696
|
+
let d;
|
|
10697
|
+
while (d = decl.exec(script)) {
|
|
10698
|
+
const name = d[1];
|
|
10699
|
+
if (!"ijknxyt_".includes(name)) cryptic.add(name);
|
|
10700
|
+
}
|
|
10701
|
+
if (cryptic.size >= 5) {
|
|
10702
|
+
findings.push({
|
|
10703
|
+
code: "CRYPTIC_NAMES",
|
|
10704
|
+
blocking: true,
|
|
10705
|
+
message: `${cryptic.size} single-letter variables (${[...cryptic].slice(0, 6).join(", ")}) \u2014 the code reads as machine output.`,
|
|
10706
|
+
fix: "Give variables and functions descriptive names (`playerSpeed`, not `p`)."
|
|
10707
|
+
});
|
|
10708
|
+
}
|
|
10709
|
+
if (scriptLines >= COMMENT_EXPECTED_LINES) {
|
|
10710
|
+
const comments = codeLines.filter((l) => /^\s*(\/\/|\/\*|\*)/.test(l)).length;
|
|
10711
|
+
if (comments === 0) {
|
|
10712
|
+
findings.push({
|
|
10713
|
+
code: "NO_COMMENTS",
|
|
10714
|
+
blocking: false,
|
|
10715
|
+
message: "The script has no comments at all.",
|
|
10716
|
+
fix: "Add a short note at each seam a remixer would change (the win condition, the spawn rule, the scoring)."
|
|
10717
|
+
});
|
|
10718
|
+
}
|
|
10719
|
+
}
|
|
10720
|
+
if (scriptLines >= SECTIONS_EXPECTED_LINES && !/CONFIG\s*=+\s*$|=== *CONFIG|-- *CONFIG/im.test(script)) {
|
|
10721
|
+
findings.push({
|
|
10722
|
+
code: "NO_SECTIONS",
|
|
10723
|
+
blocking: false,
|
|
10724
|
+
message: "No section banners in a long script.",
|
|
10725
|
+
fix: "Split it with banner comments: CONFIG \u2192 CONTENT \u2192 STATE \u2192 helpers \u2192 render \u2192 input \u2192 init."
|
|
10726
|
+
});
|
|
10727
|
+
}
|
|
10728
|
+
}
|
|
10729
|
+
return { ok: !findings.some((f) => f.blocking), findings, scriptLines };
|
|
10730
|
+
}
|
|
10731
|
+
|
|
10518
10732
|
// ../shared/src/builderEvents.ts
|
|
10519
10733
|
var BUILD_EVENT_VERSION = 1;
|
|
10520
10734
|
var BUILD_PHASES = [
|
|
@@ -11172,8 +11386,10 @@ async function bundleProject(projectDir, opts = {}) {
|
|
|
11172
11386
|
allowedOrigins
|
|
11173
11387
|
});
|
|
11174
11388
|
const result = validate(html, { maxBytes, allowedOrigins, policy: opts.policy });
|
|
11389
|
+
const remix = remixAudit(html);
|
|
11175
11390
|
return {
|
|
11176
|
-
ok: result.ok,
|
|
11391
|
+
ok: result.ok && remix.ok,
|
|
11392
|
+
remix,
|
|
11177
11393
|
html,
|
|
11178
11394
|
policy: opts.policy,
|
|
11179
11395
|
entry: resolved.entry,
|
|
@@ -11234,12 +11450,28 @@ function formatReport(result) {
|
|
|
11234
11450
|
result.sizeBytes != null ? `size: ${fmtSize(result.sizeBytes)}` : null
|
|
11235
11451
|
].filter(Boolean).join(" \xB7 ");
|
|
11236
11452
|
if (where) out2.push(where);
|
|
11453
|
+
const remixBlockers = (result.remix?.findings ?? []).filter((f) => f.blocking);
|
|
11237
11454
|
if (result.ok) {
|
|
11238
11455
|
out2.push("\u2713 Vibeable \u2014 this project can be published to vibed.");
|
|
11239
|
-
} else {
|
|
11456
|
+
} else if (result.blockers.length > 0) {
|
|
11240
11457
|
out2.push(`\u2717 Not vibeable yet \u2014 ${result.blockers.length} blocker(s):`);
|
|
11241
11458
|
out2.push(...summarize(result).lines);
|
|
11242
11459
|
}
|
|
11460
|
+
if (remixBlockers.length > 0) {
|
|
11461
|
+
out2.push("");
|
|
11462
|
+
out2.push(`\u2717 Not remixable yet \u2014 ${remixBlockers.length} thing(s) to fix before publishing:`);
|
|
11463
|
+
for (const f of remixBlockers) {
|
|
11464
|
+
out2.push(` \u2022 ${f.message}`);
|
|
11465
|
+
out2.push(` \u2192 ${f.fix}`);
|
|
11466
|
+
}
|
|
11467
|
+
out2.push(" Run `vibed remixable` for the full guide.");
|
|
11468
|
+
}
|
|
11469
|
+
const remixAdvice = (result.remix?.findings ?? []).filter((f) => !f.blocking);
|
|
11470
|
+
if (remixAdvice.length > 0) {
|
|
11471
|
+
out2.push("");
|
|
11472
|
+
out2.push("Remix polish (not blocking):");
|
|
11473
|
+
for (const f of remixAdvice) out2.push(` \u2022 ${f.message} \u2192 ${f.fix}`);
|
|
11474
|
+
}
|
|
11243
11475
|
if (result.policy && ruleActive("no_vertical_scroll", result.policy)) {
|
|
11244
11476
|
out2.push(
|
|
11245
11477
|
"",
|
|
@@ -11521,6 +11753,82 @@ const m = await vibed.storage.meta(); // { postId, ... } \u2014 this post'
|
|
|
11521
11753
|
5. Re-run \`vibed check\` \u2014 the \`BROWSER_STORAGE\` warning should be gone.
|
|
11522
11754
|
`;
|
|
11523
11755
|
|
|
11756
|
+
// src/version.ts
|
|
11757
|
+
var import_node_os3 = require("node:os");
|
|
11758
|
+
var import_node_path4 = require("node:path");
|
|
11759
|
+
var import_promises3 = require("node:fs/promises");
|
|
11760
|
+
var VERSION = true ? "0.2.0" : "0.0.0-dev";
|
|
11761
|
+
var PKG = "@amirhosseinnateghi/vibed-cli";
|
|
11762
|
+
var REGISTRY = `https://registry.npmjs.org/${PKG}/latest`;
|
|
11763
|
+
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
11764
|
+
var FETCH_TIMEOUT_MS = 2e3;
|
|
11765
|
+
var stateFile = () => (0, import_node_path4.join)((0, import_node_os3.homedir)(), ".vibed", "update-check.json");
|
|
11766
|
+
function isOutdated(current, latest) {
|
|
11767
|
+
const parse4 = (v) => {
|
|
11768
|
+
const core = v.trim().replace(/^v/, "").split("-")[0];
|
|
11769
|
+
if (!core || !/^\d+(\.\d+)*$/.test(core)) return null;
|
|
11770
|
+
return core.split(".").map(Number);
|
|
11771
|
+
};
|
|
11772
|
+
const a = parse4(current);
|
|
11773
|
+
const b = parse4(latest);
|
|
11774
|
+
if (!a || !b) return false;
|
|
11775
|
+
for (let i = 0; i < Math.max(a.length, b.length); i++) {
|
|
11776
|
+
const x = a[i] ?? 0;
|
|
11777
|
+
const y = b[i] ?? 0;
|
|
11778
|
+
if (y > x) return true;
|
|
11779
|
+
if (y < x) return false;
|
|
11780
|
+
}
|
|
11781
|
+
return false;
|
|
11782
|
+
}
|
|
11783
|
+
function updateCheckEnabled(argv, env) {
|
|
11784
|
+
if (argv.includes("--json")) return false;
|
|
11785
|
+
if (env.VIBED_NO_UPDATE_CHECK === "1" || env.NO_UPDATE_NOTIFIER === "1") return false;
|
|
11786
|
+
if (env.CI) return false;
|
|
11787
|
+
if (VERSION === "0.0.0-dev") return false;
|
|
11788
|
+
return true;
|
|
11789
|
+
}
|
|
11790
|
+
function updateHint(latest, env) {
|
|
11791
|
+
const viaPlugin = !!env.CLAUDE_PLUGIN_ROOT;
|
|
11792
|
+
const how = viaPlugin ? "update it with: /plugin update make-it-vibed" : `update it with: npm i -g ${PKG}`;
|
|
11793
|
+
return `vibed ${VERSION} is out of date (latest ${latest}) \u2014 ${how}`;
|
|
11794
|
+
}
|
|
11795
|
+
async function readState() {
|
|
11796
|
+
try {
|
|
11797
|
+
return JSON.parse(await (0, import_promises3.readFile)(stateFile(), "utf8"));
|
|
11798
|
+
} catch {
|
|
11799
|
+
return {};
|
|
11800
|
+
}
|
|
11801
|
+
}
|
|
11802
|
+
async function writeState(latest) {
|
|
11803
|
+
try {
|
|
11804
|
+
await (0, import_promises3.mkdir)((0, import_node_path4.join)((0, import_node_os3.homedir)(), ".vibed"), { recursive: true });
|
|
11805
|
+
await (0, import_promises3.writeFile)(stateFile(), JSON.stringify({ checkedAt: Date.now(), latest }), "utf8");
|
|
11806
|
+
} catch {
|
|
11807
|
+
}
|
|
11808
|
+
}
|
|
11809
|
+
async function checkForUpdate(argv = process.argv, env = process.env) {
|
|
11810
|
+
if (!updateCheckEnabled(argv, env)) return null;
|
|
11811
|
+
const state = await readState();
|
|
11812
|
+
if (state.checkedAt && Date.now() - state.checkedAt < CHECK_INTERVAL_MS) {
|
|
11813
|
+
return state.latest && isOutdated(VERSION, state.latest) ? updateHint(state.latest, env) : null;
|
|
11814
|
+
}
|
|
11815
|
+
let latest = null;
|
|
11816
|
+
try {
|
|
11817
|
+
const res = await fetch(REGISTRY, {
|
|
11818
|
+
headers: { accept: "application/vnd.npm.install-v1+json, application/json" },
|
|
11819
|
+
signal: AbortSignal.timeout?.(FETCH_TIMEOUT_MS)
|
|
11820
|
+
});
|
|
11821
|
+
if (res.ok) {
|
|
11822
|
+
const body = await res.json();
|
|
11823
|
+
if (typeof body.version === "string") latest = body.version;
|
|
11824
|
+
}
|
|
11825
|
+
} catch {
|
|
11826
|
+
}
|
|
11827
|
+
if (!latest) return null;
|
|
11828
|
+
await writeState(latest);
|
|
11829
|
+
return isOutdated(VERSION, latest) ? updateHint(latest, env) : null;
|
|
11830
|
+
}
|
|
11831
|
+
|
|
11524
11832
|
// src/index.ts
|
|
11525
11833
|
function parseArgs(argv) {
|
|
11526
11834
|
const _ = [];
|
|
@@ -11554,6 +11862,7 @@ Usage:
|
|
|
11554
11862
|
vibed login Authenticate this machine (opens the browser)
|
|
11555
11863
|
vibed check [path] Check if a project can be published (no network)
|
|
11556
11864
|
vibed storage Print the persistence guide (leaderboards, saves, shared state)
|
|
11865
|
+
vibed remixable Print the remix-friendly authoring guide (CONFIG block, readable code)
|
|
11557
11866
|
vibed preview [path] Bundle + open the artifact locally (no publish)
|
|
11558
11867
|
vibed draft [path] Upload a private, hosted preview link (no publish)
|
|
11559
11868
|
vibed publish [path] Bundle the project and publish it to vibed
|
|
@@ -11561,6 +11870,7 @@ Usage:
|
|
|
11561
11870
|
vibed delete <id|url> Delete one of your posts
|
|
11562
11871
|
vibed whoami Show the signed-in account
|
|
11563
11872
|
vibed logout Remove the saved token
|
|
11873
|
+
vibed version Print the connector version (--version works too)
|
|
11564
11874
|
|
|
11565
11875
|
Flags:
|
|
11566
11876
|
--api <url> API base (or VIBED_API env). Default: production
|
|
@@ -11572,7 +11882,7 @@ Publish only:
|
|
|
11572
11882
|
tags (pick from): ${TAGS.join(", ")}`;
|
|
11573
11883
|
async function cmdCheck(args) {
|
|
11574
11884
|
const json = isTrue(args.flags.json);
|
|
11575
|
-
const dir2 = (0,
|
|
11885
|
+
const dir2 = (0, import_node_path5.resolve)(args._[1] ?? ".");
|
|
11576
11886
|
const cfg = await loadConfig({ api: str(args.flags.api) });
|
|
11577
11887
|
const policy = isTrue(args.flags["local-policy"]) ? void 0 : await fetchPolicy(cfg.apiBase);
|
|
11578
11888
|
if (!json && policy) console.error(`Rules: ${cfg.apiBase}`);
|
|
@@ -11596,7 +11906,7 @@ async function cmdCheck(args) {
|
|
|
11596
11906
|
}
|
|
11597
11907
|
async function cmdPreview(args) {
|
|
11598
11908
|
const json = isTrue(args.flags.json);
|
|
11599
|
-
const dir2 = (0,
|
|
11909
|
+
const dir2 = (0, import_node_path5.resolve)(args._[1] ?? ".");
|
|
11600
11910
|
const cfg = await loadConfig({ api: str(args.flags.api) });
|
|
11601
11911
|
const policy = isTrue(args.flags["local-policy"]) ? void 0 : await fetchPolicy(cfg.apiBase);
|
|
11602
11912
|
const result = await bundleProject(dir2, {
|
|
@@ -11610,8 +11920,8 @@ async function cmdPreview(args) {
|
|
|
11610
11920
|
if (json) out(json, "", { ok: false, fatal: result.fatal });
|
|
11611
11921
|
return 1;
|
|
11612
11922
|
}
|
|
11613
|
-
const file2 = (0,
|
|
11614
|
-
await (0,
|
|
11923
|
+
const file2 = (0, import_node_path5.join)((0, import_node_os4.tmpdir)(), `vibed-preview-${result.sizeBytes ?? 0}.html`);
|
|
11924
|
+
await (0, import_promises4.writeFile)(file2, result.html, "utf8");
|
|
11615
11925
|
const url = (0, import_node_url.pathToFileURL)(file2).href;
|
|
11616
11926
|
if (json) {
|
|
11617
11927
|
out(json, "", { ok: result.ok, previewFile: file2, previewUrl: url });
|
|
@@ -11648,7 +11958,7 @@ function reportPublishError(e) {
|
|
|
11648
11958
|
}
|
|
11649
11959
|
async function cmdDraft(args) {
|
|
11650
11960
|
const json = isTrue(args.flags.json);
|
|
11651
|
-
const dir2 = (0,
|
|
11961
|
+
const dir2 = (0, import_node_path5.resolve)(args._[1] ?? ".");
|
|
11652
11962
|
const cfg = await loadConfig({ api: str(args.flags.api) });
|
|
11653
11963
|
if (!cfg.token) {
|
|
11654
11964
|
console.error("Not signed in. Run `vibed login` first.");
|
|
@@ -11700,7 +12010,7 @@ async function cmdPublish(args) {
|
|
|
11700
12010
|
return reportPublishError(e);
|
|
11701
12011
|
}
|
|
11702
12012
|
}
|
|
11703
|
-
const dir2 = (0,
|
|
12013
|
+
const dir2 = (0, import_node_path5.resolve)(args._[1] ?? ".");
|
|
11704
12014
|
const policy = await fetchPolicy(cfg.apiBase);
|
|
11705
12015
|
const result = await bundleProject(dir2, {
|
|
11706
12016
|
entry: str(args.flags.entry),
|
|
@@ -11710,7 +12020,7 @@ async function cmdPublish(args) {
|
|
|
11710
12020
|
});
|
|
11711
12021
|
if (!result.ok || !result.html) {
|
|
11712
12022
|
out(json, formatReport(result), { ...result, html: void 0 });
|
|
11713
|
-
console.error("\nFix
|
|
12023
|
+
console.error("\nFix everything above, then run `vibed publish` again.");
|
|
11714
12024
|
return 1;
|
|
11715
12025
|
}
|
|
11716
12026
|
if (!json) console.log(formatReport(result));
|
|
@@ -11791,7 +12101,12 @@ async function cmdLogout() {
|
|
|
11791
12101
|
return 0;
|
|
11792
12102
|
}
|
|
11793
12103
|
async function main() {
|
|
11794
|
-
const
|
|
12104
|
+
const raw = process.argv.slice(2);
|
|
12105
|
+
if (raw.includes("--version") || raw.includes("-v")) {
|
|
12106
|
+
console.log(VERSION);
|
|
12107
|
+
return 0;
|
|
12108
|
+
}
|
|
12109
|
+
const args = parseArgs(raw);
|
|
11795
12110
|
const cmd = args._[0];
|
|
11796
12111
|
switch (cmd) {
|
|
11797
12112
|
case "login":
|
|
@@ -11805,6 +12120,9 @@ async function main() {
|
|
|
11805
12120
|
case "storage":
|
|
11806
12121
|
console.log(STORAGE_GUIDE);
|
|
11807
12122
|
return 0;
|
|
12123
|
+
case "remixable":
|
|
12124
|
+
console.log(REMIX_GUIDE);
|
|
12125
|
+
return 0;
|
|
11808
12126
|
case "preview":
|
|
11809
12127
|
return cmdPreview(args);
|
|
11810
12128
|
case "draft":
|
|
@@ -11814,6 +12132,11 @@ async function main() {
|
|
|
11814
12132
|
case "delete":
|
|
11815
12133
|
case "rm":
|
|
11816
12134
|
return cmdDelete(args);
|
|
12135
|
+
case "version":
|
|
12136
|
+
case "--version":
|
|
12137
|
+
case "-v":
|
|
12138
|
+
console.log(VERSION);
|
|
12139
|
+
return 0;
|
|
11817
12140
|
case void 0:
|
|
11818
12141
|
case "help":
|
|
11819
12142
|
case "--help":
|
|
@@ -11826,7 +12149,15 @@ ${USAGE}`);
|
|
|
11826
12149
|
return 1;
|
|
11827
12150
|
}
|
|
11828
12151
|
}
|
|
11829
|
-
main().then(
|
|
12152
|
+
main().then(async (code) => {
|
|
12153
|
+
const nudge = await Promise.race([
|
|
12154
|
+
checkForUpdate().catch(() => null),
|
|
12155
|
+
new Promise((r) => setTimeout(() => r(null), 2500))
|
|
12156
|
+
]);
|
|
12157
|
+
if (nudge) console.error(`
|
|
12158
|
+
${nudge}`);
|
|
12159
|
+
process.exit(code);
|
|
12160
|
+
}).catch((e) => {
|
|
11830
12161
|
console.error(e instanceof Error ? e.message : String(e));
|
|
11831
12162
|
process.exit(1);
|
|
11832
12163
|
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@amirhosseinnateghi/vibed-cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "make it vibed
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "make it vibed \u2014 check, bundle, and publish a project to vibed from the terminal",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
"access": "public"
|
|
20
20
|
},
|
|
21
21
|
"scripts": {
|
|
22
|
-
"build": "esbuild src/index.ts --bundle --platform=node --target=node20 --format=esm --outfile=dist/index.js && chmod +x dist/index.js",
|
|
23
|
-
"build:cjs": "esbuild src/index.ts --bundle --platform=node --target=node20 --format=cjs --outfile=dist/vibed.cjs && chmod +x dist/vibed.cjs",
|
|
22
|
+
"build": "esbuild src/index.ts --bundle --platform=node --target=node20 --format=esm --define:__VIBED_VERSION__='\"0.2.0\"' --outfile=dist/index.js && chmod +x dist/index.js",
|
|
23
|
+
"build:cjs": "esbuild src/index.ts --bundle --platform=node --target=node20 --format=cjs --define:__VIBED_VERSION__='\"0.2.0\"' --outfile=dist/vibed.cjs && chmod +x dist/vibed.cjs",
|
|
24
24
|
"prepublishOnly": "pnpm build",
|
|
25
25
|
"dev": "tsx src/index.ts",
|
|
26
26
|
"test": "vitest run",
|