slidemind 0.1.5 → 0.1.6
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.md +6 -2
- package/README.md +13 -5
- package/bin/slidemind.mjs +311 -6
- package/package.json +1 -1
- package/skills/create-slides/SKILL.md +28 -4
- package/skills/search/SKILL.md +6 -3
- package/src/bridge/build-and-push.mjs +195 -19
- package/src/bridge/client.mjs +204 -5
- package/src/installers/codex.mjs +96 -28
- package/src/mcp/server.mjs +153 -5
package/CLAUDE.md
CHANGED
|
@@ -64,7 +64,7 @@ slidemind/
|
|
|
64
64
|
├── .claude-plugin/
|
|
65
65
|
│ └── plugin.json Claude Code plugin manifest
|
|
66
66
|
├── bin/
|
|
67
|
-
│ └── slidemind.mjs CLI dispatcher (presentations, download-bundle, init, build, build-pptx, push, ls engines, search, insert-slide, add-slide, install, pair/whoami/use/logout)
|
|
67
|
+
│ └── slidemind.mjs CLI dispatcher (presentations, read, download-bundle, init, build, build-pptx, view-draft, push, ls engines, search, insert-slide, add-slide, install, pair/whoami/use/logout)
|
|
68
68
|
├── skills/ canonical, installable skill payload — thin CLI workflow glue only
|
|
69
69
|
│ ├── create-slides/
|
|
70
70
|
│ │ └── SKILL.md authoring skill: host gate + intake/plan + create flow + CLI⇄in-app command map.
|
|
@@ -77,7 +77,7 @@ slidemind/
|
|
|
77
77
|
├── src/ implementation of the slidemind CLI
|
|
78
78
|
│ ├── bridge/ add-in local-bridge: client.mjs, bundle.mjs (materialize), build-and-push.mjs (server render; native-slide splicing)
|
|
79
79
|
│ ├── builders/ build.mjs (HTML preview), review.mjs (spatial preview-review gate + local server), resolve-skill.mjs (→ bundle), lint.mjs (runs the add-in's engine)
|
|
80
|
-
│ ├── mcp/ MCP server: list/push + list_engines/search_engine/view_slide/insert_slide/add_slide_to_deck/download_result
|
|
80
|
+
│ ├── mcp/ MCP server: list/read/push + list_engines/search_engine/view_slide/insert_slide/add_slide_to_deck/download_result
|
|
81
81
|
│ └── installers/ per-harness installers: claude.mjs, codex.mjs, opencode.mjs, lib.mjs
|
|
82
82
|
├── archive/ retired code/assets (local exporter, icon library, brand design files) — NOT shipped
|
|
83
83
|
├── decks/ the user's working decks; sources are slides/*.jsx, design via .bundle-ref.json
|
|
@@ -93,11 +93,13 @@ We **do not** ship separate slash commands. Earlier versions had a top-level `co
|
|
|
93
93
|
|
|
94
94
|
```bash
|
|
95
95
|
slidemind presentations # list PowerPoint windows registered with the add-in
|
|
96
|
+
slidemind read [presId] # pull the current content of an open presentation (--mode summary|raw) (HOST REQUIRED)
|
|
96
97
|
slidemind download-bundle [deck] # materialize the active corporate design's bundle locally
|
|
97
98
|
slidemind new <name> # scaffold ./decks/<name>/
|
|
98
99
|
slidemind build <deck> # build self-contained HTML preview
|
|
99
100
|
slidemind review <deck> # spatial preview-review gate; blocks for approve/comments (host-free)
|
|
100
101
|
slidemind build-pptx <deck> # render native .pptx via the add-in (HOST REQUIRED)
|
|
102
|
+
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)
|
|
101
103
|
slidemind push <deck> # render + push slides into a running PowerPoint (HOST REQUIRED)
|
|
102
104
|
slidemind ls engines # list the live engine catalog (image + slide; --type/--subtype) (HOST REQUIRED)
|
|
103
105
|
slidemind search "<query>" # search an engine for imagery or slides (--engine/--intent/--top) (HOST REQUIRED)
|
|
@@ -144,7 +146,9 @@ slidemind build <deck> # HTML preview (reads the materialized bundle)
|
|
|
144
146
|
slidemind build <deck> --open # HTML + open in browser
|
|
145
147
|
slidemind review <deck> # spatial preview-review gate; blocks until approve/comments (host-free)
|
|
146
148
|
slidemind build-pptx <deck> # native PPTX rendered server-side via the add-in (host required)
|
|
149
|
+
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)
|
|
147
150
|
slidemind push <deck> # render + push into a running PowerPoint (host required)
|
|
151
|
+
slidemind read [presId] # pull current content of an open presentation (--mode summary|raw) (host required)
|
|
148
152
|
slidemind ls engines # list the live engine catalog (image + slide) (host required)
|
|
149
153
|
slidemind search "<query>" # search an engine for imagery or slides (host required)
|
|
150
154
|
slidemind insert-slide [opts] # reuse found slide(s) live into a window (host required)
|
package/README.md
CHANGED
|
@@ -128,21 +128,23 @@ npx slidemind <command>
|
|
|
128
128
|
|
|
129
129
|
The CLI talks to PowerPoint through the add-in's cloud relay, so you pair once per machine.
|
|
130
130
|
|
|
131
|
-
1. In PowerPoint, open the **slidemind add-in** task pane
|
|
132
|
-
2. It shows a one-time command. Copy and run it:
|
|
131
|
+
1. In PowerPoint, open the **slidemind add-in** task pane → **Settings → General → Connect CLI**, and press **Pair**.
|
|
132
|
+
2. It shows a one-time command (it expires in ~10 min — press **Pair** again for a fresh one). Copy and run it:
|
|
133
133
|
|
|
134
134
|
```bash
|
|
135
135
|
slidemind pair <code>
|
|
136
136
|
```
|
|
137
137
|
|
|
138
|
-
3.
|
|
138
|
+
3. **Restart the add-in once.** Pairing turns CLI integration on, but the task pane that's already running may not pick up the connection — so close the slidemind task pane and reopen it from the ribbon (if needed, reopen the presentation). The wizard doesn't prompt for this, but a freshly-booted pane is what registers your window with the CLI.
|
|
139
|
+
|
|
140
|
+
4. Verify the link:
|
|
139
141
|
|
|
140
142
|
```bash
|
|
141
143
|
slidemind whoami # lists the paired environment(s)
|
|
142
144
|
slidemind presentations # lists open PowerPoint windows the add-in sees
|
|
143
145
|
```
|
|
144
146
|
|
|
145
|
-
If `slidemind presentations` is empty
|
|
147
|
+
If `slidemind presentations` is empty even though PowerPoint is open with the add-in and an active corporate design, restart the add-in (step 3) and retry.
|
|
146
148
|
|
|
147
149
|
```bash
|
|
148
150
|
slidemind logout # unpair this machine
|
|
@@ -158,6 +160,7 @@ slidemind build my-deck # build a self-contained HTML preview (offline
|
|
|
158
160
|
slidemind review my-deck # spatial preview review: leave comments / approve in the browser (offline)
|
|
159
161
|
slidemind build-pptx my-deck # render a native .pptx via the add-in (host required)
|
|
160
162
|
slidemind push my-deck # stream slides straight into an open PowerPoint (host required)
|
|
163
|
+
slidemind read <presId> # pull the current content of an open presentation (host required)
|
|
161
164
|
slidemind ls engines # list the active design's image + slide engines (host required)
|
|
162
165
|
slidemind search "shield" # search an engine for imagery or slides (host required)
|
|
163
166
|
slidemind insert-slide ... # reuse a found slide live into an open PowerPoint (host required)
|
|
@@ -191,6 +194,8 @@ slidemind new <name> Scaffold ./decks/<name>/
|
|
|
191
194
|
slidemind build <deck> Build a self-contained HTML preview (offline)
|
|
192
195
|
slidemind review <deck> Spatial preview review; blocks for approve/comments (offline)
|
|
193
196
|
slidemind build-pptx <deck> Render a native .pptx via the add-in (host required)
|
|
197
|
+
slidemind view-draft <deck> Render slides to per-slide PNGs, no push — look before review (offline once cached)
|
|
198
|
+
--to <presId> --out <dir> --open
|
|
194
199
|
slidemind push <deck> Build + push slides into an open PowerPoint (host required)
|
|
195
200
|
slidemind ls engines List the live engine catalog (image + slide) (host required)
|
|
196
201
|
slidemind search "<query>" Search an engine for imagery or slides (host required)
|
|
@@ -201,6 +206,8 @@ slidemind add-slide [opts] Fold reused slide results into a deck as native
|
|
|
201
206
|
--search <id> --pick <n[,n,...]> --deck <deck> [--to <presId>] [--at <N>]
|
|
202
207
|
slidemind download-bundle [deck] Materialize the active corporate design locally (host required)
|
|
203
208
|
slidemind presentations List PowerPoint windows registered with the add-in
|
|
209
|
+
slidemind read [presId] Pull the current content of an open presentation (host required)
|
|
210
|
+
--mode summary|raw (default raw) [--to <presId>] [--json]
|
|
204
211
|
slidemind pair <code> Pair with an add-in (copy the code from "Connect CLI")
|
|
205
212
|
slidemind whoami Show paired environment(s)
|
|
206
213
|
slidemind use <url|env> Set the default environment
|
|
@@ -287,7 +294,8 @@ Equivalently: `npm install -g slidemind@latest && slidemind init --for all`.
|
|
|
287
294
|
| `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`. |
|
|
288
295
|
| PowerShell: *running scripts is disabled* | npm's `.ps1` shim hit the execution policy. Use `slidemind.cmd <command>`, or `Set-ExecutionPolicy -Scope CurrentUser RemoteSigned`. |
|
|
289
296
|
| `slidemind presentations` is empty | The add-in isn't reachable. Open PowerPoint with the slidemind add-in and an active corporate design; click the task pane once to re-register, then retry. |
|
|
290
|
-
| `
|
|
297
|
+
| `slidemind presentations` is empty right after pairing (PowerPoint + add-in already open) | The just-paired task pane never registered. **Restart the add-in** — close the slidemind task pane and reopen it from the ribbon (or reopen the presentation). The pairing wizard doesn't prompt for this. |
|
|
298
|
+
| `Not paired` | In the add-in, open **Settings → General → Connect CLI**, press **Pair**, and run the shown `slidemind pair <code>`. Codes are one-time and expire — generate a fresh one if it's stale. |
|
|
291
299
|
| `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. |
|
|
292
300
|
| `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 set a user-writable npm prefix. |
|
|
293
301
|
|
package/bin/slidemind.mjs
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
// slidemind build <deck> build self-contained HTML deck
|
|
11
11
|
// slidemind review <deck> spatial preview-review gate (blocks for approve/comments)
|
|
12
12
|
// slidemind build-pptx <deck> render the deck as native .pptx (via the add-in)
|
|
13
|
+
// slidemind view-draft <deck> render the deck's slides to per-slide PNGs (no push)
|
|
13
14
|
// slidemind uninstall --for <target>
|
|
14
15
|
// slidemind update [--for <target>] [--ref <version>]
|
|
15
16
|
// npm install -g slidemind@latest + redeploy
|
|
@@ -52,6 +53,12 @@ function printHelp() {
|
|
|
52
53
|
` slidemind build-pptx <deck> Export native .pptx via the add-in (requires an open host)\n` +
|
|
53
54
|
` --to <presId> target a specific window (required if >1 open)\n` +
|
|
54
55
|
` --out <path> output path (default <deck>/<deck>.pptx)\n` +
|
|
56
|
+
` slidemind view-draft <deck> Render the deck's slides to per-slide PNGs (no push, no .pptx)\n` +
|
|
57
|
+
` so the agent can look before review; host-free once the\n` +
|
|
58
|
+
` bundle is materialized\n` +
|
|
59
|
+
` --to <presId> window to fetch the bundle from (only if not cached)\n` +
|
|
60
|
+
` --out <dir> output dir (default <deck>/.draft-preview/)\n` +
|
|
61
|
+
` --open open each rendered PNG\n` +
|
|
55
62
|
` slidemind search "<query>" Search an engine for images or slides (requires an open host)\n` +
|
|
56
63
|
` --engine <id> engine to search (default: first image engine; required for slides)\n` +
|
|
57
64
|
` --intent <i> auto | content | visual (slide engines)\n` +
|
|
@@ -77,6 +84,12 @@ function printHelp() {
|
|
|
77
84
|
` --to <presId> target a specific window\n` +
|
|
78
85
|
` --force re-download even if the cached bundle is current\n` +
|
|
79
86
|
` slidemind presentations List PowerPoint windows registered with the add-in\n` +
|
|
87
|
+
` slidemind doctor Diagnose CLI/add-in pairing, backend, and pane readiness\n` +
|
|
88
|
+
` --json print machine-readable status\n` +
|
|
89
|
+
` slidemind read [presId] Pull the current content of an open presentation (requires a host)\n` +
|
|
90
|
+
` --mode <m> summary | raw (default: raw)\n` +
|
|
91
|
+
` --to <presId> target a specific window (required if >1 open)\n` +
|
|
92
|
+
` --json print the raw content payload\n` +
|
|
80
93
|
` slidemind push <deck> [opts] Build deck and push slides into a running PowerPoint\n` +
|
|
81
94
|
` --to <presId> target a specific window (required if >1 open)\n` +
|
|
82
95
|
` --at <index> insert after slide N (0-based); default = append\n` +
|
|
@@ -247,6 +260,87 @@ async function cmdBuildPptx(rest) {
|
|
|
247
260
|
}
|
|
248
261
|
}
|
|
249
262
|
|
|
263
|
+
function dataUriToPng(dataUri) {
|
|
264
|
+
const m = /^data:image\/png;base64,(.+)$/.exec(dataUri || "");
|
|
265
|
+
return m ? Buffer.from(m[1], "base64") : null;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async function cmdViewDraft(rest) {
|
|
269
|
+
const { deck, passthrough } = splitDeckArg(rest, "view-draft");
|
|
270
|
+
let to = null;
|
|
271
|
+
let outDir = null;
|
|
272
|
+
let open = false;
|
|
273
|
+
for (let i = 0; i < passthrough.length; i++) {
|
|
274
|
+
const a = passthrough[i];
|
|
275
|
+
if (a === "--to") {
|
|
276
|
+
to = passthrough[++i];
|
|
277
|
+
if (!to) fail("--to expects a presId");
|
|
278
|
+
} else if (a === "--out" || a === "-o") {
|
|
279
|
+
outDir = passthrough[++i];
|
|
280
|
+
if (!outDir) fail("--out expects a directory");
|
|
281
|
+
} else if (a === "--open") {
|
|
282
|
+
open = true;
|
|
283
|
+
} else if (a.startsWith("-")) {
|
|
284
|
+
fail(`unknown flag: ${a}`);
|
|
285
|
+
} else {
|
|
286
|
+
fail(`unexpected argument: ${a}`);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
const deckDir = resolveDeckArg(deck);
|
|
290
|
+
if (!fs.existsSync(path.join(deckDir, "slides"))) {
|
|
291
|
+
fail(`expected 'slides/' folder in ${deckDir}`);
|
|
292
|
+
}
|
|
293
|
+
// Draft previews are produced locally by the add-in's shipped capture engine
|
|
294
|
+
// (no push, no .pptx) — the same module that hosts the render+push path.
|
|
295
|
+
const mod = await import(
|
|
296
|
+
pathToFileURL(path.join(SRC_DIR, "bridge", "build-and-push.mjs")).href
|
|
297
|
+
);
|
|
298
|
+
const result = await mod.viewDraft(deckDir, {
|
|
299
|
+
presentationId: to,
|
|
300
|
+
onProgress: renderProgressPrinter(),
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
const previewDir = outDir
|
|
304
|
+
? path.resolve(process.cwd(), outDir)
|
|
305
|
+
: path.join(deckDir, ".draft-preview");
|
|
306
|
+
fs.mkdirSync(previewDir, { recursive: true });
|
|
307
|
+
|
|
308
|
+
const written = [];
|
|
309
|
+
for (const slide of result.slides ?? []) {
|
|
310
|
+
const png = slide.dataUri ? dataUriToPng(slide.dataUri) : null;
|
|
311
|
+
if (png) {
|
|
312
|
+
const stem = slide.fileName.replace(/\.[^.]+$/, "");
|
|
313
|
+
const file = path.join(previewDir, `${stem}.png`);
|
|
314
|
+
fs.writeFileSync(file, png);
|
|
315
|
+
written.push(file);
|
|
316
|
+
const flag = slide.status === "degraded" ? " (DEGRADED)" : "";
|
|
317
|
+
process.stdout.write(` ${slide.fileName}${flag} → ${file}\n`);
|
|
318
|
+
if (slide.note) process.stderr.write(` note: ${slide.note}\n`);
|
|
319
|
+
} else {
|
|
320
|
+
process.stdout.write(` ${slide.fileName} — ${slide.note ?? slide.status}\n`);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
if (written.length === 0) {
|
|
324
|
+
process.stdout.write(
|
|
325
|
+
"No draft preview images were produced (no freeform slides to capture).\n",
|
|
326
|
+
);
|
|
327
|
+
} else {
|
|
328
|
+
process.stdout.write(
|
|
329
|
+
`Wrote ${written.length} draft preview${written.length === 1 ? "" : "s"} to ${previewDir} — NOT pushed. ` +
|
|
330
|
+
"Fix anything visibly wrong in the JSX, then re-run, or 'slidemind review' / 'push' when sound.\n",
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
for (const w of result.warnings ?? []) {
|
|
334
|
+
process.stderr.write(` warning: ${w}\n`);
|
|
335
|
+
}
|
|
336
|
+
if (open && written.length > 0) {
|
|
337
|
+
const { openInBrowser } = await import(
|
|
338
|
+
pathToFileURL(path.join(BUILDERS_DIR, "build.mjs")).href
|
|
339
|
+
);
|
|
340
|
+
for (const file of written) openInBrowser(file);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
250
344
|
async function cmdSearch(rest) {
|
|
251
345
|
let asJson = false;
|
|
252
346
|
let to = null;
|
|
@@ -298,7 +392,7 @@ async function cmdSearch(rest) {
|
|
|
298
392
|
const bundle = await import(
|
|
299
393
|
pathToFileURL(path.join(SRC_DIR, "bridge", "bundle.mjs")).href
|
|
300
394
|
);
|
|
301
|
-
const registry = await client.
|
|
395
|
+
const registry = await client.requirePaneHost();
|
|
302
396
|
// The engine is per-user, not window-bound — so search just attaches to any
|
|
303
397
|
// connected window (preferring a live one). No "which window?" prompt; pass
|
|
304
398
|
// --to only to pin a specific one. The chosen window is printed below so
|
|
@@ -522,7 +616,7 @@ async function cmdInsertSlide(rest) {
|
|
|
522
616
|
const client = await import(
|
|
523
617
|
pathToFileURL(path.join(SRC_DIR, "bridge", "client.mjs")).href
|
|
524
618
|
);
|
|
525
|
-
const registry = await client.
|
|
619
|
+
const registry = await client.requirePaneHost();
|
|
526
620
|
const entry = client.pickPresentationEntry(registry, to);
|
|
527
621
|
const res = await client.insertSlideLive({
|
|
528
622
|
presId: entry.presId,
|
|
@@ -583,7 +677,7 @@ async function cmdAddSlide(rest) {
|
|
|
583
677
|
const client = await import(
|
|
584
678
|
pathToFileURL(path.join(SRC_DIR, "bridge", "client.mjs")).href
|
|
585
679
|
);
|
|
586
|
-
const registry = await client.
|
|
680
|
+
const registry = await client.requirePaneHost();
|
|
587
681
|
// The searchId is presentation-scoped, so --to targets the window the search
|
|
588
682
|
// ran on. Without it, a registry with >1 open window is a dead-end.
|
|
589
683
|
const entry = client.pickPresentationEntry(registry, to);
|
|
@@ -656,7 +750,7 @@ async function cmdDownloadBundle(rest) {
|
|
|
656
750
|
const deckRef = positional[0]
|
|
657
751
|
? bundle.readDeckBundleRef(resolveDeckArg(positional[0]))
|
|
658
752
|
: null;
|
|
659
|
-
const registry = await client.
|
|
753
|
+
const registry = await client.requireRenderHost();
|
|
660
754
|
const entry = client.pickPresentationEntry(registry, to, {
|
|
661
755
|
preferBrandId: deckRef?.presFP ?? null,
|
|
662
756
|
});
|
|
@@ -714,7 +808,185 @@ async function cmdPresentations(rest) {
|
|
|
714
808
|
for (const p of list) {
|
|
715
809
|
const label = p.title || p.fileName || "(untitled)";
|
|
716
810
|
const env = p.env ? `[${p.env}]\t` : "";
|
|
717
|
-
|
|
811
|
+
const state =
|
|
812
|
+
p.connectionState ||
|
|
813
|
+
(p.connected === false || p.suspendedAt ? "suspended" : "connected");
|
|
814
|
+
const design = p.designState ? `\tcd:${p.designState}` : "";
|
|
815
|
+
process.stdout.write(`${p.presId}\t${env}${label}\t${state}${design}\n`);
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
function publicConnectionStatus(status, client) {
|
|
820
|
+
return {
|
|
821
|
+
paired: status.paired,
|
|
822
|
+
backendCount: status.backendCount,
|
|
823
|
+
backends: (status.backends ?? []).map((b) => ({
|
|
824
|
+
env: b.env,
|
|
825
|
+
ok: b.ok,
|
|
826
|
+
readinessV1: b.readinessV1 === true,
|
|
827
|
+
capabilities: b.capabilities ?? null,
|
|
828
|
+
error: b.error,
|
|
829
|
+
summary: b.summary ?? null,
|
|
830
|
+
presentations: (b.presentations ?? []).map(client.toPublicPresentation),
|
|
831
|
+
})),
|
|
832
|
+
presentations: (status.presentations ?? []).map(client.toPublicPresentation),
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
function connectionNextAction(pub) {
|
|
837
|
+
if (!pub.paired) {
|
|
838
|
+
return 'Not paired. In the add-in open Settings → General → Connect CLI, press Pair, then run the shown `slidemind pair <code>`.';
|
|
839
|
+
}
|
|
840
|
+
if ((pub.backends ?? []).every((b) => !b.ok)) {
|
|
841
|
+
return "Every paired backend is unreachable. Check network/VPN/backend status, or re-pair if the environment moved.";
|
|
842
|
+
}
|
|
843
|
+
if ((pub.presentations ?? []).length === 0) {
|
|
844
|
+
return "No PowerPoint window is registered. Open PowerPoint, load the slidemind add-in, and activate or create a corporate design.";
|
|
845
|
+
}
|
|
846
|
+
const renderReady = pub.presentations.some(
|
|
847
|
+
(p) =>
|
|
848
|
+
p.capabilities?.render === true ||
|
|
849
|
+
(!p.connectionState && p.connected !== false) ||
|
|
850
|
+
p.designState === "ready",
|
|
851
|
+
);
|
|
852
|
+
if (!renderReady) {
|
|
853
|
+
return "PowerPoint is connected, but no window is render-ready. Wait for corporate design matching or activate/create a corporate design.";
|
|
854
|
+
}
|
|
855
|
+
const paneReady = pub.presentations.some(
|
|
856
|
+
(p) =>
|
|
857
|
+
p.capabilities?.paneOps === true ||
|
|
858
|
+
((!p.connectionState || p.connectionState === "connected") &&
|
|
859
|
+
p.connected !== false &&
|
|
860
|
+
!p.suspendedAt),
|
|
861
|
+
);
|
|
862
|
+
if (!paneReady) {
|
|
863
|
+
return "Rendering is available, but pane operations are suspended. Bring PowerPoint forward and click/reopen the slidemind pane before push/search/read.";
|
|
864
|
+
}
|
|
865
|
+
return "Ready.";
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
async function cmdDoctor(rest) {
|
|
869
|
+
const asJson = rest.includes("--json");
|
|
870
|
+
const cfg = await loadBridgeModule("config");
|
|
871
|
+
const sessions = cfg.readSessions();
|
|
872
|
+
const client = await import(
|
|
873
|
+
pathToFileURL(path.join(SRC_DIR, "bridge", "client.mjs")).href
|
|
874
|
+
);
|
|
875
|
+
if (sessions.length === 0) {
|
|
876
|
+
const pub = {
|
|
877
|
+
paired: false,
|
|
878
|
+
backendCount: 0,
|
|
879
|
+
backends: [],
|
|
880
|
+
presentations: [],
|
|
881
|
+
nextAction: connectionNextAction({ paired: false }),
|
|
882
|
+
};
|
|
883
|
+
process[asJson ? "stdout" : "stderr"].write(
|
|
884
|
+
asJson ? `${JSON.stringify(pub, null, 2)}\n` : `${pub.nextAction}\n`,
|
|
885
|
+
);
|
|
886
|
+
return;
|
|
887
|
+
}
|
|
888
|
+
const status = publicConnectionStatus(await client.connectionStatus(), client);
|
|
889
|
+
status.nextAction = connectionNextAction(status);
|
|
890
|
+
if (asJson) {
|
|
891
|
+
process.stdout.write(`${JSON.stringify(status, null, 2)}\n`);
|
|
892
|
+
return;
|
|
893
|
+
}
|
|
894
|
+
process.stdout.write(`Paired backends: ${status.backendCount}\n`);
|
|
895
|
+
for (const b of status.backends) {
|
|
896
|
+
process.stdout.write(
|
|
897
|
+
` ${b.env}: ${b.ok ? "reachable" : `unreachable — ${b.error}`}` +
|
|
898
|
+
(b.readinessV1 ? " (readiness)" : " (legacy)") +
|
|
899
|
+
`\n`,
|
|
900
|
+
);
|
|
901
|
+
}
|
|
902
|
+
if (status.presentations.length === 0) {
|
|
903
|
+
process.stdout.write("Open presentations: none\n");
|
|
904
|
+
} else {
|
|
905
|
+
process.stdout.write("Open presentations:\n");
|
|
906
|
+
for (const p of status.presentations) {
|
|
907
|
+
const label = p.title || p.fileName || "(untitled)";
|
|
908
|
+
const state =
|
|
909
|
+
p.connectionState ||
|
|
910
|
+
(p.connected === false || p.suspendedAt ? "suspended" : "connected");
|
|
911
|
+
const design = p.designState || (p.brandId ? "ready" : "unknown");
|
|
912
|
+
const caps = p.capabilities
|
|
913
|
+
? ` render:${p.capabilities.render === true ? "yes" : "no"} pane:${p.capabilities.paneOps === true ? "yes" : "no"}`
|
|
914
|
+
: "";
|
|
915
|
+
process.stdout.write(
|
|
916
|
+
` ${p.presId}\t${p.env ? `[${p.env}] ` : ""}${label}\t${state}\tcd:${design}${caps}\n`,
|
|
917
|
+
);
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
process.stdout.write(`Next action: ${status.nextAction}\n`);
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
// Pull the current content of an open presentation. Content is window-bound, so
|
|
924
|
+
// pick exactly one window (positional presId or --to; pickPresentationEntry errors
|
|
925
|
+
// listing the windows if several are open and none was named). `--mode summary`
|
|
926
|
+
// prints a per-slide outline; default `raw` prints each slide's plain text.
|
|
927
|
+
async function cmdRead(rest) {
|
|
928
|
+
let presId = null;
|
|
929
|
+
let mode = "raw";
|
|
930
|
+
let asJson = false;
|
|
931
|
+
for (let i = 0; i < rest.length; i++) {
|
|
932
|
+
const a = rest[i];
|
|
933
|
+
if (a === "--mode") {
|
|
934
|
+
const next = rest[++i];
|
|
935
|
+
if (next !== "summary" && next !== "raw") {
|
|
936
|
+
fail(`--mode expects 'summary' or 'raw', got '${next ?? ""}'`);
|
|
937
|
+
}
|
|
938
|
+
mode = next;
|
|
939
|
+
} else if (a === "--to") {
|
|
940
|
+
presId = rest[++i];
|
|
941
|
+
if (!presId) fail("--to expects a presId");
|
|
942
|
+
} else if (a === "--json") {
|
|
943
|
+
asJson = true;
|
|
944
|
+
} else if (a.startsWith("-")) {
|
|
945
|
+
fail(`unknown flag: ${a}`);
|
|
946
|
+
} else if (!presId) {
|
|
947
|
+
presId = a; // positional presId
|
|
948
|
+
} else {
|
|
949
|
+
fail(`unexpected argument: ${a}`);
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
const client = await import(
|
|
953
|
+
pathToFileURL(path.join(SRC_DIR, "bridge", "client.mjs")).href
|
|
954
|
+
);
|
|
955
|
+
const registry = await client.requirePaneHost();
|
|
956
|
+
const entry = client.pickPresentationEntry(registry, presId);
|
|
957
|
+
const res = await client.getPresentationContent({
|
|
958
|
+
presId: entry.presId,
|
|
959
|
+
mode,
|
|
960
|
+
backendURL: entry.backendURL ?? null,
|
|
961
|
+
});
|
|
962
|
+
if (!res?.ok) {
|
|
963
|
+
fail(`read failed: ${res?.error ?? "unknown error"}`);
|
|
964
|
+
}
|
|
965
|
+
if (asJson) {
|
|
966
|
+
process.stdout.write(JSON.stringify(res, null, 2) + "\n");
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
969
|
+
const slides = Array.isArray(res.slides) ? res.slides : [];
|
|
970
|
+
if (slides.length === 0) {
|
|
971
|
+
process.stderr.write("(presentation has no slides)\n");
|
|
972
|
+
return;
|
|
973
|
+
}
|
|
974
|
+
if (res.mode === "summary") {
|
|
975
|
+
for (const s of slides) {
|
|
976
|
+
const label = s.title || "(untitled)";
|
|
977
|
+
const sub = s.subtitle ? ` — ${s.subtitle}` : "";
|
|
978
|
+
const layout = s.layout ? ` [${s.layout}]` : "";
|
|
979
|
+
process.stdout.write(
|
|
980
|
+
`${String(s.index).padStart(3)}.${layout} ${label}${sub}\n`,
|
|
981
|
+
);
|
|
982
|
+
}
|
|
983
|
+
} else {
|
|
984
|
+
slides.forEach((s, i) => {
|
|
985
|
+
if (i > 0) process.stdout.write("\n");
|
|
986
|
+
const layout = s.layout ? ` (${s.layout})` : "";
|
|
987
|
+
process.stdout.write(`── Slide ${s.index}${layout} ──\n`);
|
|
988
|
+
process.stdout.write(`${s.text && s.text.length ? s.text : "(no text)"}\n`);
|
|
989
|
+
});
|
|
718
990
|
}
|
|
719
991
|
}
|
|
720
992
|
|
|
@@ -765,8 +1037,30 @@ async function cmdPair(rest) {
|
|
|
765
1037
|
others > 0
|
|
766
1038
|
? ` (${sessions.length} environments connected — commands now fan out across all of them)`
|
|
767
1039
|
: "";
|
|
1040
|
+
let verify = "";
|
|
1041
|
+
try {
|
|
1042
|
+
const client = await import(
|
|
1043
|
+
pathToFileURL(path.join(SRC_DIR, "bridge", "client.mjs")).href
|
|
1044
|
+
);
|
|
1045
|
+
const deadline = Date.now() + 8000;
|
|
1046
|
+
for (;;) {
|
|
1047
|
+
const status = await client.connectionStatus();
|
|
1048
|
+
if ((status.presentations ?? []).length > 0) {
|
|
1049
|
+
verify = " Pane is registered.";
|
|
1050
|
+
break;
|
|
1051
|
+
}
|
|
1052
|
+
if (Date.now() >= deadline) {
|
|
1053
|
+
verify =
|
|
1054
|
+
" No pane is registered yet; if PowerPoint is already open, reopen or click the slidemind task pane, then run 'slidemind doctor'.";
|
|
1055
|
+
break;
|
|
1056
|
+
}
|
|
1057
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1058
|
+
}
|
|
1059
|
+
} catch {
|
|
1060
|
+
// Pairing itself succeeded; diagnostics remain available via doctor.
|
|
1061
|
+
}
|
|
768
1062
|
process.stdout.write(
|
|
769
|
-
`Paired with ${label || env}${suffix}
|
|
1063
|
+
`Paired with ${label || env}${suffix}.${verify} Run 'slidemind doctor' to verify.\n`,
|
|
770
1064
|
);
|
|
771
1065
|
}
|
|
772
1066
|
|
|
@@ -1192,6 +1486,9 @@ async function main(rawArgv) {
|
|
|
1192
1486
|
case "build-pptx":
|
|
1193
1487
|
await cmdBuildPptx(rest);
|
|
1194
1488
|
return 0;
|
|
1489
|
+
case "view-draft":
|
|
1490
|
+
await cmdViewDraft(rest);
|
|
1491
|
+
return 0;
|
|
1195
1492
|
case "search":
|
|
1196
1493
|
await cmdSearch(rest);
|
|
1197
1494
|
return 0;
|
|
@@ -1225,6 +1522,14 @@ async function main(rawArgv) {
|
|
|
1225
1522
|
case "presentations":
|
|
1226
1523
|
await cmdPresentations(rest);
|
|
1227
1524
|
return 0;
|
|
1525
|
+
case "doctor":
|
|
1526
|
+
case "connection":
|
|
1527
|
+
case "status":
|
|
1528
|
+
await cmdDoctor(rest);
|
|
1529
|
+
return 0;
|
|
1530
|
+
case "read":
|
|
1531
|
+
await cmdRead(rest);
|
|
1532
|
+
return 0;
|
|
1228
1533
|
case "push":
|
|
1229
1534
|
await cmdPush(rest);
|
|
1230
1535
|
return 0;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "slidemind",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
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": {
|
|
@@ -18,11 +18,32 @@ So this file owns only what is genuinely CLI-specific: the **host precondition**
|
|
|
18
18
|
This skill drives the `slidemind` CLI + its MCP server (`slidemind_list_open_presentations`, `slidemind_push_deck`, `slidemind_search_engine`, `slidemind_download_result`). The CLI reaches PowerPoint through the add-in's **cloud relay** — it doesn't run any local server. Patch what's missing yourself, but the **pairing** step needs the user once (they hold the add-in's identity):
|
|
19
19
|
|
|
20
20
|
- **CLI missing** (`slidemind` not on `PATH`, or the `slidemind_*` MCP tools absent): `npm install -g slidemind`, then `slidemind init --for <claude|codex|opencode|all>` (registers the MCP server + (re)deploys this skill). The harness may need a restart for the `slidemind_*` tools to appear.
|
|
21
|
-
- **Not paired** (a command says *slidemind is not paired*):
|
|
21
|
+
- **Not paired** (a command says *slidemind is not paired*): the one step you can't do for the user — they hold the add-in's identity. Walk them through **Pairing the CLI with the add-in** below (it carries the restart step the in-app wizard never mentions). `slidemind whoami` lists every paired environment.
|
|
22
22
|
- `npx slidemind <command>` runs one-off without a global install; `slidemind --version` confirms PATH; `slidemind update` upgrades (`--ref <version>` to pin).
|
|
23
23
|
|
|
24
24
|
After pairing once: **CLI paired + the add-in open ≡ it just works**, against whatever environment (dev / prod / a client deployment) the add-in is wired to. The CLI is **multi-environment**: pair once per backend (local / dev / prod) and every command fans out across all of them, tags each open window with its `env`, and routes to the right backend automatically — no manual switching. (Scope to one with `--backend <url|env>` when you need to.)
|
|
25
25
|
|
|
26
|
+
## Pairing the CLI with the add-in (one-time; you guide the user)
|
|
27
|
+
|
|
28
|
+
Pairing is the **only** step that genuinely needs the user — the pairing code carries the add-in's own session, so only they can mint it. When a command fails with *slidemind is not paired* (or `slidemind whoami` lists nothing for this machine), don't retry blindly: walk the user through the steps below, then resume. It's one-time per environment; once paired, `slidemind whoami` shows it.
|
|
29
|
+
|
|
30
|
+
**1 — Mint a pairing code in the add-in.** Tell the user: in PowerPoint, open the **slidemind add-in** task pane → **Settings** → **General** → the **Connect CLI** section (it's marked *Preview*) → press **Pair**. That opens a small 3-step wizard with copy buttons:
|
|
31
|
+
1. `npm install -g slidemind` — skip if the CLI is already installed.
|
|
32
|
+
2. pick the coding agent(s) → the wizard prints the exact `slidemind init --for <…>` — skip if you already ran `init`.
|
|
33
|
+
3. a **one-time** `slidemind pair <code>` command — this is the one that matters.
|
|
34
|
+
|
|
35
|
+
**2 — Run the pair command in this terminal.** Have the user copy the step-3 `slidemind pair <code>` line and paste it here; run it. The code is single-use and expires (~10 min) — if it fails as expired/invalid, have them press **Pair** again for a fresh one. On success the add-in's Connect CLI panel flips to **Connected**.
|
|
36
|
+
|
|
37
|
+
**3 — Restart the add-in once (the wizard does NOT tell the user this).** Pairing flips CLI integration on, but the task pane that's *already running* often won't pick up the live connection — so the freshly-paired window may not show up yet. **If, right after pairing, `slidemind presentations` still doesn't list the window** (or a push/search behaves as though nothing is connected) even though PowerPoint is open with the add-in loaded and a corporate design active, tell the user to **restart the add-in**: close the slidemind task pane and reopen it from the ribbon; if the window still doesn't appear, close and reopen the presentation (or PowerPoint). A fresh pane re-reads the now-enabled pairing and registers itself so the CLI can see it. Then re-run `slidemind presentations`. This is a one-time nudge right after the first pairing — once the window appears it stays, and the add-in can't trigger this restart itself, so you have to ask for it.
|
|
38
|
+
|
|
39
|
+
**Two states, two different fixes — don't conflate them:**
|
|
40
|
+
|
|
41
|
+
| What you see | What it means | What to tell the user |
|
|
42
|
+
|---|---|---|
|
|
43
|
+
| *slidemind is not paired* (or empty `whoami`) | this machine has no session | mint a code via **Connect CLI** and run `slidemind pair <code>` (steps 1–2) |
|
|
44
|
+
| `slidemind presentations` is **empty**, but PowerPoint is open with the add-in + a CD active, **and** this machine is already paired | the pane hasn't registered on the relay — typically a just-paired pane that needs a fresh boot | **restart the add-in** (step 3), then retry |
|
|
45
|
+
| `slidemind presentations` is **empty** and no PowerPoint is connected | nothing to talk to | open PowerPoint, load the add-in, pick/create a corporate design |
|
|
46
|
+
|
|
26
47
|
## Precondition gate (check this first, every time)
|
|
27
48
|
|
|
28
49
|
Authoring and rendering hard-require an **open PowerPoint host with the slidemind add-in loaded and an active corporate design**. Without a host there is no bundle to read and no renderer to push to; `download-bundle`, `build-pptx`, and `push` all fail.
|
|
@@ -31,7 +52,7 @@ Authoring and rendering hard-require an **open PowerPoint host with the slidemin
|
|
|
31
52
|
2. One registered → that's your target `presentation_id`. **Several → ask the user which**, by `title`/`fileName` (never raw ids) — once for **which presentation's corporate design to download**, and again at render time for **which presentation to push into** (the CD source and the push target may be different presentations).
|
|
32
53
|
3. **None registered → stop.** Tell the user: *open PowerPoint, load the slidemind add-in, and pick or create a corporate design, then retry.*
|
|
33
54
|
|
|
34
|
-
> **Empty list vs. not paired — different fixes
|
|
55
|
+
> **Empty list vs. not paired — different fixes** (full triage in *Pairing the CLI with the add-in* above). An *empty* registry with no PowerPoint connected means open it / enable Connect CLI (step 3). A *not paired* error means this machine has no session — pair once via the add-in's **"Connect CLI"**; don't ask them to open PowerPoint for that. An *empty* registry **while** PowerPoint is open with the add-in **and** this machine is already paired usually means the just-paired pane never registered — ask the user to **restart the add-in** (close + reopen the task pane), then retry.
|
|
35
56
|
|
|
36
57
|
**Backgrounded PowerPoint is expected to work.** Do not pre-emptively ask the user to focus PowerPoint or the task pane, and never ask them to keep it foregrounded while something runs. The render itself (`build-pptx`, the render half of `push`) **never needs the task pane awake** — it captures the slides with a local headless browser and assembles the `.pptx` server-side — streams its own `rendering slide n/m…` progress, and survives long decks. Only the pane-touching ops (the insert half of `push`, engine search/download) need the pane, and the CLI handles that itself: it waits up to ~2 min, prints one *"click the slidemind pane"* line when user help is genuinely needed, and retries safely. If you see that line, relay it once and keep waiting — do not rebuild, re-plan, or babysit. If a `push` still times out, the rendered `.pptx` was saved next to the deck (`paneSuspended` + `fallbackFile` in the result) — nothing is lost; retry the push after the user clicks the pane. Reserve the full "open PowerPoint…" instruction in (3) for when nothing is registered at all.
|
|
37
58
|
|
|
@@ -57,12 +78,13 @@ Keep planning about intent and content. Don't materialize the bundle or read bra
|
|
|
57
78
|
3. **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.
|
|
58
79
|
4. **Read, then author.** Read the four 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.) 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.
|
|
59
80
|
5. **Imagery from the engine.** Per the `### Brand Image Sources` block in `design.md`, search the brand engine and place results. The engine search/reuse mechanics live in **`slidemind-search`** — use it to find and fetch imagery; the create-only icon-recolour detail is in *Recolouring engine icons* 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.
|
|
60
|
-
6. **
|
|
81
|
+
6. **Look at your own draft first (you, the agent).** This is `see_draft` in `workflow/build-and-export.md` — the agent's pre-check, *before* any human sees the deck. The local-harness port is `slidemind view-draft <deck>` (MCP `slidemind_view_draft({ deck_path })`): 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.
|
|
82
|
+
7. **Preview review, then push.** This is `build_and_push` in `workflow/build-and-export.md`, including its preview review gate — the local harness has a native port: before the FIRST export of new slides, run `slidemind review <deck>`. It builds the HTML preview, opens it in the browser with a spatial comment overlay, and **blocks until the user decides** (host-free — no open PowerPoint needed for the gate). The command then prints its verdict to stdout:
|
|
61
83
|
- `DECISION: approved` → proceed to the export the user actually asked for: `slidemind push <deck>` (into PowerPoint) or `slidemind build-pptx <deck>` (to a file).
|
|
62
84
|
- `DECISION: comments` → the user left spatially-anchored notes, each citing a `slides/<file>.jsx:line:col`, the source line, and the click position (plus a readonly-layout note where the click landed on a layout slot). **Edit the cited files in place at the given line**, then **re-run `slidemind review <deck>`** — the prior round shows as read-only ghost pins so the user can confirm each note was addressed. Loop until approved. Nothing is exported on a comments round.
|
|
63
85
|
|
|
64
86
|
`slidemind review` is the only step that pauses; `push`/`build-pptx` never pause — they render pane-free (local capture + server-side assembly, with live per-slide progress). One approval covers the task: push follow-up fixes without re-running review unless the user wants another look. If several presentations are open, confirm **which one to push into** first (it can differ from the CD source). After approval, run the push without asking; it works while PowerPoint is backgrounded, and the CLI itself waits + retries if the pane is needed (see the precondition gate). If a push still times out, the rendered `.pptx` is saved next to the deck — re-run `push` after the user clicks the pane, without rebuilding.
|
|
65
|
-
|
|
87
|
+
8. **Sanity pass.** Run the checks in `workflow/create.md` before reporting done.
|
|
66
88
|
|
|
67
89
|
## Recolouring engine icons (create-authoring specifics)
|
|
68
90
|
|
|
@@ -83,11 +105,13 @@ The bundle's `workflow/*.md` are written for the in-app design agent and name **
|
|
|
83
105
|
| In `workflow/*.md` (in-app agent) | Your equivalent (local harness) |
|
|
84
106
|
|---|---|
|
|
85
107
|
| `build_and_push` | `slidemind push <deck>` (into the open PowerPoint) · `slidemind build-pptx <deck>` (to a `.pptx` file) — both render server-side via the bridge |
|
|
108
|
+
| `read_presentation_context` (read the live deck) | `slidemind read [presId] [--mode summary\|raw]` · MCP `slidemind_read_presentation({ presentation_id?, mode? })` — pull the current content of an open presentation (`summary` = per-slide outline; `raw` = each slide's plain text) to inspect what's there before editing. Plain text, not authoring markup |
|
|
86
109
|
| `engine_search` / `engine_add` / `insert_image` | **`slidemind-search`** owns the engine search + reuse mechanics (`slidemind_search_engine` → `slidemind_download_result`, plus whole-slide reuse via `add-slide`). For placing a fetched icon in your JSX, pass `icon_color` / batch with `selections: [{ selection, icon_color }, …]` and colour per *Recolouring engine icons* below |
|
|
87
110
|
| writing `slides/<topic>.jsx` in the chat workspace — unprefixed kebab-case names; play order = the ordered list passed to `build_and_push` | write `decks/<slug>/slides/NN-<topic>.jsx` on disk with a **zero-padded numeric prefix** (`01-cover.jsx`, `02-agenda.jsx`, …) — over the CLI play order is **numeric-aware filename order**; there is no ordered-list channel, so reordering = renaming files. The bundle's "don't pad with index prefixes" rule is in-app-only; here it inverts |
|
|
88
111
|
| reading workspace files (`design.md`, `layouts/`, `workflow/*`) | read them from the materialized bundle dir (`<bundle>/…`) and your `decks/<slug>/` |
|
|
89
112
|
| reference-slide thumbnails injected at `add_slide` (in-app persona) | **Read** 3 random images from the materialized bundle's `samples/` dir — the Read tool renders them (see *The create flow* step 3) |
|
|
90
113
|
| `present_plan_for_review` (manager-owned) | **you** run intake → plan with the user (above); persist `decks/<slug>/plan.md` for decks |
|
|
114
|
+
| `see_draft` (agent looks at its OWN draft before pushing) | `slidemind view-draft <deck>` · MCP `slidemind_view_draft({ deck_path })` — 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 the agent self-check; `slidemind review` is the *human* gate |
|
|
91
115
|
| the `build_and_push` **preview review gate** (pause on rendered previews → approve / per-element comments) | `slidemind review <deck>` — a native port of the same gate: serves the HTML preview 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 → run the export (`slidemind push` / `build-pptx`); on comments → edit the cited files and re-run `slidemind review` (prior round shows as ghost pins). One approval covers the task. `push`/`build-pptx` never pause; only `review` does. |
|
|
92
116
|
|
|
93
117
|
Everything else in `workflow/*.md` — the composition craft, the substrate/authoring rules, the layout discovery and slot/working-area rules, the sanity bar — applies to you unchanged.
|