supipowers 1.0.0 → 1.0.1

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.ts CHANGED
@@ -186,65 +186,139 @@ function installToPlatform(platformDir: string, packageRoot: string): string {
186
186
  }
187
187
 
188
188
  /**
189
- * Register context-mode MCP server in the given platform's mcp.json.
189
+ * Install context-mode as a platform extension and register MCP server.
190
+ *
191
+ * Per upstream docs (Pi Coding Agent):
192
+ * 1. git clone → ~/<platformDir>/extensions/context-mode
193
+ * 2. npm install && npm run build
194
+ * 3. Register MCP in ~/<platformDir>/settings/mcp.json
195
+ *
196
+ * Build requires Node.js 18+ (tsc, esbuild, node -e in build script).
197
+ * Runtime uses bun to leverage bun:sqlite — context-mode auto-detects
198
+ * Bun and skips better-sqlite3 entirely.
190
199
  */
191
- function registerContextMode(platformDir: string, extDir: string): void {
192
- const ctxSpinner = spinner();
193
- ctxSpinner.start(`Checking for context-mode (${platformDir})...`);
194
-
195
- // Find context-mode installation (Claude Code plugin cache)
196
- const ctxCacheBase = join(
197
- homedir(),
198
- ".claude",
199
- "plugins",
200
- "cache",
201
- "context-mode",
202
- "context-mode",
203
- );
204
- let ctxInstallPath: string | null = null;
205
- if (existsSync(ctxCacheBase)) {
206
- const versions = readdirSync(ctxCacheBase, { withFileTypes: true })
207
- .filter((d) => d.isDirectory())
208
- .map((d) => d.name)
209
- .sort()
210
- .reverse();
211
- if (versions.length > 0) {
212
- const candidate = join(ctxCacheBase, versions[0], "start.mjs");
213
- if (existsSync(candidate)) {
214
- ctxInstallPath = join(ctxCacheBase, versions[0]);
215
- }
216
- }
200
+ async function installContextMode(platformDir: string): Promise<void> {
201
+ const extDir = join(homedir(), platformDir, "extensions", "context-mode");
202
+ const startMjs = join(extDir, "node_modules", "context-mode", "start.mjs");
203
+
204
+ // Check if already installed and built
205
+ if (existsSync(startMjs)) {
206
+ // Already installed — just ensure MCP registration is up to date
207
+ registerContextModeMcp(platformDir, startMjs);
208
+ return;
217
209
  }
218
210
 
219
- if (ctxInstallPath) {
220
- const mcpConfigPath = join(homedir(), platformDir, "agent", "mcp.json");
221
- let mcpConfig: { mcpServers: Record<string, unknown> } = { mcpServers: {} };
222
- if (existsSync(mcpConfigPath)) {
223
- try {
224
- mcpConfig = JSON.parse(readFileSync(mcpConfigPath, "utf8"));
225
- if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};
226
- } catch {
227
- mcpConfig = { mcpServers: {} };
228
- }
229
- }
211
+ const shouldInstall = await confirm({
212
+ message: `Install context-mode extension for context window protection? (${platformDir})`,
213
+ });
214
+ if (isCancel(shouldInstall) || !shouldInstall) {
215
+ note(
216
+ `Skipped. You can install later:\n` +
217
+ ` git clone https://github.com/mksglu/context-mode.git ~/${platformDir}/extensions/context-mode\n` +
218
+ ` cd ~/${platformDir}/extensions/context-mode && npm install && npm run build`,
219
+ `context-mode (${platformDir})`,
220
+ );
221
+ return;
222
+ }
230
223
 
231
- const startMjs = join(ctxInstallPath, "start.mjs");
232
- // Use our wrapper script that captures cwd as CLAUDE_PROJECT_DIR
233
- // before context-mode's start.mjs clobbers it with process.chdir(__dirname)
234
- const wrapperMjs = join(extDir, "bin", "ctx-mode-wrapper.mjs");
235
- mcpConfig.mcpServers["context-mode"] = {
236
- command: "node",
237
- args: [wrapperMjs, startMjs],
238
- };
239
-
240
- mkdirSync(join(homedir(), platformDir, "agent"), { recursive: true });
241
- writeFileSync(mcpConfigPath, JSON.stringify(mcpConfig, null, 2));
242
- ctxSpinner.stop(`context-mode registered in ~/${platformDir}/agent/mcp.json`);
243
- } else {
244
- ctxSpinner.stop(
245
- `context-mode not found install it as a Claude Code plugin for context window protection (${platformDir})`,
224
+ // Check Node.js 18+ (required for build: tsc, esbuild, node -e in build script)
225
+ const nodeCheck = run("node", ["--version"]);
226
+ if (nodeCheck.error || nodeCheck.status !== 0) {
227
+ note(
228
+ "Node.js 18+ is required to build context-mode.\n" +
229
+ "Install from https://nodejs.org then re-run the installer.",
230
+ "context-mode requires Node.js",
231
+ );
232
+ return;
233
+ }
234
+ const nodeVersion = parseInt((nodeCheck.stdout ?? "").replace(/^v/, ""), 10);
235
+ if (nodeVersion < 18) {
236
+ note(
237
+ `Found Node.js v${nodeCheck.stdout?.trim()} but context-mode requires v18+.\n` +
238
+ "Update Node.js from https://nodejs.org then re-run the installer.",
239
+ "context-mode requires Node.js 18+",
240
+ );
241
+ return;
242
+ }
243
+
244
+ const s = spinner();
245
+ s.start(`Cloning context-mode to ~/${platformDir}/extensions/context-mode...`);
246
+
247
+ // Clone
248
+ const cloneResult = run("git", [
249
+ "clone",
250
+ "https://github.com/mksglu/context-mode.git",
251
+ extDir,
252
+ ]);
253
+ if (cloneResult.status !== 0) {
254
+ s.stop(`Failed to clone context-mode`);
255
+ note(
256
+ cloneResult.stderr?.trim() || "Unknown git clone error",
257
+ "context-mode install failed",
258
+ );
259
+ return;
260
+ }
261
+
262
+ // npm install (builds better-sqlite3 native bindings for Node.js fallback;
263
+ // at runtime under Bun, bun:sqlite is used instead via auto-detection)
264
+ s.message("Installing context-mode dependencies...");
265
+ const npmInstall = run("npm", ["install"], { cwd: extDir });
266
+ if (npmInstall.status !== 0) {
267
+ s.stop(`Failed to install context-mode dependencies`);
268
+ note(
269
+ npmInstall.stderr?.trim() || "Unknown npm install error",
270
+ "context-mode install failed",
271
+ );
272
+ return;
273
+ }
274
+
275
+ // npm run build (requires tsc + esbuild from devDeps, runs under Node.js)
276
+ s.message("Building context-mode...");
277
+ const npmBuild = run("npm", ["run", "build"], { cwd: extDir });
278
+ if (npmBuild.status !== 0) {
279
+ s.stop(`Failed to build context-mode`);
280
+ note(
281
+ npmBuild.stderr?.trim() || "Unknown build error",
282
+ "context-mode install failed",
246
283
  );
284
+ return;
285
+ }
286
+
287
+ s.stop(`context-mode installed to ~/${platformDir}/extensions/context-mode`);
288
+
289
+ // Register MCP server
290
+ registerContextModeMcp(platformDir, startMjs);
291
+ }
292
+
293
+ /**
294
+ * Register context-mode MCP entry in the platform's settings/mcp.json.
295
+ *
296
+ * Uses "bun" as the command so context-mode auto-detects Bun runtime
297
+ * and uses bun:sqlite — no better-sqlite3 native bindings needed at runtime.
298
+ */
299
+ function registerContextModeMcp(platformDir: string, startMjs: string): void {
300
+ const mcpConfigPath = join(homedir(), platformDir, "settings", "mcp.json");
301
+ let mcpConfig: { mcpServers: Record<string, unknown> } = { mcpServers: {} };
302
+ if (existsSync(mcpConfigPath)) {
303
+ try {
304
+ mcpConfig = JSON.parse(readFileSync(mcpConfigPath, "utf8"));
305
+ if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};
306
+ } catch {
307
+ mcpConfig = { mcpServers: {} };
308
+ }
247
309
  }
310
+
311
+ mcpConfig.mcpServers["context-mode"] = {
312
+ command: "bun",
313
+ args: [startMjs],
314
+ };
315
+
316
+ mkdirSync(dirname(mcpConfigPath), { recursive: true });
317
+ writeFileSync(mcpConfigPath, JSON.stringify(mcpConfig, null, 2));
318
+ note(
319
+ `Registered in ~/${platformDir}/settings/mcp.json`,
320
+ `context-mode (${platformDir})`,
321
+ );
248
322
  }
249
323
 
250
324
  // ── Main ─────────────────────────────────────────────────────
@@ -332,10 +406,10 @@ async function main(): Promise<void> {
332
406
  const packageRoot = resolve(__dirname, "..");
333
407
 
334
408
  for (const target of targets) {
335
- const extDir = installToPlatform(target.dir, packageRoot);
409
+ installToPlatform(target.dir, packageRoot);
336
410
 
337
- // ── Step 3b: Register context-mode MCP server ───────────
338
- registerContextMode(target.dir, extDir);
411
+ // ── Step 3b: Install context-mode extension + register MCP ──
412
+ await installContextMode(target.dir);
339
413
  }
340
414
 
341
415
  // ── Step 4: Unified dependency check (--skip-deps to skip) ──
@@ -365,21 +439,33 @@ async function main(): Promise<void> {
365
439
  );
366
440
  }
367
441
 
368
- // Offer to install missing deps that have an installCmd
442
+ // Offer to install missing deps let user choose which ones
369
443
  const installable = statuses.filter(
370
444
  (s) => !s.installed && s.installCmd !== null,
371
445
  );
372
446
 
373
447
  if (installable.length > 0) {
374
- const depList = installable
375
- .map((s) => ` • ${s.name} (${s.installCmd})`)
376
- .join("\n");
377
- const shouldInstall = await confirm({
378
- message: `Install ${installable.length} missing tool(s)?\n${depList}`,
448
+ const categoryLabels: Record<string, string> = {
449
+ core: "Core",
450
+ mcp: "MCP",
451
+ lsp: "Language Server",
452
+ };
453
+
454
+ const selected = await multiselect({
455
+ message: "Select tools to install (space to toggle, enter to confirm):",
456
+ options: installable.map((s) => ({
457
+ value: s.name,
458
+ label: s.name,
459
+ hint: `${categoryLabels[s.category] ?? s.category} — ${s.description}`,
460
+ })),
461
+ required: false,
379
462
  });
380
463
 
381
- if (!isCancel(shouldInstall) && shouldInstall) {
382
- for (const dep of installable) {
464
+ if (!isCancel(selected) && (selected as string[]).length > 0) {
465
+ const toInstall = installable.filter((s) =>
466
+ (selected as string[]).includes(s.name),
467
+ );
468
+ for (const dep of toInstall) {
383
469
  const s = spinner();
384
470
  s.start(`Installing ${dep.name}...`);
385
471
  const result = await installDep(exec, dep.name);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "supipowers",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Workflow extension for Pi and OMP coding agents.",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -97,9 +97,21 @@ export const DEPENDENCIES: Dependency[] = [
97
97
  required: false,
98
98
  category: "mcp",
99
99
  description: "Context-mode MCP server for context window protection",
100
- checkFn: (exec) => checkBinary(exec, "context-mode"),
101
- installCmd: "npm install -g context-mode",
102
- url: "https://github.com/context-mode/context-mode",
100
+ checkFn: async (_exec) => {
101
+ // context-mode is installed as a platform extension, not globally.
102
+ // Check for start.mjs in the standard extension locations.
103
+ const { existsSync } = await import("node:fs");
104
+ const { join } = await import("node:path");
105
+ const { homedir } = await import("node:os");
106
+ const home = homedir();
107
+ for (const dir of [".pi", ".omp"]) {
108
+ const startMjs = join(home, dir, "extensions", "context-mode", "node_modules", "context-mode", "start.mjs");
109
+ if (existsSync(startMjs)) return { installed: true, version: "extension" };
110
+ }
111
+ return { installed: false };
112
+ },
113
+ installCmd: null, // Handled by installer (git clone + npm install + npm run build)
114
+ url: "https://github.com/mksglu/context-mode",
103
115
  },
104
116
  {
105
117
  name: "TypeScript LSP",