supipowers 0.7.0 → 0.7.2

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/bin/install.mjs CHANGED
@@ -83,6 +83,11 @@ function isInstalled(binary) {
83
83
  return result.status === 0;
84
84
  }
85
85
 
86
+ // ── CLI Flags ────────────────────────────────────────────────
87
+
88
+ const args = process.argv.slice(2);
89
+ const skipLsp = args.includes("--skip-lsp");
90
+
86
91
  // ── Main ─────────────────────────────────────────────────────
87
92
 
88
93
  async function main() {
@@ -191,41 +196,94 @@ async function main() {
191
196
  }
192
197
  }
193
198
 
194
- // ── Step 3: LSP setup (optional) ──────────────────────────
199
+ // ── Step 2b: Register context-mode MCP server (if installed) ──
200
+
201
+ const ctxSpinner = spinner();
202
+ ctxSpinner.start("Checking for context-mode...");
203
+
204
+ // Find context-mode installation (Claude Code plugin cache)
205
+ const ctxCacheBase = join(homedir(), ".claude", "plugins", "cache", "context-mode", "context-mode");
206
+ let ctxInstallPath = null;
207
+ if (existsSync(ctxCacheBase)) {
208
+ // Find the latest version directory
209
+ const versions = readdirSync(ctxCacheBase, { withFileTypes: true })
210
+ .filter((d) => d.isDirectory())
211
+ .map((d) => d.name)
212
+ .sort()
213
+ .reverse();
214
+ if (versions.length > 0) {
215
+ const candidate = join(ctxCacheBase, versions[0], "start.mjs");
216
+ if (existsSync(candidate)) {
217
+ ctxInstallPath = join(ctxCacheBase, versions[0]);
218
+ }
219
+ }
220
+ }
195
221
 
196
- const lspSpinner = spinner();
197
- lspSpinner.start("Checking installed LSP servers...");
198
- const lspOptions = LSP_SERVERS.map((srv) => {
199
- const installed = isInstalled(srv.server);
200
- return {
201
- value: srv,
202
- label: srv.language,
203
- hint: installed ? `${srv.server} (installed)` : srv.server,
222
+ if (ctxInstallPath) {
223
+ // Register as MCP server in ~/.omp/agent/mcp.json
224
+ const mcpConfigPath = join(homedir(), ".omp", "agent", "mcp.json");
225
+ let mcpConfig = { mcpServers: {} };
226
+ if (existsSync(mcpConfigPath)) {
227
+ try {
228
+ mcpConfig = JSON.parse(readFileSync(mcpConfigPath, "utf8"));
229
+ if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};
230
+ } catch {
231
+ mcpConfig = { mcpServers: {} };
232
+ }
233
+ }
234
+
235
+ const startMjs = join(ctxInstallPath, "start.mjs");
236
+ mcpConfig.mcpServers["context-mode"] = {
237
+ command: "node",
238
+ args: [startMjs],
204
239
  };
205
- });
206
- const installedCount = lspOptions.filter((o) => o.hint.includes("(installed)")).length;
207
- lspSpinner.stop(`Found ${installedCount}/${LSP_SERVERS.length} LSP servers installed`);
208
240
 
209
- const selected = await multiselect({
210
- message: "Install LSP servers for better code intelligence?",
211
- options: lspOptions,
212
- required: false,
213
- });
241
+ const { writeFileSync: writeFs } = await import("node:fs");
242
+ writeFs(mcpConfigPath, JSON.stringify(mcpConfig, null, 2));
243
+ ctxSpinner.stop(`context-mode registered as MCP server (${ctxInstallPath})`);
244
+ } else {
245
+ ctxSpinner.stop("context-mode not found (install it as a Claude Code plugin for context window protection)");
246
+ }
214
247
 
215
- if (!isCancel(selected) && selected.length > 0) {
216
- for (const srv of selected) {
217
- if (isInstalled(srv.server)) {
218
- note(`${srv.server} is already installed, skipping.`, srv.language);
219
- continue;
220
- }
221
- const ls = spinner();
222
- ls.start(`Installing ${srv.server}...`);
223
- const [cmd, ...args] = srv.installCmd.split(" ");
224
- const r = run(cmd, args);
225
- if (r.status !== 0) {
226
- ls.stop(`Failed to install ${srv.server} — you can install manually: ${srv.installCmd}`);
227
- } else {
228
- ls.stop(`${srv.server} installed`);
248
+ // ── Step 3: LSP setup (optional, skipped with --skip-lsp) ──
249
+
250
+ if (skipLsp) {
251
+ note("LSP setup skipped (--skip-lsp)", "LSP");
252
+ } else {
253
+ const lspSpinner = spinner();
254
+ lspSpinner.start("Checking installed LSP servers...");
255
+ const lspOptions = LSP_SERVERS.map((srv) => {
256
+ const installed = isInstalled(srv.server);
257
+ return {
258
+ value: srv,
259
+ label: srv.language,
260
+ hint: installed ? `${srv.server} (installed)` : srv.server,
261
+ };
262
+ });
263
+ const installedCount = lspOptions.filter((o) => o.hint.includes("(installed)")).length;
264
+ lspSpinner.stop(`Found ${installedCount}/${LSP_SERVERS.length} LSP servers installed`);
265
+
266
+ const selected = await multiselect({
267
+ message: "Install LSP servers for better code intelligence?",
268
+ options: lspOptions,
269
+ required: false,
270
+ });
271
+
272
+ if (!isCancel(selected) && selected.length > 0) {
273
+ for (const srv of selected) {
274
+ if (isInstalled(srv.server)) {
275
+ note(`${srv.server} is already installed, skipping.`, srv.language);
276
+ continue;
277
+ }
278
+ const ls = spinner();
279
+ ls.start(`Installing ${srv.server}...`);
280
+ const [cmd, ...installArgs] = srv.installCmd.split(" ");
281
+ const r = run(cmd, installArgs);
282
+ if (r.status !== 0) {
283
+ ls.stop(`Failed to install ${srv.server} — you can install manually: ${srv.installCmd}`);
284
+ } else {
285
+ ls.stop(`${srv.server} installed`);
286
+ }
229
287
  }
230
288
  }
231
289
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "supipowers",
3
- "version": "0.7.0",
3
+ "version": "0.7.2",
4
4
  "description": "OMP-native workflow extension inspired by supipowers.",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -24,13 +24,26 @@ const TOOL_SUFFIXES: Array<[string, keyof ContextModeStatus["tools"]]> = [
24
24
  ];
25
25
 
26
26
  /**
27
- * Extract the short tool name from a potentially MCP-namespaced tool name.
28
- * MCP tools use the format: mcp__<server>__<tool_name>
29
- * Native tools use bare names like: lsp, bash, etc.
27
+ * Check if a tool name matches a context-mode tool suffix.
28
+ * Handles multiple naming conventions:
29
+ * - Bare names: "ctx_execute"
30
+ * - Claude Code MCP: "mcp__plugin_context-mode_context-mode__ctx_execute"
31
+ * - OMP MCP: "mcp_context_mode_ctx_execute"
32
+ *
33
+ * We match by checking if the tool contains a known context-mode server
34
+ * prefix followed by the suffix, or is the bare suffix itself.
30
35
  */
31
- function getShortName(tool: string): string {
32
- const lastSep = tool.lastIndexOf("__");
33
- return lastSep >= 0 ? tool.slice(lastSep + 2) : tool;
36
+ const CONTEXT_MODE_PREFIXES = [
37
+ "mcp__plugin_context-mode_context-mode__", // Claude Code
38
+ "mcp_context_mode_", // OMP
39
+ ];
40
+
41
+ function matchesSuffix(tool: string, suffix: string): boolean {
42
+ if (tool === suffix) return true;
43
+ for (const prefix of CONTEXT_MODE_PREFIXES) {
44
+ if (tool === prefix + suffix) return true;
45
+ }
46
+ return false;
34
47
  }
35
48
 
36
49
  /** Detect context-mode MCP tool availability from the active tools list */
@@ -45,9 +58,8 @@ export function detectContextMode(activeTools: string[]): ContextModeStatus {
45
58
  };
46
59
 
47
60
  for (const tool of activeTools) {
48
- const shortName = getShortName(tool);
49
61
  for (const [suffix, key] of TOOL_SUFFIXES) {
50
- if (shortName === suffix) {
62
+ if (matchesSuffix(tool, suffix)) {
51
63
  tools[key] = true;
52
64
  break;
53
65
  }
@@ -67,9 +67,11 @@ export function registerContextModeHooks(pi: ExtensionAPI, config: SupipowersCon
67
67
 
68
68
  // Phase 1: Tool routing — block native tools and redirect to ctx_* equivalents
69
69
  pi.on("tool_call", (event) => {
70
- if (!cachedStatus) cachedStatus = detectContextMode(pi.getActiveTools());
70
+ // Always re-detect: MCP tools may load after extension init
71
+ const status = detectContextMode(pi.getActiveTools());
72
+ cachedStatus = status;
71
73
 
72
- return routeToolCall(event.toolName, event.input as any, cachedStatus, {
74
+ return routeToolCall(event.toolName, event.input as any, status, {
73
75
  enforceRouting: config.contextMode.enforceRouting,
74
76
  blockHttpCommands: config.contextMode.blockHttpCommands,
75
77
  });
@@ -73,6 +73,28 @@ export function routeToolCall(
73
73
  };
74
74
  }
75
75
 
76
+ // Find/Glob → block, redirect to ctx_execute or ctx_batch_execute
77
+ if (options.enforceRouting && toolName === "find") {
78
+ if (!status.tools.ctxExecute) return undefined;
79
+ return {
80
+ block: true,
81
+ reason:
82
+ 'Use ctx_execute(language: "shell", code: "find ...") or ctx_batch_execute instead of Find/Glob. ' +
83
+ "Results are indexed and compressed to save context window.",
84
+ };
85
+ }
86
+
87
+ // Fetch/WebFetch → block, redirect to ctx_fetch_and_index
88
+ if (toolName === "fetch" || toolName === "web_fetch") {
89
+ if (!status.tools.ctxFetchAndIndex) return undefined;
90
+ return {
91
+ block: true,
92
+ reason:
93
+ "Use ctx_fetch_and_index instead of Fetch/WebFetch. " +
94
+ "It fetches the URL, indexes the content, and returns a compressed summary.",
95
+ };
96
+ }
97
+
76
98
  // Read (full-file, no limit/offset) → block, redirect to ctx_execute_file
77
99
  if (options.enforceRouting && toolName === "read") {
78
100
  if (!status.tools.ctxExecuteFile) return undefined;