slidemind 0.2.4 → 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.4",
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
  ],
@@ -77,13 +77,15 @@ Everything about how a deck is authored, styled, and rendered is shaped by the c
77
77
 
78
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
+ **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
+
80
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).
81
83
 
82
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:
83
85
 
84
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.
85
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>`.
86
- - **`permissions_stale`** — the permission snapshot behind **corpus search** has aged out; the user opens the slidemind add-in once and it refreshes. It never blocks authoring, rendering, or the bundle — only searching the corpus — so relay the line, keep working, and re-run the search afterwards. When a slidemind pane is already open the CLI says so and routes around the snapshot instead: follow what it printed rather than asking for an add-in the user already has open.
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.
87
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.)
88
90
 
89
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>`.
@@ -105,7 +107,7 @@ This is your mapping of `workflow/flow.md` stages 1–2 (*clarify* + *plan sign-
105
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.
106
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).
107
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:
108
- - `## 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.
109
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.
110
112
 
111
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.
@@ -139,7 +141,7 @@ This is your mapping of `workflow/flow.md` stages 1–2 (*clarify* + *plan sign-
139
141
  }
140
142
  ```
141
143
 
142
- 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.
143
145
 
144
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.**
145
147
 
@@ -154,13 +156,35 @@ Keep planning about intent and content. The bundle is already materialized by `s
154
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.
155
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:
156
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).
157
- - `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.
158
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).
159
161
 
160
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.
161
163
 
162
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.
163
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
+
164
188
  ## Placing engine imagery (create-authoring specifics)
165
189
 
166
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.
@@ -182,14 +206,14 @@ The bundle's `workflow/*.md` speak the harness-neutral verbs defined in `workflo
182
206
  |---|---|
183
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 |
184
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) |
185
- | **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) |
186
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 |
187
211
  | **read** / **list** the workspace | read the files from the materialized bundle dir (`<bundle>/…`) and your `decks/<slug>/` |
188
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 |
189
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) |
190
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 |
191
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 |
192
- | **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. |
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. |
193
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 |
194
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 |
195
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 |
@@ -31,7 +31,9 @@ Search and reuse need a **connected** CLI. Whether they also need an open PowerP
31
31
 
32
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
33
 
34
- **`permissions_stale` → the user opens the add-in once.** Corpus search runs against a snapshot of your permissions that the add-in refreshes whenever it runs; when it ages out, the CLI prints one line asking the user to open the slidemind add-in once. Only corpus search is affected — reuse of results you already have, renders, and bundles keep working. Relay it and wait; don't retry the search until they've opened it, 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; don't ask for an add-in that is already open.
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.
35
37
 
36
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.
37
39