slidemind 0.3.20 → 0.4.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 (185) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/AGENTS.md +591 -15
  4. package/README.md +202 -7
  5. package/bin/slidemind.mjs +50 -2
  6. package/package.json +2 -2
  7. package/skills/create-slides/SKILL.md +484 -886
  8. package/skills/search/SKILL.md +46 -23
  9. package/src/bridge/build-and-push.mjs +548 -73
  10. package/src/bridge/bundle.mjs +100 -31
  11. package/src/bridge/capture.mjs +14 -17
  12. package/src/bridge/client/brands.mjs +157 -18
  13. package/src/bridge/client/engine-routing.mjs +48 -13
  14. package/src/bridge/client/engine-search.mjs +24 -1
  15. package/src/bridge/client/render.mjs +27 -1
  16. package/src/bridge/client/transport.mjs +499 -23
  17. package/src/bridge/client.mjs +10 -0
  18. package/src/bridge/config.mjs +67 -6
  19. package/src/bridge/engine-download.mjs +0 -10
  20. package/src/bridge/graph-auth.mjs +55 -3
  21. package/src/bridge/image-intent-envelope.mjs +239 -0
  22. package/src/bridge/package-preview.mjs +67 -0
  23. package/src/bridge/sharepoint.mjs +914 -0
  24. package/src/builders/asset-fetch.mjs +203 -0
  25. package/src/builders/block-dividers.mjs +83 -0
  26. package/src/builders/build.mjs +171 -87
  27. package/src/builders/card-height.mjs +294 -0
  28. package/src/builders/chart-hole.mjs +183 -0
  29. package/src/builders/chart-jsx.mjs +410 -0
  30. package/src/builders/chart-labels.mjs +326 -0
  31. package/src/builders/concept-geometry.mjs +370 -0
  32. package/src/builders/concept-intent.mjs +200 -0
  33. package/src/builders/concept-layout.mjs +79 -0
  34. package/src/builders/concept-lines.mjs +509 -0
  35. package/src/builders/concept-lists.mjs +270 -0
  36. package/src/builders/concept-marks.mjs +84 -0
  37. package/src/builders/concept-relationships.mjs +12 -0
  38. package/src/builders/concept-spec.mjs +645 -0
  39. package/src/builders/concept-text.mjs +140 -0
  40. package/src/builders/concept-waves.mjs +57 -0
  41. package/src/builders/copy-fit.mjs +139 -0
  42. package/src/builders/design-tokens.mjs +347 -0
  43. package/src/builders/draft-check.mjs +552 -340
  44. package/src/builders/draft-lines.mjs +369 -0
  45. package/src/builders/edit-route.mjs +29 -0
  46. package/src/builders/figure-sketch.mjs +375 -0
  47. package/src/builders/fold-sets.mjs +639 -0
  48. package/src/builders/gate-server.mjs +9 -4
  49. package/src/builders/gate-session.js +273 -12
  50. package/src/builders/gate-shell.js +1494 -0
  51. package/src/builders/image-intent-check.mjs +308 -0
  52. package/src/builders/image-use-reconcile.mjs +107 -0
  53. package/src/builders/kit-helpers.mjs +121 -0
  54. package/src/builders/layout-names.mjs +32 -0
  55. package/src/builders/legacy-reference-uses.mjs +179 -0
  56. package/src/builders/line-fit.mjs +224 -0
  57. package/src/builders/line-native.mjs +895 -0
  58. package/src/builders/line-relations.mjs +230 -0
  59. package/src/builders/lint-fix.mjs +117 -9
  60. package/src/builders/lint.mjs +25 -20
  61. package/src/builders/list-jsx.mjs +88 -0
  62. package/src/builders/placement.mjs +133 -0
  63. package/src/builders/plan-checks.mjs +135 -0
  64. package/src/builders/plan-comments.mjs +19 -0
  65. package/src/builders/plan-handlers.mjs +95 -0
  66. package/src/builders/plan-markdown-edit.mjs +3 -3
  67. package/src/builders/plan-overlay.js +1530 -1608
  68. package/src/builders/plan.mjs +689 -186
  69. package/src/builders/preset-definitions.json +1 -0
  70. package/src/builders/preset-eval.mjs +244 -0
  71. package/src/builders/preview.html +51 -120
  72. package/src/builders/red-thread-backbone.mjs +135 -36
  73. package/src/builders/repair.mjs +205 -0
  74. package/src/builders/repeat-groups.mjs +540 -0
  75. package/src/builders/resolve-skill.mjs +5 -4
  76. package/src/builders/review-overlay.js +296 -293
  77. package/src/builders/review.mjs +305 -84
  78. package/src/builders/rule-shift.mjs +96 -0
  79. package/src/builders/seat.mjs +354 -0
  80. package/src/builders/settle-driver.mjs +236 -0
  81. package/src/builders/settle.mjs +1448 -0
  82. package/src/builders/slide-prep.mjs +67 -0
  83. package/src/builders/spec-assets.mjs +236 -0
  84. package/src/builders/spec-match.mjs +56 -0
  85. package/src/builders/stack.mjs +413 -0
  86. package/src/builders/storyboard-markdown.mjs +70 -27
  87. package/src/builders/storyboard-schema.mjs +54 -114
  88. package/src/builders/text-blocks.mjs +212 -0
  89. package/src/builders/text-card-fit.mjs +552 -0
  90. package/src/builders/text-stack.mjs +276 -0
  91. package/src/builders/transcribe.mjs +1069 -0
  92. package/src/cli/assets.mjs +164 -0
  93. package/src/cli/common.mjs +173 -12
  94. package/src/cli/concept-spec-support.mjs +76 -0
  95. package/src/cli/concepts.mjs +1002 -103
  96. package/src/cli/deck.mjs +401 -65
  97. package/src/cli/diagnose.mjs +4 -2
  98. package/src/cli/engine-helpers.mjs +17 -10
  99. package/src/cli/engine-results.mjs +76 -3
  100. package/src/cli/help.mjs +232 -28
  101. package/src/cli/install.mjs +9 -0
  102. package/src/cli/maintenance.mjs +104 -0
  103. package/src/cli/read.mjs +2 -1
  104. package/src/cli/ready.mjs +6 -3
  105. package/src/cli/record.mjs +522 -0
  106. package/src/cli/remote.mjs +818 -0
  107. package/src/cli/revision.mjs +686 -0
  108. package/src/cli/search.mjs +30 -11
  109. package/src/cli/session.mjs +56 -13
  110. package/src/cli/setup.mjs +46 -2
  111. package/src/cli/status.mjs +142 -33
  112. package/src/cli/tasks.mjs +246 -0
  113. package/src/cli/transcribe.mjs +854 -0
  114. package/src/cli/workspace-ui.mjs +260 -0
  115. package/src/cli/workspace.mjs +212 -34
  116. package/src/install-channel.mjs +64 -7
  117. package/src/lib/adopt.mjs +346 -25
  118. package/src/lib/concept-spec-binding.mjs +96 -0
  119. package/src/lib/conversions.mjs +70 -6
  120. package/src/lib/convert-folded.mjs +107 -0
  121. package/src/lib/deck-paths.mjs +418 -0
  122. package/src/lib/durable-ids.mjs +47 -0
  123. package/src/lib/edit-operations.mjs +316 -0
  124. package/src/lib/edit-state.mjs +1244 -0
  125. package/src/lib/edit-transitions.mjs +120 -0
  126. package/src/lib/extensions.mjs +501 -0
  127. package/src/lib/image-assets.mjs +465 -0
  128. package/src/lib/image-derivatives.mjs +234 -0
  129. package/src/lib/image-intent-fingerprint.mjs +70 -0
  130. package/src/lib/image-intent-tags.mjs +176 -0
  131. package/src/lib/image-intent.mjs +802 -0
  132. package/src/lib/image-use-overrides.mjs +279 -0
  133. package/src/lib/inline-components.mjs +613 -0
  134. package/src/lib/lint-converted.mjs +68 -4
  135. package/src/lib/merge-json.mjs +163 -0
  136. package/src/lib/overwrite-guard.mjs +3 -3
  137. package/src/lib/presentation-context.mjs +531 -0
  138. package/src/lib/preservation-tags.mjs +157 -0
  139. package/src/lib/preservation-verdict.mjs +1342 -0
  140. package/src/lib/publish.mjs +1557 -0
  141. package/src/lib/rebase.mjs +945 -0
  142. package/src/lib/reconcile-ack.mjs +4 -6
  143. package/src/lib/reconcile.mjs +62 -24
  144. package/src/lib/record-client.mjs +175 -0
  145. package/src/lib/record-state.mjs +151 -0
  146. package/src/lib/remote.mjs +661 -0
  147. package/src/lib/revision.mjs +1210 -0
  148. package/src/lib/slide-guids.mjs +4 -6
  149. package/src/lib/slide-preservation.mjs +423 -0
  150. package/src/lib/slide-selection.mjs +9 -0
  151. package/src/lib/workspace-transaction.mjs +117 -0
  152. package/src/pptx/content-digest.mjs +334 -0
  153. package/src/pptx/deck-assemble.mjs +1278 -0
  154. package/src/pptx/fill/engine.mjs +2 -3
  155. package/src/pptx/fill/freeform.mjs +6 -3
  156. package/src/pptx/ooxml.mjs +20 -0
  157. package/src/pptx/package-evidence.mjs +383 -0
  158. package/src/pptx/tags.mjs +163 -11
  159. package/src/update-check.mjs +22 -4
  160. package/src/workspace/adapters.mjs +222 -0
  161. package/src/workspace/auth.mjs +256 -0
  162. package/src/workspace/autostart.mjs +204 -0
  163. package/src/workspace/daemon.mjs +360 -0
  164. package/src/workspace/discovery.mjs +180 -0
  165. package/src/workspace/edit.mjs +167 -0
  166. package/src/workspace/events.mjs +52 -0
  167. package/src/workspace/gate-client.mjs +290 -0
  168. package/src/workspace/handback.mjs +73 -0
  169. package/src/workspace/lifecycle.mjs +635 -0
  170. package/src/workspace/operations.mjs +200 -0
  171. package/src/workspace/pick-folder.mjs +152 -0
  172. package/src/workspace/registry.mjs +359 -0
  173. package/src/workspace/remote-links.mjs +397 -0
  174. package/src/workspace/remote-status.mjs +359 -0
  175. package/src/workspace/routes.mjs +1331 -0
  176. package/src/workspace/server.mjs +159 -0
  177. package/src/workspace/sync.mjs +1119 -0
  178. package/src/workspace/task-actions.mjs +215 -0
  179. package/src/workspace/tasks.mjs +507 -0
  180. package/src/workspace/ui/app.js +1886 -0
  181. package/src/workspace/ui/index.html +141 -0
  182. package/src/workspace/ui/plan-bridge.js +120 -0
  183. package/src/workspace/ui/sharepoint.svg +59 -0
  184. package/src/workspace/ui/styles.css +423 -0
  185. package/src/builders/visual-families.mjs +0 -46
@@ -37,7 +37,7 @@ export async function runDiagnose(rest) {
37
37
  // push/insert-slide and reading an open presentation. Reporting them
38
38
  // separately is the point — a dead pane is not "cannot work".
39
39
  const authoringBlocker = backendBlocker(status);
40
- let engines = { available: false, envs: [] };
40
+ let engines = { available: false, envs: [], unknown: [] };
41
41
  if (!authoringBlocker) {
42
42
  try {
43
43
  engines = await client.engineReachability({ backends: statusRaw.backends });
@@ -73,7 +73,9 @@ export async function runDiagnose(rest) {
73
73
  ` engine search (no window needed): ${
74
74
  engines.available
75
75
  ? `yes [${engines.envs.join(", ")}]`
76
- : "no — relayed through an open pane"
76
+ : engines.unknown?.length
77
+ ? `could not be checked [${engines.unknown.join(", ")}] — the capability probe got no answer; retry`
78
+ : "no — relayed through an open pane"
77
79
  }\n`,
78
80
  );
79
81
  // Human output only — the --json shape is a contract and the m365 state
@@ -387,18 +387,25 @@ export function writeAddSlideResult({ asJson, searchId, deckDir, deck, written,
387
387
 
388
388
  // Map a `--icon-color` value onto the `--pick` list. Accepts either one colour
389
389
  // (applied to every pick) or exactly one per pick (positional); an empty
390
- // segment means "no colour for this pick". Returns an array aligned to picks.
390
+ // segment means "no colour for this pick". The `#` is optional: an unquoted
391
+ // `#0A7E8C` starts a shell comment (zsh, bash) that swallows the value and
392
+ // every flag after it, so the bare form is the one no shell eats (x74: 38
393
+ // failed downloads). `raw` is null when the flag is absent, "" when it arrived
394
+ // empty. Returns an array aligned to picks, each colour `#`-prefixed.
391
395
  export function parseIconColors(raw, pickCount) {
392
- if (!raw) return new Array(pickCount).fill(null);
393
- const colors = raw
394
- .split(",")
395
- .map((s) => s.trim())
396
- .map((s) => (s.length ? s : null));
397
- for (const c of colors) {
398
- if (c !== null && !/^#[0-9a-fA-F]{3,8}$/.test(c)) {
399
- fail(`--icon-color expects hex colours like #0A7E8C, got '${c}'`);
400
- }
396
+ if (raw == null) return new Array(pickCount).fill(null);
397
+ if (!raw.trim()) {
398
+ fail(
399
+ "--icon-color got no value — an unquoted '#RRGGBB' starts a shell comment in zsh and bash " +
400
+ "(which also drops every flag after it); pass 0A7E8C, or quote it: '#0A7E8C'",
401
+ );
401
402
  }
403
+ const colors = raw.split(",").map((s) => {
404
+ const c = s.trim();
405
+ if (!c) return null;
406
+ if (!/^#?[0-9a-fA-F]{3,8}$/.test(c)) fail(`--icon-color expects hex colours like 0A7E8C or '#0A7E8C', got '${c}'`);
407
+ return c.startsWith("#") ? c : `#${c}`;
408
+ });
402
409
  if (colors.length === 1) return new Array(pickCount).fill(colors[0]);
403
410
  if (colors.length !== pickCount) {
404
411
  fail(
@@ -187,6 +187,45 @@ export async function cmdReadSource(rest) {
187
187
  * across every command.
188
188
  */
189
189
 
190
+
191
+ // Register a downloaded image with the deck's image registry (task 2.6 of the
192
+ // image-intent contract): the bytes get a digest-backed identity and engine
193
+ // provenance, so a later plan can bind a use to THIS result rather than to a
194
+ // file name someone might overwrite.
195
+ //
196
+ // Deliberately two things it is not: it is not a use — registering creates no
197
+ // obligation to place the picture on any slide — and it is not allowed to sink
198
+ // a download. A registry that cannot be written (the deck is held by another
199
+ // command, the format is one the registry does not ship) leaves the asset file
200
+ // exactly where it always landed and every existing output field untouched.
201
+ async function registerDownloadedAsset(deckDir, result, searchId) {
202
+ try {
203
+ const { importImageAsset } = await import(
204
+ pathToFileURL(path.join(SRC_DIR, "lib", "image-assets.mjs")).href
205
+ );
206
+ const { asset, reused } = await importImageAsset(deckDir, result.path, {
207
+ displayName: path.basename(result.path),
208
+ provenance: { kind: "engine", sourceId: `${searchId}:${result.selection}` },
209
+ });
210
+ return {
211
+ assetId: asset.assetId,
212
+ sha256: asset.sha256,
213
+ mimeType: asset.mimeType,
214
+ width: asset.width,
215
+ height: asset.height,
216
+ byteLength: asset.byteLength,
217
+ workspacePath: asset.workspacePath,
218
+ reused,
219
+ };
220
+ } catch (err) {
221
+ process.stderr.write(
222
+ `note: ${result.reference} was downloaded but not registered as an image asset ` +
223
+ `(${err?.code ?? "error"}: ${err?.message ?? err}).\n`,
224
+ );
225
+ return null;
226
+ }
227
+ }
228
+
190
229
  export async function cmdDownloadResult(rest) {
191
230
  let searchId = null;
192
231
  let picks = null;
@@ -194,6 +233,7 @@ export async function cmdDownloadResult(rest) {
194
233
  let to = null;
195
234
  let asJson = false;
196
235
  let iconColorsRaw = null;
236
+ let name = null;
197
237
  for (let i = 0; i < rest.length; i++) {
198
238
  const a = rest[i];
199
239
  if (a === "--search") {
@@ -208,9 +248,16 @@ export async function cmdDownloadResult(rest) {
208
248
  if (!to) fail("--to expects a presId");
209
249
  } else if (a === "--json") {
210
250
  asJson = true;
251
+ } else if (a === "--name") {
252
+ // The asset's file name under assets/ (one pick only): lets a fetcher
253
+ // write `02-card-1-icon.svg` so the slide author references a path it
254
+ // knew before the download ran — the wiring gap that left fetched icons
255
+ // unused (x25/x26 sims).
256
+ name = rest[++i];
257
+ if (!name || /[\\/]/.test(name)) fail("--name expects a bare file name (no directories)");
211
258
  } else if (a === "--icon-color" || a === "--icon-colour") {
212
- iconColorsRaw = rest[++i];
213
- if (!iconColorsRaw) fail("--icon-color expects a hex colour (or comma-separated list aligned to --pick)");
259
+ // Empty when the shell ate an unquoted '#…' as a comment: parseIconColors explains that.
260
+ iconColorsRaw = rest[++i] ?? "";
214
261
  } else if (a.startsWith("-")) {
215
262
  fail(`unknown flag: ${a}`);
216
263
  } else {
@@ -222,6 +269,7 @@ export async function cmdDownloadResult(rest) {
222
269
  "usage: slidemind download-result --search <id> --pick <n[,n,...]> --deck <deck> [--to <presId>] [--icon-color <hex[,hex,...]>] [--json]",
223
270
  );
224
271
  }
272
+ if (name && picks.length !== 1) fail("--name takes exactly one --pick");
225
273
  const iconColors = parseIconColors(iconColorsRaw, picks.length);
226
274
  const deckDir = resolveDeckArg(deck);
227
275
  const assetsDir = path.join(deckDir, "assets");
@@ -292,6 +340,31 @@ export async function cmdDownloadResult(rest) {
292
340
  continue;
293
341
  }
294
342
  ok++;
343
+ if (name && r.path) {
344
+ // Keep the extension the bytes decided; only the stem is the caller's.
345
+ const ext = path.extname(r.path);
346
+ // `NN-<id>.asset` is the placeholder a transcribed slide references
347
+ // before its bytes exist; the file takes the real extension and every
348
+ // slide pointing at the placeholder follows it. Kept as `.asset`, the
349
+ // renderer dropped the picture with a warning and the check stayed clean.
350
+ const placeholder = /\.asset$/i.test(name) ? name : null;
351
+ const target = path.join(assetsDir, placeholder ? name.replace(/\.asset$/i, ext) : path.extname(name) ? name : `${name}${ext}`);
352
+ fs.renameSync(r.path, target);
353
+ r.path = target;
354
+ r.reference = `./assets/${path.basename(target)}`;
355
+ if (placeholder) {
356
+ const slidesDir = path.join(deckDir, "slides");
357
+ for (const f of fs.existsSync(slidesDir) ? fs.readdirSync(slidesDir).filter((n) => n.endsWith(".jsx")) : []) {
358
+ const file = path.join(slidesDir, f);
359
+ const src = fs.readFileSync(file, "utf8");
360
+ const from = JSON.stringify(`./assets/${placeholder}`);
361
+ if (src.includes(from)) fs.writeFileSync(file, src.split(from).join(JSON.stringify(r.reference)));
362
+ }
363
+ }
364
+ }
365
+ // Identity for the registry — additive: `file`, `size` and `recolor` below
366
+ // keep their exact shape for callers that predate the image contract.
367
+ r.asset = r.path ? await registerDownloadedAsset(deckDir, r, searchId) : null;
295
368
  const note = r.recolor?.note ? ` — ${r.recolor.note}` : "";
296
369
  // The intrinsic size is the agent's only handle on the asset's aspect
297
370
  // ratio — without it the <img> box is a guess and the render stretches.
@@ -302,7 +375,7 @@ export async function cmdDownloadResult(rest) {
302
375
  }
303
376
  if (asJson) {
304
377
  process.stdout.write(
305
- `${JSON.stringify({ searchId, assetsDir, downloaded: ok, results: results.map((r) => (r.error ? { selection: r.selection, error: r.error } : { selection: r.selection, file: r.reference, size: r.size ?? null, recolor: r.recolor ?? null })) }, null, 2)}\n`,
378
+ `${JSON.stringify({ searchId, assetsDir, downloaded: ok, results: results.map((r) => (r.error ? { selection: r.selection, error: r.error } : { selection: r.selection, file: r.reference, size: r.size ?? null, recolor: r.recolor ?? null, asset: r.asset ?? null })) }, null, 2)}\n`,
306
379
  );
307
380
  if (ok === 0) process.exit(1);
308
381
  return;
package/src/cli/help.mjs CHANGED
@@ -29,8 +29,8 @@ export function printHelp(cmd = null) {
29
29
  ` --force re-download the bundle even if current\n` +
30
30
  ` --connect-only check the connection only (no design/bundle)\n` +
31
31
  ` --diagnose read-only report instead: both ladders (backend\n` +
32
- ` authoring / live PowerPoint), every paired backend,\n` +
33
- ` Microsoft 365 state and every registered window\n` +
32
+ ` authoring / live PowerPoint), Microsoft 365 state\n` +
33
+ ` and every registered window\n` +
34
34
  ` --json machine-readable readiness payload\n` +
35
35
  ` With a deck, also prints its stage (plan / concepts / assets / slides /\n` +
36
36
  ` draft check / review) and the next command — read from disk.\n` +
@@ -38,10 +38,15 @@ export function printHelp(cmd = null) {
38
38
  ` --brand <presFP> bind to a chosen (cached) design instead\n` +
39
39
  ` --to <presId> bind to a specific open window\n` +
40
40
  ` --no-bind skip the auto-bind\n` +
41
- ` slidemind plan <deck> Plan sign-off gate on <deck>/plan.md: opens it in the browser\n` +
42
- ` as a markdown editor + live preview + overall feedback box,\n` +
43
- ` blocks until the user decides, prints DECISION: approved | comments\n` +
44
- ` --port <n> bind a fixed port (default: ephemeral)\n` +
41
+ ` slidemind plan <deck> Plan sign-off gate on <deck>/.slidemind/plan.md (## Red thread +\n` +
42
+ ` ## Per-slide storyboards, one storyboard-canvas block per slide):\n` +
43
+ ` the user answers in the workspace. It asks there and returns at once:\n` +
44
+ ` ASK: <link>, SAY: <the line to end your turn with>, DECISION: pending.\n` +
45
+ ` The user's pasted message names this command again; that run prints\n` +
46
+ ` their answer (DECISION: approved | comments). BLOCKED: <code> when the\n` +
47
+ ` workspace cannot be used (exit 2). Only SLIDEMIND_WORKSPACE=0 serves\n` +
48
+ ` the standalone page that blocks until the user decides\n` +
49
+ ` --port <n> standalone page: a fixed port (default: per deck)\n` +
45
50
  ` slidemind concepts <deck> After plan sign-off, generate one concept mockup PNG per planned\n` +
46
51
  ` slide into <deck>/concepts/concept-slide-NN.png (bound deck,\n` +
47
52
  ` pane-free — no PowerPoint window needed).\n` +
@@ -51,19 +56,62 @@ export function printHelp(cmd = null) {
51
56
  ` mockup for slides N,M (default: all)\n` +
52
57
  ` --style <image>[@N,M] the user's look-and-feel reference,\n` +
53
58
  ` leads the generation (repeatable)\n` +
59
+ ` --status whether a run is in progress and how many mockups and\n` +
60
+ ` specs are on disk (read-only; never starts a run)\n` +
61
+ ` Both flags IMPORT the file into the deck and record it in plan.md\n` +
62
+ ` as an image use (--concept: rebuild this slide; --style: reference).\n` +
63
+ ` A new use re-arms the plan gate — sign off again, then re-run.\n` +
54
64
  ` --json machine-readable result (paths, missing, status)\n` +
55
65
  ` Always ends on one line: CONCEPTS: <n>/<planned> <status>\n` +
56
- ` slidemind review <deck> Open a spatial preview review; blocks until the user\n` +
57
- ` approves or leaves comments (host-free)\n` +
58
- ` --port <n> bind a fixed port (default: ephemeral)\n` +
59
- ` --allow-draft-defects open even while the last view-draft's measured defects stand\n` +
66
+ ` With an add-in that extracts concept specs, also writes\n` +
67
+ ` state/concept-specs/concept-slide-NN.json (the mockup's element inventory)\n` +
68
+ ` slidemind transcribe <deck> --read-spec [--only NN] Read complete specs through END OF SPEC\n` +
69
+ ` slidemind transcribe <deck> Write each slide's JSX skeleton from its concept spec: one element per\n` +
70
+ ` spec line at the spec's slot coordinates, connectors by id, icons as\n` +
71
+ ` ./assets/NN-<icon id>.asset, fetched and renamed to the real file by\n` +
72
+ ` transcribe itself (download-result --name NN-<id>.asset for a miss).\n` +
73
+ ` --only NN write just this slide (a SELECTION, not permission to rewrite)\n` +
74
+ ` --force rebuild a slide that already exists from its spec\n` +
75
+ ` --discard-edits with --force: also rebuild a slide edited since transcribe\n` +
76
+ ` --repair bounded repair of RECORDED source, inside an open repair scope\n` +
77
+ ` (\`revision begin <deck> --operation repair --slides <sel>\`;\n` +
78
+ ` a targeted-edit scope is refused). Runs the WHOLE scope —\n` +
79
+ ` --only cannot carve a subset out of it. Budget: <=3 passes,\n` +
80
+ ` <=8 slot-px move and <=10% size change per element measured\n` +
81
+ ` from the ORIGINAL source, and zero text/asset/element-count/\n` +
82
+ ` endpoint change. Refuses (before staging anything) unrecorded\n` +
83
+ ` source, converted source, an unparseable slide, and a bundle\n` +
84
+ ` with no capture engine. Not combinable with --force/--discard-edits.\n` +
85
+ ` --no-assets do not fetch or retry the slides' icons/images\n` +
86
+ ` --no-settle write the JSX as the spec gives it; no geometry settling\n` +
87
+ ` --json machine-readable result\n` +
88
+ ` A slide edited since transcribe wrote it, and any converted slide, is kept\n` +
89
+ ` byte-for-byte — edit its JSX directly.\n` +
90
+ ` A bare re-run RETRIES assets an earlier run could not resolve, including on\n` +
91
+ ` slides it now skips: the missing picture is fetched without re-extracting\n` +
92
+ ` the spec, regenerating the source or re-settling it. A reference you\n` +
93
+ ` repointed at your own file is left alone.\n` +
94
+ ` slidemind review <deck> Spatial preview review (host-free): builds the preview, asks in the\n` +
95
+ ` workspace like \`plan\` and returns at once (ASK + SAY, DECISION:\n` +
96
+ ` pending); the user's pasted message names it again to receive the answer\n` +
97
+ ` --port <n> standalone page: a fixed port (default: per deck)\n` +
60
98
  ` slidemind build-pptx <deck> Export native .pptx — via the add-in for compose decks (pane-free: the deck's design, else the one design cached/listed; a window only with --to), LOCALLY for template packs (host-free)\n` +
61
99
  ` --pack <dir> explicit template-pack directory (plugin integrations)\n` +
62
100
  ` --to <presId> target a specific window (required if >1 open)\n` +
63
101
  ` --out <path> output path (default <deck>/<deck>.pptx)\n` +
64
102
  ` --force discard manual PowerPoint edits instead of reconciling them\n` +
65
- ` --json machine-readable result\n` +
66
- ` (writes per-slide PNGs of the built deck to <deck>/.build-preview/)\n` +
103
+ ` --refresh-design adopted decks: also re-render the untouched slides onto the current\n` +
104
+ ` corporate design (by default only what you changed renders)\n` +
105
+ ` --no-publish keep this build local (see below)\n` +
106
+ ` --json machine-readable result (\`publish\`: the publish outcome, or null)\n` +
107
+ ` (writes per-slide PNGs of the built deck to <deck>/.slidemind/preview/build/)\n` +
108
+ ` A compose deck connected to SharePoint — the file it was opened from, or a\n` +
109
+ ` folder connected in the workspace — is PUBLISHED right after the build, as\n` +
110
+ ` \`slidemind publish\` would; a publish that did not save exits 2.\n` +
111
+ ` Refuses a slide whose recorded preservation obligations are unmet or\n` +
112
+ ` unproven, naming the elements. A record is seeded by adopt / revision\n` +
113
+ ` begin, so an adopted deck is protected WITHOUT opening a scope; a deck\n` +
114
+ ` authored from scratch has no record and is never gated.\n` +
67
115
  ` slidemind inspect-template-deck <deck> Inspect a template deck as JSON for pack-owned validation\n` +
68
116
  ` --pack <dir> explicit template-pack directory (required)\n` +
69
117
  ` --allow-unrendered-freeform include JSX inserts without .pptx artifacts\n` +
@@ -71,18 +119,23 @@ export function printHelp(cmd = null) {
71
119
  ` slidemind view-draft <deck> Lint + build the HTML preview, then render the deck's slides to\n` +
72
120
  ` per-slide PNGs (no push, no .pptx)\n` +
73
121
  ` so the agent can look before review; host-free once the\n` +
74
- ` bundle is materialized. Prints the measured defects ([fill:warn], [inventory:warn])\n` +
122
+ ` bundle is materialized. Prints the draft lines (which hold: workflow/create.md\n` +
123
+ ` step 6) and, per unmet preservation obligation, a [preserve:warn] naming the\n` +
124
+ ` element and its repair ([preserve:error] names a preservation record that\n` +
125
+ ` cannot be read).\n` +
126
+ ` review, build-pptx and push refuse a slide never checked, changed since its\n` +
127
+ ` check, or with a measured defect standing.\n` +
75
128
  ` Also runs the server's export gate on the captured slides (slot-fit,\n` +
76
- ` diagram geometry, connector endpoints) so build-pptx's 422s surface here.\n` +
129
+ ` diagram geometry, text escape, chrome) so build-pptx's 422s surface here.\n` +
77
130
  ` --only <file|N>[,…] render just these slides (a per-slide builder's own check)\n` +
78
- ` --fix apply the deterministic lint autofixes first\n` +
131
+ ` --no-fix skip the deterministic lint autofixes (on by default)\n` +
79
132
  ` --lint-only lint + HTML build, no render (fast fix loop)\n` +
80
133
  ` --strict converted slides: gate here like review does (they only warn by default)\n` +
81
134
  ` --no-translate skip the export gate (purely local check)\n` +
82
135
  ` --translate insist on the export gate (fail instead of skipping when the backend is unreachable)\n` +
83
136
  ` --json machine-readable result (slides, defects, reviewCanOpen)\n` +
84
137
  ` --to <presId> window to fetch the bundle from (only if not cached)\n` +
85
- ` --out <dir> output dir (default <deck>/.draft-preview/)\n` +
138
+ ` --out <dir> output dir (default <deck>/.slidemind/preview/draft/)\n` +
86
139
  ` --open open each rendered PNG\n` +
87
140
  ` slidemind search "<query>" Search an engine or scope for images or slides (no window needed on\n` +
88
141
  ` add-ins that serve the pane-free engine routes; older ones relay\n` +
@@ -92,7 +145,7 @@ export function printHelp(cmd = null) {
92
145
  ` --result-type <t> slide | image — required for a scope:* target\n` +
93
146
  ` --intent <i> auto | content | visual (slide engines)\n` +
94
147
  ` --top <N> max results\n` +
95
- ` --deck <deck> route by this deck's bound design (multi-environment)\n` +
148
+ ` --deck <deck> use this deck's bound design\n` +
96
149
  ` --to <presId> pin a window on the relayed path (ignored pane-free)\n` +
97
150
  ` --json print the raw result payload (carries the searchId)\n` +
98
151
  ` slidemind view-slide [opts] Fetch a slide search result's preview image to a file\n` +
@@ -113,8 +166,7 @@ export function printHelp(cmd = null) {
113
166
  ` slidemind ls engines List the live engine catalog + search scopes\n` +
114
167
  ` --type <t> collection | search | scope\n` +
115
168
  ` --subtype <s> image | slide | template | content\n` +
116
- ` --to <presId> target a specific window/backend (relayed path only)\n` +
117
- ` --all include every paired backend\n` +
169
+ ` --to <presId> target a specific window (relayed path only)\n` +
118
170
  ` --json print the raw engine list\n` +
119
171
  ` slidemind insert-slide [opts] Insert reused slide search results live into a window (requires a host)\n` +
120
172
  ` --search <id> searchId from 'slidemind search'\n` +
@@ -131,16 +183,162 @@ export function printHelp(cmd = null) {
131
183
  ` --json machine-readable result (carries the searchId)\n` +
132
184
  ` slidemind adopt <file.pptx> Rebuild a deck workspace from a file's embedded source; foreign\n` +
133
185
  ` slides are converted to editable jsx where the add-in offers it\n` +
186
+ ` Repeatable: the SAME file again rewrites nothing, a wider --slides\n` +
187
+ ` hydrates only the new slides, a DIFFERENT file reconciles onto the\n` +
188
+ ` deck (with unpublished edits it refuses and names 'slidemind rebase')\n` +
134
189
  ` --deck <slug> deck folder to write (default: the file's name)\n` +
135
190
  ` --brand <presFP> design to convert against (when the file names none)\n` +
191
+ ` --slides <sel> hydrate only these slides to editable jsx — '2,7',\n` +
192
+ ` '2-4' or 'all'; every other slide stays a lossless\n` +
193
+ ` native one-slide .pptx that keeps its notes,\n` +
194
+ ` comments, animations and embeddings\n` +
136
195
  ` --no-convert fold every untagged slide as native .pptx instead\n` +
137
- ` slidemind download-result [opts] Download image/icon search result(s) into a deck's assets/\n` +
196
+ ` slidemind adopt <deck>/.slidemind/slides/<NN-name>.pptx --in-place Convert ONE folded slide (add-slide) in place to\n` +
197
+ ` editable jsx: same play position, the .pptx is replaced; a slide\n` +
198
+ ` the add-in cannot convert stays native and nothing changes\n` +
199
+ ` slidemind revision <sub> <deck> Local revision protocol for an ADOPTED deck: which bytes are the\n` +
200
+ ` accepted base, and which build may replace them\n` +
201
+ ` begin take ownership for a whole edit session; prints an\n` +
202
+ ` owner token to export as SLIDEMIND_OWNER so nested\n` +
203
+ ` commands and parallel lanes re-enter instead of blocking\n` +
204
+ ` --operation targeted-edit|repair|redesign declare what this\n` +
205
+ ` session may do; needs --slides <sel> (a scope with no\n` +
206
+ ` slides would claim the deck). 'create' is a build's own\n` +
207
+ ` inference for a new slide and is refused here.\n` +
208
+ ` The operation PERMITS a named removal; it is not what\n` +
209
+ ` makes a build a replacement — see build-pptx below.\n` +
210
+ ` --scope <file.json> the elements and properties it may touch\n` +
211
+ ` finish release it — work, candidates and journals are kept\n` +
212
+ ` status accepted base, generation, dirty slides, added/removed,\n` +
213
+ ` candidates, staged snapshots, conflicts, owner, journal\n` +
214
+ ` accept --candidate <id>: make a validated local candidate the\n` +
215
+ ` accepted base; refuses a stale candidate, a deck with\n` +
216
+ ` unresolved conflicts, and a remote-bound deck\n` +
217
+ ` --json machine-readable result (begin | status | accept)\n` +
218
+ ` slidemind rebase <deck> [opts] Move an adopted deck onto a newer version of the same file:\n` +
219
+ ` their edits, added/deleted slides and moves apply, reorders on both\n` +
220
+ ` sides combine, local edits stay unpublished against the new base.\n` +
221
+ ` Only a slide BOTH sides changed is a conflict, and it activates\n` +
222
+ ` NOTHING — it writes a conflict manifest plus a resolution skeleton\n` +
223
+ ` with every hash already filled in. There is deliberately no\n` +
224
+ ` generic force flag\n` +
225
+ ` --incoming <file.pptx|revisionId> the newer file, or a staged one\n` +
226
+ ` --resolve <file.json> apply the filled-in resolution skeleton\n` +
227
+ ` --dry-run classify only (incoming-only / local-only / unchanged /\n` +
228
+ ` conflicts) and write the skeleton if any conflict\n` +
229
+ ` --json machine-readable result\n` +
230
+ ` (no flags) finish a rebase that was interrupted partway\n` +
231
+ ` slidemind open <link|file> Open a SharePoint presentation as a deck: resolve the item the LINK\n` +
232
+ ` names, take one consistent snapshot, adopt it, and bind the deck to\n` +
233
+ ` that item. A local file path adopts only — it never infers where to\n` +
234
+ ` publish. Metadata in the file never redirects the target\n` +
235
+ ` --deck <slug> deck folder to write (default: the file's name)\n` +
236
+ ` --slides <sel> hydrate only these slides to editable jsx (as adopt)\n` +
237
+ ` --rebind point an already-bound deck at this item instead\n` +
238
+ ` --ordinary classify unknown origin as ordinary (cannot remove gates)\n` +
239
+ ` --companion id=<file|link> restore a hash-verified manual companion\n` +
240
+ ` --keep local file: adopt these bytes, keep publishing where it does\n` +
241
+ ` --detach local file: forget the binding, make it local-only\n` +
242
+ ` --json machine-readable result\n` +
243
+ ` slidemind pull <deck> Check the bound item and every independently mutable input it names.\n` +
244
+ ` Unchanged costs one metadata call and downloads nothing. A newer save\n` +
245
+ ` stages immutably and is merged in around unpublished local edits;\n` +
246
+ ` only a slide both sides changed stops it (then: slidemind rebase)\n` +
247
+ ` --check report freshness only (never stages, never rebases)\n` +
248
+ ` --json machine-readable result\n` +
249
+ ` slidemind publish <deck> Publish the accepted candidate back to the bound item: recover, pull,\n` +
250
+ ` rebase, run every required extension's release checks on the FINAL\n` +
251
+ ` bytes, then replace under a server-enforced expected revision. Only a\n` +
252
+ ` confirmed receipt counts as saved; a 412 stages their save instead.\n` +
253
+ ` build-pptx runs this itself for a connected deck; run it to retry one\n` +
254
+ ` whose publish did not save. Unbound with no flags: the folder\n` +
255
+ ` connected in the workspace is the first create's target\n` +
256
+ ` --folder <link> --name <file.pptx> first create, refusing name conflicts\n` +
257
+ ` --candidate <id> publish this candidate (default: the newest valid one)\n` +
258
+ ` --manual prepare the validated handoff without any remote write\n` +
259
+ ` --shared-workbook declare authority to replace a shared mutable input\n` +
260
+ ` (without it a changed workbook FORKS, never overwrites)\n` +
261
+ ` --json machine-readable result\n` +
262
+ ` slidemind rename <deck> --name <file> Rename the bound item under its expected revision. Same item, no\n` +
263
+ ` render, no second file; an existing name refuses\n` +
264
+ ` --json machine-readable result\n` +
265
+ ` slidemind workspace [<deck>] The user's workspace: one browser page with the deck library, plan\n` +
266
+ ` and preview review, served by a detached daemon on 127.0.0.1 that\n` +
267
+ ` outlives this shell; \`setup\` starts it at every login. \`plan\`/\`review\`\n` +
268
+ ` ask there (SLIDEMIND_WORKSPACE=0 opts out). An answer given there\n` +
269
+ ` copies a message the user pastes to their agent; it names the run\n` +
270
+ ` that receives it\n` +
271
+ ` (no sub) [<deck>] start or reuse the daemon, register <deck>, open the page only\n` +
272
+ ` when none is open, print its sign-in link (Workspace: <link>)\n` +
273
+ ` --no-open print the link only\n` +
274
+ ` --foreground stay attached (when the environment forbids a detached process)\n` +
275
+ ` start start it unless it runs — silent, never opens a browser (the login item runs this)\n` +
276
+ ` status running | url | pages | protocolVersion | pendingJobs | reason\n` +
277
+ ` stop drain local commits (<= 5s), keep queued remote work, exit\n` +
278
+ ` add <path> register one deck folder\n` +
279
+ ` discover <root> remember a root and scan it for decks (no symlinks, no scratch)\n` +
280
+ ` list registered decks\n` +
281
+ ` open-remote <url> resolve a SharePoint link (pptx | plan record | hosted page |\n` +
282
+ ` folder); a folder answers a selection, never a guess\n` +
283
+ ` select <jobId> --selection <id> --choice <id> pick one child of that selection\n` +
284
+ ` connect <deck> --folder <url> [--name <file.pptx>] save the deck's SharePoint\n` +
285
+ ` destination; the plan is created there once it is editable\n` +
286
+ ` and the backend allows writes (destination-only until then)\n` +
287
+ ` --json on every subcommand\n` +
288
+ ` slidemind tasks <sub> Durable review tasks of a deck (survive agent exit; local, never published)\n` +
289
+ ` list [<deck>] [--status pending] tasks of one deck or every registered deck\n` +
290
+ ` (agents run \`slidemind plan\` / \`review\`; these are for packs and scripts)\n` +
291
+ ` request <deck> --kind <k> plan-edit | plan-approval | preview-review;\n` +
292
+ ` hashes the current subject, dedupes, prints the task, its\n` +
293
+ ` view's sign-in link and the SAY line; returns at once\n` +
294
+ ` get <deck> <id> the task and its verified outcome (records that you received it)\n` +
295
+ ` wait <deck> <id> [--timeout 45] scripts only — saved answer first, else a\n` +
296
+ ` bounded wait; {pending:true} means still waiting\n` +
297
+ ` --json one JSON value on stdout, diagnostics on stderr\n` +
298
+ ` Exit codes (workspace + tasks): 0 done or read (a pending wait included)\n` +
299
+ ` | 1 operational error | 2 blocked, busy or incompatible | 3 conflict\n` +
300
+ ` slidemind record <sub> Share ONE JSON file through the backend, which owns the merge:\n` +
301
+ ` several people and their agents edit the same record and\n` +
302
+ ` different fields combine instead of overwriting. The CLI never\n` +
303
+ ` reads what is in the file — whichever pack owns the schema does\n` +
304
+ ` create <deck> --folder <link> --name <file.json> --file <path>\n` +
305
+ ` share the file into a SharePoint folder; prints the\n` +
306
+ ` permanent link colleagues open\n` +
307
+ ` open <link> --deck <slug> --file <path>\n` +
308
+ ` fetch a shared record into a (new) deck workspace\n` +
309
+ ` sync <deck> --file <path>\n` +
310
+ ` send your version, get everyone's back merged into\n` +
311
+ ` <path>; same-field clashes write\n` +
312
+ ` state/record-conflicts.json and exit 3\n` +
313
+ ` --approval also send the file's 'approval' field (stripped otherwise)\n` +
314
+ ` --resolve <f> send this resolved version against the conflicts file\n` +
315
+ ` status <deck> current | behind (v…) | local edits\n` +
316
+ ` --json machine-readable result on every subcommand\n` +
317
+ ` slidemind download-result [opts] Download image/icon search result(s) into a deck's assets/ (--name <file> to choose the asset's file name)\n` +
138
318
  ` --search <id> searchId from 'slidemind search'\n` +
139
319
  ` --pick <n,..> 1-based selection number(s)\n` +
140
320
  ` --deck <deck> deck to write the asset(s) into\n` +
141
321
  ` --to <presId> the window the search ran on (relayed path only)\n` +
142
322
  ` --icon-color <hex[,hex,..]> bake a colour into SVG result(s) (one for all, or one per --pick)\n` +
143
323
  ` --json machine-readable result (file, size, recolor per pick)\n` +
324
+ ` slidemind assets <sub> The deck's image registry — immutable bytes, identified by their own\n` +
325
+ ` digest. Registering an image creates NO obligation to place it:\n` +
326
+ ` what it is FOR is an assetUses record in the plan, approved at the\n` +
327
+ ` plan gate. Import BEFORE you plan — a use names an assetId.\n` +
328
+ ` import <deck> <file> copy a file in (file paths only, never a URL),\n` +
329
+ ` sniff its real format + size, verify and record it;\n` +
330
+ ` the same bytes twice reuse the first assetId\n` +
331
+ ` list <deck> what this deck has registered\n` +
332
+ ` --json machine-readable result on both subcommands\n` +
333
+ ` A use is original (place THESE bytes — never searched for, generated\n` +
334
+ ` or substituted), reconstruct (rebuild it as editable slide content;\n` +
335
+ ` reconstructionScope slide | element) or reference (the model sees it,\n` +
336
+ ` no picture required). useId/assetId are durable — keep them byte-\n` +
337
+ ` identical in plan.md, the spec and the slide's data-asset-use.\n` +
338
+ ` view-draft reports unmet obligations; build-pptx/push refuse them.\n` +
339
+ ` Needs an add-in advertising imageIntentV1 (else the command stops\n` +
340
+ ` before generating, with image_intent_unsupported and nothing\n` +
341
+ ` written). Decks with no assetUses are unaffected.\n` +
144
342
  ` slidemind imagen "<prompt>" Generate ONE image with the add-in's image model into a deck's assets/ (paired backend, pane-free)\n` +
145
343
  ` --deck <deck> bound deck to write the image into (required)\n` +
146
344
  ` --out <name> file name under assets/ (default imagen-N.png)\n` +
@@ -167,13 +365,18 @@ export function printHelp(cmd = null) {
167
365
  ` --at <index> insert after slide N (0-based); default = append\n` +
168
366
  ` --replace delete existing slides first (destructive)\n` +
169
367
  ` --slides <ids> comma-separated source slide ids to push\n` +
170
- ` --accept-conversions replace even when a converted slide is unconfirmed\n` +
171
- ` --force discard manual PowerPoint edits instead of reconciling them\n` +
368
+ ` --accept-conversions replace an unconfirmed converted slide (only on the user's go-ahead)\n` +
369
+ ` --force discard manual PowerPoint edits instead of reconciling them\n` + ` Refuses a slide whose recorded preservation obligations are unmet, same\n` +
370
+ ` as build-pptx; view-draft is where they are reported and repaired.\n` +
371
+ ` slidemind clean <deck> Delete the deck's regenerable renders (.slidemind/preview/ and\n` +
372
+ ` abandoned .part downloads). Slides, assets, conversions, state,\n` +
373
+ ` concepts and a download still in flight are never touched\n` +
172
374
  ` slidemind login Sign in to Microsoft 365 in your browser so SharePoint-backed\n` +
173
375
  ` slides and images download without an open PowerPoint window\n` +
174
- ` --backend <url|env> environment to sign in for (default: the default one)\n` +
175
- ` slidemind whoami Show every connected environment (* = default)\n` +
176
- ` slidemind logout [url|env] Unpair one environment (--all to unpair every one)\n` +
376
+ ` slidemind whoami Show the connection and its Microsoft 365 sign-in, named as\n` +
377
+ ` the account that signed in\n` +
378
+ ` --json machine-readable (microsoft365.username = that account)\n` +
379
+ ` slidemind logout Disconnect the CLI (drops its Microsoft 365 sign-in too)\n` +
177
380
  ` slidemind init --for <t> Set up slidemind in a harness (deploys the skill; clears the bundle cache)\n` +
178
381
  ` <t> = claude | codex | opencode | all | auto (every harness found here)\n` +
179
382
  ` 'setup' already does this — only needed after installing a new agent\n` +
@@ -186,9 +389,10 @@ export function printHelp(cmd = null) {
186
389
  ` slidemind --version Print version\n` +
187
390
  `\n` +
188
391
  `Install targets: claude | codex | opencode | all\n` +
189
- `Decks live in ./decks/ relative to your current directory.\n` +
190
- `Override with env: SLIDEMIND_DECKS=/abs/path/to/decks\n` +
191
- `Global: --backend <url|env> scope a command to one paired environment (also SLIDEMIND_BACKEND)\n`;
392
+ `Decks live in ./decks/ relative to your current directory. A deck keeps its\n` +
393
+ `deliverables at the root (<slug>.pptx, plan.html) and everything the CLI\n` +
394
+ `works with in <deck>/.slidemind/ — decks without it are used as-is.\n` +
395
+ `Override with env: SLIDEMIND_DECKS=/abs/path/to/decks\n`;
192
396
  process.stdout.write(cmd ? commandBlock(text, cmd) : text);
193
397
  }
194
398
 
@@ -136,6 +136,15 @@ export async function cmdUninstall(rest) {
136
136
  value: cleared ? `${cleared} bundle${cleared === 1 ? "" : "s"}` : "empty",
137
137
  });
138
138
 
139
+ // Removing slidemind from every harness is removing it: the workspace's login
140
+ // item goes too (one harness out of several still needs it).
141
+ if (target === "all") {
142
+ const { removeAutostart } = await import("../workspace/autostart.mjs");
143
+ const r = removeAutostart();
144
+ await ui.section("workspace", "login item");
145
+ ui.step({ status: "remove", label: "at login", value: r.removed ? "removed" : "none" });
146
+ }
147
+
139
148
  ui.done(target === "all" ? "removed from all harnesses" : `removed from ${target}`);
140
149
  }
141
150