slidemind 0.3.4 → 0.3.5

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.
@@ -5,83 +5,232 @@ description: Discover, search, and reuse content from the active corporate desig
5
5
 
6
6
  # slidemind-search
7
7
 
8
- This skill is **thin workflow glue for the local harness**. The capability it drives — engine discovery, image/slide search, and slide reuse — runs on the **slidemind PowerPoint add-in**: the engines, the corporate design they belong to, and the search/extraction execution all live there and reach you over the add-in's cloud relay. The **craft** of using it well — how to discover engines, choose content-vs-visual search, pick a result, and reuse a slide responsibly — is authored once by the add-in and served on the materialized bundle as `workflow/search.md`, **byte-identical** to what the in-app design agent reads.
9
-
10
- So this file owns only what is genuinely CLI-specific: the **host precondition**, the **discover → search → pick → reuse** loop, the **reuse targets** (insert live vs fold into a deck), and the **command mapping** that translates `workflow/search.md`'s harness-neutral verbs into your `slidemind` CLI commands. **Read `workflow/search.md` from the bundle; never restate the craft here.**
11
-
12
- **slidemind is a CLI you drive directly.** Run `slidemind <command>` in your shell — the same way you'd run `git` or `npm`. There is no separate tool layer to go through; every step below is a real command you type.
8
+ This skill is **thin workflow glue for the local harness**. The capability it drives — engine
9
+ discovery, image/slide search, and slide reuse — runs on the **slidemind PowerPoint add-in**: the
10
+ engines, the corporate design they belong to, and the search/extraction execution all live there and
11
+ reach you over the add-in's cloud relay. The **craft** of using it well — how to discover engines,
12
+ choose content-vs-visual search, pick a result, and reuse a slide responsibly — is authored once by
13
+ the add-in and served on the materialized bundle as `workflow/search.md`, **byte-identical** to what
14
+ the in-app design agent reads.
15
+
16
+ So this file owns only what is genuinely CLI-specific: the **host precondition**, the **discover →
17
+ search → pick → reuse** loop, the **reuse targets** (insert live vs fold into a deck), and the
18
+ **command mapping** that translates `workflow/search.md`'s harness-neutral verbs into your
19
+ `slidemind` CLI commands. **Read `workflow/search.md` from the bundle; never restate the craft
20
+ here.**
21
+
22
+ **slidemind is a CLI you drive directly.** Run `slidemind <command>` in your shell — the same way
23
+ you'd run `git` or `npm`. There is no separate tool layer to go through; every step below is a real
24
+ command you type.
13
25
 
14
26
  ## Bootstrap (set yourself up — don't make the user do it)
15
27
 
16
- **The first step of a search session is `slidemind ready --connect-only`** (see *Precondition gate* below). It drives the `slidemind` CLI, which reaches PowerPoint through the add-in's **cloud relay** — no local server. Patch a missing CLI yourself; **connecting** needs the user once (they hold the add-in's identity):
17
-
18
- - **CLI missing** (`slidemind` not on `PATH`): `npm install -g slidemind` — that alone is the fix. Add `slidemind init --for <claude|codex|opencode|all>` ((re)deploys this skill) only when this skill did **not** come from a plugin: a `SKILL.md` under a harness plugin cache (`~/.claude/plugins/…`, `~/.codex/plugins/…`) is already deployed, and `init` would just plant a second, drifting copy.
19
- - **Not connected:** `ready --connect-only` exits with `not_connected` — the CLI cannot start a connection by itself, because the claim carrying the deployment's address is minted in the add-in. Ask the user to open PowerPoint → slidemind → **Settings → General → Connect CLI**, press the **link button**, and copy the command shown (**CLI already installed?** gives the short `slidemind setup …` form). Have them paste it to you and run it **verbatim**. `slidemind whoami` lists connected environments.
20
-
21
- This is the same connection as `slidemind-create-slides`, whose **Connecting the CLI** section carries the full step-by-step. If it was approved there, it is already available here.
28
+ **There is no preflight for a search session.** Every engine command checks the connection itself
29
+ and, on failure, names exactly one blocker and one remediation — so start directly with
30
+ `slidemind ls engines` (the first real-work step, see *Precondition gate* below) and act on what a
31
+ failing command prints. The CLI reaches PowerPoint through the add-in's **cloud relay** — no local
32
+ server. Patch a missing CLI yourself; **connecting** needs the user once (they hold the add-in's
33
+ identity):
34
+
35
+ - **CLI missing** (`slidemind` not on `PATH`): `npm install -g slidemind` — that alone is the fix.
36
+ Add `slidemind init --for <claude|codex|opencode|all>` ((re)deploys this skill) only when this
37
+ skill did **not** come from a plugin: a `SKILL.md` under a harness plugin cache
38
+ (`~/.claude/plugins/…`, `~/.codex/plugins/…`) is already deployed, and `init` would just plant a
39
+ second, drifting copy.
40
+ - **Not connected:** the command exits with `not_connected` — the CLI cannot start a connection by
41
+ itself, because the claim carrying the deployment's address is minted in the add-in. Ask the user
42
+ to open PowerPoint → slidemind → **Settings → General → Connect CLI**, press the **link button**,
43
+ and copy the command shown (**CLI already installed?** gives the short `slidemind setup …` form).
44
+ Have them paste it to you and run it **verbatim**. `slidemind whoami` lists connected
45
+ environments.
46
+
47
+ This is the same connection as `slidemind-create-slides`, whose **Connecting the CLI** section
48
+ carries the full step-by-step. If it was approved there, it is already available here.
22
49
 
23
50
  ## Precondition gate (check this first, every time)
24
51
 
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
-
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
-
32
- **Host-required, always:** `insert-slide` (it types into an open deck) and `slidemind read <presId>` (the live, possibly unsaved content of an open presentation). Everything else here — `ls engines`, `search`, `view-slide`, `read-source`, `download-result`, `add-slide` — is host-free wherever the add-in serves the pane-free routes, and self-waits for a pane where it doesn't: the CLI waits up to ~2 min, prints one *"click the slidemind pane"* line when user help is genuinely needed, and retries safely. Relay that line once and keep waiting — don't re-plan or babysit.
33
-
34
- **Microsoft 365 sign-in — automatic; just let it happen.** Every engine operation here — the catalog, `search`, previews, downloads, `add-slide` extraction — runs **as the user**. When no sign-in is stored yet, the first engine command opens the user's browser ITSELF, prints `Microsoft 365 sign-in needed — opening your browser…`, and **blocks until the human finishes**. When you see that line, tell the user to complete the sign-in in the browser window that just opened (you cannot do it) and keep waiting — don't kill or re-run the command. After that one sign-in, every command runs silently for ~90 days. Rendering, bundles, and already-downloaded assets never need it.
35
-
36
- **`login_required` → the sign-in was refused or abandoned.** If the automatic sign-in didn't finish (browser closed, 5-minute window elapsed), the command fails with one line naming `slidemind login`; relay it — the user runs it whenever they're ready — and don't work around it with a different engine. **Read the line before relaying it:** when a slidemind pane is already open, the CLI says so — a new `search` simply runs through that window instead (one `note:` on stderr, and the follow-up commands then carry its `--to`), and a command that can't be re-routed tells you to re-run `slidemind search` there. Follow whichever the CLI printed.
37
-
38
- **Add-in unreachable → block and name the tab.** When a host-required command can't reach the add-in (a connection blocker, a pane that never wakes, the *click the slidemind pane* line with no effect), **stop** — don't skip the step, work around it, or retry silently. Ask the user to go back into the PowerPoint tab of presentation **<title/fileName>** (name it from the command output or `slidemind presentations` — never a raw id), then re-run the failed command once they have.
52
+ Search and reuse need a **connected** CLI. Whether they also need an open PowerPoint depends on the
53
+ add-in: newer deployments answer engine discovery, search, previews, downloads, and slide extraction
54
+ **off the connection itself**, so PowerPoint can be backgrounded or closed; older ones relay every
55
+ one of those through the add-in's task pane and need a live window. The CLI detects which and picks
56
+ the path — you only have to know which ops stay host-required either way.
57
+
58
+ 1. Start with `slidemind ls engines` — the live catalog is authoritative, and the command doubles
59
+ as the connection check (no separate preflight; search needs no design bundle).
60
+ - **Exit 0 → you're set.** The catalog is in front of you; proceed to choose a target. Which
61
+ path you're on shows up in each command's own output: on the pane-free path every search step
62
+ below runs with PowerPoint closed, on the relayed path the follow-up commands name the window
63
+ they ran on.
64
+ - **Non-zero →** the CLI names exactly one blocker and one remediation; relay it (copy the setup
65
+ command / open PowerPoint / click the pane / run `slidemind update` / open the add-in once).
66
+ The CLI owns the remediation text — don't restate it.
67
+ 2. **Never pick a window for discovery.** The engine catalog is **per-user, not bound to a window**.
68
+ On the pane-free path there is no window in the loop at all; on the relayed path search attaches
69
+ to **any** connected window by itself. Either way, **never ask the user which window to search
70
+ with** and don't pass `--to` to disambiguate. (A window matters in exactly two places: when you
71
+ *insert a slide live*, and when the pane-free path refuses a download — see Reuse.)
72
+
73
+ **Host-required, always:** `insert-slide` (it types into an open deck) and `slidemind read <presId>`
74
+ (the live, possibly unsaved content of an open presentation). Everything else here — `ls engines`,
75
+ `search`, `view-slide`, `read-source`, `download-result`, `add-slide` — is host-free wherever the
76
+ add-in serves the pane-free routes, and self-waits for a pane where it doesn't: the CLI waits up to
77
+ ~2 min, prints one *"click the slidemind pane"* line when user help is genuinely needed, and retries
78
+ safely. Relay that line once and keep waiting — don't re-plan or babysit.
79
+
80
+ **Microsoft 365 sign-in — automatic; just let it happen.** Every engine operation here — the
81
+ catalog, `search`, previews, downloads, `add-slide` extraction — runs **as the user**. When no
82
+ sign-in is stored yet, the first engine command opens the user's browser ITSELF, prints `Microsoft
83
+ 365 sign-in needed — opening your browser…`, and **blocks until the human finishes**. When you see
84
+ that line, tell the user to complete the sign-in in the browser window that just opened (you cannot
85
+ do it) and keep waiting — don't kill or re-run the command. After that one sign-in, every command
86
+ runs silently for ~90 days. Rendering, bundles, and already-downloaded assets never need it.
87
+
88
+ **`login_required` → the sign-in was refused or abandoned.** If the automatic sign-in didn't finish
89
+ (browser closed, 5-minute window elapsed), the command fails with one line naming `slidemind login`;
90
+ relay it — the user runs it whenever they're ready — and don't work around it with a different
91
+ engine. **Read the line before relaying it:** when a slidemind pane is already open, the CLI says so
92
+ — a new `search` simply runs through that window instead (one `note:` on stderr, and the follow-up
93
+ commands then carry its `--to`), and a command that can't be re-routed tells you to re-run
94
+ `slidemind search` there. Follow whichever the CLI printed.
95
+
96
+ **Add-in unreachable → block and name the tab.** When a host-required command can't reach the add-in
97
+ (a connection blocker, a pane that never wakes, the *click the slidemind pane* line with no effect),
98
+ **stop** — don't skip the step, work around it, or retry silently. Ask the user to go back into the
99
+ PowerPoint tab of presentation **<title/fileName>** (name it from the command output or `slidemind
100
+ presentations` — never a raw id), then re-run the failed command once they have.
39
101
 
40
102
  ## Discover → search → pick → reuse
41
103
 
42
104
  Follow `workflow/search.md` for the discipline; this is the CLI shape of its loop.
43
105
 
44
- 1. **Discover targets — never guess an id.** Run `slidemind ls engines` and read the catalog before searching. It lists two kinds of target: **engines** (`--type collection|search`) and **scopes** (`--type scope` — `scope:all`, and `scope:sharepoint` once a site is connected), which search the corpus unfiltered. Filter with `--subtype <image|slide|template|content>` (e.g. `slidemind ls engines --type search --subtype slide`). The catalog is **per-user, not per-presentation** — there is no presId to scope; spanning every paired environment, each row is tagged with its `env`. (`--to` is accepted but never needed here.) Pick an **id** from the list — do not invent one. **An empty or thin engine list is not "nothing to search"** (see `workflow/search.md`): fall through to a scope. An empty list **under a `--type`/`--subtype` filter** says so explicitly — it reports how many engines and scopes exist unfiltered — so the next move there is to drop the filter, not to jump to a scope. A row marked *nothing indexed yet* / *indexing* exists but will return nothing right now; pick another target.
45
- 2. **Choose the target + intent.** Slide engines (`subtype slide`) and scopes support two intents: **content** (the slide's text is the signal — title, body, tables, notes) and **visual** (the slide image is the signal). Image/icon engines (`subtype image`) are **visual-only**. Match the intent to the ask: "a slide that says X" / "covers topic Y" → `--intent content`; "a slide that looks like X" / brand imagery → `--intent visual`. `--intent auto` lets the engine decide.
46
- 3. **Search — no window to choose.** `slidemind search "<query>" --engine <id> --intent <auto|content|visual> [--top N]` (leave `--to` off). A **scope** target additionally needs `--result-type slide|image` (it has no subtype of its own; the CLI defaults it to `slide`). Omit `--engine` entirely and the CLI picks for you: a slide intent searches `scope:all` for slides, anything else resolves a live image engine. It returns a `searchId` plus numbered results — **keep that `searchId`** (it also rides `--json`): every follow-up below is `--search <searchId> --pick <n>`, so an interrupted run resumes its picks without re-searching.
47
- - **How the follow-ups are addressed** is printed for you, and differs by path: *"searchId N is held by the add-in"* means the id lives on the connection — the follow-up commands carry **no `--to`** and work with PowerPoint closed; *"searchId N ran on window `<presId>`"* means the older relayed path, where the id is bound to that window — thread that `presId` in as `--to <presId>`. **Copy the next-step commands the search prints** rather than composing them from memory.
48
- - Connected to **several environments**? Pass `--deck <deck>` so the search routes by that deck's bound design instead of asking you to choose an environment.
49
- - **On `0 result(s)`, read the diagnostic the CLI prints and act on it** — it lists the causes that look identical from here (nothing connected, still indexing, the engine's folder filter excludes the content) and the next moves. Reuse from a scope works exactly like reuse from an engine, so widening costs you nothing.
50
- 4. **Read / view, then pick.** For **content** results, read the per-slide **text + score** the search prints — that is the on-the-wire signal; you usually don't need the picture. For **visual** results, scan the numbered thumbnail list; when the search prints a **contact sheet** path, Read that ONE file — every candidate is on it with its selection number burned in — and when it says there is none on this path, pull individual previews with `slidemind view-slide --search <searchId> --pick <n>` (it writes one result's preview to a file you can Read), or `slidemind search … --json` for the structured payload. Pick by **selection number(s)**.
51
- 5. **Read the source deck when the story matters.** A found slide came out of a real presentation, and often the answer the user needs is in the *rest* of it — how the slide was framed, what came before and after, whether that deck already covers more of the ask. `slidemind read-source --search <searchId> --pick <n>` prints the whole source presentation as Markdown, one section per slide. Skip it when you only need the one slide. Two things to know: it is the **last-synced** text, so very recent edits to the original may be missing; and a long deck comes back as a slide map instead, which you narrow with `--from-slide`/`--to-slide`. Only slide results have a source deck — image and collection results do not.
106
+ 1. **Discover targets — never guess an id.** Run `slidemind ls engines` and read the catalog before
107
+ searching. It lists two kinds of target: **engines** (`--type collection|search`) and **scopes**
108
+ (`--type scope` — `scope:all`, and `scope:sharepoint` once a site is connected), which search the
109
+ corpus unfiltered. Filter with `--subtype <image|slide|template|content>` (e.g. `slidemind ls
110
+ engines --type search --subtype slide`). The catalog is **per-user, not per-presentation** —
111
+ there is no presId to scope; spanning every paired environment, each row is tagged with its
112
+ `env`. (`--to` is accepted but never needed here.) Pick an **id** from the list — do not invent
113
+ one. **An empty or thin engine list is not "nothing to search"** (see `workflow/search.md`): fall
114
+ through to a scope. An empty list **under a `--type`/`--subtype` filter** says so explicitly — it
115
+ reports how many engines and scopes exist unfiltered — so the next move there is to drop the
116
+ filter, not to jump to a scope. A row marked *nothing indexed yet* / *indexing* exists but will
117
+ return nothing right now; pick another target.
118
+ 2. **Choose the target + intent.** Slide engines (`subtype slide`) and scopes support two intents:
119
+ **content** (the slide's text is the signal — title, body, tables, notes) and **visual** (the
120
+ slide image is the signal). Image/icon engines (`subtype image`) are **visual-only**. Match the
121
+ intent to the ask: "a slide that says X" / "covers topic Y" → `--intent content`; "a slide that
122
+ looks like X" / brand imagery → `--intent visual`. `--intent auto` lets the engine decide.
123
+ 3. **Search — no window to choose.** `slidemind search "<query>" --engine <id> --intent
124
+ <auto|content|visual> [--top N]` (leave `--to` off). A **scope** target additionally needs
125
+ `--result-type slide|image` (it has no subtype of its own; the CLI defaults it to `slide`). Omit
126
+ `--engine` entirely and the CLI picks for you: a slide intent searches `scope:all` for slides,
127
+ anything else resolves a live image engine. It returns a `searchId` plus numbered results —
128
+ **keep that `searchId`** (it also rides `--json`): every follow-up below is `--search <searchId>
129
+ --pick <n>`, so an interrupted run resumes its picks without re-searching.
130
+ - **How the follow-ups are addressed** is printed for you, and differs by path: *"searchId N is
131
+ held by the add-in"* means the id lives on the connection — the follow-up commands carry **no
132
+ `--to`** and work with PowerPoint closed; *"searchId N ran on window `<presId>`"* means the
133
+ older relayed path, where the id is bound to that window — thread that `presId` in as `--to
134
+ <presId>`. **Copy the next-step commands the search prints** rather than composing them from
135
+ memory.
136
+ - Connected to **several environments**? Pass `--deck <deck>` so the search routes by that deck's
137
+ bound design instead of asking you to choose an environment.
138
+ - **On `0 result(s)`, read the diagnostic the CLI prints and act on it** — it lists the causes
139
+ that look identical from here (nothing connected, still indexing, the engine's folder filter
140
+ excludes the content) and the next moves. Reuse from a scope works exactly like reuse from an
141
+ engine, so widening costs you nothing.
142
+ 4. **Read / view, then pick.** For **content** results, read the per-slide **text + score** the
143
+ search prints — that is the on-the-wire signal; you usually don't need the picture. For
144
+ **visual** results, scan the numbered thumbnail list; when the search prints a **contact sheet**
145
+ path, Read that ONE file — every candidate is on it with its selection number burned in — and
146
+ when it says there is none on this path, pull individual previews with `slidemind view-slide
147
+ --search <searchId> --pick <n>` (it writes one result's preview to a file you can Read), or
148
+ `slidemind search … --json` for the structured payload. Pick by **selection number(s)**.
149
+ 5. **Read the source deck when the story matters.** A found slide came out of a real presentation,
150
+ and often the answer the user needs is in the *rest* of it — how the slide was framed, what came
151
+ before and after, whether that deck already covers more of the ask. `slidemind read-source
152
+ --search <searchId> --pick <n>` prints the whole source presentation as Markdown, one section per
153
+ slide. Skip it when you only need the one slide. Two things to know: it is the **last-synced**
154
+ text, so very recent edits to the original may be missing; and a long deck comes back as a slide
155
+ map instead, which you narrow with `--from-slide`/`--to-slide`. Only slide results have a source
156
+ deck — image and collection results do not.
52
157
  6. **Reuse.** Insert live or fold into a deck — see the next section.
53
158
 
54
159
  ## Reuse targets — insert live, or fold into a deck
55
160
 
56
- A found slide can be reused two ways. Both extract the picked result(s) as native PowerPoint slides — **reuse is JSX-free; reused slides are native `.pptx`, never JSX.**
57
-
58
- - **Insert LIVE into an open PowerPoint** — `slidemind insert-slide --search <searchId> --pick <n[,n,…]> --to <presId> [--at <N>]`. **This is the one host-required step:** the picked slides land in that open window (at position `--at`, else appended), so PowerPoint must be open with the slidemind pane. Name the target window from `slidemind presentations` (or, on the relayed path, use the window the search ran on). A window that reconnects mid-run gets a **new** id; the CLI re-finds it and retries once by itself, so a stale id is not your problem. If it still reports the pane asleep, **don't loop** — relay its line, or fold the same pick into a deck (below) and push it once the user is back in PowerPoint.
59
- - **Fold into a DECK on disk** — `slidemind add-slide --search <searchId> --pick <n[,n,…]> --deck <deck> [--at <N>]` (add `--to <searchWindow>` only on the relayed path, where the extraction must run on the window that holds the `searchId`). Each picked result is written as a native one-slide file into `decks/<slug>/slides/NN-reused-*.pptx`, in play order; no live window is changed, and nothing needs one. `--json` returns `{ searchId, deck, slides[] }`. Use this when the reused slide should become **part of a deck the user keeps and re-renders** — and prefer it when PowerPoint isn't open: fold now, `slidemind push <deck>` later.
161
+ A found slide can be reused two ways. Both extract the picked result(s) as native PowerPoint slides
162
+ — **reuse is JSX-free; reused slides are native `.pptx`, never JSX.**
163
+
164
+ - **Insert LIVE into an open PowerPoint** — `slidemind insert-slide --search <searchId> --pick
165
+ <n[,n,…]> --to <presId> [--at <N>]`. **This is the one host-required step:** the picked slides
166
+ land in that open window (at position `--at`, else appended), so PowerPoint must be open with the
167
+ slidemind pane. Name the target window from `slidemind presentations` (or, on the relayed path,
168
+ use the window the search ran on). A window that reconnects mid-run gets a **new** id; the CLI
169
+ re-finds it and retries once by itself, so a stale id is not your problem. If it still reports the
170
+ pane asleep, **don't loop** — relay its line, or fold the same pick into a deck (below) and push
171
+ it once the user is back in PowerPoint.
172
+ - **Fold into a DECK on disk** — `slidemind add-slide --search <searchId> --pick <n[,n,…]> --deck
173
+ <deck> [--at <N>]` (add `--to <searchWindow>` only on the relayed path, where the extraction must
174
+ run on the window that holds the `searchId`). Each picked result is written as a native one-slide
175
+ file into `decks/<slug>/slides/NN-reused-*.pptx`, in play order; no live window is changed, and
176
+ nothing needs one. `--json` returns `{ searchId, deck, slides[] }`. Use this when the reused slide
177
+ should become **part of a deck the user keeps and re-renders** — and prefer it when PowerPoint
178
+ isn't open: fold now, `slidemind push <deck>` later.
60
179
 
61
180
  ### A local `.pptx` file as the source (host-free — no engine, no search)
62
181
 
63
- When the slide to reuse already lives in a `.pptx` **on disk** (another presentation, an exported deck), skip discovery entirely — no host, no window, no engine:
64
-
65
- 1. **List the file's slides.** `slidemind read <file.pptx> --mode summary` prints one outline line per slide **in play order**; note the number(s) you want (1-based).
66
- 2. **Fold the picked slide(s) into a deck.** `slidemind add-slide --from <file.pptx> --pick <n[,n,…]> --deck <deck> [--at <N>]`. `--from` is the local-file alternative to `--search` (they are **mutually exclusive**), and it needs no `--to` — there is no window in the loop.
67
-
68
- This writes the **same** `decks/<slug>/slides/NN-reused-*.pptx` as the engine path, so the durable rule below applies unchanged (deck source-of-truth, spliced by play-order filename, survives every re-render). To deliver a local slide **live** into an open window there is no `insert-slide --from`: fold it into a deck as above, then `slidemind push <deck>`.
69
-
70
- > **The durable rule:** a slide folded into a deck with `add-slide` is **deck source-of-truth**. The render pipeline splices native `slides/*.pptx` into the deck **by play-order filename**, alongside any authored `.jsx`, so a reused slide **survives every `build-pptx` and `push` re-render** — it is never lost on re-push. Reorder it the same way you reorder JSX: rename its numeric prefix.
71
-
72
- > **Building a `.pptx` file from found slides → one combined deck, always.** When the user wants the reused slides as a **`.pptx` file** (rather than a live insert), collect **every** picked slide into **one** deck and render **one** file — never one `.pptx` per slide and never a deck per slide. Fold all picks into the same deck — pass every selection in a single `add-slide` call (`--pick n1,n2,…`), repeating only to cover picks that came from different `searchId`s (always the same `--deck`) — then run `slidemind build-pptx <deck>` once. The render splices every native `slides/*.pptx` into a **single `decks/<slug>/<slug>.pptx`** in play-order, so all the found slides land in one file. That combined `.pptx` is the deliverable; hand back its path. (Need new authored slides interleaved with the reused ones in that same file? That's still one deck — hand off to `slidemind-create-slides` to add the JSX, then `build-pptx` the whole deck once.)
73
-
74
- For brand **imagery** (icons/images, not whole slides), the reuse step is `slidemind download-result` — it writes the asset into the deck's `assets/`. That mechanism (including the icon-recolor descriptor and `--icon-color`) is documented where it's used, in `slidemind-create-slides`.
75
-
76
- Image results print their **intrinsic `width×height`** next to the title — the engine indexes it, so the aspect ratio is yours *before* you fetch anything. Carry it into the pick: a result whose shape doesn't suit the slot you have in mind is the wrong pick, whatever it depicts. (`download-result` re-states the size, measured from the delivered bytes.)
182
+ When the slide to reuse already lives in a `.pptx` **on disk** (another presentation, an exported
183
+ deck), skip discovery entirely — no host, no window, no engine:
184
+
185
+ 1. **List the file's slides.** `slidemind read <file.pptx> --mode summary` prints one outline line
186
+ per slide **in play order**; note the number(s) you want (1-based).
187
+ 2. **Fold the picked slide(s) into a deck.** `slidemind add-slide --from <file.pptx> --pick
188
+ <n[,n,…]> --deck <deck> [--at <N>]`. `--from` is the local-file alternative to `--search` (they
189
+ are **mutually exclusive**), and it needs no `--to` — there is no window in the loop.
190
+
191
+ This writes the **same** `decks/<slug>/slides/NN-reused-*.pptx` as the engine path, so the durable
192
+ rule below applies unchanged (deck source-of-truth, spliced by play-order filename, survives every
193
+ re-render). To deliver a local slide **live** into an open window there is no `insert-slide --from`:
194
+ fold it into a deck as above, then `slidemind push <deck>`.
195
+
196
+ > **The durable rule:** a slide folded into a deck with `add-slide` is **deck source-of-truth**. The
197
+ > render pipeline splices native `slides/*.pptx` into the deck **by play-order filename**, alongside
198
+ > any authored `.jsx`, so a reused slide **survives every `build-pptx` and `push` re-render** — it
199
+ > is never lost on re-push. Reorder it the same way you reorder JSX: rename its numeric prefix.
200
+
201
+ > **Building a `.pptx` file from found slides → one combined deck, always.** When the user wants the
202
+ > reused slides as a **`.pptx` file** (rather than a live insert), collect **every** picked slide
203
+ > into **one** deck and render **one** file — never one `.pptx` per slide and never a deck per
204
+ > slide. Fold all picks into the same deck — pass every selection in a single `add-slide` call
205
+ > (`--pick n1,n2,…`), repeating only to cover picks that came from different `searchId`s (always the
206
+ > same `--deck`) — then run `slidemind build-pptx <deck>` once. The render splices every native
207
+ > `slides/*.pptx` into a **single `decks/<slug>/<slug>.pptx`** in play-order, so all the found
208
+ > slides land in one file. That combined `.pptx` is the deliverable; hand back its path. (Need new
209
+ > authored slides interleaved with the reused ones in that same file? That's still one deck — hand
210
+ > off to `slidemind-create-slides` to add the JSX, then `build-pptx` the whole deck once.)
211
+
212
+ For brand **imagery** (icons/images, not whole slides), the reuse step is `slidemind
213
+ download-result` — it writes the asset into the deck's `assets/`. That mechanism (including the
214
+ icon-recolor descriptor and `--icon-color`) is documented where it's used, in
215
+ `slidemind-create-slides`.
216
+
217
+ Image results print their **intrinsic `width×height`** next to the title — the engine indexes it, so
218
+ the aspect ratio is yours *before* you fetch anything. Carry it into the pick: a result whose shape
219
+ doesn't suit the slot you have in mind is the wrong pick, whatever it depicts. (`download-result`
220
+ re-states the size, measured from the delivered bytes.)
77
221
 
78
222
  ## Working with slidemind-create-slides
79
223
 
80
- These two skills compose. When the user is **authoring** a deck (that's `slidemind-create-slides`) and wants an **existing** slide incorporated, reach for this skill's `add-slide`: it folds the existing slide into the same `decks/<slug>/slides/` directory as the authored JSX. The reused `.pptx` and the authored `.jsx` coexist in play order (numeric filename prefix) and the reused slide is **not lost on re-push**. Hand back to `slidemind-create-slides` for any new content authoring.
224
+ These two skills compose. When the user is **authoring** a deck (that's `slidemind-create-slides`)
225
+ and wants an **existing** slide incorporated, reach for this skill's `add-slide`: it folds the
226
+ existing slide into the same `decks/<slug>/slides/` directory as the authored JSX. The reused
227
+ `.pptx` and the authored `.jsx` coexist in play order (numeric filename prefix) and the reused slide
228
+ is **not lost on re-push**. Hand back to `slidemind-create-slides` for any new content authoring.
81
229
 
82
230
  ## Command mapping (the search vocabulary ⇄ your CLI)
83
231
 
84
- `workflow/search.md` speaks harness-neutral verbs (discover engines, search with intent, pick, insert live, fold into a deck, engine fetch). You are the local harness — this is your mapping:
232
+ `workflow/search.md` speaks harness-neutral verbs (discover engines, search with intent, pick,
233
+ insert live, fold into a deck, engine fetch). You are the local harness — this is your mapping:
85
234
 
86
235
  | Verb (`workflow/search.md`) | Your equivalent (local harness) |
87
236
  |---|---|
@@ -96,10 +245,17 @@ These two skills compose. When the user is **authoring** a deck (that's `slidemi
96
245
  | **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>` |
97
246
  | **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`) |
98
247
 
99
- Everything else in `workflow/search.md` — the engine-discovery discipline, content-vs-visual judgement, the picking and reuse craft — applies to you unchanged.
248
+ Everything else in `workflow/search.md` — the engine-discovery discipline, content-vs-visual
249
+ judgement, the picking and reuse craft — applies to you unchanged.
100
250
 
101
251
  ## The engines and the workflow both live in the bundle, not here
102
252
 
103
- Do not restate which engines exist, what they contain, or any brand rule — the engine catalog comes from `slidemind ls engines` (live discovery — the bundle carries no engine list), and the search/reuse craft is in `workflow/search.md`. Read them and follow them. If the corporate design moves mid-task, re-list engines before searching again, since the catalog may have changed.
253
+ Do not restate which engines exist, what they contain, or any brand rule — the engine catalog comes
254
+ from `slidemind ls engines` (live discovery — the bundle carries no engine list), and the
255
+ search/reuse craft is in `workflow/search.md`. Read them and follow them. If the corporate design
256
+ moves mid-task, re-list engines before searching again, since the catalog may have changed.
104
257
 
105
- `workflow/search.md` also owns the judgement this skill deliberately does **not** restate: what an engine actually is (a saved filter over a shared corpus, not a library of its own), when to reach for a scope instead, and why zero results are not evidence that the content doesn't exist. Read it before concluding a search came up empty.
258
+ `workflow/search.md` also owns the judgement this skill deliberately does **not** restate: what an
259
+ engine actually is (a saved filter over a shared corpus, not a library of its own), when to reach
260
+ for a scope instead, and why zero results are not evidence that the content doesn't exist. Read it
261
+ before concluding a search came up empty.
@@ -3100,6 +3100,28 @@ export async function getPresentationContent({
3100
3100
  });
3101
3101
  }
3102
3102
 
3103
+ // Ask the pane whether its host document is actually still alive (the
3104
+ // `ping-document` op — one PowerPoint.run in the webview). This exists for
3105
+ // `ready`'s zombie check: a webview whose document was closed keeps its socket
3106
+ // (and thus its registry lease) alive, so only a host round-trip tells the
3107
+ // truth. Deliberately NOT engineOpWithPaneWait — a probe must fail fast, never
3108
+ // park in the 120s pane wait. Callers decide what a failure means; this just
3109
+ // returns the ack or throws the transport error.
3110
+ export async function pingPresentation({ presId, backendURL, timeoutMs = 12000 }) {
3111
+ if (!presId) throw new Error("pingPresentation: presId is required");
3112
+ const res = await bridgeFetch(
3113
+ `/api/cli/presentations/${encodeURIComponent(presId)}/ping`,
3114
+ {
3115
+ method: "POST",
3116
+ headers: { "content-type": "application/json" },
3117
+ body: JSON.stringify({}),
3118
+ timeoutMs,
3119
+ },
3120
+ backendURL,
3121
+ );
3122
+ return res.json(); // { ok, presId?, fileName? } | { ok: false, code, error }
3123
+ }
3124
+
3103
3125
  function ensurePaneAckOk(data, operation) {
3104
3126
  if (!data || data.ok !== false) return data ?? {};
3105
3127
  const detail = data.error || `${operation} failed`;