slidemind 0.1.5 → 0.1.7

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.
@@ -23,6 +23,7 @@
23
23
 
24
24
  import fs from "node:fs";
25
25
  import path from "node:path";
26
+ import { spawnSync } from "node:child_process";
26
27
  import {
27
28
  expandHome,
28
29
  installDir,
@@ -40,7 +41,10 @@ const TOML_MARK_BEGIN = "# slidemind:begin";
40
41
  const TOML_MARK_END = "# slidemind:end";
41
42
 
42
43
  function codexRoot() {
43
- return expandHome("~/.codex");
44
+ // Codex reads everything (skills, AGENTS.md, config.toml) from $CODEX_HOME
45
+ // when set, defaulting to ~/.codex — mirror that so our artifacts land where
46
+ // it actually looks.
47
+ return process.env.CODEX_HOME || expandHome("~/.codex");
44
48
  }
45
49
 
46
50
  function promptsDir() {
@@ -81,38 +85,34 @@ function configTomlFile() {
81
85
  return path.join(codexRoot(), "config.toml");
82
86
  }
83
87
 
84
- function writeMcpServerBlock(packageRoot) {
85
- const mcpServer = path.join(packageRoot, "src", "mcp", "server.mjs");
86
- const block =
87
- `${TOML_MARK_BEGIN}\n` +
88
- `[mcp_servers.slidemind]\n` +
89
- `command = "node"\n` +
90
- `args = ["${mcpServer.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"]\n` +
91
- `${TOML_MARK_END}\n`;
92
-
93
- const file = configTomlFile();
94
- let existing = fs.existsSync(file) ? fs.readFileSync(file, "utf8") : "";
95
- const re = new RegExp(
96
- `${TOML_MARK_BEGIN}[\\s\\S]*?${TOML_MARK_END}\\n?`,
97
- "g",
88
+ // slidemind is a CLI steered by the skill — there is no MCP server. We only ever
89
+ // tear down a `[mcp_servers.slidemind]` table an older install registered.
90
+ // Prefer Codex's own `codex mcp remove` (TOML-aware); fall back to a marker strip
91
+ // when the binary is missing or too old.
92
+ export function unregisterMcpServer() {
93
+ const viaCli = spawnSync(
94
+ "codex",
95
+ ["mcp", "remove", NAMESPACE],
96
+ { stdio: "ignore", shell: process.platform === "win32" },
98
97
  );
99
- if (re.test(existing)) {
100
- existing = existing.replace(re, "");
101
- }
102
- const sep = existing.length && !existing.endsWith("\n") ? "\n\n" : existing.length ? "\n" : "";
103
- fs.writeFileSync(file, existing + sep + block);
98
+ if (!viaCli.error && viaCli.status === 0) return "unregistered via codex mcp";
99
+ return fallbackClearMcpServerBlock();
104
100
  }
105
101
 
106
- function clearMcpServerBlock() {
102
+ // Fallback uninstall: strip a legacy marker-wrapped block if one is present;
103
+ // otherwise point the user at `codex mcp remove` (we won't guess at the bounds
104
+ // of a marker-less table without Codex's TOML parser).
105
+ export function fallbackClearMcpServerBlock() {
107
106
  const file = configTomlFile();
108
- if (!fs.existsSync(file)) return;
109
- let existing = fs.readFileSync(file, "utf8");
110
- const re = new RegExp(
111
- `${TOML_MARK_BEGIN}[\\s\\S]*?${TOML_MARK_END}\\n?`,
112
- "g",
113
- );
114
- existing = existing.replace(re, "");
115
- fs.writeFileSync(file, existing);
107
+ if (!fs.existsSync(file)) return "unregistered";
108
+ const existing = fs.readFileSync(file, "utf8");
109
+ const re = new RegExp(`${TOML_MARK_BEGIN}[\\s\\S]*?${TOML_MARK_END}\\n?`, "g");
110
+ const stripped = existing.replace(re, "");
111
+ if (stripped !== existing) {
112
+ fs.writeFileSync(file, stripped);
113
+ return "unregistered (fallback)";
114
+ }
115
+ return "run `codex mcp remove slidemind` to remove the entry";
116
116
  }
117
117
 
118
118
  function writeAgentsBlock() {
@@ -192,12 +192,17 @@ export async function install({ packageRoot, flags }) {
192
192
  writeAgentsBlock();
193
193
  ui.step({ status: "ok", label: "AGENTS.md", value: agentsFile() });
194
194
 
195
- // Register the slidemind MCP server so agents can list/push into open
196
- // PowerPoint windows via slidemind_list_open_presentations / slidemind_push_deck.
197
- writeMcpServerBlock(packageRoot);
198
- ui.step({ status: "ok", label: "mcp server", value: configTomlFile() });
195
+ // slidemind is a CLI now — clean up any [mcp_servers.slidemind] table an older
196
+ // install registered so re-running init converges to the CLI-only setup.
197
+ if (
198
+ fs.existsSync(configTomlFile()) &&
199
+ fs.readFileSync(configTomlFile(), "utf8").includes(`[mcp_servers.${NAMESPACE}]`)
200
+ ) {
201
+ const mcpNote = unregisterMcpServer();
202
+ ui.step({ status: "remove", label: "mcp server", value: mcpNote });
203
+ }
199
204
 
200
- ui.note("restart Codex to pick up the skill + MCP server");
205
+ ui.note("restart Codex to pick up the skill");
201
206
  }
202
207
 
203
208
  export async function uninstall() {
@@ -224,6 +229,6 @@ export async function uninstall() {
224
229
  }
225
230
  clearAgentsBlock();
226
231
  ui.step({ status: "remove", label: "AGENTS.md", value: agentsFile() });
227
- clearMcpServerBlock();
228
- ui.step({ status: "info", label: "mcp server", value: "unregistered" });
232
+ const mcpNote = unregisterMcpServer();
233
+ ui.step({ status: "info", label: "mcp server", value: mcpNote });
229
234
  }
@@ -71,28 +71,20 @@ function saveOpencodeConfig(cfg) {
71
71
  fs.writeFileSync(file, JSON.stringify(cfg, null, 2) + "\n");
72
72
  }
73
73
 
74
+ // slidemind is a CLI steered by the skill — there is no MCP server. This only
75
+ // tears down any `mcp.slidemind` entry an older install registered, so re-running
76
+ // `init` converges to the CLI-only setup. Returns whether it removed one.
74
77
  function unregisterMcpServer() {
75
78
  const file = opencodeConfigFile();
76
- if (!fs.existsSync(file)) return;
79
+ if (!fs.existsSync(file)) return false;
77
80
  const cfg = loadOpencodeConfig();
78
81
  if (cfg.mcp && cfg.mcp[NAMESPACE]) {
79
82
  delete cfg.mcp[NAMESPACE];
80
83
  if (Object.keys(cfg.mcp).length === 0) delete cfg.mcp;
81
84
  saveOpencodeConfig(cfg);
85
+ return true;
82
86
  }
83
- }
84
-
85
- function registerMcpServer(packageRoot) {
86
- const mcpServer = path.join(packageRoot, "src", "mcp", "server.mjs");
87
- const cfg = loadOpencodeConfig();
88
- if (!cfg["$schema"]) cfg["$schema"] = "https://opencode.ai/config.json";
89
- cfg.mcp = cfg.mcp || {};
90
- cfg.mcp[NAMESPACE] = {
91
- type: "local",
92
- command: ["node", mcpServer],
93
- enabled: true,
94
- };
95
- saveOpencodeConfig(cfg);
87
+ return false;
96
88
  }
97
89
 
98
90
  function removeLegacyArtifacts() {
@@ -153,10 +145,10 @@ export async function install({ packageRoot, flags }) {
153
145
  );
154
146
  }
155
147
 
156
- // Register the slidemind MCP server so agents can list/push into open
157
- // PowerPoint windows via slidemind_list_open_presentations / slidemind_push_deck.
158
- registerMcpServer(packageRoot);
159
- ui.step({ status: "ok", label: "mcp server", value: opencodeConfigFile() });
148
+ // slidemind is a CLI now — remove any MCP registration an older install left.
149
+ if (unregisterMcpServer()) {
150
+ ui.step({ status: "remove", label: "mcp server", value: "removed legacy registration (slidemind is a CLI)" });
151
+ }
160
152
  }
161
153
 
162
154
  export async function uninstall() {
@@ -1,9 +1,7 @@
1
1
  // Shared numeric-prefix + filename logic for folding reused slide-search
2
2
  // results into a deck as native one-slide .pptx (the "reuse → fold into a deck"
3
- // path). Both `slidemind add-slide` (bin/slidemind.mjs) and the
4
- // `slidemind_add_slide_to_deck` MCP tool (src/mcp/server.mjs) drive the SAME
5
- // math, so it lives here as one pure, tested unit — the callers keep the bridge
6
- // round-trip and the fs write.
3
+ // path behind `slidemind add-slide`, bin/slidemind.mjs). Kept here as one pure,
4
+ // tested unit — the caller keeps the bridge round-trip and the fs write.
7
5
  //
8
6
  // Play order over the CLI is filename order (see src/lib/slide-order.mjs): a
9
7
  // reused slide is named `NN-reused-<shortid>.pptx`, zero-padded to width 2, so