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 +150 -64
- package/package.json +1 -1
- package/src/deps/registry.ts +15 -3
package/bin/install.ts
CHANGED
|
@@ -186,65 +186,139 @@ function installToPlatform(platformDir: string, packageRoot: string): string {
|
|
|
186
186
|
}
|
|
187
187
|
|
|
188
188
|
/**
|
|
189
|
-
*
|
|
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
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
//
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
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
|
-
|
|
409
|
+
installToPlatform(target.dir, packageRoot);
|
|
336
410
|
|
|
337
|
-
// ── Step 3b:
|
|
338
|
-
|
|
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
|
|
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
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
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(
|
|
382
|
-
|
|
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
package/src/deps/registry.ts
CHANGED
|
@@ -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: (
|
|
101
|
-
|
|
102
|
-
|
|
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",
|