slidemind 0.1.4 → 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.
@@ -10,10 +10,20 @@
10
10
  // Lists PowerPoint windows registered with the local bridge, including
11
11
  // retained entries whose task-pane socket is temporarily disconnected.
12
12
  //
13
+ // slidemind_read_presentation
14
+ // Pulls the current content of an open PowerPoint window — a per-slide
15
+ // outline (summary) or each slide's full plain text (raw) — so the agent can
16
+ // inspect the live deck before editing it.
17
+ //
13
18
  // slidemind_push_deck
14
19
  // Renders a deck server-side via the add-in and pushes its slides into one
15
20
  // open PowerPoint window.
16
21
  //
22
+ // slidemind_view_draft
23
+ // Renders the deck's authored slides to per-slide preview images locally and
24
+ // returns them WITHOUT pushing — the agent's "look at my own draft" check
25
+ // before push / the human review gate.
26
+ //
17
27
  // slidemind_list_engines
18
28
  // Lists the live engine catalog (image + slide) for the active corporate
19
29
  // design, filterable by type/subtype.
@@ -151,6 +161,9 @@ const server = new McpServer(
151
161
  "with an active corporate design is required, and all brand rules come from that " +
152
162
  "design's bundle (design.md), never hardcoded here. " +
153
163
  "Always call slidemind_list_open_presentations first to learn which presentation_id to target. " +
164
+ "If connection/readiness is unclear, call slidemind_connection_status before asking the user for help. " +
165
+ "To inspect what's already in a window before editing, call slidemind_read_presentation (summary outline or raw plain text). " +
166
+ "After authoring or editing slides, call slidemind_view_draft to LOOK at your own draft (per-slide images, nothing pushed) and self-correct before slidemind_push_deck. " +
154
167
  "The CLI may be paired with several add-in environments (local/dev/prod) at once; the list spans " +
155
168
  "all of them and each entry is tagged with its env, and routing to the right backend is automatic — " +
156
169
  "just pass the presentation_id back. " +
@@ -183,6 +196,85 @@ server.registerTool(
183
196
  },
184
197
  );
185
198
 
199
+ server.registerTool(
200
+ "slidemind_connection_status",
201
+ {
202
+ title: "Diagnose CLI/add-in connection readiness",
203
+ description:
204
+ "Return paired backend reachability and per-window readiness. Use this when a slidemind tool fails or before asking the user for manual action.",
205
+ inputSchema: {},
206
+ },
207
+ async () => {
208
+ try {
209
+ const client = await loadBridgeClient();
210
+ const status = await client.connectionStatus();
211
+ return textResult({
212
+ paired: status.paired,
213
+ backendCount: status.backendCount,
214
+ backends: (status.backends ?? []).map((b) => ({
215
+ env: b.env,
216
+ ok: b.ok,
217
+ readinessV1: b.readinessV1 === true,
218
+ capabilities: b.capabilities ?? null,
219
+ error: b.error,
220
+ summary: b.summary ?? null,
221
+ presentations: (b.presentations ?? []).map(client.toPublicPresentation),
222
+ })),
223
+ presentations: (status.presentations ?? []).map(client.toPublicPresentation),
224
+ });
225
+ } catch (err) {
226
+ return errorResult(err);
227
+ }
228
+ },
229
+ );
230
+
231
+ server.registerTool(
232
+ "slidemind_read_presentation",
233
+ {
234
+ title: "Read the current content of an open presentation",
235
+ description:
236
+ "Pull the CURRENT content of an open PowerPoint window so you can inspect the live deck before editing it. " +
237
+ "mode='summary' returns a per-slide outline (index, layout, title, subtitle); mode='raw' (default) returns each slide's full plain text. " +
238
+ "This is plain text content, NOT editing markup. " +
239
+ "presentation_id is the presId from slidemind_list_open_presentations; it's optional when exactly one window is open, but required to disambiguate when several are open.",
240
+ inputSchema: {
241
+ presentation_id: z
242
+ .string()
243
+ .nullable()
244
+ .optional()
245
+ .describe("presId from slidemind_list_open_presentations. Optional when only one window is open; required to pick one when several are."),
246
+ mode: z
247
+ .enum(["summary", "raw"])
248
+ .nullable()
249
+ .optional()
250
+ .describe("'summary' = per-slide outline; 'raw' (default) = each slide's full plain text."),
251
+ },
252
+ },
253
+ async ({ presentation_id, mode }) => {
254
+ try {
255
+ const client = await loadBridgeClient();
256
+ const registry = await client.requirePaneHost();
257
+ const entry = client.pickPresentationEntry(registry, presentation_id ?? null);
258
+ const res = await client.getPresentationContent({
259
+ presId: entry.presId,
260
+ mode: mode ?? "raw",
261
+ backendURL: entry.backendURL ?? null,
262
+ });
263
+ if (!res?.ok) {
264
+ return errorResult(new Error(res?.error ?? "read presentation failed"));
265
+ }
266
+ return textResult({
267
+ presentation_id: entry.presId,
268
+ mode: res.mode,
269
+ slideCount: res.slideCount,
270
+ slides: res.slides ?? [],
271
+ });
272
+ } catch (err) {
273
+ return errorResult(err);
274
+ }
275
+ },
276
+ );
277
+
186
278
  server.registerTool(
187
279
  "slidemind_push_deck",
188
280
  {
@@ -316,7 +408,7 @@ server.registerTool(
316
408
  const client = await loadBridgeClient();
317
409
  // All engine tools hard-require an open host (the truth lives in the
318
410
  // add-in); surface the actionable "open PowerPoint…" message up front.
319
- const registry = await client.requireHost();
411
+ const registry = await client.requirePaneHost();
320
412
  // The engine is per-user, not window-bound: attach to any connected window
321
413
  // when the caller didn't pin one. The chosen presId is echoed back so the
322
414
  // agent threads it to the follow-up tools (the searchId is bound to it).
@@ -353,15 +445,20 @@ server.registerTool(
353
445
  engineId = pick.id;
354
446
  subtype = "image";
355
447
  }
356
- const res = await client.engineSearch({
357
- presId,
448
+ // Window-agnostic: tries the picked pane, then fails over to any other
449
+ // live window on the same backend if it's suspended/frozen. res.presId is
450
+ // the window that actually answered — echo THAT back (the searchId is
451
+ // bound to it) so follow-up view/insert/add target the right pane.
452
+ const res = await client.engineSearchAnyPane({
453
+ registry,
454
+ explicitPresId: presentation_id ?? null,
358
455
  engineId,
359
456
  query,
360
457
  subtype,
361
458
  intent: intent ?? null,
362
459
  top: top ?? null,
363
- backendURL,
364
460
  });
461
+ const ranPresId = res?.presId ?? presId;
365
462
  const content = [];
366
463
  const grid = imageContentFromDataUri(res?.gridDataUri);
367
464
  if (grid) content.push(grid);
@@ -385,9 +482,10 @@ server.registerTool(
385
482
  {
386
483
  ok: res?.ok ?? false,
387
484
  // The window the search ran on (auto-attached when the caller didn't
388
- // pin one). The searchId is bound to it — pass this presentation_id
389
- // to slidemind_view_slide / _insert_slide / _add_slide_to_deck.
390
- presentation_id: presId,
485
+ // pin one; may have failed over from the picked pane). The searchId
486
+ // is bound to it — pass this presentation_id to slidemind_view_slide
487
+ // / _insert_slide / _add_slide_to_deck.
488
+ presentation_id: ranPresId,
391
489
  engineId,
392
490
  subtype: res?.subtype ?? null,
393
491
  intent: res?.intent ?? null,
@@ -406,6 +504,62 @@ server.registerTool(
406
504
  },
407
505
  );
408
506
 
507
+ server.registerTool(
508
+ "slidemind_view_draft",
509
+ {
510
+ title: "Look at a deck's draft slides (no push)",
511
+ description:
512
+ "Render the deck's authored slides/*.jsx to per-slide preview images and return them to you WITHOUT pushing anything — the 'look at my own draft before the user sees it' tool. " +
513
+ "Use this AFTER authoring/editing slides and BEFORE slidemind_push_deck (and before the human slidemind review gate): look at each screenshot, fix anything visibly wrong in the JSX, then view again until each slide is sound. " +
514
+ "Renders locally against the materialized bundle, so it needs no awake task pane (and no open host at all once the bundle has been downloaded once). " +
515
+ "Returns one image per freeform slide; purpose-built layout slides (covers/dividers) and reused native .pptx slides have no draft preview and report as a status line. Nothing is pushed, nothing is written to the deck.",
516
+ inputSchema: {
517
+ deck_path: z
518
+ .string()
519
+ .describe("Path to the deck folder (must contain slides/). Absolute or relative to CWD."),
520
+ presentation_id: z
521
+ .string()
522
+ .nullable()
523
+ .optional()
524
+ .describe("Optional presId — only used to fetch the corporate-design bundle if it isn't cached yet. Omit when the deck's bundle is already materialized (the normal case after authoring)."),
525
+ },
526
+ },
527
+ async ({ deck_path, presentation_id }) => {
528
+ try {
529
+ const { viewDraft } = await loadBuildAndPush();
530
+ const deckDir = path.resolve(process.cwd(), deck_path);
531
+ const result = await viewDraft(deckDir, {
532
+ presentationId: presentation_id ?? null,
533
+ });
534
+ const images = [];
535
+ const lines = [];
536
+ for (const slide of result.slides ?? []) {
537
+ const image = slide.dataUri ? imageContentFromDataUri(slide.dataUri) : null;
538
+ if (image) {
539
+ images.push(image);
540
+ lines.push(
541
+ `${slide.fileName} — preview attached (image ${images.length})` +
542
+ (slide.status === "degraded" && slide.note ? ` — ${slide.note}` : ""),
543
+ );
544
+ } else {
545
+ lines.push(`${slide.fileName} — ${slide.note ?? slide.status}`);
546
+ }
547
+ }
548
+ const header =
549
+ images.length > 0
550
+ ? `Rendered ${images.length} draft preview image(s) — NOT pushed to PowerPoint, nothing written to the deck. ` +
551
+ `Judge each screenshot with your eyes, fix what is visibly wrong in the slide JSX, view the draft again, ` +
552
+ `then push with slidemind_push_deck (or build-pptx) once each slide is visually sound.`
553
+ : `No draft preview images were produced — NOTHING was pushed.`;
554
+ const warnLines = (result.warnings ?? []).map((w) => ` warning: ${w}`);
555
+ const text = [header, ...lines, ...warnLines].join("\n");
556
+ return { content: [{ type: "text", text }, ...images] };
557
+ } catch (err) {
558
+ return errorResult(err);
559
+ }
560
+ },
561
+ );
562
+
409
563
  server.registerTool(
410
564
  "slidemind_list_engines",
411
565
  {
@@ -473,7 +627,7 @@ server.registerTool(
473
627
  async ({ presentation_id, search_id, selection }) => {
474
628
  try {
475
629
  const client = await loadBridgeClient();
476
- await client.requireHost();
630
+ await client.requirePaneHost();
477
631
  const res = await client.viewSlide({
478
632
  presId: presentation_id,
479
633
  searchId: search_id,
@@ -526,7 +680,7 @@ server.registerTool(
526
680
  async ({ presentation_id, search_id, selections, at }) => {
527
681
  try {
528
682
  const client = await loadBridgeClient();
529
- await client.requireHost();
683
+ await client.requirePaneHost();
530
684
  const res = await client.insertSlideLive({
531
685
  presId: presentation_id,
532
686
  searchId: search_id,
@@ -584,7 +738,7 @@ server.registerTool(
584
738
  async ({ presentation_id, deck_path, search_id, selections, at }) => {
585
739
  try {
586
740
  const client = await loadBridgeClient();
587
- await client.requireHost();
741
+ await client.requirePaneHost();
588
742
  const deckDir = path.resolve(process.cwd(), deck_path);
589
743
  const slidesDir = path.join(deckDir, "slides");
590
744
  fs.mkdirSync(slidesDir, { recursive: true });
@@ -748,7 +902,7 @@ server.registerTool(
748
902
  }
749
903
 
750
904
  const client = await loadBridgeClient();
751
- await client.requireHost();
905
+ await client.requirePaneHost();
752
906
  const deckDir = path.resolve(process.cwd(), deck_path);
753
907
  const assetsDir = path.join(deckDir, "assets");
754
908
  fs.mkdirSync(assetsDir, { recursive: true });