slidemind 0.2.3 → 0.3.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/install.ps1 ADDED
@@ -0,0 +1,70 @@
1
+ # slidemind zero-prerequisite bootstrap (Windows).
2
+ #
3
+ # Served straight out of the published npm tarball by unpkg/jsdelivr, so the
4
+ # add-in's Connect CLI panel can print ONE command that works on a machine
5
+ # with nothing but PowerShell:
6
+ #
7
+ # powershell -NoProfile -ExecutionPolicy Bypass -Command "& ([scriptblock]::Create((irm https://unpkg.com/slidemind/install.ps1))) smc1_<claim>"
8
+ #
9
+ # It ensures Node.js >= 18 (installing — or upgrading an outdated one — via
10
+ # winget when available),
11
+ # then hands off to `npx --yes slidemind@latest setup <claim>` — which does ALL
12
+ # the persistent work (redeem claim, npm install -g, deploy skills, ready). If
13
+ # Node cannot be installed here, we abort BEFORE the claim is touched: the
14
+ # one-time code stays valid and the same pasted command works again after a
15
+ # manual Node install.
16
+ #
17
+ # The macOS/Linux counterpart is install.sh, same contract.
18
+ param([string]$Claim)
19
+
20
+ $ErrorActionPreference = "Stop"
21
+ $NodeMin = 18
22
+
23
+ if (-not $Claim -or -not $Claim.StartsWith("smc1_")) {
24
+ Write-Host 'usage: powershell -NoProfile -ExecutionPolicy Bypass -Command "& ([scriptblock]::Create((irm https://unpkg.com/slidemind/install.ps1))) smc1_<claim>"'
25
+ Write-Host "Copy the full command from PowerPoint: slidemind add-in -> Settings -> General -> Connect CLI."
26
+ exit 2
27
+ }
28
+
29
+ function Test-NodeOk {
30
+ if (-not (Get-Command node -ErrorAction SilentlyContinue)) { return $false }
31
+ try { $major = [int](& node -p "process.versions.node.split('.')[0]" 2>$null) } catch { return $false }
32
+ return $major -ge $NodeMin
33
+ }
34
+
35
+ function Update-SessionPath {
36
+ # The installer edits the registry PATH; this session's copy is stale.
37
+ $env:Path = [Environment]::GetEnvironmentVariable("Path", "Machine") + ";" +
38
+ [Environment]::GetEnvironmentVariable("Path", "User")
39
+ }
40
+
41
+ if (-not (Test-NodeOk)) {
42
+ Write-Host "slidemind: Node.js >= $NodeMin not found - trying to install it..."
43
+ if (Get-Command winget -ErrorAction SilentlyContinue) {
44
+ try {
45
+ winget install --id OpenJS.NodeJS.LTS --accept-source-agreements --accept-package-agreements
46
+ } catch {}
47
+ Update-SessionPath
48
+ if (-not (Test-NodeOk)) {
49
+ # An OLD Node is installed: `install` on an existing package fails on
50
+ # older winget versions — `upgrade` is the call that moves it. Try both
51
+ # ids, whichever line the machine has (LTS or current).
52
+ foreach ($id in "OpenJS.NodeJS.LTS", "OpenJS.NodeJS") {
53
+ try { winget upgrade --id $id --accept-source-agreements --accept-package-agreements } catch {}
54
+ }
55
+ Update-SessionPath
56
+ }
57
+ }
58
+ if (-not (Test-NodeOk)) {
59
+ Write-Host ""
60
+ Write-Host "Could not install Node.js automatically. Pairing aborted - your connect code was NOT used"
61
+ Write-Host "and stays valid. Install Node.js $NodeMin or newer, then paste the SAME command again:"
62
+ Write-Host " - Installer: https://nodejs.org/en/download (LTS; adds node + npm to PATH)"
63
+ Write-Host " - winget: winget install OpenJS.NodeJS.LTS"
64
+ Write-Host "Close and reopen your terminal after installing so PATH is fresh."
65
+ exit 1
66
+ }
67
+ }
68
+
69
+ & npx --yes slidemind@latest setup $Claim
70
+ exit $LASTEXITCODE
package/install.sh ADDED
@@ -0,0 +1,65 @@
1
+ #!/bin/sh
2
+ # slidemind zero-prerequisite bootstrap (macOS / Linux).
3
+ #
4
+ # Served straight out of the published npm tarball by unpkg/jsdelivr, so the
5
+ # add-in's Connect CLI panel can print ONE command that works on a machine
6
+ # with nothing but a shell:
7
+ #
8
+ # curl -fsSL https://unpkg.com/slidemind/install.sh | sh -s -- smc1_<claim>
9
+ #
10
+ # It ensures Node.js >= 18 (installing — or upgrading an outdated one — via
11
+ # Homebrew when available), then
12
+ # hands off to `npx --yes slidemind@latest setup <claim>` — which does ALL the
13
+ # persistent work (redeem claim, npm install -g, deploy skills, ready). If Node
14
+ # cannot be installed here, we abort BEFORE the claim is touched: the one-time
15
+ # code stays valid and the same pasted command works again after a manual
16
+ # Node install.
17
+ #
18
+ # The Windows counterpart is install.ps1, same contract.
19
+ set -eu
20
+
21
+ NODE_MIN=18
22
+
23
+ CLAIM="${1:-}"
24
+ case "$CLAIM" in
25
+ smc1_*) ;;
26
+ *)
27
+ echo "usage: curl -fsSL https://unpkg.com/slidemind/install.sh | sh -s -- smc1_<claim>" >&2
28
+ echo "Copy the full command from PowerPoint: slidemind add-in -> Settings -> General -> Connect CLI." >&2
29
+ exit 2
30
+ ;;
31
+ esac
32
+
33
+ node_ok() {
34
+ command -v node >/dev/null 2>&1 || return 1
35
+ major="$(node -p 'process.versions.node.split(".")[0]' 2>/dev/null)" || return 1
36
+ [ "$major" -ge "$NODE_MIN" ] 2>/dev/null
37
+ }
38
+
39
+ if ! node_ok; then
40
+ echo "slidemind: Node.js >= $NODE_MIN not found — trying to install it..." >&2
41
+ if command -v brew >/dev/null 2>&1; then
42
+ # `install` covers a missing formula; when it is already installed but
43
+ # outdated (an old brew node shadowing the check), install errors with
44
+ # "already installed" and `upgrade` is the call that actually fixes it.
45
+ brew install node >&2 || brew upgrade node >&2 || true
46
+ hash -r 2>/dev/null || true
47
+ fi
48
+ if ! node_ok; then
49
+ echo "" >&2
50
+ echo "Could not install Node.js automatically. Pairing aborted — your connect code was NOT used" >&2
51
+ echo "and stays valid. Install Node.js $NODE_MIN or newer, then paste the SAME command again:" >&2
52
+ echo " - Installer: https://nodejs.org/en/download" >&2
53
+ echo " - Homebrew: https://brew.sh (then: brew install node)" >&2
54
+ echo " - nvm: https://github.com/nvm-sh/nvm" >&2
55
+ exit 1
56
+ fi
57
+ fi
58
+
59
+ # stdin is the curl pipe (already consumed by `sh -s`), so re-attach the real
60
+ # terminal — setup's interactive Microsoft 365 sign-in offer needs a TTY.
61
+ if [ -r /dev/tty ]; then
62
+ exec npx --yes slidemind@latest setup "$CLAIM" </dev/tty
63
+ else
64
+ exec npx --yes slidemind@latest setup "$CLAIM"
65
+ fi
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "slidemind",
3
- "version": "0.2.3",
3
+ "version": "0.3.0",
4
4
  "description": "Connect Claude Code, Codex & opencode to the slidemind PowerPoint add-in — let your coding agent build and edit native, on-brand PowerPoint decks against your active corporate design, from the terminal.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -22,6 +22,8 @@
22
22
  ".codex-plugin/",
23
23
  ".agents/plugins/",
24
24
  "assets/",
25
+ "install.sh",
26
+ "install.ps1",
25
27
  "AGENTS.md",
26
28
  "README.md"
27
29
  ],
@@ -73,25 +73,31 @@ Everything about how a deck is authored, styled, and rendered is shaped by the c
73
73
 
74
74
  **The open add-in is the source of truth.** `ready` binds to whatever design is open *now*; a deck's `.bundle-ref.json` records the last-resolved design for the render-time etag contract and host-free bundle reads — it is **not** a pinned design preference. So you never "start a session with design A" from a deck ref; you start it from what the add-in has open.
75
75
 
76
- **On exit 0** the JSON reports `presId`, `presFP`, `designName`, `bundleDir`, `bundleEtag`, `bound`, and `paneSuspended`, with `nextAction: "Ready."`. Read `workflow/flow.md` and `design.md` from `bundleDir` (cached at `${XDG_CACHE_HOME:-~/.cache}/slidemind/bundles/<presFP>/`, alongside `corporate-design.css`, `layouts/*.jsx`, `layouts/layouts.css`, `assets/`, `samples/`, and the rest of the add-in-owned `workflow/*.md`) and start planning. A render-ready window whose pane is suspended still exits 0 with `paneSuspended: true` — push/search/read wait for a pane click on their own; **don't treat it as a blocker.**
76
+ **On exit 0** the JSON reports `presId`, `presFP`, `designName`, `bundleDir`, `bundleEtag`, `bound`, `paneSuspended`, and `engines` (`{available, envs}` — whether engine search reaches the corpus without a window), with `nextAction: "Ready."`. Read `workflow/flow.md` and `design.md` from `bundleDir` (cached at `${XDG_CACHE_HOME:-~/.cache}/slidemind/bundles/<presFP>/`, alongside `corporate-design.css`, `layouts/*.jsx`, `layouts/layouts.css`, `assets/`, `samples/`, and the rest of the add-in-owned `workflow/*.md`) and start planning. A render-ready window whose pane is suspended still exits 0 with `paneSuspended: true` — the pane ops wait for a pane click on their own; **don't treat it as a blocker.**
77
77
 
78
- **A bound deck can be ready with NO window at all.** `slidemind ready <deck>` for a deck that was bound once before may exit 0 with `windowless: true` even when no PowerPoint window is registered — the design identity lives in the deck's `.bundle-ref.json` and the bundle syncs over a pane-free route. In that state authoring, `view-draft`, `build`, `review`, and `build-pptx` all work end-to-end (PowerPoint backgrounded or closed); only `push`'s insert, `search`, and `read` need a window — the `nextAction` says exactly that. Don't ask the user to open PowerPoint until one of those pane ops is actually next.
78
+ **A bound deck readies with NO window at all — that is the normal case, not a degraded one.** `slidemind ready <deck>` for a deck that was bound once before exits 0 with `windowless: true` even when PowerPoint is closed: the design identity lives in the deck's `.bundle-ref.json` and the bundle syncs over a pane-free route, so its ladder is *session → bundle freshness → engine reachability*, with **no pane precondition** — the pane is reported, never required. In that state authoring, `view-draft`, `build`, `review`, `build-pptx`, and (where `engines.available` is true) engine search and download all work end-to-end; only `push`'s insert, `insert-slide`, and `read <presId>` of an open presentation need a window — the `nextAction` says exactly that. Don't ask the user to open PowerPoint until one of those is actually next.
79
79
 
80
- **On non-zero** the CLI names exactly one blocker `{code, actor, message}` and one `nextAction`. **The CLI owns the remediation text — relay `nextAction`, don't restate it.** Only two blockers ever need the user:
80
+ **Microsoft 365 sign-in — automatic on first need; just let it happen.** The engine surface — corpus search, brand-imagery downloads, slide extraction — runs **as the user**. `ready` reports the state (JSON: `m365.signedIn`, `m365.degradation`) so you know it's coming, but you never initiate it: the first engine command opens the user's browser ITSELF, prints `Microsoft 365 sign-in needed — opening your browser…`, and **blocks until the human finishes**. When that line appears, tell the user to complete the sign-in in the browser (you cannot) and keep waiting — don't kill or re-run the command. One sign-in lasts ~90 days. Rendering, bundles, and already-materialized assets never need it, so a pure author→render session runs signed-out without ever seeing this.
81
+
82
+ For a deck that was **never** bound and no window is open, `ready` asks the backend which corporate designs the user may build against: exactly one binds automatically, several stop with `ambiguous_brand` (below).
83
+
84
+ **On non-zero** the CLI names exactly one blocker `{code, actor, message}` and one `nextAction`. **The CLI owns the remediation text — relay `nextAction`, don't restate it.** These are the ones that need the user:
81
85
 
82
86
  - **`ambiguous_design`** — several windows with **different** corporate designs are open, so `ready` can't choose. The CLI lists the candidates (each with its `env` and window `title`/`fileName`, plus a design fingerprint); if the deck was last built against one of them it's marked `suggested`. **Ask the user which design to build against, naming the windows by `title`/`fileName` (and `env`) — never by raw id** — and mention the suggested one if present ("this deck was last built against X — continue with it?"). Then re-run `slidemind ready --to <presId>` for the window they pick.
83
- - **Connection blockers** (`not_connected`, `all_unreachable`, `cli_update_required`, `no_window`, `not_render_ready`) — relay the CLI's `nextAction` verbatim (copy the setup command / open PowerPoint / click the pane / run `slidemind update`). Connecting is walked through above. (`no_window` only ever surfaces for an **unbound** deck or deckless `ready` — a bound deck rides the pane-free rung above instead.)
87
+ - **`ambiguous_brand`** — the same question with no window to ask it from: the backend offers several designs (`brands[]`, each with a `name` and `env`). Ask the user by **name**, then re-run `slidemind ready [deck] --brand <presFP>`.
88
+ - **`login_required`** — the automatic browser sign-in was refused or abandoned, so **corpus search and engine downloads** stop until the user runs `slidemind login` (browser step, whenever they're ready). It never blocks authoring, rendering, or the bundle — relay the line, keep working, and re-run the search after they've signed in. When a slidemind pane is already open the CLI says so and routes the search through that window instead: follow what it printed.
89
+ - **Connection blockers** (`not_connected`, `all_unreachable`, `cli_update_required`, `no_window`, `not_render_ready`) — relay the CLI's `nextAction` verbatim (copy the setup command / open PowerPoint / click the pane / run `slidemind update`). Connecting is walked through above. (`no_window` only ever surfaces for a deck that is neither bound nor bindable from the backend's design list — a bound deck rides the pane-free rung above instead.)
84
90
 
85
91
  **The session spine** (= the stage todos you created up front): `slidemind ready` → read `workflow/flow.md` + `design.md` from `bundleDir` → intake/plan (below) → `slidemind new <slug>` (scaffolds **and** auto-binds the deck to the open design — zero extra download, because `ready` already materialized it) → `slidemind plan <slug>` (plan sign-off gate for decks; single slides stay in chat) → `slidemind concepts <slug>` (mandatory concept-mockup pass for a deck, then rebuild toward each) → author → `view-draft` → `review` (mandatory, the moment the deck builds clean) → deliver (`push` / `build-pptx`). `slidemind new` best-effort binds when the open design is unambiguous and the bundle is already materialized; if it can't (ambiguous, or no host), it scaffolds anyway and hints to run `slidemind ready <slug>`.
86
92
 
87
- **Backgrounded — or closed — PowerPoint is expected to work.** Do not pre-emptively ask the user to focus PowerPoint or the task pane, and never ask them to keep it foregrounded while something runs. The render itself (`build-pptx`, the render half of `push`) **never needs a window** for a bound deck — it captures the slides with a local headless browser and assembles the `.pptx` server-side over the pane-free brand route, streams its own `rendering slide n/m…` progress, and survives arbitrarily long authoring sessions (the old "author 30 minutes and the add-in is gone" failure no longer applies). Only the pane-touching ops (the insert half of `push`, engine search/download, `read`) need a live pane, and the CLI handles that itself: it waits up to ~2 min, prints one *"click the slidemind pane"* line when user help is genuinely needed, and retries safely. If you see that line, relay it once and keep waiting — do not rebuild, re-plan, or babysit. If a `push` can't deliver (pane never woke, or no window at all), the rendered `.pptx` was saved next to the deck (`paneSuspended` + `fallbackFile` in the result) — nothing is lost; re-run the push once a window exists. Reserve the full "open PowerPoint…" instruction for when a command actually reports it as the blocker.
93
+ **Backgrounded — or closed — PowerPoint is expected to work.** Do not pre-emptively ask the user to focus PowerPoint or the task pane, and never ask them to keep it foregrounded while something runs. **The whole authoring loop is host-free for a bound deck** — intake → plan → concepts → author → `view-draft` → `review` → `slidemind build-pptx <deck> [--out <file>]` — because the render captures the slides with a local headless browser and assembles the `.pptx` server-side over the pane-free brand route (streaming its own `rendering slide n/m…` progress), and engine discovery, search, downloads, and slide reuse run off the connection wherever the add-in serves them (`engines.available` in `ready`'s payload; older add-ins relay them through a pane instead). Exactly three things need a live PowerPoint: **`push`'s insert**, **`insert-slide`**, and **`read <presId>`** of an open presentation. Where a pane *is* involved, the CLI handles the wait itself: up to ~2 min, one *"click the slidemind pane"* line when user help is genuinely needed, then a safe retry. If you see that line, relay it once and keep waiting — do not rebuild, re-plan, or babysit. If a `push` can't deliver (pane never woke, or no window at all), the rendered `.pptx` was saved next to the deck (`paneSuspended` + `fallbackFile` in the result) — nothing is lost; re-run the push once a window exists. Reserve the full "open PowerPoint…" instruction for when a command actually reports it as the blocker.
88
94
 
89
- `slidemind build`, `review`, and `view-draft` run offline, but only against a **previously materialized** bundle — so `ready` (or a prior `ready`) must have succeeded at least once. `build-pptx` additionally needs the backend reachable (the OOXML assembly is server-side), but no PowerPoint window for a bound deck.
95
+ `slidemind build`, `review`, and `view-draft` run offline, but only against a **previously materialized** bundle — so `ready` (or a prior `ready`) must have succeeded at least once. `build-pptx` additionally needs the backend reachable (the OOXML assembly is server-side), but no PowerPoint window for a bound deck — so *"a `.pptx` file to send around"* is deliverable end-to-end with PowerPoint closed.
90
96
 
91
97
  ## Two UX rules (gates + host)
92
98
 
93
99
  - **Announce every gate, with a clickable link.** The moment `slidemind plan` or `slidemind review` starts it prints `ready at <url>` and a `BROWSER:` status line. Immediately tell the user in chat whether a browser tab opened (new tab / reused tab / failed to open) and give the gate URL as a clickable link — every round, and again whenever you report that you're still waiting on their decision.
94
- - **Add-in unreachable → block and name the tab.** When a host-required command can't reach the add-in (a connection blocker, a pane that never wakes, the *click the slidemind pane* line with no effect), **stop the workflow** — don't skip the step, work around it, or retry silently. Ask the user to go back into the PowerPoint tab of presentation **<title/fileName>** (name it from the command output or `slidemind presentations` — never a raw id), then re-run the failed command once they have.
100
+ - **Add-in unreachable → block and name the tab.** When a genuinely host-required command (`push`'s insert, `insert-slide`, `read <presId>`) can't reach the add-in (a connection blocker, a pane that never wakes, the *click the slidemind pane* line with no effect), **stop the workflow** — don't skip the step, work around it, or retry silently. Ask the user to go back into the PowerPoint tab of presentation **<title/fileName>** (name it from the command output or `slidemind presentations` — never a raw id), then re-run the failed command once they have. Everything before delivery keeps running meanwhile — never park the whole task on a window it doesn't need.
95
101
 
96
102
  ## Intake → plan (you own this; do it in chat)
97
103
 
@@ -101,7 +107,7 @@ This is your mapping of `workflow/flow.md` stages 1–2 (*clarify* + *plan sign-
101
107
  - **Interview just enough** to pin: audience/goal, the content and its sources (none / pasted / local files / existing deck / web research — a source that's already a local `.pptx` you read host-free with `slidemind read <file.pptx>`), and the slide outline (each slide's role + a layout *job* like cover/agenda/section/quote/closing/body — the concrete brand layout is chosen later against the bundle). If the content source resolves to "none", don't silently plan from generic knowledge — say so, and either interview the user for the substance (their claims, numbers, examples) or offer web research before writing storyboards.
102
108
  - **Lock the output target — it decides your deliver command.** Into the open PowerPoint (`slidemind push`), or a `.pptx` file (`slidemind build-pptx`) — or an HTML preview / a mix. Infer it when the wording is clear ("add a slide to this deck" → push; "make me a deck I can send around" → a file) and ask only when genuinely ambiguous — but **always confirm the concrete target before delivering** (which window, or which output path).
103
109
  - **Replay and get sign-off.** Single slides / tiny sets → a compact in-chat plan (no gate). Decks → persist a **story-focused plan with body storyboards** in `decks/<slug>/plan.md`, two top-level sections in order:
104
- - `## Red thread` — one presentation **move** per slide, as bullets or one arrow-separated line. Name the *teaching function* of each slide — short verb+noun labels for what the audience is doing cognitively at that step — never its content: no claims, facts, metrics, client-specific terms, slide copy, generic section names, or wording copied from these instructions.
110
+ - `## Red thread` — one presentation **move** per slide, as bullets or one arrow-separated line. The number of moves MUST equal the number of `### Slide N` sections: when the user names a slide count, write exactly that many moves and sections — never expand the narrative arc beyond the requested count. Name the *teaching function* of each slide — short verb+noun labels for what the audience is doing cognitively at that step — never its content: no claims, facts, metrics, client-specific terms, slide copy, generic section names, or wording copied from these instructions.
105
111
  - `## Per-slide storyboards` — one `### Slide N: <role>` subsection per slide, each a one-sentence `Core message:` followed by **exactly one** fenced `storyboard-canvas` JSON block.
106
112
 
107
113
  The `storyboard-canvas` is a **body-only spatial sketch**, not final slide design: it plans the body region only and never the template title/header/footer/logo/slide-number areas. Blocks live in a 1600 × 900 coordinate space, kept inside the body-safe area (roughly x 80–1520, y 110–780); position, size, and proximity must express the slide's meaning — hierarchy, sequence, causality, contrast, grouping, flow direction, or the main focus — not random placement. Each block shows only two visible text fields: `designElement` (the visual mechanism in up to 5 words — e.g. "hub-spoke visual", "loop diagram", "timeline", "matrix", "before/after contrast", "scorecard") and `content` (a 1–2 word label), plus `emphasis` ("primary"/"secondary"/"tertiary") for hierarchy. `keyMessage` is the slide's **story beat**: one plain sentence naming what this slide does to move the story forward, stated abstractly — NOT the headline or copy that will appear on the slide, and not the red-thread move restated. `contentNotes` is the **content draft** that delivers that beat — facts, claims, examples, source points, caveats, draft copy, proof points — and carries no design, layout, tone, or authoring-process instructions. It is the slide's full content budget: the create stage and the concept-mockup generator build from `contentNotes` alone and never add substance, so anything not in the notes will not be on the slide. Write them so the slide body could be authored from them without inventing anything — every claim spelled out concretely, with the numbers, named examples, or proof points that make it land. Two or three abstract topic bullets ("X is expensive", "Y improves") is an unfinished draft, not a storyboard.
@@ -135,7 +141,7 @@ This is your mapping of `workflow/flow.md` stages 1–2 (*clarify* + *plan sign-
135
141
  }
136
142
  ```
137
143
 
138
- Make the connection to the current deck explicit in the red thread, core messages, `keyMessage`, or `contentNotes` whenever the work extends the open presentation, and fold in any content findings from slides/files you inspected. Then run `slidemind plan <deck>`: it opens the plan as **editable storyboards** — the user reorders/renames/inserts/removes slides on the red-thread strip, drags/resizes/edits blocks and the content draft directly on each slide's canvas, and/or leaves overall feedback — and it **blocks until the user decides** — announce it per *Two UX rules* (browser opened? + the clickable gate URL) and give the shell call a **generous timeout** (the gate waits up to 30 min; if your harness kills the call early, just re-run it — the user's open tab reconnects). Outcomes: `DECISION: approved` → proceed to author; `DECISION: comments` → apply the feedback to `plan.md` — the feedback may include a **generated story-iteration instruction** (when the user restructured the red thread by adding or renaming slides, the gate asks you to build storyboards and content for exactly those moves while keeping the other slides intact) — (the round is persisted verbatim in `<deck>/plan-review.json` if your capture of the output was truncated) and **re-run the gate in the same turn** — after a comments round the user's tab sits on a waiting spinner that only the next `slidemind plan` run releases, so never end your turn, never claim to be "polling", and never send the user back to the browser between rounds (that deadlocks: the tab waits for you while you wait for the tab); `DECISION: pending` → the gate timed out with no answer — not approval, not rejection: don't author; wrap up telling the user the plan still awaits their sign-off and re-run `slidemind plan <deck>` on their go-ahead. Loop until approved. A `plan.md` **approved in a prior gate round and unchanged since** counts as approved (skip straight to authoring); any new or revised plan must pass the gate. When the gate reports the user edited `plan.md` directly, re-read it — the edited storyboards **are** the approved plan; author from them.
144
+ Make the connection to the current deck explicit in the red thread, core messages, `keyMessage`, or `contentNotes` whenever the work extends the open presentation, and fold in any content findings from slides/files you inspected. Then run `slidemind plan <deck>`: it opens the plan as **editable storyboards** — the user reorders/renames/inserts/removes slides on the red-thread strip, drags/resizes/edits blocks and the content draft directly on each slide's canvas, and/or leaves comments anchored to a slide or to the whole story — and it **blocks until the user decides** — announce it per *Two UX rules* (browser opened? + the clickable gate URL) and give the shell call a **generous timeout** (the gate waits up to 30 min; if your harness kills the call early, just re-run it — the user's open tab reconnects). Outcomes: `DECISION: approved` → proceed to author; `DECISION: comments` → apply the comments to `plan.md` — each is **slide-anchored** (`- [Slide 3: "<beat>"] …`) or whole-story, and the batch may include a **generated story-iteration instruction** (when the user restructured the red thread by adding or renaming slides, the gate asks you to build storyboards and content for exactly those moves while keeping the other slides intact) — (the round is persisted verbatim in `<deck>/plan-review.json` if your capture of the output was truncated). Change only what the comments require and keep unaffected slides verbatim (that is what makes the reviewer's per-slide "updated" badges meaningful), then **re-run the gate in the same turn**. **Expect several consecutive `DECISION: comments` rounds — that is the normal shape of a review, not an error.** The reviewer's tab stays live while you revise, so they keep reading and keep queueing comments; those queued comments are delivered by your next run, which then returns *immediately* with them (a drain round) instead of waiting. So never end your turn on a comments round, never claim to be "polling", and never send the user back to the browser between rounds — nothing reaches them until the gate runs again. Only `DECISION: approved` or `DECISION: pending` ends the loop; `DECISION: pending` → the gate timed out with no answer — not approval, not rejection: don't author; wrap up telling the user the plan still awaits their sign-off and re-run `slidemind plan <deck>` on their go-ahead. Loop until approved. A `plan.md` **approved in a prior gate round and unchanged since** counts as approved (skip straight to authoring); any new or revised plan must pass the gate. When the gate reports the user edited `plan.md` directly, re-read it — the edited storyboards **are** the approved plan; author from them.
139
145
 
140
146
  Keep planning about intent and content. The bundle is already materialized by `slidemind ready`, so `design.md` is there if you need to sanity-check feasibility — but **don't dive into brand/layout/composition detail until create/edit.**
141
147
 
@@ -146,17 +152,39 @@ Keep planning about intent and content. The bundle is already materialized by `s
146
152
  1. **Study brand samples (before composing).** The materialized bundle's `samples/` dir holds the brand's own reference slides. If it's non-empty, pick **3 at random** and **Read** each (the Read tool renders images) *before* writing any JSX — this is the local-harness stand-in for the in-app persona's reference thumbnails, and it is what keeps a deck looking on-brand rather than merely compliant. Study them for whitespace rhythm, shape density, hierarchy, accent use, and layering, then compose in that spirit (never copy a sample 1:1). This is `composition.md`'s samples discipline; skip only when `samples/` is empty.
147
153
  2. **Read, then author.** Read the create-stage references together — `workflow/create.md` (the procedure), `workflow/composition.md` (the composition baseline), `workflow/layouts.md` (palette + working area), and `workflow/authoring.md` (the substrate rules) — plus the deck's `design.md` and the specific `layouts/*.jsx` the plan uses. (That is the read set `workflow/create.md` → Context loading prescribes; follow it as written — `workflow/flow.md` you already read after `ready`.) Then write `decks/<slug>/slides/NN-<topic>.jsx` — zero-padded numeric prefix from the first file on (play order is filename order here; see the slide-file row in the command mapping), one default export per slide, composing one bundle layout (Mode A) or a blank-content layout with freeform children (Mode B), exactly as those docs prescribe. The approved `plan.md` is a **content contract, not a rendering spec** — you drew its storyboards yourself, but approval covered the story and content, not the sketch's geometry; how to interpret the `storyboard-canvas` blocks at author time (design hypothesis: reinterpret freely, never transcribe coordinates, block geometry, or block labels) is owned by `workflow/create.md` → Preconditions. **Generate concept mockups first — mandatory for a deck (stage 4).** Before authoring a deck from an approved plan, run `slidemind concepts <deck>` — the local-harness equivalent of the in-app manager's *unconditional* concept pass (`generateConceptImages` fires for every `approvedPlan && !targetSlideScope` delegation). Then **Read** each `decks/<slug>/concepts/concept-slide-NN.png` as the per-slide design target and rebuild it under your constraints — the rebuild rule is owned by `workflow/create.md` → Preconditions (reproduce the mockup's composition, hierarchy, graphic mechanism, AND granularity — its organism count, badges, satellites, and annotation layers are part of the target, and restraint is a colour budget, never an element budget; texts follow the story). It never hard-fails: if it produces no mockups (exit 0 — the add-in can't generate them) author from the storyboards alone. Skip it only where the add-in's `!targetSlideScope` gate would — a single slide, a tiny in-chat set, or a targeted rework of existing slides.
148
154
  When storyboard objects share a `visualFamily`, rebuild those mockups as one recognizable visual series. The lowest-numbered member is the anchor. Preserve its shape vocabulary, framing, hierarchy, annotation style, illustration treatment, and spatial rhythm across the family, but adapt every member's element count, sizing, arrangement, labels, emphasis, and connections to its own complete `keyMessage`, `contentNotes`, role, and structure. Never copy another member's text, claims, or data, never force its exact geometry, and never omit approved content because it does not fit the anchor composition.
149
- 3. **Imagery from the engine.** Discover the available image engines by listing them — `slidemind ls engines --subtype image` (the local-harness equivalent of the in-app design agent's `list_engines` tool) — judge which engine fits from its label and description, then search it and place results. (`slidemind search` without `--engine` picks an image engine the same way.) The engine search/reuse mechanics live in **`slidemind-search`** — use it to find and fetch imagery; the create-only detail — sizing a fetched asset's box and recolouring an icon — is in *Placing engine imagery* below. To pull in an **existing whole slide** (not just an image), use `slidemind-search`'s `add-slide` — it folds the slide into `decks/<slug>/slides/` as a native `.pptx` that coexists with your JSX in play order and survives re-push. The slide can come from an engine search **or** from a local `.pptx` on disk (`add-slide --from <file.pptx> --pick <n> --deck <slug>`, host-free); both fold in identically — see `slidemind-search`.
155
+ 3. **Imagery from the engine.** Discover the available image engines by listing them — `slidemind ls engines --subtype image` (the local-harness equivalent of the in-app design agent's `list_engines` tool; no PowerPoint window needed where the add-in serves the pane-free engine routes) — judge which engine fits from its label and description, then search it and place results. (`slidemind search` without `--engine` picks an image engine the same way.) The engine search/reuse mechanics live in **`slidemind-search`** — use it to find and fetch imagery; the create-only detail — sizing a fetched asset's box and recolouring an icon — is in *Placing engine imagery* below. To pull in an **existing whole slide** (not just an image), use `slidemind-search`'s `add-slide` — it folds the slide into `decks/<slug>/slides/` as a native `.pptx` that coexists with your JSX in play order and survives re-push. The slide can come from an engine search **or** from a local `.pptx` on disk (`add-slide --from <file.pptx> --pick <n> --deck <slug>`, host-free); both fold in identically — see `slidemind-search`.
150
156
  4. **Look at your own draft first (you, the agent).** This is the **draft check** in `workflow/build-and-export.md` — your pre-check, *before* any human sees the deck. Run `slidemind view-draft <deck>`: it renders each authored `slides/*.jsx` to a per-slide PNG and hands the images back to you — **nothing is pushed, no `.pptx` is written, no review gate opens**. Read every image the way the audience will, fix anything visibly wrong **in the JSX** (re-express, never delete), and view again until each slide is sound. One round usually suffices. It runs locally against the materialized bundle, so it needs no awake pane and no open host once the bundle has been downloaded once. Two faithful limits to know: a slide on a **purpose-built (Mode A) layout** (cover/divider/agenda) and a **reused native `.pptx`** slide have no freeform region to capture, so they come back as a status line, not an image — judge those at the full-deck `review`/`build` step below. Do this self-check before the human review gate; don't push a draft you haven't looked at.
151
157
  5. **The review gate — the moment the deck builds clean.** This is the **review gate** in `workflow/build-and-export.md`; the CLI's gate command is `slidemind review <deck>`. Run it the instant the authored set builds clean — before any deliver, without asking whether the user wants a review. It builds the HTML preview first (the same lint the render enforces, so a gate that *opens* is a clean build; while lint errors remain it exits non-zero — fix and re-run), then opens the browser with a spatial comment overlay and **blocks until the user decides** (host-free — no open PowerPoint needed for the gate). Announce it per *Two UX rules*: tell the user the browser opened and give the clickable gate URL. **Blocking is the point: give the shell call a generous timeout (the gate waits up to 30 min) and let it sit — a long quiet wait means the user is reviewing, not that something hung. Never cancel it; if your harness kills the call early, just re-run it (the user's open tab reconnects).** The verdict prints to stdout:
152
158
  - `DECISION: approved` → proceed to the deliver the user chose at intake: `slidemind push <deck>` (into PowerPoint) or `slidemind build-pptx <deck>` (to a file).
153
- - `DECISION: comments` → the user left spatially-anchored notes, each citing a `slides/<file>.jsx:line:col`, the source line, and the click position (plus a readonly-layout note where the click landed on a layout slot). **Edit the cited files in place at the given line and re-run `slidemind review <deck>` in the same turn** — after a comments round the user's tab waits for the next round, so never end your turn, never claim to be "polling", and never send the user back to the browser between rounds. The prior round shows as read-only ghost pins so the user can confirm each note was addressed. Loop until approved. Nothing is exported on a comments round. **Every round is persisted verbatim in `<deck>/review.json`** — if your shell capture of the gate's output was truncated or lost (backgrounded command, clipped scrollback), read that file for the complete comment list; never guess at missing comments or ask the user to re-type them.
159
+ - `DECISION: comments` → the user left spatially-anchored notes, each citing a `slides/<file>.jsx:line:col`, the source line, the click position, and a **round tag** (`[2, round 1] …`) — plus a readonly-layout note where the click landed on a layout slot. A pin carrying an *older* round was dropped on the previous render, so read the file before you edit it rather than trusting the cited line blindly. **Edit the cited files in place at the given line and re-run `slidemind review <deck>` in the same turn.** **Expect several consecutive `DECISION: comments` rounds — that is the normal shape of a review, not an error.** The user's tab stays live while you fix: they keep browsing the preview and keep dropping pins, and those queued pins are delivered by your next run, which then returns *immediately* with them (a drain round) instead of waiting. So never end your turn on a comments round, never claim to be "polling", and never send the user back to the browser between rounds — nothing reaches them until the gate runs again. Addressed pins come back as read-only ghost pins so the user can confirm each note was worked in. Only `DECISION: approved` or `DECISION: pending` ends the loop. Nothing is exported on a comments round. **Every round is persisted verbatim in `<deck>/review.json`** — if your shell capture of the gate's output was truncated or lost (backgrounded command, clipped scrollback), read that file for the complete comment list; never guess at missing comments or ask the user to re-type them.
154
160
  - `DECISION: pending` → the gate timed out with no answer (the user stepped away). Not approval, not rejection — **do not deliver**. Wrap up your turn: tell the user the preview is still waiting for their review and that on their go-ahead you'll re-run `slidemind review <deck>` (their tab reconnects).
155
161
 
156
162
  `slidemind review` is the only step that pauses; `push`/`build-pptx` never pause — they render pane-free (local capture + server-side assembly, with live per-slide progress). **CLI exports happen out of the user's sight, so the flow invariant — *the user sees every change before the task is done* — makes re-review mandatory here: every deliver is preceded by an approved review of exactly what it delivers. After any later slide change, the deck re-enters `slidemind review` before it can be pushed or exported again — never deliver an unreviewed change.** If several presentations are open, confirm **which one to push into** first (it can differ from the design source `slidemind ready` locked in). After approval, run the push without asking; it works while PowerPoint is backgrounded, and the CLI itself waits + retries if the pane is needed (see *First step* above). If a push still times out, the rendered `.pptx` is saved next to the deck — re-run `push` after the user clicks the pane, without rebuilding.
157
163
 
158
164
  **On a clean approve + deliver, the task is done.** Report what landed (slide count + target) and stop — no sanity pass, no re-verification. The visual QA already happened at the draft check (step 4) and the user signed off at the review gate.
159
165
 
166
+ ## Manual PowerPoint edits (the reconcile loop)
167
+
168
+ The user may have edited the deck directly in PowerPoint since your last deliver — moved a shape, retyped a line, recoloured a box. Those edits live only in that file or that window; the deck's jsx knows nothing about them. `slidemind build-pptx` and `slidemind push --replace` therefore check before they write, and play the changes back to you:
169
+
170
+ - **Plain text edits are already folded into your jsx** — the command prints `note: folded N manual text edit(s) … into slides/<file>` and the fold is now the source of truth on disk. **Re-read that file before your next edit** or you will write over it from a stale copy.
171
+ - **Everything else refuses the deliver** with a `RECONCILE REQUIRED` report per slide (geometry in EMU, style changes, added/removed shapes) and exit code non-zero. Nothing was written or pushed.
172
+
173
+ On a `RECONCILE REQUIRED`:
174
+
175
+ 1. **Fold every listed change into the slide's jsx** — the user made them on purpose; losing them is unacceptable. Translate geometry deltas into **layout intent** (spacing, order, relative sizing, which region a thing sits in) — never literal offsets, absolute coordinates, or transform/translate hacks bolted onto a working composition. Recreate added shapes as jsx elements; drop deleted ones. If a folded position genuinely cannot fit the content area, re-express its intent (reflow, reorder, resize) rather than discarding it.
176
+ 2. Lines marked `[preserved]` are foreign add-in content (e.g. an embedded chart) — do **not** recreate them; they are spliced back into the rendered slide automatically.
177
+ 3. **Re-run the same command.** The re-run proceeds: the report was acknowledged (`decks/<slug>/.reconcile-ack.json`), and only a NEW manual edit re-arms the check. The deck changed, so the flow invariant applies — re-run `slidemind review <deck>` before delivering again.
178
+ 4. **Do this silently.** Reconcile is bookkeeping, not a deliverable: never explain the report, the baseline, or your folding steps, and never ask the user about it. Fold and carry on with their actual request in the same turn. At most one short clause in your final answer ("kept your manual layout change").
179
+
180
+ `--force` on either command **discards the user's manual edits** and writes anyway. It is a last resort, only on the user's explicit say-so — never reach for it to get past a report you could fold.
181
+
182
+ Three refusals are not foldable and need the user, not a retry:
183
+
184
+ - `the live deck contains N slide(s) not in this deck` — a whole-deck replace would delete slides someone added by hand or with another tool. Push **without** `--replace` to insert instead, or ask the user what should happen to them.
185
+ - `<file> was edited in PowerPoint …` naming slides with no jsx behind them (a reused native slide, or one pushed by another deck) — build to a different target with `--out <other file>` so their edits survive, or ask.
186
+ - `backend does not support manual-edit reconcile` / `the live deck could not be inspected` — the add-in is older than this contract, or its PowerPoint build cannot export slides. Tell the user to update the slidemind add-in. Do not paper over it with `--force`.
187
+
160
188
  ## Placing engine imagery (create-authoring specifics)
161
189
 
162
190
  Finding and fetching engine imagery is `slidemind-search`'s job (`slidemind search` → `slidemind download-result`). What follows is **create-only**: how to size and colour a fetched asset so it reads on-brand in your JSX, honoured in both the `slidemind build` preview and the render.
@@ -166,9 +194,9 @@ Engine results land as files referenced with `<img src="./assets/<file>">`. Thes
166
194
  - **Size the box from the asset's own dimensions.** Both `slidemind search` (from the engine index, before any fetch) and `slidemind download-result` (measured from the delivered bytes) print each image's intrinsic `width×height`. **Use it.** A plain `<img>` is stretched to whatever box you write, so a box whose aspect ratio disagrees with the asset ships a distorted picture. Derive one side from your layout and compute the other from the printed ratio. `object-fit: contain`/`cover` is honoured in the render as well as the preview, but it is a *fallback*, not a substitute: it converts a wrong ratio into letterboxing or a crop, both of which you chose blind. When the size prints as `(size unknown)`, the format carried no readable header — give the asset a square-ish box and check it at the draft check.
167
195
  - **Never open the SVG to find out whether it recolours.** `slidemind download-result` prints a `recolor` descriptor for each asset — `{ recolorable, mode, applied, note }`, where `mode` is `currentColor`, `mono`, `multicolor`, or `raster`. Read it; that *is* the answer.
168
196
  - **Two ways to colour an icon, both honoured in the `slidemind build` preview *and* the render:**
169
- - **Bake it in (prefer this):** pass `--icon-color <hex>` — a hex you resolve from a token in the bundle's `corporate-design.css` — to `slidemind download-result`. Works for any `recolorable` glyph and the colour is in the saved file, so preview and render always agree.
197
+ - **Bake it in (prefer this):** pass `--icon-color <hex>` — a hex you resolve from a token in the bundle's `corporate-design.css` — to `slidemind download-result`. Works for any `recolorable` glyph and the colour is in the saved file, so preview and render always agree. The tint is applied by the add-in, so on the window-free path it can be **refused**: the asset is then saved in its original colours and the `recolor` note says so. Believe the note over your request — to actually bake the colour in, re-run the search and the download with `--to <presId>` against an open PowerPoint window.
170
198
  - **Tint via CSS:** set `color` to a token on the `<img>`. This works only for `mode: "currentColor"` icons (the preview inlines the SVG so `color` resolves, and the render substitutes `currentColor`). A `mono` icon will not respond to CSS colour — bake it instead.
171
- - **Batch the fetch.** Pull every icon a slide needs in one call — `slidemind download-result --search <id> --pick <n,n,…> --deck <deck> --to <presId> [--icon-color <hex,hex,…>]` (one colour applies to all picks, or give one per pick) — the downloads run concurrently. Don't run `download-result` once per icon.
199
+ - **Batch the fetch.** Pull every icon a slide needs in one call — `slidemind download-result --search <id> --pick <n,n,…> --deck <deck> [--icon-color <hex,hex,…>]` (one colour applies to all picks, or give one per pick; add `--to <presId>` only on an add-in that still relays the fetch through a pane, which the `search` output names) — the downloads run concurrently. Don't run `download-result` once per icon.
172
200
 
173
201
  ## Command mapping (the workflow vocabulary ⇄ your CLI)
174
202
 
@@ -176,18 +204,18 @@ The bundle's `workflow/*.md` speak the harness-neutral verbs defined in `workflo
176
204
 
177
205
  | Verb (`workflow/flow.md`) | Your equivalent (local harness) |
178
206
  |---|---|
179
- | (session setup — a CLI-only stage before the spine) | `slidemind ready [deck]` — the one-shot session start: connection ladder + open-design resolution + bundle materialize + deck bind. **Run it first, every session** (see *First step* above). `slidemind download-bundle` is the lower-level materialize-only command `ready` now covers — you rarely call it directly |
207
+ | (session setup — a CLI-only stage before the spine) | `slidemind ready [deck]` — the one-shot session start: connection ladder + design resolution (open window, the deck's own binding, or `--brand <presFP>`) + bundle materialize + deck bind. **Run it first, every session** (see *First step* above). `slidemind download-bundle` is the lower-level materialize-only command `ready` now covers — you rarely call it directly |
180
208
  | **track the stages** | your harness's task tracker — explicitly create the stage todo list before your first command and keep it current (see *Create a todo list* above) |
181
- | **clarify** / **plan sign-off** | intake stays in chat with the user (above); for decks, persist `decks/<slug>/plan.md` as `## Red thread` + `## Per-slide storyboards` (one `storyboard-canvas` JSON block per slide), then run `slidemind plan <deck>` — the same blocking `DECISION:` gate contract as `slidemind review`, on the plan (renders editable storyboards — red-thread strip + spatial canvases — plus overall feedback; loop until approved) |
209
+ | **clarify** / **plan sign-off** | intake stays in chat with the user (above); for decks, persist `decks/<slug>/plan.md` as `## Red thread` + `## Per-slide storyboards` (one `storyboard-canvas` JSON block per slide), then run `slidemind plan <deck>` — the same blocking `DECISION:` gate contract as `slidemind review`, on the plan (renders editable storyboards — red-thread strip + spatial canvases — plus slide-anchored comments; expect several comment rounds, some returning instantly with comments queued while you revised; loop until approved) |
182
210
  | concept mockups (mandatory deck stage, after plan sign-off) | `slidemind concepts <deck>` — renders one image mockup per approved-plan slide into `decks/<slug>/concepts/concept-slide-NN.png`; the local-harness stand-in for the in-app manager's *unconditional* concept pass on every full-deck delegation (`approvedPlan && !targetSlideScope`). **Read each and rebuild it** toward its target under your constraints (rule owned by `workflow/create.md` → Preconditions — don't restate it). It never hard-fails: on an empty result (exit 0 — the add-in can't generate them) author from the storyboards alone, but for a deck you always run it. Skip only where the add-in's `!targetSlideScope` gate would — a single slide, a tiny in-chat set, or a targeted rework of existing slides |
183
211
  | **read** / **list** the workspace | read the files from the materialized bundle dir (`<bundle>/…`) and your `decks/<slug>/` |
184
212
  | **create / update a slide file** | write `decks/<slug>/slides/NN-<topic>.jsx` on disk with a **zero-padded numeric prefix** (`01-cover.jsx`, `02-agenda.jsx`, …) — over the CLI, "the ordered set you hand to build & push" is expressed as **numeric-aware filename order**; there is no ordered-list channel, so reordering = renaming files |
185
213
  | brand reference samples (studied before composing) | **Read** 3 random images from the materialized bundle's `samples/` dir — the Read tool renders them (see *The create flow* step 1) |
186
214
  | **draft check** | `slidemind view-draft <deck>` — renders each `slides/*.jsx` to a per-slide PNG and returns the images to **you**; **no push, no `.pptx`, no review gate**. Look, fix the JSX, view again. Host-free once the bundle is materialized. Freeform slides only — purpose-built (Mode A) and reused native `.pptx` slides report as a status line (judge those at `review`/`build`). This is your self-check; `slidemind review` is the *human* gate |
187
- | **build & push** | `slidemind push <deck>` (into the open PowerPoint) · `slidemind build-pptx <deck>` (to a `.pptx` file) — deliver whichever target was locked at intake; both render server-side via the bridge, and for a bound deck the render is pane-free (`build-pptx` needs no PowerPoint window at all) |
188
- | **the review gate** | `slidemind review <deck>` — builds the HTML preview (lint-clean or it doesn't open), serves it with a spatial comment overlay and **blocks until the user decides**, then prints `DECISION: approved` or `DECISION: comments` (each comment cited to `slides/<file>.jsx:line:col`). On approve → deliver (`slidemind push` / `build-pptx`); on comments → edit the cited files and re-run `slidemind review` (prior round shows as ghost pins). CLI delivers happen out of the user's sight, so **every deliver follows an approved review of what it delivers — re-run `review` after any slide change**. `push`/`build-pptx` never pause; only `review` does. |
189
- | **inspect the deck** | `slidemind read [presId\|file.pptx] [--mode summary\|raw]` — one verb, two sources: pull the current content of an open presentation (a presId; host) **or** read a local `.pptx` on disk (a file path; host-free — no PowerPoint, no pairing). `summary` = per-slide outline; `raw` = each slide's plain text — to inspect what's there before editing. Plain text, not authoring markup |
190
- | **discover engines** (`list_engines`) | `slidemind ls engines [--type <collection\|search\|scope>] [--subtype <image\|slide\|template\|content>]` — the live catalog is the only engine list (the bundle carries none); pick an `id` from it, never guess one. It also lists **search scopes** for unfiltered corpus search — those belong to `slidemind-search`, not to imagery |
215
+ | **build & push** | `slidemind push <deck>` (into the open PowerPoint — the insert needs a window) · `slidemind build-pptx <deck> [--out <file>]` (to a `.pptx` file — **host-free** for a bound deck: no PowerPoint window at all) — deliver whichever target was locked at intake; both render server-side via the bridge and for a bound deck the render itself is pane-free |
216
+ | **the review gate** | `slidemind review <deck>` — builds the HTML preview (lint-clean or it doesn't open), serves it with a spatial comment overlay and **blocks until the user decides**, then prints `DECISION: approved` or `DECISION: comments` (each comment cited to `slides/<file>.jsx:line:col` + the round it was dropped on). On approve → deliver (`slidemind push` / `build-pptx`); on comments → edit the cited files and re-run `slidemind review` in the same turn (addressed pins show as ghost pins); expect several comment rounds, some returning instantly with pins the user queued while you were fixing. CLI delivers happen out of the user's sight, so **every deliver follows an approved review of what it delivers — re-run `review` after any slide change**. `push`/`build-pptx` never pause; only `review` does. |
217
+ | **inspect the deck** | `slidemind read [presId\|file.pptx] [--mode summary\|raw]` — one verb, two sources: pull the current content of an open presentation (a presId; **host-required** — it reads the live, possibly unsaved file) **or** read a local `.pptx` on disk (a file path; host-free — no PowerPoint, no pairing). Prefer the file path when the deck is saved. `summary` = per-slide outline; `raw` = each slide's plain text — to inspect what's there before editing. Plain text, not authoring markup |
218
+ | **discover engines** (`list_engines`) | `slidemind ls engines [--type <collection\|search\|scope>] [--subtype <image\|slide\|template\|content>]` — the live catalog is the only engine list (the bundle carries none); pick an `id` from it, never guess one. Needs no PowerPoint window where the add-in serves the pane-free engine routes. It also lists **search scopes** for unfiltered corpus search — those belong to `slidemind-search`, not to imagery |
191
219
  | **engine search / fetch** | **`slidemind-search`** owns the engine search + reuse mechanics (`slidemind search` → `slidemind download-result`, plus whole-slide reuse via `add-slide`). For placing a fetched asset in your JSX, size its box from the printed `width×height`, pass `--icon-color` / batch with `--pick <n,n,…>` and colour per *Placing engine imagery* below |
192
220
  | **see** a live slide | no CLI analog — once delivered, the user is looking at the result; your visual authority runs at the draft check and the review gate instead |
193
221
 
@@ -22,14 +22,18 @@ This is the same connection as `slidemind-create-slides`, whose **Connecting the
22
22
 
23
23
  ## Precondition gate (check this first, every time)
24
24
 
25
- Engine search and slide reuse are **host-required** operations — without an open PowerPoint host with the slidemind add-in loaded and an active corporate design, there are no engines to query and nowhere to insert. Check the connection in one call:
25
+ Search and reuse need a **connected** CLI. Whether they also need an open PowerPoint depends on the add-in: newer deployments answer engine discovery, search, previews, downloads, and slide extraction **off the connection itself**, so PowerPoint can be backgrounded or closed; older ones relay every one of those through the add-in's task pane and need a live window. The CLI detects which and picks the path — you only have to know which ops stay host-required either way.
26
26
 
27
- 1. Run `slidemind ready --connect-only`. It walks the connection ladder (a saved session → reachable backend → CLI version → an open window) but does **not** resolve a design or download a bundle — search doesn't need one.
28
- - **Exit 0 → you're set.** Proceed to `slidemind ls engines` (the live catalog is authoritative — the first real-work step).
29
- - **Non-zero →** the CLI names exactly one blocker and one `nextAction`; relay it (copy the setup command / open PowerPoint / click the pane / run `slidemind update`). The CLI owns the remediation text — don't restate it.
30
- 2. **Never pick a window for discovery.** The engine catalog is **per-user, not bound to a window**, so search and discovery attach to **any** connected window automatically. **Never ask the user which window to search with**, and don't pass `--to` to search just to disambiguate — leave it off and let the CLI grab one. (A window only matters when you *insert a slide live*, and even then it's simply the window the search already ran on — see Reuse.)
27
+ 1. Run `slidemind ready --connect-only`. It walks the connection ladder (a saved session → reachable backend → CLI version → whether the engines are reachable without a window) but does **not** resolve a design or download a bundle — search doesn't need one.
28
+ - **Exit 0 → you're set.** Proceed to `slidemind ls engines` (the live catalog is authoritative — the first real-work step). The payload's `engines: { available, envs }` tells you which path you're on: `available: true` means every search step below runs with PowerPoint closed.
29
+ - **Non-zero →** the CLI names exactly one blocker and one `nextAction`; relay it (copy the setup command / open PowerPoint / click the pane / run `slidemind update` / open the add-in once). The CLI owns the remediation text — don't restate it.
30
+ 2. **Never pick a window for discovery.** The engine catalog is **per-user, not bound to a window**. On the pane-free path there is no window in the loop at all; on the relayed path search attaches to **any** connected window by itself. Either way, **never ask the user which window to search with** and don't pass `--to` to disambiguate. (A window matters in exactly two places: when you *insert a slide live*, and when the pane-free path refuses a download — see Reuse.)
31
31
 
32
- The pane-touching ops here (`search`, `view_slide`, `insert-slide`, `download-result`) wait for the pane themselves: the CLI waits up to ~2 min, prints one *"click the slidemind pane"* line when user help is genuinely needed, and retries safely. Relay that line once and keep waiting — don't re-plan or babysit.
32
+ **Host-required, always:** `insert-slide` (it types into an open deck) and `slidemind read <presId>` (the live, possibly unsaved content of an open presentation). Everything else here — `ls engines`, `search`, `view-slide`, `read-source`, `download-result`, `add-slide` — is host-free wherever the add-in serves the pane-free routes, and self-waits for a pane where it doesn't: the CLI waits up to ~2 min, prints one *"click the slidemind pane"* line when user help is genuinely needed, and retries safely. Relay that line once and keep waiting — don't re-plan or babysit.
33
+
34
+ **Microsoft 365 sign-in — automatic; just let it happen.** Every engine operation here — the catalog, `search`, previews, downloads, `add-slide` extraction — runs **as the user**. When no sign-in is stored yet, the first engine command opens the user's browser ITSELF, prints `Microsoft 365 sign-in needed — opening your browser…`, and **blocks until the human finishes**. When you see that line, tell the user to complete the sign-in in the browser window that just opened (you cannot do it) and keep waiting — don't kill or re-run the command. After that one sign-in, every command runs silently for ~90 days. Rendering, bundles, and already-downloaded assets never need it.
35
+
36
+ **`login_required` → the sign-in was refused or abandoned.** If the automatic sign-in didn't finish (browser closed, 5-minute window elapsed), the command fails with one line naming `slidemind login`; relay it — the user runs it whenever they're ready — and don't work around it with a different engine. **Read the line before relaying it:** when a slidemind pane is already open, the CLI says so — a new `search` simply runs through that window instead (one `note:` on stderr, and the follow-up commands then carry its `--to`), and a command that can't be re-routed tells you to re-run `slidemind search` there. Follow whichever the CLI printed.
33
37
 
34
38
  **Add-in unreachable → block and name the tab.** When a host-required command can't reach the add-in (a connection blocker, a pane that never wakes, the *click the slidemind pane* line with no effect), **stop** — don't skip the step, work around it, or retry silently. Ask the user to go back into the PowerPoint tab of presentation **<title/fileName>** (name it from the command output or `slidemind presentations` — never a raw id), then re-run the failed command once they have.
35
39
 
@@ -37,19 +41,22 @@ The pane-touching ops here (`search`, `view_slide`, `insert-slide`, `download-re
37
41
 
38
42
  Follow `workflow/search.md` for the discipline; this is the CLI shape of its loop.
39
43
 
40
- 1. **Discover targets — never guess an id.** Run `slidemind ls engines` and read the catalog before searching. It lists two kinds of target: **engines** (`--type collection|search`) and **scopes** (`--type scope` — `scope:all`, and `scope:sharepoint` once a site is connected), which search the corpus unfiltered. Filter with `--subtype <image|slide|template|content>` (e.g. `slidemind ls engines --type search --subtype slide`). The catalog is **per-user, not per-presentation** — there is no presId to scope; spanning every paired environment, each row is tagged with its `env`. (`--to` is accepted for symmetry but ignored.) Pick an **id** from the list — do not invent one. **An empty or thin engine list is not "nothing to search"** (see `workflow/search.md`): fall through to a scope.
44
+ 1. **Discover targets — never guess an id.** Run `slidemind ls engines` and read the catalog before searching. It lists two kinds of target: **engines** (`--type collection|search`) and **scopes** (`--type scope` — `scope:all`, and `scope:sharepoint` once a site is connected), which search the corpus unfiltered. Filter with `--subtype <image|slide|template|content>` (e.g. `slidemind ls engines --type search --subtype slide`). The catalog is **per-user, not per-presentation** — there is no presId to scope; spanning every paired environment, each row is tagged with its `env`. (`--to` is accepted but never needed here.) Pick an **id** from the list — do not invent one. **An empty or thin engine list is not "nothing to search"** (see `workflow/search.md`): fall through to a scope. An empty list **under a `--type`/`--subtype` filter** says so explicitly — it reports how many engines and scopes exist unfiltered — so the next move there is to drop the filter, not to jump to a scope. A row marked *nothing indexed yet* / *indexing* exists but will return nothing right now; pick another target.
41
45
  2. **Choose the target + intent.** Slide engines (`subtype slide`) and scopes support two intents: **content** (the slide's text is the signal — title, body, tables, notes) and **visual** (the slide image is the signal). Image/icon engines (`subtype image`) are **visual-only**. Match the intent to the ask: "a slide that says X" / "covers topic Y" → `--intent content`; "a slide that looks like X" / brand imagery → `--intent visual`. `--intent auto` lets the engine decide.
42
- 3. **Search — no window to choose.** `slidemind search "<query>" --engine <id> --intent <auto|content|visual> [--top N]` (leave `--to` off — search attaches to any connected window itself). A **scope** target additionally needs `--result-type slide|image` (it has no subtype of its own; the CLI defaults it to `slide`). Omit `--engine` entirely and the CLI picks for you: a slide intent searches `scope:all` for slides, anything else resolves a live image engine. It returns a `searchId` plus numbered results, and prints **which window it ran on** (`searchId N ran on window <presId>`). **Thread that `presId` into every follow-up** as `--to <presId>` (view-slide / insert-slide / add-slide / download-result): the `searchId` is bound to that window, so a follow-up aimed at a different window won't find it.
46
+ 3. **Search — no window to choose.** `slidemind search "<query>" --engine <id> --intent <auto|content|visual> [--top N]` (leave `--to` off). A **scope** target additionally needs `--result-type slide|image` (it has no subtype of its own; the CLI defaults it to `slide`). Omit `--engine` entirely and the CLI picks for you: a slide intent searches `scope:all` for slides, anything else resolves a live image engine. It returns a `searchId` plus numbered results — **keep that `searchId`** (it also rides `--json`): every follow-up below is `--search <searchId> --pick <n>`, so an interrupted run resumes its picks without re-searching.
47
+ - **How the follow-ups are addressed** is printed for you, and differs by path: *"searchId N is held by the add-in"* means the id lives on the connection — the follow-up commands carry **no `--to`** and work with PowerPoint closed; *"searchId N ran on window `<presId>`"* means the older relayed path, where the id is bound to that window — thread that `presId` in as `--to <presId>`. **Copy the next-step commands the search prints** rather than composing them from memory.
48
+ - Connected to **several environments**? Pass `--deck <deck>` so the search routes by that deck's bound design instead of asking you to choose an environment.
43
49
  - **On `0 result(s)`, read the diagnostic the CLI prints and act on it** — it lists the causes that look identical from here (nothing connected, still indexing, the engine's folder filter excludes the content) and the next moves. Reuse from a scope works exactly like reuse from an engine, so widening costs you nothing.
44
- 4. **Read / view, then pick.** For **content** results, read the per-slide **text + score** the search prints — that is the on-the-wire signal; you usually don't need the picture. For **visual** results, scan the numbered thumbnail list; pull a single result's image on demand with `slidemind view-slide --search <searchId> --pick <n> --to <presId>` (it writes the preview to a file you can Read), or `slidemind search … --json` for the structured payload. Pick by **selection number(s)**.
45
- 5. **Reuse.** Insert live or fold into a deck — see the next section.
50
+ 4. **Read / view, then pick.** For **content** results, read the per-slide **text + score** the search prints — that is the on-the-wire signal; you usually don't need the picture. For **visual** results, scan the numbered thumbnail list; when the search prints a **contact sheet** path, Read that ONE file — every candidate is on it with its selection number burned in — and when it says there is none on this path, pull individual previews with `slidemind view-slide --search <searchId> --pick <n>` (it writes one result's preview to a file you can Read), or `slidemind search … --json` for the structured payload. Pick by **selection number(s)**.
51
+ 5. **Read the source deck when the story matters.** A found slide came out of a real presentation, and often the answer the user needs is in the *rest* of it — how the slide was framed, what came before and after, whether that deck already covers more of the ask. `slidemind read-source --search <searchId> --pick <n>` prints the whole source presentation as Markdown, one section per slide. Skip it when you only need the one slide. Two things to know: it is the **last-synced** text, so very recent edits to the original may be missing; and a long deck comes back as a slide map instead, which you narrow with `--from-slide`/`--to-slide`. Only slide results have a source deck — image and collection results do not.
52
+ 6. **Reuse.** Insert live or fold into a deck — see the next section.
46
53
 
47
54
  ## Reuse targets — insert live, or fold into a deck
48
55
 
49
56
  A found slide can be reused two ways. Both extract the picked result(s) as native PowerPoint slides — **reuse is JSX-free; reused slides are native `.pptx`, never JSX.**
50
57
 
51
- - **Insert LIVE into an open PowerPoint** — `slidemind insert-slide --search <searchId> --pick <n[,n,…]> --to <searchWindow> [--at <N>]`, where `<searchWindow>` is the presId the search returned. The picked slides land in **that** window (the one the search ran on; at position `--at`, else appended). If the slide must go into a *specific* open window, run the search with `--to <thatWindow>` so the search — and therefore the insert — happen there.
52
- - **Fold into a DECK on disk** — `slidemind add-slide --search <searchId> --pick <n[,n,…]> --deck <deck> --to <searchWindow> [--at <N>]`, where `<searchWindow>` is the presId the search returned — the extraction must run on the window that holds the `searchId`. Each picked result is written as a native one-slide file into `decks/<slug>/slides/NN-reused-*.pptx`, in play order; no live window is changed. Use this when the reused slide should become **part of a deck the user keeps and re-renders.**
58
+ - **Insert LIVE into an open PowerPoint** — `slidemind insert-slide --search <searchId> --pick <n[,n,…]> --to <presId> [--at <N>]`. **This is the one host-required step:** the picked slides land in that open window (at position `--at`, else appended), so PowerPoint must be open with the slidemind pane. Name the target window from `slidemind presentations` (or, on the relayed path, use the window the search ran on). A window that reconnects mid-run gets a **new** id; the CLI re-finds it and retries once by itself, so a stale id is not your problem. If it still reports the pane asleep, **don't loop** — relay its line, or fold the same pick into a deck (below) and push it once the user is back in PowerPoint.
59
+ - **Fold into a DECK on disk** — `slidemind add-slide --search <searchId> --pick <n[,n,…]> --deck <deck> [--at <N>]` (add `--to <searchWindow>` only on the relayed path, where the extraction must run on the window that holds the `searchId`). Each picked result is written as a native one-slide file into `decks/<slug>/slides/NN-reused-*.pptx`, in play order; no live window is changed, and nothing needs one. `--json` returns `{ searchId, deck, slides[] }`. Use this when the reused slide should become **part of a deck the user keeps and re-renders** — and prefer it when PowerPoint isn't open: fold now, `slidemind push <deck>` later.
53
60
 
54
61
  ### A local `.pptx` file as the source (host-free — no engine, no search)
55
62
 
@@ -78,15 +85,16 @@ These two skills compose. When the user is **authoring** a deck (that's `slidemi
78
85
 
79
86
  | Verb (`workflow/search.md`) | Your equivalent (local harness) |
80
87
  |---|---|
81
- | **discover engines** ("list what you can reach") | `slidemind ls engines [--type collection\|search\|scope] [--subtype …]` — returns the engine catalog **plus the search scopes** (pick an `id`; never guess one). The catalog is backend/user-scoped; `--to` is accepted but ignored (informational only). |
82
- | **engine search** (with `intent`) | `slidemind search "<query>" --engine <id> --intent <auto\|content\|visual> [--top N]` — leave `--to` off (attaches to any window) → a `searchId` + numbered results, **plus the presId it ran on** (thread that in as `--to` on the follow-ups below) |
88
+ | **discover engines** ("list what you can reach") | `slidemind ls engines [--type collection\|search\|scope] [--subtype …]` — returns the engine catalog **plus the search scopes** (pick an `id`; never guess one). The catalog is backend/user-scoped; `--to` is accepted but never needed. No window needed where the add-in serves the pane-free routes. |
89
+ | **engine search** (with `intent`) | `slidemind search "<query>" --engine <id> --intent <auto\|content\|visual> [--top N] [--deck <deck>]` — leave `--to` off → a `searchId` + numbered results, plus the exact follow-up commands for the path it took (`--deck` routes by that deck's design when several environments are connected) |
83
90
  | **search a scope** (unfiltered corpus, no engine) | `slidemind search "<query>" --engine <scope:all\|scope:sharepoint> --result-type <slide\|image> [--intent …]` — `--result-type` is required for a scope (defaults to `slide`). Results reuse identically to engine results. |
84
- | **pull an on-demand thumbnail** for a stored result | `slidemind view-slide --search <searchId> --pick <n> --to <searchWindow>` — writes one result's preview image to a file (`--to` = the window the search returned) · or `slidemind search … --json` for the structured payload. |
85
- | **insert live** (reuse a slide → the open window) | `slidemind insert-slide --search <searchId> --pick <n[,n,…]> --to <searchWindow> [--at N]` — `--to` = the window the search returned; the slide lands there |
86
- | **fold into a deck** (reuse a slide → deck source) | `slidemind add-slide --search <searchId> --pick <n[,n,…]> --deck <deck> --to <searchWindow> [--at N]` → writes `decks/<slug>/slides/NN-reused-*.pptx`; `--to` = the window the search returned (extraction runs there) |
91
+ | **pull an on-demand thumbnail** for a stored result | `slidemind view-slide --search <searchId> --pick <n>` — writes one result's preview image to a file (add `--to <searchWindow>` only on the relayed path) · or `slidemind search … --json` for the structured payload. |
92
+ | **read a result's SOURCE deck** (the presentation it came from) | `slidemind read-source --search <searchId> --pick <n> [--from-slide N] [--to-slide N] [--out <file.md>]` — Markdown of the whole source presentation, one section per slide (add `--to <searchWindow>` only on the relayed path). Indexed text, not the live file. Slide results only. |
93
+ | **insert live** (reuse a slide → the open window) | `slidemind insert-slide --search <searchId> --pick <n[,n,…]> --to <presId> [--at N]` — **host-required**: the slide lands in that open window |
94
+ | **fold into a deck** (reuse a slide → deck source) | `slidemind add-slide --search <searchId> --pick <n[,n,…]> --deck <deck> [--at N] [--json]` → writes `decks/<slug>/slides/NN-reused-*.pptx` (add `--to <searchWindow>` only on the relayed path, where extraction runs on the search's window) |
87
95
  | **read a local `.pptx`** (host-free — no engine, no window) | `slidemind read <file.pptx> --mode summary\|raw` — list/read a local file's slides in play order without a host; use the printed numbers as `--pick` for `add-slide --from` |
88
96
  | **fold a LOCAL slide into a deck** (`--from`, host-free) | `slidemind add-slide --from <file.pptx> --pick <n[,n,…]> --deck <deck> [--at N]` → same `decks/<slug>/slides/NN-reused-*.pptx` output as the engine path; `--from` replaces `--search`/`--to` (mutually exclusive with `--search`; no host). Live delivery = fold + `slidemind push <deck>` |
89
- | **engine fetch** (reuse an image asset) | `slidemind download-result --search <searchId> --pick <n[,n,…]> --deck <deck> --to <searchWindow> [--icon-color <hex[,hex,…]>]` — writes into `decks/<slug>/assets/` (recolor descriptor + tinting are covered in `slidemind-create-slides`) |
97
+ | **engine fetch** (reuse an image asset) | `slidemind download-result --search <searchId> --pick <n[,n,…]> --deck <deck> [--icon-color <hex[,hex,…]>]` — writes into `decks/<slug>/assets/` (add `--to <searchWindow>` only on the relayed path; recolor descriptor + tinting are covered in `slidemind-create-slides`) |
90
98
 
91
99
  Everything else in `workflow/search.md` — the engine-discovery discipline, content-vs-visual judgement, the picking and reuse craft — applies to you unchanged.
92
100