slidemind 0.2.3 → 0.2.4
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/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/AGENTS.md +31 -10
- package/README.md +4 -0
- package/bin/slidemind.mjs +1170 -199
- package/package.json +1 -1
- package/skills/create-slides/SKILL.md +18 -14
- package/skills/search/SKILL.md +24 -18
- package/src/bridge/build-and-push.mjs +19 -6
- package/src/bridge/client.mjs +890 -101
- package/src/bridge/engine-download.mjs +61 -8
- package/src/builders/resolve-skill.mjs +64 -2
- package/src/pptx/fill/engine.mjs +112 -134
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "slidemind",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
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": {
|
|
@@ -73,25 +73,29 @@ 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`,
|
|
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
|
|
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
|
-
|
|
80
|
+
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
|
+
|
|
82
|
+
**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
83
|
|
|
82
84
|
- **`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
|
-
-
|
|
85
|
+
- **`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.
|
|
87
|
+
- **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
88
|
|
|
85
89
|
**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
90
|
|
|
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
|
|
91
|
+
**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
92
|
|
|
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.
|
|
93
|
+
`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
94
|
|
|
91
95
|
## Two UX rules (gates + host)
|
|
92
96
|
|
|
93
97
|
- **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.
|
|
98
|
+
- **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
99
|
|
|
96
100
|
## Intake → plan (you own this; do it in chat)
|
|
97
101
|
|
|
@@ -146,7 +150,7 @@ Keep planning about intent and content. The bundle is already materialized by `s
|
|
|
146
150
|
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
151
|
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
152
|
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`.
|
|
153
|
+
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
154
|
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
155
|
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
156
|
- `DECISION: approved` → proceed to the deliver the user chose at intake: `slidemind push <deck>` (into PowerPoint) or `slidemind build-pptx <deck>` (to a file).
|
|
@@ -166,9 +170,9 @@ Engine results land as files referenced with `<img src="./assets/<file>">`. Thes
|
|
|
166
170
|
- **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
171
|
- **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
172
|
- **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.
|
|
173
|
+
- **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
174
|
- **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>
|
|
175
|
+
- **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
176
|
|
|
173
177
|
## Command mapping (the workflow vocabulary ⇄ your CLI)
|
|
174
178
|
|
|
@@ -176,7 +180,7 @@ The bundle's `workflow/*.md` speak the harness-neutral verbs defined in `workflo
|
|
|
176
180
|
|
|
177
181
|
| Verb (`workflow/flow.md`) | Your equivalent (local harness) |
|
|
178
182
|
|---|---|
|
|
179
|
-
| (session setup — a CLI-only stage before the spine) | `slidemind ready [deck]` — the one-shot session start: connection ladder +
|
|
183
|
+
| (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
184
|
| **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
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) |
|
|
182
186
|
| 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 |
|
|
@@ -184,10 +188,10 @@ The bundle's `workflow/*.md` speak the harness-neutral verbs defined in `workflo
|
|
|
184
188
|
| **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
189
|
| 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
190
|
| **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
|
|
191
|
+
| **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 |
|
|
188
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. |
|
|
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 |
|
|
193
|
+
| **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
|
+
| **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
195
|
| **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
196
|
| **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
197
|
|
package/skills/search/SKILL.md
CHANGED
|
@@ -22,14 +22,16 @@ 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
|
-
|
|
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 →
|
|
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
|
|
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
|
-
|
|
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
|
+
**`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.
|
|
33
35
|
|
|
34
36
|
**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
37
|
|
|
@@ -37,19 +39,22 @@ The pane-touching ops here (`search`, `view_slide`, `insert-slide`, `download-re
|
|
|
37
39
|
|
|
38
40
|
Follow `workflow/search.md` for the discipline; this is the CLI shape of its loop.
|
|
39
41
|
|
|
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
|
|
42
|
+
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
43
|
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
|
|
44
|
+
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.
|
|
45
|
+
- **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.
|
|
46
|
+
- 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
47
|
- **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;
|
|
45
|
-
5. **
|
|
48
|
+
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)**.
|
|
49
|
+
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.
|
|
50
|
+
6. **Reuse.** Insert live or fold into a deck — see the next section.
|
|
46
51
|
|
|
47
52
|
## Reuse targets — insert live, or fold into a deck
|
|
48
53
|
|
|
49
54
|
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
55
|
|
|
51
|
-
- **Insert LIVE into an open PowerPoint** — `slidemind insert-slide --search <searchId> --pick <n[,n,…]> --to <
|
|
52
|
-
- **Fold into a DECK on disk** — `slidemind add-slide --search <searchId> --pick <n[,n,…]> --deck <deck>
|
|
56
|
+
- **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.
|
|
57
|
+
- **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
58
|
|
|
54
59
|
### A local `.pptx` file as the source (host-free — no engine, no search)
|
|
55
60
|
|
|
@@ -78,15 +83,16 @@ These two skills compose. When the user is **authoring** a deck (that's `slidemi
|
|
|
78
83
|
|
|
79
84
|
| Verb (`workflow/search.md`) | Your equivalent (local harness) |
|
|
80
85
|
|---|---|
|
|
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
|
|
82
|
-
| **engine search** (with `intent`) | `slidemind search "<query>" --engine <id> --intent <auto\|content\|visual> [--top N]` — leave `--to` off
|
|
86
|
+
| **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. |
|
|
87
|
+
| **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
88
|
| **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
|
|
85
|
-
| **
|
|
86
|
-
| **
|
|
89
|
+
| **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. |
|
|
90
|
+
| **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. |
|
|
91
|
+
| **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 |
|
|
92
|
+
| **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
93
|
| **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
94
|
| **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>
|
|
95
|
+
| **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
96
|
|
|
91
97
|
Everything else in `workflow/search.md` — the engine-discovery discipline, content-vs-visual judgement, the picking and reuse craft — applies to you unchanged.
|
|
92
98
|
|
|
@@ -389,7 +389,7 @@ async function tryDetachedRender(deckDir, deckRef, onProgress) {
|
|
|
389
389
|
return { result, backendURL };
|
|
390
390
|
}
|
|
391
391
|
|
|
392
|
-
async function renderWithRetarget(deckDir, entry, onProgress) {
|
|
392
|
+
async function renderWithRetarget(deckDir, entry, onProgress, registry = []) {
|
|
393
393
|
const run = (target) =>
|
|
394
394
|
renderDeckToBase64(deckDir, {
|
|
395
395
|
presId: target.presId,
|
|
@@ -406,6 +406,9 @@ async function renderWithRetarget(deckDir, entry, onProgress) {
|
|
|
406
406
|
backendURL: entry.backendURL ?? null,
|
|
407
407
|
fileName: entry.fileName ?? entry.title ?? null,
|
|
408
408
|
brandId: entry.brandId ?? null,
|
|
409
|
+
otherPresIds: registry
|
|
410
|
+
.map((p) => p.presId)
|
|
411
|
+
.filter((id) => id && id !== entry.presId),
|
|
409
412
|
timeoutMs: 15000,
|
|
410
413
|
onWait: (seconds) =>
|
|
411
414
|
process.stderr.write(
|
|
@@ -446,16 +449,21 @@ export async function buildAndPush({
|
|
|
446
449
|
// backgrounded during the render); the pane matters only for the insert. ---
|
|
447
450
|
let result = null;
|
|
448
451
|
let renderEntry = null;
|
|
452
|
+
// Every window we saw next to the target. The retarget wait treats these as
|
|
453
|
+
// definitely-not-the-target, so a second deck on the same corporate design
|
|
454
|
+
// can never inherit this push (waitForPaneReady's `otherPresIds`).
|
|
455
|
+
let seenRegistry = [];
|
|
449
456
|
if (!presentationId && deckRef?.presFP) {
|
|
450
457
|
const detached = await tryDetachedRender(deckDir, deckRef, onProgress);
|
|
451
458
|
if (detached) result = detached.result;
|
|
452
459
|
}
|
|
453
460
|
if (!result) {
|
|
454
461
|
const registry = await requireRenderHost();
|
|
462
|
+
seenRegistry = registry;
|
|
455
463
|
const entry = pickPresentationEntry(registry, presentationId, {
|
|
456
464
|
preferBrandId: deckRef?.presFP ?? null,
|
|
457
465
|
});
|
|
458
|
-
const rendered = await renderWithRetarget(deckDir, entry, onProgress);
|
|
466
|
+
const rendered = await renderWithRetarget(deckDir, entry, onProgress, registry);
|
|
459
467
|
renderEntry = rendered.entry;
|
|
460
468
|
result = rendered.result;
|
|
461
469
|
}
|
|
@@ -494,11 +502,15 @@ export async function buildAndPush({
|
|
|
494
502
|
} catch {
|
|
495
503
|
return saveFallback(null);
|
|
496
504
|
}
|
|
505
|
+
seenRegistry = registry;
|
|
497
506
|
renderEntry = pickPresentationEntry(registry, presentationId, {
|
|
498
507
|
preferBrandId: deckRef?.presFP ?? null,
|
|
499
508
|
});
|
|
500
509
|
}
|
|
501
510
|
const presId = renderEntry.presId;
|
|
511
|
+
const otherPresIds = seenRegistry
|
|
512
|
+
.map((p) => p.presId)
|
|
513
|
+
.filter((id) => id && id !== presId);
|
|
502
514
|
const backendURL = renderEntry.backendURL ?? null;
|
|
503
515
|
|
|
504
516
|
// The SAME operationId on every attempt: the pane keeps an LRU of applied
|
|
@@ -550,6 +562,7 @@ export async function buildAndPush({
|
|
|
550
562
|
backendURL,
|
|
551
563
|
fileName: renderEntry.fileName ?? renderEntry.title ?? null,
|
|
552
564
|
brandId: renderEntry.brandId ?? null,
|
|
565
|
+
otherPresIds,
|
|
553
566
|
onWait: (seconds) =>
|
|
554
567
|
process.stderr.write(
|
|
555
568
|
"task pane suspended — click the slidemind pane in PowerPoint to " +
|
|
@@ -557,10 +570,10 @@ export async function buildAndPush({
|
|
|
557
570
|
),
|
|
558
571
|
});
|
|
559
572
|
if (!live) {
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
//
|
|
573
|
+
// Nothing came back — a pane that never woke, or a window that closed for
|
|
574
|
+
// good. Either way the render is done and there is no one to hand it to,
|
|
575
|
+
// which is the same recovery as no window at all: keep the bytes, and let
|
|
576
|
+
// the caller print the wake-PowerPoint blocker instead of a relay error.
|
|
564
577
|
return saveFallback(presId);
|
|
565
578
|
}
|
|
566
579
|
livePresId = live.presId;
|