@marver-design/marver 0.11.0 → 0.12.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.
Files changed (41) hide show
  1. package/CHANGELOG.md +83 -5
  2. package/README.md +17 -4
  3. package/dist/auth-DdUKeVKt.mjs +1150 -0
  4. package/dist/{build-Cr_ZLMdq.mjs → build-DmfxJc6L.mjs} +188 -33
  5. package/dist/cli.mjs +16 -7
  6. package/dist/collab-BmaOjwP1.mjs +781 -0
  7. package/dist/{comments-CRqaO2MH.mjs → comments-pNxjTrGN.mjs} +3 -2
  8. package/dist/{daemon-DSmaS453.mjs → daemon-WjonKlPz.mjs} +3 -3
  9. package/dist/{dev-CLBjRs0J.mjs → dev-BXq7yE1p.mjs} +5 -5
  10. package/dist/events-C9k-ceOj.mjs +101 -0
  11. package/dist/{init-BSclDg4I.mjs → init-BpitOqRQ.mjs} +2 -3
  12. package/dist/{config-t9coJ-Pq.mjs → manifest-CS6krOTe.mjs} +236 -4
  13. package/dist/{marver-id-B8-3WiHk.mjs → marver-id-CVCoaMGI.mjs} +106 -1
  14. package/dist/{marver-id-gate-BDzW6ahN.mjs → marver-id-gate-DOjnZakC.mjs} +94 -6
  15. package/dist/{plugin-vE7sxEeT.mjs → plugin-Boy7I_JH.mjs} +82 -9
  16. package/dist/{profile-DcsJyppw.mjs → profile-ChpTPd-X.mjs} +1 -1
  17. package/dist/secure-cookie-_K1Hsx8H.mjs +26 -0
  18. package/dist/{serve-CZ1KEQxd.mjs → serve-BeLLizNg.mjs} +72 -18
  19. package/dist/share-C64b1tdN.mjs +159 -0
  20. package/dist/summary-Bf9tiGrE.mjs +211 -0
  21. package/dist/{sync-DELGomPk.mjs → sync-WYxDN9IT.mjs} +3 -102
  22. package/package.json +1 -1
  23. package/src/client/shell/App.tsx +43 -8
  24. package/src/client/shell/Comments.tsx +9 -5
  25. package/src/client/shell/LockedApp.tsx +113 -0
  26. package/src/client/shell/Play.tsx +97 -18
  27. package/src/client/shell/Toolbar.tsx +26 -2
  28. package/src/client/shell/canvas/Canvas.tsx +5 -15
  29. package/src/client/shell/canvas/ctl.ts +26 -0
  30. package/src/client/shell/comments-store.ts +39 -9
  31. package/src/client/shell/hash.ts +19 -0
  32. package/src/client/shell/icons.tsx +1 -0
  33. package/src/client/shell/store.ts +54 -3
  34. package/src/client/shell/styles.css +27 -0
  35. package/src/shared/events.ts +8 -3
  36. package/templates/instructions/publish.md +15 -1
  37. package/dist/auth-B5yuwnOq.mjs +0 -494
  38. package/dist/collab-C-n-uQdm.mjs +0 -364
  39. package/dist/manifest-C2tzkNaC.mjs +0 -233
  40. package/dist/update-DuWDj5nR.mjs +0 -77
  41. package/dist/utm-CxC3QN5X.mjs +0 -20
@@ -1,10 +1,10 @@
1
1
  import { i as ROUTE, n as NAME } from "./cli.mjs";
2
- import { i as detectHost, n as loadConfig } from "./config-t9coJ-Pq.mjs";
3
- import { r as scanFrames } from "./manifest-C2tzkNaC.mjs";
4
- import { n as tailwind3Css, r as tailwind4Plugin, t as marverPlugin } from "./plugin-vE7sxEeT.mjs";
5
- import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, writeFileSync } from "node:fs";
2
+ import { l as detectHost, r as scanFrames, s as loadConfig } from "./manifest-CS6krOTe.mjs";
3
+ import { n as tailwind3Css, r as tailwind4Plugin, t as marverPlugin } from "./plugin-Boy7I_JH.mjs";
4
+ import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, writeFileSync } from "node:fs";
6
5
  import { basename, dirname, join, sep } from "node:path";
7
6
  import { fileURLToPath } from "node:url";
7
+ import { createHash, randomBytes } from "node:crypto";
8
8
  import { build } from "vite";
9
9
  import react from "@vitejs/plugin-react";
10
10
  //#region src/server/build.ts
@@ -44,19 +44,47 @@ function scanAssetRefs(src, moduleId) {
44
44
  }
45
45
  /** Same shape the client's assetUrl accepts: relative, inside design/assets/, no tricks. */
46
46
  const isLocalAssetRef = (p) => !!p && !p.includes(":") && !p.startsWith("/") && !p.startsWith("\\") && !p.split("/").some((s) => s === ".." || s === "");
47
- /** The publish policy: design/publish.json names what ships and with what
48
- * rights. Publishing is default-CLOSED - no policy and no explicit flag = no build.
49
- * Returns name -> 'read' | 'comment'. Boards absent from the result do not ship. */
50
- function resolvePublish(root, allBoards, boardsFlag, allBoardsFlag) {
47
+ const ARTIFACT_TYPES = [
48
+ "doc",
49
+ "slides",
50
+ "design",
51
+ "sketch",
52
+ "refs",
53
+ "mix"
54
+ ];
55
+ const VIEW_MODES = [
56
+ "canvas",
57
+ "board",
58
+ "present",
59
+ "focus",
60
+ "slides"
61
+ ];
62
+ /** Publishing is default-CLOSED - no policy and no explicit flag = no build.
63
+ * Boards absent from the result do not ship. */
64
+ function resolvePolicy(root, allBoards, boardsFlag, allBoardsFlag) {
51
65
  const known = (n) => n === "all-scenes" || !!allBoards[n];
66
+ const reveal = {
67
+ structure: true,
68
+ source: false
69
+ };
70
+ const row = (max) => ({
71
+ max,
72
+ type: "mix"
73
+ });
52
74
  if (boardsFlag !== void 0) {
53
75
  const names = boardsFlag.split(",").map((s) => s.trim()).filter(Boolean);
54
76
  if (!names.length) throw new Error("--boards was given but named no boards");
55
77
  const missing = names.filter((n) => !known(n));
56
78
  if (missing.length) throw new Error(`--boards names not found in design/boards/: ${missing.join(", ")}`);
57
- return Object.fromEntries(names.map((n) => [n, "comment"]));
79
+ return {
80
+ boards: Object.fromEntries(names.map((n) => [n, row("comment")])),
81
+ reveal
82
+ };
58
83
  }
59
- if (allBoardsFlag) return Object.fromEntries(["all-scenes", ...Object.keys(allBoards).filter((n) => n !== "all-scenes")].map((n) => [n, "comment"]));
84
+ if (allBoardsFlag) return {
85
+ boards: Object.fromEntries(["all-scenes", ...Object.keys(allBoards).filter((n) => n !== "all-scenes")].map((n) => [n, row("comment")])),
86
+ reveal
87
+ };
60
88
  const policyFile = join(root, "design", "publish.json");
61
89
  if (!existsSync(policyFile)) throw new Error("publishing is default-closed and no publish policy exists.\n Either declare one in design/publish.json: { \"boards\": { \"<board>\": \"read\" | \"comment\" } }\n or be explicit: --boards a,b (publish just those) · --all-boards (publish everything)");
62
90
  let policy;
@@ -70,10 +98,33 @@ function resolvePublish(root, allBoards, boardsFlag, allBoardsFlag) {
70
98
  const out = {};
71
99
  for (const [n, level] of entries) {
72
100
  if (!known(n)) throw new Error(`design/publish.json names an unknown board: ${n}`);
73
- if (level !== "read" && level !== "comment") throw new Error(`design/publish.json: board "${n}" has level "${level}" - use "read" or "comment"`);
74
- out[n] = level;
101
+ if (level === "read" || level === "comment") {
102
+ out[n] = row(level);
103
+ continue;
104
+ }
105
+ if (typeof level !== "object" || level === null) throw new Error(`design/publish.json: board "${n}" has level "${level}" - use "read" or "comment", or a v2 object`);
106
+ const p = level;
107
+ if (p.max !== "read" && p.max !== "comment") throw new Error(`design/publish.json: board "${n}" needs "max": "read" | "comment"`);
108
+ const type = p.type ?? "mix";
109
+ if (!ARTIFACT_TYPES.includes(type)) throw new Error(`design/publish.json: board "${n}" has type "${type}" - use ${ARTIFACT_TYPES.join(" | ")}`);
110
+ if (p.open !== void 0 && !VIEW_MODES.includes(p.open)) throw new Error(`design/publish.json: board "${n}" has open "${p.open}" - use ${VIEW_MODES.join(" | ")}`);
111
+ if (p.lock && p.open === void 0) throw new Error(`design/publish.json: board "${n}" sets "lock" without "open" - name the mode the lock freezes`);
112
+ out[n] = {
113
+ max: p.max,
114
+ type,
115
+ ...p.open ? { open: p.open } : {},
116
+ ...p.lock ? { lock: true } : {}
117
+ };
75
118
  }
76
- return out;
119
+ if (policy.reveal !== void 0) {
120
+ if (typeof policy.reveal !== "object" || policy.reveal === null) throw new Error("design/publish.json: \"reveal\" must be an object");
121
+ if (policy.reveal.structure !== void 0) reveal.structure = policy.reveal.structure === true;
122
+ if (policy.reveal.source !== void 0) reveal.source = policy.reveal.source === true;
123
+ }
124
+ return {
125
+ boards: out,
126
+ reveal
127
+ };
77
128
  }
78
129
  /** Read every board file; returns name -> parsed json. Bad JSON fails the build loudly. */
79
130
  function readBoards(root) {
@@ -91,6 +142,58 @@ function readBoards(root) {
91
142
  }
92
143
  return out;
93
144
  }
145
+ /**
146
+ * The generated registry with OPAQUE keys - the source strip's half of the
147
+ * bundle (01-sharing §6.2). Object keys are string literals that survive
148
+ * bundling verbatim, so with `reveal.source` off they must not be repo paths.
149
+ * The real paths appear only inside import() arguments, which rollup rewrites
150
+ * to chunk imports - they never reach the output. Layout chains are
151
+ * precomputed per key, because the path-walking logic needs the very strings
152
+ * being hidden.
153
+ */
154
+ function registrySourceOpaque(root, frames, salt) {
155
+ const bases = ["/design/scenes", "/design/components"];
156
+ const opaque = (s) => "k" + createHash("sha256").update(salt).update(s).digest("hex").slice(0, 10);
157
+ const layoutFiles = /* @__PURE__ */ new Set();
158
+ const chainFor = (key) => {
159
+ const base = bases.find((b) => key.startsWith(b + "/")) ?? bases[0];
160
+ const chain = [];
161
+ let dir = key.slice(0, key.lastIndexOf("/"));
162
+ while (dir.length >= base.length) {
163
+ for (const ext of ["tsx", "jsx"]) {
164
+ const lk = `${dir}/_layout.${ext}`;
165
+ if (existsSync(join(root, lk.slice(1)))) {
166
+ chain.unshift(lk);
167
+ layoutFiles.add(lk);
168
+ break;
169
+ }
170
+ }
171
+ if (dir === base) break;
172
+ dir = dir.slice(0, dir.lastIndexOf("/"));
173
+ }
174
+ return chain;
175
+ };
176
+ const rows = frames.map((f) => {
177
+ const key = "/" + posix(f.file);
178
+ return {
179
+ id: f.id,
180
+ key,
181
+ ok: opaque(key),
182
+ chain: []
183
+ };
184
+ });
185
+ for (const r of rows) r.chain = chainFor(r.key).map(opaque);
186
+ const providers = ["tsx", "jsx"].map((e) => `/design/providers.${e}`).filter((p) => existsSync(join(root, p.slice(1))));
187
+ return `// generated by \`${NAME} build\` - source paths are opaque (reveal.source is off)
188
+ export const frames = { ${rows.map((r) => `'${r.ok}': () => import('${r.key}')`).join(", ")} }
189
+ export const layouts = { ${[...layoutFiles].map((lk) => `'${opaque(lk)}': () => import('${lk}')`).join(", ")} }
190
+ export const providers = { ${providers.map((p, i) => `'p${i}': () => import('${p}')`).join(", ")} }
191
+ const idToKey = { ${rows.map((r) => `'${r.id}': '${r.ok}'`).join(", ")} }
192
+ export function frameFile(frameId) { return idToKey[frameId] ?? null }
193
+ const chains = { ${rows.map((r) => `'${r.ok}': ${JSON.stringify(r.chain)}`).join(", ")} }
194
+ export function layoutChain(fileKey) { return chains[fileKey] ?? [] }
195
+ `;
196
+ }
94
197
  /** The generated registry for filtered builds: explicit imports only, same exports. */
95
198
  function registrySource(root, frameKeys) {
96
199
  const bases = ["/design/scenes", "/design/components"];
@@ -136,7 +239,7 @@ export function layoutChain(fileKey) {
136
239
  }
137
240
  `;
138
241
  }
139
- async function buildSite(root, boardsFlag, allBoardsFlag) {
242
+ async function buildSite(root, boardsFlag, allBoardsFlag, embedSeeds) {
140
243
  const config = await loadConfig(root);
141
244
  const host = detectHost(root);
142
245
  const pkgDir = packageDir();
@@ -144,7 +247,11 @@ async function buildSite(root, boardsFlag, allBoardsFlag) {
144
247
  const outDir = join(root, "design", ".dist");
145
248
  const manifest = scanFrames(root);
146
249
  const allBoards = readBoards(root);
147
- const rights = resolvePublish(root, allBoards, boardsFlag, allBoardsFlag);
250
+ const policy = resolvePolicy(root, allBoards, boardsFlag, allBoardsFlag);
251
+ const rights = Object.fromEntries(Object.entries(policy.boards).map(([n, p]) => [n, p.max]));
252
+ const strip = !policy.reveal.source;
253
+ const buildSalt = randomBytes(16).toString("hex");
254
+ const opaquePath = (id, ext) => `__mv/f/mv${createHash("sha256").update(buildSalt).update(id).digest("hex").slice(0, 12)}${ext}`;
148
255
  const boardOrder = (n) => {
149
256
  const o = allBoards[n]?.order;
150
257
  return typeof o === "number" && Number.isFinite(o) ? o : Infinity;
@@ -159,29 +266,64 @@ async function buildSite(root, boardsFlag, allBoardsFlag) {
159
266
  for (const b of Object.values(boards)) for (const node of Array.isArray(b?.nodes) ? b.nodes : []) if (typeof node?.frame === "string") wanted.add(node.frame);
160
267
  frames = manifest.frames.filter((f) => wanted.has(f.id));
161
268
  }
269
+ const pubFrames = strip ? frames.map((f) => ({
270
+ ...f,
271
+ file: opaquePath(f.id, f.kind === "html" ? ".html" : "")
272
+ })) : frames;
273
+ const pubManifest = {
274
+ frames: pubFrames,
275
+ scenes: [...new Set(pubFrames.map((f) => f.scene))].sort().map((name) => ({
276
+ name,
277
+ frames: pubFrames.filter((f) => f.scene === name).length
278
+ }))
279
+ };
280
+ const boardsMeta = Object.fromEntries(publishedNames.filter((n) => policy.boards[n]).map((n) => {
281
+ const p = policy.boards[n];
282
+ return [n, {
283
+ type: p.type,
284
+ ...p.open ? { open: p.open } : {},
285
+ ...p.lock ? { lock: true } : {}
286
+ }];
287
+ }));
288
+ const lockedShell = publishedNames.length > 0 && publishedNames.every((n) => {
289
+ const p = policy.boards[n];
290
+ return !!p?.lock && !!p.open && [
291
+ "present",
292
+ "focus",
293
+ "slides"
294
+ ].includes(p.open);
295
+ });
162
296
  const data = {
163
- manifest: {
164
- frames,
165
- scenes: [...new Set(frames.map((f) => f.scene))].sort().map((name) => ({
166
- name,
167
- frames: frames.filter((f) => f.scene === name).length
168
- }))
169
- },
297
+ manifest: pubManifest,
170
298
  boards,
171
299
  names: publishedNames,
172
300
  default: publishedNames.find((n) => n !== "all-scenes") ?? publishedNames[0],
173
- rights
301
+ rights,
302
+ policy: {
303
+ boards: boardsMeta,
304
+ reveal: policy.reveal,
305
+ ...lockedShell ? { lockedShell: true } : {}
306
+ }
174
307
  };
175
308
  const registryFile = posix(join(clientDir, "frame-host", "registry.ts"));
309
+ const lockedFile = posix(join(clientDir, "shell", "LockedApp.tsx"));
310
+ const tsxFrames = frames.filter((f) => f.kind === "tsx");
176
311
  const plugins = [{
177
312
  name: "marver-build",
178
313
  enforce: "pre",
179
- resolveId(id) {
314
+ resolveId(id, importer) {
180
315
  if (id === "virtual:sh-data") return "\0sh-data-build";
316
+ if (lockedShell && (id === "./App.tsx" || id.endsWith("/shell/App.tsx")) && importer && posix(importer).endsWith("/shell/main.tsx")) return lockedFile;
181
317
  },
182
318
  load(id) {
183
319
  if (id === "\0sh-data-build") return `export default ${JSON.stringify(data)}`;
184
- if (!includeAll && posix(id) === registryFile) return registrySource(root, frames.filter((f) => f.kind === "tsx").map((f) => "/" + posix(f.file)));
320
+ if (posix(id) === registryFile) {
321
+ if (strip) return registrySourceOpaque(root, tsxFrames.map((f) => ({
322
+ id: f.id,
323
+ file: posix(f.file)
324
+ })), buildSalt);
325
+ if (!includeAll) return registrySource(root, tsxFrames.map((f) => "/" + posix(f.file)));
326
+ }
185
327
  }
186
328
  }, react()];
187
329
  if (host.tailwind === 4) {
@@ -196,8 +338,10 @@ async function buildSite(root, boardsFlag, allBoardsFlag) {
196
338
  config,
197
339
  detectedThemeCss: host.themeCss
198
340
  }));
341
+ process.env.NODE_ENV = "production";
199
342
  const result = await build({
200
343
  configFile: false,
344
+ mode: "production",
201
345
  root,
202
346
  css,
203
347
  plugins,
@@ -218,8 +362,8 @@ async function buildSite(root, boardsFlag, allBoardsFlag) {
218
362
  },
219
363
  output: {
220
364
  entryFileNames: "assets/[name]-[hash].js",
221
- chunkFileNames: "assets/[name]-[hash].js",
222
- assetFileNames: "assets/[name]-[hash][extname]"
365
+ chunkFileNames: strip ? "assets/c-[hash].js" : "assets/[name]-[hash].js",
366
+ assetFileNames: strip ? "assets/a-[hash][extname]" : "assets/[name]-[hash][extname]"
223
367
  }
224
368
  }
225
369
  },
@@ -258,8 +402,9 @@ async function buildSite(root, boardsFlag, allBoardsFlag) {
258
402
  let html = src.includes("</head>") ? src.replace("</head>", `${inject}</head>`) : inject + src;
259
403
  const shim = `<script>(function(){var a=Element.prototype.attachShadow;if(a)Element.prototype.attachShadow=function(i){if(i&&i.mode==='closed')window.__mvClosedShadow=1;return a.call(this,i)};})();<\/script>`;
260
404
  html = /<head[^>]*>/i.test(html) ? html.replace(/<head[^>]*>/i, (m) => m + shim) : shim + html;
261
- mkdirSync(dirname(join(outDir, f.file)), { recursive: true });
262
- writeFileSync(join(outDir, f.file), html);
405
+ const dest = strip ? opaquePath(f.id, ".html") : f.file;
406
+ mkdirSync(dirname(join(outDir, dest)), { recursive: true });
407
+ writeFileSync(join(outDir, dest), html);
263
408
  }
264
409
  const assetsDir = join(root, "design", "assets");
265
410
  const rootP = posix(root);
@@ -293,16 +438,22 @@ async function buildSite(root, boardsFlag, allBoardsFlag) {
293
438
  copiedAssets++;
294
439
  }
295
440
  const commentsDir = join(root, "design", "comments");
441
+ const seedOut = embedSeeds ? join(outDir, "design", "comments") : join(root, "design", ".dist-seeds");
442
+ if (!embedSeeds) rmSync(seedOut, {
443
+ recursive: true,
444
+ force: true
445
+ });
296
446
  if (existsSync(commentsDir)) {
297
447
  let seeded = 0;
298
448
  for (const n of publishedNames) {
299
449
  const f = join(commentsDir, `${n}.jsonl`);
300
450
  if (!existsSync(f)) continue;
301
- mkdirSync(join(outDir, "design", "comments"), { recursive: true });
302
- cpSync(f, join(outDir, "design", "comments", `${n}.jsonl`));
451
+ mkdirSync(seedOut, { recursive: true });
452
+ cpSync(f, join(seedOut, `${n}.jsonl`));
303
453
  seeded++;
304
454
  }
305
- if (seeded) console.log(` comments: ${seeded} board log${seeded === 1 ? "" : "s"} seeded`);
455
+ if (seeded) console.log(` comments: ${seeded} board log${seeded === 1 ? "" : "s"} seeded${embedSeeds ? "" : " (design/.dist-seeds, outside the web root)"}`);
456
+ if (seeded && embedSeeds) console.log(" WARNING: --embed-seeds put comment history in the web root - every event carries\n its author's email, readable by anyone who can reach the deployed files.");
306
457
  }
307
458
  let name = basename(root);
308
459
  try {
@@ -329,7 +480,11 @@ async function buildSite(root, boardsFlag, allBoardsFlag) {
329
480
  name,
330
481
  branding: config.share.branding,
331
482
  logo,
332
- rights
483
+ rights,
484
+ boards: boardsMeta,
485
+ reveal: policy.reveal,
486
+ ...config.share.frontDoor === false ? { frontDoor: false } : {},
487
+ ...config.share.notify === false ? { notify: false } : {}
333
488
  }));
334
489
  console.log(`\n ${NAME} build → design/.dist`);
335
490
  console.log(` boards: ${publishedNames.map((n) => `${n} (${rights[n]})`).join(", ")}`);
package/dist/cli.mjs CHANGED
@@ -39,14 +39,14 @@ function version() {
39
39
  }
40
40
  const cli = cac(NAME);
41
41
  cli.command("init", "Scaffold design/ in this repo").option("--mode <mode>", "studio | embedded", { default: "studio" }).option("--no-demo", "Skip the demo scene (the demo ships unless this flag is passed)").option("--root <dir>", "Host repo root", { default: "." }).action(async (opts) => {
42
- const { init } = await import("./init-BSclDg4I.mjs");
42
+ const { init } = await import("./init-BpitOqRQ.mjs");
43
43
  init(resolve(opts.root), {
44
44
  mode: opts.mode === "embedded" ? "embedded" : "studio",
45
45
  demo: opts.demo !== false
46
46
  });
47
47
  });
48
48
  for (const [name, desc] of [["dev", "Start the local canvas (everything on: hot reload, comments, Live Jam)"], ["canvas", "Start the local canvas - same as dev"]]) cli.command(name, desc).option("--root <dir>", "Host repo root", { default: "." }).option("--port <port>", "Port (default 5199)").action(async (opts) => {
49
- const { dev } = await import("./dev-CLBjRs0J.mjs");
49
+ const { dev } = await import("./dev-BXq7yE1p.mjs");
50
50
  let port;
51
51
  if (opts.port !== void 0) {
52
52
  const n = Number(opts.port);
@@ -55,18 +55,18 @@ for (const [name, desc] of [["dev", "Start the local canvas (everything on: hot
55
55
  }
56
56
  await dev(resolve(opts.root), port);
57
57
  });
58
- cli.command("build", "Static export → design/.dist (what ships comes from design/publish.json - publishing is default-closed)").option("--boards <names>", "Publish only these boards (comma-separated); overrides the publish policy").option("--all-boards", "Publish every board - the loud override for the default-closed policy").option("--root <dir>", "Host repo root", { default: "." }).action(async (opts) => {
59
- const { buildSite } = await import("./build-Cr_ZLMdq.mjs");
58
+ cli.command("build", "Static export → design/.dist (what ships comes from design/publish.json - publishing is default-closed)").option("--boards <names>", "Publish only these boards (comma-separated); overrides the publish policy").option("--all-boards", "Publish every board - the loud override for the default-closed policy").option("--embed-seeds", "Copy comment history INTO the web root (identifying - every event carries its author's email)").option("--root <dir>", "Host repo root", { default: "." }).action(async (opts) => {
59
+ const { buildSite } = await import("./build-DmfxJc6L.mjs");
60
60
  try {
61
61
  const boards = opts.boards === void 0 ? void 0 : typeof opts.boards === "string" ? opts.boards : "";
62
- await buildSite(resolve(opts.root), boards, opts.allBoards === true);
62
+ await buildSite(resolve(opts.root), boards, opts.allBoards === true, opts.embedSeeds === true);
63
63
  } catch (err) {
64
64
  console.error(`[${NAME}] build failed: ${err.message}`);
65
65
  process.exit(1);
66
66
  }
67
67
  });
68
68
  cli.command("serve", "Serve design/.dist (set MARVER_PASSWORD to gate it)").option("--root <dir>", "Host repo root", { default: "." }).option("--port <port>", "Port (default $PORT or 4199)").action(async (opts) => {
69
- const { serve } = await import("./serve-CZ1KEQxd.mjs");
69
+ const { serve } = await import("./serve-BeLLizNg.mjs");
70
70
  let port;
71
71
  if (opts.port !== void 0) {
72
72
  const n = Number(opts.port);
@@ -75,7 +75,7 @@ cli.command("serve", "Serve design/.dist (set MARVER_PASSWORD to gate it)").opti
75
75
  serve(resolve(opts.root), port);
76
76
  });
77
77
  cli.command("comments <action> [value]", "Comment collaboration: connect <url> · sync · list · reply <thread> · resolve <thread> · invite <email> · revoke <email>").option("--root <dir>", "Host repo root", { default: "." }).option("--token <token>", "connect: the canvas's MARVER_CLI_TOKEN (default $MARVER_CLI_TOKEN) - the identity-mode path").option("--invite <token>", "connect: claim this invite instead of signing in").option("--canvas-password <password>", "connect: the canvas gate password (default $MARVER_PASSWORD or prompt)").option("--email <email>", "connect: account email (skips the prompt)").option("--password <password>", "connect: account password (skips the prompt - mind your shell history)").option("--name <name>", "connect --invite: display name for the new account").option("--open", "list: only unresolved threads").option("--json", "list: machine-readable output").option("--board <board>", "scope to one board").option("--body <text>", "reply: the reply text").option("--addressed-in <frame>", "resolve: the variant frame that answered the feedback").action(async (action, value, opts) => {
78
- const { commentsCommand } = await import("./comments-CRqaO2MH.mjs");
78
+ const { commentsCommand } = await import("./comments-pNxjTrGN.mjs");
79
79
  try {
80
80
  await commentsCommand(resolve(opts.root), action, value, opts);
81
81
  } catch (err) {
@@ -83,6 +83,15 @@ cli.command("comments <action> [value]", "Comment collaboration: connect <url>
83
83
  process.exit(1);
84
84
  }
85
85
  });
86
+ cli.command("share <action> [value]", "Sharing roster (owner): add <who> · remove <who> · block/unblock <email> · general <mode> · list · requests · explain <who> · who").option("--root <dir>", "Host repo root", { default: "." }).option("--role <role>", "add / requests --approve: view (default) or comment").option("--expires <iso>", "add: expiry timestamp, e.g. 2026-12-31T00:00:00Z").option("--approve <email>", "requests: approve this pending request (canvas-wide in v1)").option("--decline <email>", "requests: decline this pending request (silent to the asker)").option("--json", "list: machine-readable output").action(async (action, value, opts) => {
87
+ const { shareCommand } = await import("./share-C64b1tdN.mjs");
88
+ try {
89
+ await shareCommand(resolve(opts.root), action, value, opts);
90
+ } catch (err) {
91
+ console.error(`[${NAME}] ${err.message}`);
92
+ process.exit(1);
93
+ }
94
+ });
86
95
  cli.command("work <action> [...frames]", "Working state on the canvas: start <scene/frame ...> · done <scene/frame ...> | --all · list").option("--root <dir>", "Host repo root", { default: "." }).option("--ttl <minutes>", "start: minutes before the glow self-expires (default 10, max 30)").option("--all", "done: clear every working frame").action(async (action, frames, opts) => {
87
96
  const { workCommand } = await import("./work-lzC-lPY0.mjs");
88
97
  try {