slidemind 0.0.28 → 0.0.29

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "slidemind",
3
- "version": "0.0.28",
3
+ "version": "0.0.29",
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": {
@@ -358,16 +358,27 @@ export function loadEnginesForDeck(deckDir) {
358
358
  return readEnginesFromDir(resolveBundleDirForDeck(deckDir));
359
359
  }
360
360
 
361
- // Pick the default engine to search: the first image/icon engine, else the
362
- // first engine. Single source of truth for the heuristic shared by the CLI
363
- // dispatcher (`slidemind search`) and the MCP `slidemind_search_engine` tool.
361
+ // Image-producing engine kinds — the modality "image" plus the finer image
362
+ // content kinds an authored bundle may carry. Excludes "slide" (a slide-search
363
+ // engine returns whole slides, not insertable bytes). Mirrors the add-in's
364
+ // local-bridge `engine_not_image` gate so the CLI never picks an engine the
365
+ // bridge would reject.
366
+ const IMAGE_ENGINE_KIND_RE = /^(image|icon|photo|illustration|vector)$/i;
367
+
368
+ // True when an engine produces insertable image bytes. Gates on the engine's
369
+ // real `kind` (its modality from the design bundle) — NOT a guess over its
370
+ // label/description.
371
+ export function isImageEngine(engine) {
372
+ return IMAGE_ENGINE_KIND_RE.test(engine?.kind ?? "");
373
+ }
374
+
375
+ // Pick the default engine to search: the first image engine, by its real
376
+ // `kind`. Returns null when the CD exposes no image engine (e.g. only a
377
+ // slide-search engine) — callers surface a "wire an image engine" message
378
+ // rather than falling back to an engine the bridge can't insert. Single source
379
+ // of truth for the heuristic shared by the CLI dispatcher (`slidemind search`)
380
+ // and the MCP `slidemind_search_engine` tool.
364
381
  export function pickImageEngine(engines) {
365
382
  const list = Array.isArray(engines) ? engines : [];
366
- return (
367
- list.find((e) =>
368
- /image|icon/i.test(`${e.kind ?? ""} ${e.label ?? ""} ${e.description ?? ""}`),
369
- ) ||
370
- list[0] ||
371
- null
372
- );
383
+ return list.find(isImageEngine) ?? null;
373
384
  }
@@ -44,6 +44,26 @@ function firstExisting(candidates) {
44
44
  return null;
45
45
  }
46
46
 
47
+ // Does a design root actually carry brand files? A root qualifies if it has the
48
+ // corporate-design CSS or a layouts dir that holds at least one real layout
49
+ // (a `*.jsx` or `layouts.css`). A bare/junk-only `layouts/` — e.g. one left
50
+ // behind on disk holding only a macOS `.DS_Store` — must NOT count, or it would
51
+ // mask the "materialize the bundle first" guidance for a deck that hasn't
52
+ // downloaded one yet.
53
+ function carriesDesign(root, cssName) {
54
+ if (!root) return false;
55
+ if (fs.existsSync(path.join(root, cssName))) return true;
56
+ const layoutsDir = path.join(root, "layouts");
57
+ if (!fs.existsSync(layoutsDir)) return false;
58
+ try {
59
+ return fs
60
+ .readdirSync(layoutsDir)
61
+ .some((f) => f.endsWith(".jsx") || f === "layouts.css");
62
+ } catch {
63
+ return false;
64
+ }
65
+ }
66
+
47
67
  // First existing candidate, or `fallback` when none exist. Encodes the
48
68
  // deck-override → bundle → skill precedence in one place so every resolved path
49
69
  // shares the same lookup rule.
@@ -73,12 +93,7 @@ export function resolveSkill(skillName, planMeta = {}, opts = {}) {
73
93
  const designRoots = [
74
94
  bundleDir,
75
95
  fs.existsSync(skillDir) ? skillDir : null,
76
- ].filter(
77
- (r) =>
78
- r &&
79
- (fs.existsSync(path.join(r, cssName)) ||
80
- fs.existsSync(path.join(r, "layouts"))),
81
- );
96
+ ].filter((r) => carriesDesign(r, cssName));
82
97
  if (designRoots.length === 0) {
83
98
  throw new Error(
84
99
  `No design source for skill "${skillName}". The corporate design now lives ` +