slidemind 0.3.13 → 0.3.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/AGENTS.md +86 -21
  4. package/README.md +14 -17
  5. package/bin/slidemind.mjs +65 -4969
  6. package/package.json +1 -1
  7. package/skills/create-slides/SKILL.md +113 -74
  8. package/skills/search/SKILL.md +25 -7
  9. package/src/bridge/build-and-push.mjs +108 -15
  10. package/src/bridge/bundle.mjs +69 -0
  11. package/src/bridge/capture.mjs +19 -0
  12. package/src/bridge/client/brands.mjs +622 -0
  13. package/src/bridge/client/engine-routing.mjs +237 -0
  14. package/src/bridge/client/engine-search.mjs +310 -0
  15. package/src/bridge/client/engine-slides.mjs +435 -0
  16. package/src/bridge/client/pane-ops.mjs +335 -0
  17. package/src/bridge/client/presentations.mjs +587 -0
  18. package/src/bridge/client/render.mjs +317 -0
  19. package/src/bridge/client/transport.mjs +695 -0
  20. package/src/bridge/client.mjs +80 -3452
  21. package/src/bridge/config.mjs +1 -1
  22. package/src/bridge/engine-download.mjs +15 -2
  23. package/src/builders/build.mjs +11 -804
  24. package/src/builders/draft-check.mjs +376 -0
  25. package/src/builders/lint.mjs +38 -2
  26. package/src/builders/preview.html +799 -0
  27. package/src/builders/review.mjs +23 -4
  28. package/src/builders/visual-families.mjs +6 -8
  29. package/src/cli/common.mjs +179 -0
  30. package/src/cli/concepts.mjs +310 -0
  31. package/src/cli/connection.mjs +147 -0
  32. package/src/cli/deck.mjs +608 -0
  33. package/src/cli/designs.mjs +113 -0
  34. package/src/cli/diagnose.mjs +141 -0
  35. package/src/cli/download-bundle.mjs +144 -0
  36. package/src/cli/engine-helpers.mjs +429 -0
  37. package/src/cli/engine-results.mjs +320 -0
  38. package/src/cli/help.mjs +201 -0
  39. package/src/cli/install.mjs +257 -0
  40. package/src/cli/read.mjs +120 -0
  41. package/src/cli/ready.mjs +805 -0
  42. package/src/cli/search.mjs +608 -0
  43. package/src/cli/session.mjs +177 -0
  44. package/src/cli/setup.mjs +282 -0
  45. package/src/cli/slide-reuse.mjs +334 -0
  46. package/src/cli/status.mjs +118 -0
  47. package/src/cli/workspace.mjs +302 -0
  48. package/src/installers/codex.mjs +1 -1
  49. package/src/update-check.mjs +231 -0
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "slidemind",
3
- "version": "0.3.13",
3
+ "version": "0.3.14",
4
4
  "description": "Connect Claude Code to the slidemind PowerPoint add-in — build and edit native, on-brand PowerPoint decks against the active corporate design, from the terminal.",
5
5
  "author": {
6
6
  "name": "statworx",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "slidemind",
3
- "version": "0.3.13",
3
+ "version": "0.3.14",
4
4
  "description": "Connect Codex to the slidemind PowerPoint add-in — build and edit native, on-brand PowerPoint decks against the active corporate design. Requires the slidemind CLI (npm install -g slidemind); the skills explain the install when it is missing.",
5
5
  "author": {
6
6
  "name": "statworx",
package/AGENTS.md CHANGED
@@ -137,7 +137,17 @@ The script bumps `package.json` and commits — it does not tag (CI does that).
137
137
 
138
138
  Teammates pick up the release with `npm install -g slidemind@latest` (or `slidemind update` if already installed); pin with `slidemind@X.Y.Z` / `slidemind update --ref X.Y.Z`.
139
139
 
140
- **The add-in's version floor.** The add-in serves the oldest CLI it still speaks two ways: `minCliVersion` in `/api/cli/capabilities` + `/api/cli/readiness`, and an `x-slidemind-min-cli` header on **every** response of its CLI router. `bridgeFetch`/`bridgeStreamToFile` check that header on each call (so the pane routes — search/read/insert — are covered without a probe), `assertCliVersionSupported` pre-flights render/translate, and `doctor`/`ready` opt out via `setDiagnosticMode(true)` because they report the skew as the `cli_update_required` blocker themselves. The remediation the CLI prints is `slidemind update` → `slidemind@latest`, so **the add-in's floor must never exceed the version published to npm** — raise it only in lockstep with the release that satisfies it, or every paired CLI hard-blocks on an instruction that cannot resolve.
140
+ **The add-in's version floor.** The add-in serves the oldest CLI it still speaks two ways: `minCliVersion` in `/api/cli/capabilities` + `/api/cli/readiness`, and an `x-slidemind-min-cli` header on **every** response of its CLI router. `bridgeFetch`/`bridgeStreamToFile` check that header on each call (so the pane routes — search/read/insert — are covered without a probe), `assertCliVersionSupported` pre-flights render/translate, and `ready` (both its normal and its `--diagnose` mode) opts out via `setDiagnosticMode(true)` because they report the skew as the `cli_update_required` blocker themselves. The remediation the CLI prints is `slidemind update` → `slidemind@latest`, so **the add-in's floor must never exceed the version published to npm** — raise it only in lockstep with the release that satisfies it, or every paired CLI hard-blocks on an instruction that cannot resolve.
141
+
142
+ **The update nudge + background self-update** (`src/update-check.mjs`). The version floor above only fires once a CLI is too old to talk to the add-in at all — by then the user is hard blocked. The softer signal runs after **every** command: the npm dist-tag (`https://registry.npmjs.org/slidemind/latest`, override with `SLIDEMIND_REGISTRY_URL`) is fetched at most once per 6h per config dir (`SLIDEMIND_UPDATE_CHECK_INTERVAL_MS`), cached in `<configDir>/update-check.json`, and a newer version prints one line on **stderr** (never stdout — `--json` payloads stay clean). On the npm channel it also spawns a detached `npm install -g slidemind@latest` (log: `<configDir>/update.log`), so the next run is current without anyone typing anything.
143
+
144
+ Four rules the implementation exists to keep:
145
+
146
+ - **After the command, never before.** A background global install replaces the package directory; running it mid-command could pull the rug out from under a dynamic import.
147
+ - **Only the npm channel auto-updates.** `updateChannel()` returns `plugin` (only `/plugin` replaces that binary — a global install lands a second, unused copy), `npx` (nothing to update), `dev` (a checkout, i.e. `npm link` — an install would clobber the developer's symlink, so it is neither updated nor nudged; this is also why the test suite is silent by default, with `SLIDEMIND_UPDATE_CHECK=1` to force it on), or `npm`.
148
+ - **One auto-update attempt per released version per interval.** A root-owned npm prefix makes the install fail forever; the cache records the attempt and the next run says the update did not take instead of respawning it every command.
149
+ - **A version-floor hit forces the check.** When a command dies with `cli_update_required`, `bin/slidemind.mjs` passes `force: true`, which bypasses the 6h cache (it may predate the release that raised the floor) and the one-attempt guard, so the auto-update starts on the very command that hit the floor.
150
+ - **Never fatal, never slow.** Fetch timeout 2s (`SLIDEMIND_UPDATE_CHECK_TIMEOUT_MS`), every failure swallowed. Opt out with `SLIDEMIND_NO_UPDATE_CHECK=1` (silence) or `SLIDEMIND_NO_AUTO_UPDATE=1` (nudge only); `CI` disables the auto-install by itself.
141
151
 
142
152
  > **One-time GitHub setup:** the workflow uses the default `GITHUB_TOKEN`. If branch-protection rules on `main` block bot pushes, allow the `github-actions[bot]` actor (or relax "require pull request before merging" for that bot).
143
153
 
@@ -156,7 +166,7 @@ slidemind/
156
166
  ├── .agents/
157
167
  │ └── plugins/marketplace.json Codex marketplace — serves this repo (git clone) as the plugin; NOT in the npm tarball
158
168
  ├── bin/
159
- │ ├── slidemind.mjs CLI dispatcher (setup, connect, ready, doctor, presentations, read, download-bundle, new, init, build, build-pptx, view-draft, push, ls engines, search, insert-slide, add-slide, install, whoami/use/logout)
169
+ │ ├── slidemind.mjs CLI entry: scrubs the output streams, bootstraps the sim env, dispatches to src/cli/*
160
170
  │ └── slidemind + slidemind.cmd PATH shims for Claude Code plugin sessions (npm links slidemind.mjs itself)
161
171
  ├── skills/ canonical, installable skill payload — thin CLI workflow glue only
162
172
  │ ├── create-slides/
@@ -171,9 +181,12 @@ slidemind/
171
181
  │ loop + vocabulary→CLI command map. The craft is
172
182
  │ READ from the bundle's workflow/search.md — owned by the add-in.
173
183
  ├── src/ implementation of the slidemind CLI
174
- │ ├── bridge/ add-in local-bridge: client.mjs, bundle.mjs (materialize), build-and-push.mjs (server render; native-slide splicing), engine-download.mjs + recolor.mjs (image/icon fetch + recolour),
184
+ │ ├── cli/ the subcommands, one module per job (each <= ~600 lines; ready.mjs is the one 783-line function): common.mjs (paths, fail, deck resolution, gates, lazy loaders) · help · designs · connection (the two readiness ladders) ·
185
+ │ │ deck (plan/review/imagen/inspect-template-deck/build-pptx/view-draft) · concepts · workspace (new/adopt/push) · engine-helpers + search (search/ls) + slide-reuse (insert-slide/add-slide) + engine-results (view-slide/read-source/download-result) ·
186
+ │ │ download-bundle · ready · diagnose · read · session (login/whoami/logout) · install (init/uninstall/update) · setup
187
+ │ ├── bridge/ add-in local-bridge: client.mjs (the public surface; implementation split by job under client/: transport, presentations, pane-ops, brands, render, engine-routing, engine-search, engine-slides), bundle.mjs (materialize), build-and-push.mjs (server render; native-slide splicing), engine-download.mjs + recolor.mjs (image/icon fetch + recolour),
175
188
  │ │ setup-claim.mjs (decode/redeem the add-in's one-command claim — the only connect path) + http-json.mjs + config.mjs (per-backend sessions)
176
- │ ├── builders/ build.mjs (HTML preview), review.mjs (spatial preview-review gate + local server; stable per-deck port, graceful fallback, tab reuse across rounds), plan.mjs (plan sign-off gate) + plan-overlay.js + plan-md.mjs (markdown→HTML) + gate-server.mjs (shared gate primitives), resolve-skill.mjs (→ bundle), lint.mjs (runs the add-in's engine)
189
+ │ ├── builders/ build.mjs (HTML preview; the page itself is preview.html), review.mjs (spatial preview-review gate + local server; stable per-deck port, graceful fallback, tab reuse across rounds), plan.mjs (plan sign-off gate) + plan-overlay.js + plan-md.mjs (markdown→HTML) + gate-server.mjs (shared gate primitives), resolve-skill.mjs (→ bundle), lint.mjs (runs the add-in's engine)
177
190
  │ ├── pptx/ host-free local-.pptx support (fflate-based): read-local.mjs (read a file's slides — output-identical to the host read), slide-swap.mjs (fold a picked slide into a deck as native .pptx — swaps it into the lowest slot the splicer reads, then prunes the package to that slide's dependency closure so a slice carries only what it needs), zip.mjs + ooxml.mjs (shared unzip/order helpers)
178
191
  │ ├── lib/ deck-workspace bookkeeping: adopt.mjs (rebuild a deck from a file's embedded source + convert its foreign slides through the add-in's convert-pptx route), conversions.mjs (the deck's `conversions/<stem>.json` identity+evidence records and the preserve source package push threads back), slide-guids.mjs, slide-order.mjs, reused-slides.mjs
179
192
  │ └── installers/ per-harness installers: claude.mjs, codex.mjs, opencode.mjs, lib.mjs
@@ -185,23 +198,21 @@ slidemind/
185
198
 
186
199
  The skills at the **root** of `skills/` are deliberate: that's the layout Claude Code, Codex (0.128+), and opencode all expect for skill auto-discovery — `<harness-root>/skills/<name>/SKILL.md`. There are **two** skills, each auto-discovered by `listSkills()` (no installer change to add one): `create-slides` (installs as `slidemind-create-slides`) owns the user-facing plan / create / edit / render workflow, and `search` (installs as `slidemind-search`) owns engine discovery + slide reuse (search → insert live / fold into a deck). Both are **brand-agnostic** — every brand rule lives in the per-CD bundle's `design.md` / `workflow/*.md`, which the skills point the agent at. (Pre-skills opencode versions used an `agent/` shim under `~/.config/opencode/`; the installer removes any leftover from those installs on every run.)
187
200
 
188
- We **do not** ship separate slash commands. Earlier versions had a top-level `commands/` directory that became `/slidemind:plan` etc. on Claude Code and `~/.codex/prompts/slidemind-*.md` on Codex. The Codex prompts mechanism is broken in 0.117+ ([openai/codex#15941](https://github.com/openai/codex/issues/15941)) and officially deprecated in favour of skills, so we collapsed everything into the skill itself. Re-running `slidemind init` actively wipes legacy command/prompt entries on every harness, and clears the materialized bundle cache (`bundleCacheRoot()`) so the next `build`/`push`/`download-bundle` re-fetches every design bundle fresh from the add-in.
201
+ We **do not** ship separate slash commands. Earlier versions had a top-level `commands/` directory that became `/slidemind:plan` etc. on Claude Code and `~/.codex/prompts/slidemind-*.md` on Codex. The Codex prompts mechanism is broken in 0.117+ ([openai/codex#15941](https://github.com/openai/codex/issues/15941)) and officially deprecated in favour of skills, so we collapsed everything into the skill itself. Re-running `slidemind init` actively wipes legacy command/prompt entries on every harness, and clears the materialized bundle cache (`bundleCacheRoot()`) so the next `view-draft`/`push`/`download-bundle` re-fetches every design bundle fresh from the add-in.
189
202
 
190
203
  ## CLI surface
191
204
 
192
205
  ```bash
193
- slidemind ready [deck] # one-shot session start: connection ladder + design pick (--brand > deck binding > the one live window design > the one cached design > the one listed design; else ambiguous_design with the full menu) + bundle download + deck bind; reports every design at hand with theme colours + every verified open window; --to/--brand/--force/--connect-only/--json (PowerPoint closed is a normal case)
194
- slidemind doctor # read-only diagnostics on TWO independent ladders — backend authoring (session/backend/version/engines) and live PowerPoint (windows/pane); does NOT materialize or bind — `ready` is the preflight
195
- slidemind presentations # list PowerPoint windows registered with the add-in
206
+ slidemind ready [deck] # one-shot session start (with a deck: also its disk-read stage line — plan/concepts/assets/slides/draft check/review — and the next command): connection ladder + design pick (--brand > deck binding > the one live window design > the one cached design > the one listed design; else ambiguous_design with the full menu) + bundle download + deck bind; reports every design at hand with theme colours + every verified open window; --to/--brand/--force/--connect-only/--json (PowerPoint closed is a normal case)
207
+ slidemind ready --diagnose # read-only report on TWO independent ladders — backend authoring (session/backend/version/engines) and live PowerPoint (windows/pane, each window's state + wake hint); materializes nothing, binds nothing
196
208
  slidemind read [presId|file.pptx] # pull an open presentation's content (presId; HOST REQUIRED) OR read a local .pptx on disk (file path; host-free) — same per-slide output (--mode summary|raw)
197
- slidemind download-bundle [deck] # materialize the active corporate design's bundle locally
209
+ slidemind download-bundle [deck] # materialize a corporate design's bundle locally — pane-free for a bound deck or --brand <presFP>; a window is only consulted when the design isn't named yet (or --to targets one)
198
210
  slidemind new <name> # scaffold ./decks/<name>/ (auto-binds to the one open design, else the one cached design, when its bundle is already materialized; --brand <presFP> binds to the design the user chose instead; --to/--no-bind)
199
211
  slidemind plan <deck> # plan sign-off gate on <deck>/plan.md; markdown editor + live preview + overall feedback, blocks for approve/comments (host-free)
200
- slidemind build <deck> # build self-contained HTML preview
201
212
  slidemind review <deck> # spatial preview-review gate; blocks for approve/comments (host-free)
202
- slidemind build-pptx <deck> # compose: add-in; template pack: host-free with --pack <dir>; never overwrites manual PowerPoint edits — text edits fold back, the rest refuses, --force discards (docs/slidemind-cli-connect/16-manual-edit-reconcile.md)
213
+ slidemind build-pptx <deck> # compose: add-in, pane-free (deck binding → the one cached design → the one listed design; a window only with --to or when none of those answers); template pack: host-free with --pack <dir>; never overwrites manual PowerPoint edits — text edits fold back, the rest refuses, --force discards (docs/slidemind-cli-connect/16-manual-edit-reconcile.md)
203
214
  slidemind inspect-template-deck <deck> # normalized pack model for pack-owned validation; --pack <dir> --json
204
- slidemind view-draft <deck> # render the deck's slides to per-slide PNGs so the agent can look before review; no push, no .pptx (host-free once the bundle is materialized)
215
+ slidemind view-draft <deck> # lint + build the HTML preview (this is where `slidemind build` went), then render the deck's slides to per-slide PNGs so the agent can look before review; --fix applies the deterministic lint autofixes; --lint-only stops after the lint; no push, no .pptx (host-free once the bundle is materialized); --only NN for one slide; the server's export gate (slot-fit/diagram-geometry/connector-endpoints) runs on the captured slides BY DEFAULT — verdict only, skipped with a note when the backend is unreachable (--translate insists, --no-translate skips) — so a slide is proven buildable before review; --json
205
216
  slidemind push <deck> # render + push slides into a running PowerPoint (HOST REQUIRED); --replace reconciles the LIVE deck first (same doc), insert pushes never do
206
217
  slidemind ls engines # list the live engine catalog (image + slide; --type/--subtype) (pane-free on add-ins with engineRoutes; HOST REQUIRED on older ones)
207
218
  slidemind search "<query>" # search an engine for imagery or slides (--engine/--intent/--top/--deck) (pane-free on add-ins with engineRoutes; HOST REQUIRED on older ones)
@@ -212,17 +223,73 @@ slidemind add-slide [opts] # fold slide(s) into a deck as native .pptx
212
223
  slidemind adopt <file.pptx> # rebuild a deck workspace from a FILE's embedded source; foreign slides convert to editable jsx through the add-in's convert-pptx route where it is served (--deck/--brand/--no-convert) — live decks are add-in-only in v1 (docs/slidemind-cli-connect/14-foreign-slide-conversion.md)
213
224
  slidemind login [--backend <url|env>] # sign in to Microsoft 365 (browser + PKCE); usually unnecessary — the first engine command runs this flow ITSELF and blocks until the human finishes (graph-auth `ensureSignedIn`, once per process). One sign-in mints Graph + SharePoint tokens from one refresh token; docs/slidemind-cli-connect/15-graph-delegated-cli.md
214
225
  slidemind whoami # list every connected environment (* = default) + its Microsoft 365 sign-in state
215
- slidemind use <url|env> # set the default environment
216
226
  slidemind logout [url|env] # unpair one environment (--all unpairs every one; also drops its Microsoft 365 sign-in)
217
227
  slidemind init --for <target> # install skill into claude | codex | opencode | all | auto (also clears the bundle cache); `setup` already does this
218
228
  slidemind uninstall --for <target> # remove the skill (also clears the bundle cache)
219
229
  ```
220
230
 
231
+ **Bundle freshness is automatic (2026-09-04).** A corporate design can change
232
+ while a session runs, and every local step afterwards — HTML build, lint,
233
+ capture, the draft checks, and the etag the server render is validated against —
234
+ reads the deck's cached copy. So each deck command (`view-draft`, `review`,
235
+ `concepts`, `build-pptx` for compose decks, `push`) calls
236
+ `bundle.refreshDeckBundle(deckDir)` before it works: an etag-gated incremental
237
+ re-sync of the deck's bound design that downloads nothing when the design is
238
+ unchanged, rewrites `.bundle-ref.json` when it moved (and says so on stdout),
239
+ and returns null on an unreachable backend so the host-free steps stay host-free.
240
+ It clamps `SLIDEMIND_BRIDGE_TIMEOUT_MS` to 6s (`SLIDEMIND_BUNDLE_REFRESH_TIMEOUT_MS`)
241
+ and disables the transient retry for its own call, because a dead backend must
242
+ cost seconds, not the 35s render budget. `download-bundle --all` is the same
243
+ sync across every design cached on this machine, and `ls designs` lists what is
244
+ cached here plus what the backends serve — neither needs a PowerPoint window.
245
+
246
+ **One surface per job — four commands were folded away (2026-09-04).** The CLI
247
+ had grown parallel entry points that answered the same question, so agents (and
248
+ humans) had to know which of two commands to reach for:
249
+
250
+ - `doctor` and `presentations` → **`slidemind ready --diagnose`**. Same
251
+ implementation, same `--json` contract (`authoring` / `host` / `backends` /
252
+ `presentations`), plus each non-pane-ready window's wake hint, which used to
253
+ exist only in `presentations`. `ready` is already the session surface; a
254
+ read-only mode of it beats two more command names. What went with them:
255
+ `presentations --all` (a snapshot flag on a command nobody called) and its
256
+ bounded re-registration wait — `--diagnose` reports the registry as it stands.
257
+ - `build` → **`slidemind view-draft`**, which now runs `buildDeck()` itself
258
+ (lint + the deck's copy of `corporate-design.css`, which its own type-ramp
259
+ check already silently depended on) and carries `--fix`. The HTML preview is
260
+ still written, and `review` still builds it too. `src/builders/build.mjs`
261
+ stays as a module and a `node`-runnable script.
262
+ - `use` → nothing; `setup` sets the default backend and `--backend <url|env>` /
263
+ `SLIDEMIND_BACKEND` scope a command. A normal user has one backend.
264
+ - Aliases `connection`, `status`, `upgrade` → deleted (`update` and
265
+ `self-update` remain, `install` remains as the back-compat alias of `init`).
266
+
267
+ Don't reintroduce a second command for something `ready`, `view-draft` or
268
+ `build-pptx` already answers — add a flag to the one that owns the job. The
269
+ folded names (`build`, `doctor`, `presentations`, `status`, `connection`, `use`,
270
+ `upgrade`) still resolve to a one-line redirect naming the owner — stale skills
271
+ and habits type them — never to a bare "unknown command".
272
+
273
+ **Agent-facing conventions (2026-09-05).** Every command takes `--help` (its
274
+ own usage block, `src/cli/help.mjs` → `commandBlock`); the deck commands take
275
+ `--json` (`ready`, `view-draft`, `concepts`, `build-pptx`, `download-result`,
276
+ `search`, `add-slide`, `read`, `ls`). `ready <deck>` ends with a `stage:` +
277
+ `next:` pair read from disk (`src/cli/status.mjs` → `deckStage`): plan gate /
278
+ concepts / sourcing / authoring / draft check / review / deliver — the agent's
279
+ recovery point after a context summarization, replacing the prose table the
280
+ skill used to carry. `concepts` always ends on one `CONCEPTS: n/m <status>`
281
+ line. The lint printer (`src/builders/lint.mjs` → `collapseLintLines`) dedupes
282
+ the engine's lines and folds the "template id, may resolve at render time"
283
+ class into one `[lint:note]` per file — the rule stays the add-in's, only the
284
+ CLI's rendering changes. A fanned-out `ls engines` never opens the browser
285
+ sign-in for an environment nobody asked about (`signIn: false` on the
286
+ multi-environment listing; the refusing env is named in a note instead).
287
+
221
288
  `<deck>` resolves as: absolute path → `./<arg>` if it exists → `./decks/<arg>`. The decks directory is `./decks/` by default; override with `SLIDEMIND_DECKS=/abs/path`.
222
289
 
223
290
  **Multi-environment sessions.** The CLI stores one session **per backend** (`~/.config/slidemind/session.json`, v2 `{version, sessions[], default}`; the legacy single-record v1 shape is migrated on read). Connect once per environment (local / dev / prod) and every command **fans out** across all connected backends, tags each open window with its `env`, and **routes by the presentation's origin backend** automatically — no manual switching. Adding an environment is easiest with `slidemind setup <claim> --no-install --for none` — the claim carries its own backend, so nothing has to be typed or configured. There is no other way in: without a claim the CLI has no backend to talk to. Later commands may scope an existing session with `--backend <url|env>` / `SLIDEMIND_BACKEND`. A normal user connects exactly one backend.
224
291
 
225
- **The env label is routing metadata, not narrative.** Because a normal user has exactly one backend, naming it on every line is noise that makes ordinary output read like a debugging session — so human-facing output shows an env label **only when more than one backend is connected**. `src/bridge/config.mjs` owns the rule (`isMultiEnv()`, `envTag()`, `envPhrase()`); call those rather than interpolating `[${env}]` by hand, and word the sentence so it still reads with the label absent. Three things are deliberately NOT gated: `--json` payloads (agents route on `env`), `doctor` and `whoami` (reporting environments is their whole job), and any label the user just typed (`--backend prod`, `logout dev`, `use dev`) — suppressing an echo of their own argument is confusing, not quiet. `setup` decides once per run, **before** it spends the claim, so its four mentions agree with each other rather than flipping when `upsertSession` changes the count.
292
+ **The env label is routing metadata, not narrative.** Because a normal user has exactly one backend, naming it on every line is noise that makes ordinary output read like a debugging session — so human-facing output shows an env label **only when more than one backend is connected**. `src/bridge/config.mjs` owns the rule (`isMultiEnv()`, `envTag()`, `envPhrase()`); call those rather than interpolating `[${env}]` by hand, and word the sentence so it still reads with the label absent. Three things are deliberately NOT gated: `--json` payloads (agents route on `env`), `ready --diagnose` and `whoami` (reporting environments is their whole job), and any label the user just typed (`--backend prod`, `logout dev`) — suppressing an echo of their own argument is confusing, not quiet. `setup` decides once per run, **before** it spends the claim, so its four mentions agree with each other rather than flipping when `upsertSession` changes the count.
226
293
 
227
294
  **No credential may reach stdout or stderr.** The CLI holds four — the per-backend `sessionToken`, a Graph refresh token, a Graph access token, and the one-time `smc1_…` claim — and none is printed on purpose. The risk is transitive: a server-controlled **response body** used verbatim as an error detail (`setup-claim.mjs` redeem failures, the Entra token endpoint, `response.body.slice(0, 400)` on every non-2xx bridge call), plus `unknown command: <argv>` echoing a claim pasted without its verb. Patching those four sites would leave the fifth to whoever adds the next route, so containment lives at the output boundary instead: `src/bridge/secrets.mjs` holds the values this process knows are secret, and `bin/slidemind.mjs` wraps `process.stdout.write`/`process.stderr.write` **once, before any command runs**, so every line, `--json` payload and error message is scrubbed on its way out. Matching is by VALUE, not by pattern — exact, no false positives on ordinary prose — and the replacement is an opaque `«secret:4f9c»` tag (4 hex of sha256) that reveals nothing while still letting two occurrences of one credential be recognised as the same. Register a secret the moment it enters memory (`readSessions`, `getGraphRefreshToken`, `requestToken`, `decodeSetupClaim`, and argv); values under 8 characters are ignored on purpose, so **test fixtures must use realistic lengths** or the guard silently cannot fail. `test/secret-containment.test.mjs` drives the real bin against a relay that deliberately echoes the credential it was sent.
228
295
 
@@ -234,7 +301,7 @@ slidemind uninstall --for <target> # remove the skill (also clears the bundle
234
301
 
235
302
  **The two ops that stay pane-bound self-heal a presId churn.** Every pane reconnect mints a NEW presId for the same window, so `push`'s insert and `insert-slide` re-list the registry and re-match the window before giving up: `waitForPaneReady` matches the same presId, then `fileName`+`brandId`, then — when a Save-As renamed the window mid-run — the **design fingerprint alone** (`presFP == brandId`), and only when exactly one connected window carries it (two windows on one design are indistinguishable, and pushing into the wrong one is worse than waiting). `engineOpWithPaneWait` takes that window identity plus an opt-in `retargetOnMissing`, which **only the live insert sets**: for an idempotent engine read a 404 is the search record talking, not the window, so waiting on the registry would just stall the agent (`isMissingPresentationError` keeps a coded 404 like `unknown_search` out of it). Both retries re-send the SAME `operationId`, so the pane-side dedup makes them safe; both retry exactly ONCE and then surface the wake-PowerPoint blocker — `push` by saving the rendered `.pptx` next to the deck rather than throwing the bytes away, `insert-slide` by naming the click and the window-free `add-slide` alternative.
236
303
 
237
- **The readiness ladder is two ladders.** `bin/slidemind.mjs` authors them once: `backendBlocker(pub)` (paired → every backend unreachable → CLI below the floor) is what render, bundle and the pane-free engine routes ride on, and `paneBlocker(pub)` (no window → not render-ready → pane suspended) governs only push's insert, `insert-slide` and reading an open presentation. `connectionBlocker = backendBlocker ?? paneBlocker` keeps every pre-existing message byte-identical (one addition: when every backend failed with code `backend_unavailable`, the add-in's `503` for "my session store did not answer", the rung is `backend_unavailable` with actor `agent` and says wait, never re-pair; a `401` is reserved for "the add-in answered: no such session", and its message no longer tells the user to `logout --all`, because `setup` replaces the backend's record in place and keeps its Microsoft 365 refresh token when the add-in-reported `oid` says the same person re-paired — a different or unknown person drops it), `doctor` reports the two separately (`authoring` / `host` in `--json`) so an agent stops reading "pane gone" as "cannot work", and the Microsoft 365 sign-in state is reported next to the authoring ladder — never a rung, because it must not gate render or bundle (`ready`'s `m365.degradation.code: "login_required"`, computed LOCALLY from `hasGraphSession` — the backend advertises the contract via `cliLoginV1`, never per-user state). `ready`'s ladder for a **bound** deck is session → bundle freshness → (informational) engine + pane reachability: the deck's own binding is the design, a window on it is targeted, none at all materializes over the brand route, and a window on a DIFFERENT design is reported, never adopted. An unbound deck picks the one design the verified live windows agree on (`resolveDesignSource` lets a live pane outrank suspended windows on other designs), else the one design cached on this machine, else the one design `GET /api/cli/brands` lists (capability `brandsList`, `client.listBrands()`); several of any stop with `ambiguous_design`, whose payload is the same `designs` menu every success carries (cached + open + listed, each with name, theme colours and its open windows) naming `--brand <presFP>`. **The zombie prune runs BEFORE any of that**: on backends advertising `paneLivenessV1` every window claiming a live pane is pinged in parallel and a dead one leaves the registry, because a closed document's webview keeps its lease and used to fake a second design — the exact false `ambiguous_design` a single open deck produced. `client.engineReachability({ backends })` is the engine half of both reports — env labels only, never a URL.
304
+ **The readiness ladder is two ladders.** `bin/slidemind.mjs` authors them once: `backendBlocker(pub)` (paired → every backend unreachable → CLI below the floor) is what render, bundle and the pane-free engine routes ride on, and `paneBlocker(pub)` (no window → not render-ready → pane suspended) governs only push's insert, `insert-slide` and reading an open presentation. `connectionBlocker = backendBlocker ?? paneBlocker` keeps every pre-existing message byte-identical (one addition: when every backend failed with code `backend_unavailable`, the add-in's `503` for "my session store did not answer", the rung is `backend_unavailable` with actor `agent` and says wait, never re-pair; a `401` is reserved for "the add-in answered: no such session", and its message no longer tells the user to `logout --all`, because `setup` replaces the backend's record in place and keeps its Microsoft 365 refresh token when the add-in-reported `oid` says the same person re-paired — a different or unknown person drops it), `ready --diagnose` reports the two separately (`authoring` / `host` in `--json`) so an agent stops reading "pane gone" as "cannot work", and the Microsoft 365 sign-in state is reported next to the authoring ladder — never a rung, because it must not gate render or bundle (`ready`'s `m365.degradation.code: "login_required"`, computed LOCALLY from `hasGraphSession` — the backend advertises the contract via `cliLoginV1`, never per-user state). `ready`'s ladder for a **bound** deck is session → bundle freshness → (informational) engine + pane reachability: the deck's own binding is the design, a window on it is targeted, none at all materializes over the brand route, and a window on a DIFFERENT design is reported, never adopted. An unbound deck picks the one design the verified live windows agree on (`resolveDesignSource` lets a live pane outrank suspended windows on other designs), else the one design cached on this machine, else the one design `GET /api/cli/brands` lists (capability `brandsList`, `client.listBrands()`); several of any stop with `ambiguous_design`, whose payload is the same `designs` menu every success carries (cached + open + listed, each with name, theme colours and its open windows) naming `--brand <presFP>`. **The zombie prune runs BEFORE any of that**: on backends advertising `paneLivenessV1` every window claiming a live pane is pinged in parallel and a dead one leaves the registry, because a closed document's webview keeps its lease and used to fake a second design — the exact false `ambiguous_design` a single open deck produced. `client.engineReachability({ backends })` is the engine half of both reports — env labels only, never a URL.
238
305
 
239
306
  ## External skill packs (this repo ships none)
240
307
 
@@ -288,13 +355,11 @@ Both skills are **brand-agnostic workflow guidance** and must not duplicate any
288
355
 
289
356
  ```bash
290
357
  slidemind ready <deck> # do this first: connection ladder + resolve the open design + materialize its bundle + bind the deck (host required)
291
- slidemind download-bundle <deck> # lower-level: materialize-only (no connection ladder, no bind) — `ready` now covers this
358
+ slidemind download-bundle <deck> # lower-level: materialize-only (no connection ladder, no bind) — `ready` now covers this (host-free once the deck is bound)
292
359
  slidemind plan <deck> # plan sign-off gate on <deck>/plan.md; blocks until approve/comments (host-free)
293
- slidemind build <deck> # HTML preview (reads the materialized bundle)
294
- slidemind build <deck> --open # HTML + open in browser
295
360
  slidemind review <deck> # spatial preview-review gate; blocks until approve/comments (host-free)
296
- slidemind build-pptx <deck> # native PPTX rendered server-side via the add-in (host required)
297
- slidemind view-draft <deck> # per-slide PNG draft preview, no push/no .pptx — the agent's look-before-review self-check (host-free once the bundle is cached)
361
+ slidemind build-pptx <deck> # native PPTX rendered server-side via the add-in (pane-free; a window only with --to)
362
+ slidemind view-draft <deck> # lint + HTML build, then per-slide PNG draft preview, no push/no .pptx — the agent's look-before-review self-check (host-free once the bundle is cached); --fix autofixes lint
298
363
  slidemind push <deck> # render + push into a running PowerPoint (host required)
299
364
  slidemind read [presId|file.pptx] # pull current content of an open presentation (presId; host required) OR read a local .pptx (file path; host-free) (--mode summary|raw)
300
365
  slidemind ls engines # list the live engine catalog (image + slide) (host required)
@@ -304,4 +369,4 @@ slidemind add-slide [opts] # fold slide(s) into a deck as native .pptx
304
369
  npm run lint:parity # assert EVERY skill (create-slides + search) carries no brand rules (CI guard)
305
370
  ```
306
371
 
307
- No linter beyond the parity guard, `node --test` for the suite, no package manager beyond npm itself. Slide-JSX lint in `slidemind build` runs the **add-in's** capability-table engine, materialized into the bundle cache as `lint-engine.mjs` (+ `primitives.json`) and dynamic-imported by `src/builders/lint.mjs` — this repo owns no lint rules; they update on the next bundle fetch. CI (`.github/workflows/test.yml`) runs the suite + parity guard on Linux, **Windows**, and macOS (Node 18 + 22) — the CLI promises native-Windows support, so keep it green there. The HTML preview is self-contained except for React + Babel-standalone pulled from a CDN on first load.
372
+ No linter beyond the parity guard, `node --test` for the suite, no package manager beyond npm itself. Slide-JSX lint (run by `view-draft` and `review` via `buildDeck`) uses the **add-in's** capability-table engine, materialized into the bundle cache as `lint-engine.mjs` (+ `primitives.json`) and dynamic-imported by `src/builders/lint.mjs` — this repo owns no lint rules; they update on the next bundle fetch. CI (`.github/workflows/test.yml`) runs the suite + parity guard on Linux, **Windows**, and macOS (Node 18 + 22) — the CLI promises native-Windows support, so keep it green there. The HTML preview is self-contained except for React + Babel-standalone pulled from a CDN on first load.
package/README.md CHANGED
@@ -237,8 +237,8 @@ It does exactly the same work: connect, refresh the skills, run `ready`. Add `--
237
237
  Either way, verify with:
238
238
 
239
239
  ```bash
240
- slidemind whoami # lists connected environment(s)
241
- slidemind presentations # lists open PowerPoint windows the add-in sees
240
+ slidemind whoami # lists connected environment(s)
241
+ slidemind ready --diagnose # full report: backends, CLI version, open PowerPoint windows
242
242
  ```
243
243
 
244
244
  A setup command works once and expires after about 10 minutes; copy a fresh one from the add-in if that happens.
@@ -254,7 +254,7 @@ slidemind logout # unpair this machine
254
254
  ```bash
255
255
  slidemind ready # one-shot session start: check the connection, resolve the open design, fetch its bundle (host required)
256
256
  slidemind new my-deck # scaffold ./decks/my-deck/ (auto-binds to the open design when one is available)
257
- slidemind build my-deck # build a self-contained HTML preview (offline)
257
+ slidemind view-draft my-deck # lint + render each slide to a PNG you can look at (offline once the bundle is cached)
258
258
  slidemind review my-deck # spatial preview review: leave comments / approve in the browser (offline)
259
259
  slidemind build-pptx my-deck # render a native .pptx via the add-in (host required)
260
260
  # template packs may pass --pack <exact-pack-directory>
@@ -292,18 +292,18 @@ Under the hood the skill just runs the `slidemind` CLI in the agent's shell —
292
292
 
293
293
  ```text
294
294
  slidemind ready [deck] One-shot session start: connect + resolve the open design + fetch bundle + bind deck (host required)
295
- --to <presId> --force --connect-only --json
295
+ --to <presId> --force --connect-only --diagnose --json
296
296
  slidemind new <name> Scaffold ./decks/<name>/ (auto-binds to the open design when available)
297
297
  --to <presId> --no-bind
298
298
  slidemind plan <deck> Plan sign-off gate on <deck>/plan.md; blocks for approve/comments (offline)
299
- slidemind build <deck> Build a self-contained HTML preview (offline)
300
299
  slidemind review <deck> Spatial preview review; blocks for approve/comments (offline)
301
300
  slidemind build-pptx <deck> Render a native .pptx via the add-in (host required)
302
301
  --pack <dir> for an explicit fill-template pack
303
302
  slidemind inspect-template-deck <deck> Emit a normalized template-deck model for pack validation
304
303
  --pack <dir> --json [--allow-unrendered-freeform]
305
- slidemind view-draft <deck> Render slides to per-slide PNGs, no push — look before review (offline once cached)
306
- --to <presId> --out <dir> --open
304
+ slidemind view-draft <deck> Lint + build the HTML preview, then render slides to per-slide PNGs,
305
+ no push — look before review (offline once cached)
306
+ --to <presId> --out <dir> --open --fix --only <file|N> --translate
307
307
  slidemind push <deck> Build + push slides into an open PowerPoint (host required)
308
308
  slidemind ls engines List the live engine catalog (image + slide) (host required)
309
309
  slidemind search "<query>" Search an engine for imagery or slides (host required)
@@ -322,7 +322,6 @@ slidemind adopt <file.pptx> Rebuild a deck workspace from a .pptx — slide
322
322
  add-in offers it, the rest fold in native
323
323
  --deck <slug> --brand <presFP> --no-convert
324
324
  slidemind download-bundle [deck] Materialize the active corporate design locally (host required; lower-level — `ready` covers this)
325
- slidemind presentations List PowerPoint windows registered with the add-in
326
325
  slidemind read [presId|file.pptx] Read an open presentation (presId; host required) or a local .pptx on disk (file path; host-free)
327
326
  --mode summary|raw (default raw) [--to <presId>] [--json]
328
327
  slidemind setup <claim> One-command onboarding: connect + global install + skills + ready
@@ -330,7 +329,6 @@ slidemind setup <claim> One-command onboarding: connect + global instal
330
329
  slidemind login [--backend <e>] Sign in to Microsoft 365 (opens your browser) so SharePoint-backed
331
330
  downloads and slide extractions work without an open PowerPoint window
332
331
  slidemind whoami Show connected environment(s) and their Microsoft 365 sign-in state
333
- slidemind use <url|env> Set the default environment
334
332
  slidemind logout [url|env] Unpair an environment (--all unpairs every one; drops its Microsoft 365 sign-in too)
335
333
  slidemind init --for <target> Install the skill into a harness (clears the bundle cache)
336
334
  <target> = claude | codex | opencode | all | auto (every harness found here)
@@ -347,7 +345,7 @@ slidemind --version Print version
347
345
 
348
346
  | Command | Output | When to use |
349
347
  |---|---|---|
350
- | `slidemind build <deck>` | `decks/<deck>/presentation.html` | Self-contained HTML preview in a browser (offline) |
348
+ | `slidemind view-draft <deck>` | `decks/<deck>/.draft-preview/*.png` (+ `presentation.html`) | Look at each slide before anyone else does (offline) |
351
349
  | `slidemind build-pptx <deck>` | `decks/<deck>/<deck>.pptx` | An editable native PowerPoint file |
352
350
  | `slidemind push <deck>` | (streamed via the add-in) | Live insertion into an open PowerPoint window |
353
351
 
@@ -368,7 +366,7 @@ It runs entirely offline (no open PowerPoint needed) — review happens *before*
368
366
  With PowerPoint open and the slidemind add-in loaded, stream a deck's slides straight into the running window — no `.pptx` written to disk:
369
367
 
370
368
  ```bash
371
- slidemind presentations # list windows (presId, env, title)
369
+ slidemind ready --diagnose # list windows (presId, env, title, pane state)
372
370
  slidemind push my-deck # append after the current slides
373
371
  slidemind push my-deck --to <presId> # required if more than one window is open
374
372
  slidemind push my-deck --at 0 # insert after slide 1 (0-based index)
@@ -391,11 +389,10 @@ If you run the add-in against more than one backend (local / dev / prod), the CL
391
389
  slidemind setup <claim-from-local-add-in> --no-install --for none # each claim carries its own backend
392
390
  slidemind setup <claim-from-dev-add-in> --no-install --for none
393
391
  slidemind whoami # lists every connected environment (* = default)
394
- slidemind presentations # windows from all environments, each tagged [env]
392
+ slidemind ready --diagnose # windows from all environments, each tagged [env]
395
393
  slidemind push my-deck --to <presId> # routed to whichever environment owns that window
396
- slidemind use dev # set the default environment
397
394
  slidemind logout dev # disconnect one (or --all to disconnect every one)
398
- slidemind --backend local presentations # scope a single command to one environment
395
+ slidemind --backend local ready --diagnose # scope a single command to one environment
399
396
  ```
400
397
 
401
398
  `setup` is the easiest way to add an environment because the claim carries its backend — that is exactly the fact the CLI cannot otherwise know. `--no-install --for none` keeps it to the connection alone, leaving your existing binary and skills untouched. Because the claim carries the backend, there is no environment to select and no URL to configure — the CLI hardcodes no backend at all. Once connected, sessions stay per-environment and routing is automatic. A normal user connects one environment.
@@ -420,12 +417,12 @@ Equivalently: `npm install -g slidemind@latest && slidemind init --for auto`.
420
417
  |---|---|
421
418
  | `slidemind: command not found` (or not recognized) | The npm global bin isn't on your `PATH`. Run `npm prefix -g` and add its `bin` (Windows: `%APPDATA%\npm`) to `PATH`, or use `npx slidemind`. |
422
419
  | PowerShell: *running scripts is disabled* | npm's `.ps1` shim hit the execution policy. Use `slidemind.cmd <command>`, or `Set-ExecutionPolicy -Scope CurrentUser RemoteSigned`. |
423
- | A command can't reach PowerPoint (empty `presentations`, "no host", nothing seems connected) | Run **`slidemind ready`** — it checks the secure connection, backend, CLI version, and open windows in one shot and prints the single next step to take. |
424
- | `slidemind presentations` is empty immediately after approval | Bring PowerPoint forward and click the slidemind pane once; its authenticated relay reconnects and registers automatically. Reopen the pane only if the Office webview itself is suspended. |
420
+ | A command can't reach PowerPoint ("no host", nothing seems connected) | Run **`slidemind ready`** — it checks the secure connection, backend, CLI version, and open windows in one shot and prints the single next step to take. |
421
+ | `slidemind ready --diagnose` lists no window immediately after approval | Bring PowerPoint forward and click the slidemind pane once; its authenticated relay reconnects and registers automatically. Reopen the pane only if the Office webview itself is suspended. |
425
422
  | `Not connected` / `not_connected` | In **PowerPoint → slidemind → Settings → General → Connect CLI**, press the link button and run the command it shows. |
426
423
  | `this setup command has already been used or has expired` | A setup claim works exactly once and lasts ~10 minutes. Copy a fresh one from **Connect CLI → Set up CLI**. |
427
424
  | `this slidemind add-in is too old for one-command setup` | That deployment predates the setup route and must be updated before this CLI can connect to it. |
428
- | `build-pptx` / `push` / `search` error about no host | These require an open PowerPoint host with an active corporate design. `slidemind build` (HTML) is the only fully-offline preview. |
425
+ | `build-pptx` / `push` / `search` error about no host | These require an open PowerPoint host with an active corporate design. `slidemind view-draft` (per-slide PNGs + HTML preview) is the fully-offline preview. |
429
426
  | `EACCES` on `npm install -g` (macOS/Linux) | Don't `sudo npm`. Use a user-owned Node via [nvm](https://github.com/nvm-sh/nvm) or Homebrew, or run `mkdir -p ~/.npm-global && npm config set prefix ~/.npm-global`, add `~/.npm-global/bin` to `PATH`, then retry. |
430
427
 
431
428
  ---