supipowers 1.2.3 → 1.2.5

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
@@ -5,7 +5,11 @@ import { dirname, join } from "node:path";
5
5
 
6
6
  const isWindows = process.platform === "win32";
7
7
  const __dirname = dirname(fileURLToPath(import.meta.url));
8
- const result = spawnSync("bun", [join(__dirname, "install.ts"), ...process.argv.slice(2)], {
8
+ // When invoked via bunx/npx, always force a full install to guarantee
9
+ // node_modules/ and a consistent extension directory. The --force flag
10
+ // bypasses the "already up to date" version check.
11
+ const args = [join(__dirname, "install.ts"), "--force", ...process.argv.slice(2)];
12
+ const result = spawnSync("bun", args, {
9
13
  stdio: "inherit",
10
14
  env: process.env,
11
15
  shell: isWindows,
package/bin/install.ts CHANGED
@@ -14,6 +14,7 @@ import { spawnSync } from "node:child_process";
14
14
  import {
15
15
  readFileSync,
16
16
  writeFileSync,
17
+ appendFileSync,
17
18
  existsSync,
18
19
  mkdirSync,
19
20
  cpSync,
@@ -123,10 +124,37 @@ async function exec(cmd: string, args: string[]): Promise<ExecResult> {
123
124
  return { stdout: r.stdout ?? "", stderr: r.stderr ?? "", code: r.status ?? 1 };
124
125
  }
125
126
 
126
- // ── CLI Flags ────────────────────────────────────────────────
127
+ // ── CLI Flags ────────────────────────────────────────────────────
127
128
 
128
129
  const cliArgs = process.argv.slice(2);
129
130
  const skipDeps = cliArgs.includes("--skip-deps");
131
+ const FORCE = cliArgs.includes("--force");
132
+ const DEBUG = cliArgs.includes("--debug");
133
+
134
+ // ── Debug logging ────────────────────────────────────────────────
135
+
136
+ const LOG_FILE = resolve(process.cwd(), "supipowers-install.log");
137
+
138
+ function log(msg: string): void {
139
+ if (!DEBUG) return;
140
+ const line = `[${new Date().toISOString()}] ${msg}\n`;
141
+ appendFileSync(LOG_FILE, line);
142
+ }
143
+
144
+ if (DEBUG) {
145
+ // Start fresh log
146
+ writeFileSync(LOG_FILE, `supipowers installer debug log\n`);
147
+ log(`platform: ${process.platform}`);
148
+ log(`arch: ${process.arch}`);
149
+ log(`bun: ${process.versions?.bun ?? "N/A"}`);
150
+ log(`node: ${process.version}`);
151
+ log(`cwd: ${process.cwd()}`);
152
+ log(`homedir: ${homedir()}`);
153
+ log(`argv: ${JSON.stringify(process.argv)}`);
154
+ log(`__dirname: ${__dirname}`);
155
+ log(`packageRoot will be: ${resolve(__dirname, "..")}`);
156
+ log(`isWindows: ${isWindows}`);
157
+ }
130
158
 
131
159
  // ── Install to platform ──────────────────────────────────────
132
160
 
@@ -143,18 +171,29 @@ function installToPlatform(platformDir: string, packageRoot: string): string {
143
171
  const extDir = join(agentDir, "extensions", "supipowers");
144
172
  const installedPkgPath = join(extDir, "package.json");
145
173
 
174
+ log(`installToPlatform(platformDir=${platformDir}, packageRoot=${packageRoot})`);
175
+ log(` agentDir: ${agentDir}`);
176
+ log(` extDir: ${extDir}`);
177
+
146
178
  // Check for existing installation
147
179
  let installedVersion: string | null = null;
148
180
  if (existsSync(installedPkgPath)) {
149
181
  try {
150
182
  const installed = JSON.parse(readFileSync(installedPkgPath, "utf8"));
151
183
  installedVersion = installed.version;
184
+ log(` existing version: ${installedVersion}`);
152
185
  } catch {
153
- // corrupted package.json treat as not installed
186
+ log(` existing package.json corrupted, treating as fresh install`);
154
187
  }
188
+ } else {
189
+ log(` no existing installation found`);
155
190
  }
156
191
 
157
- if (installedVersion === VERSION) {
192
+ const hasNodeModules = existsSync(join(extDir, "node_modules"));
193
+ log(` node_modules present: ${hasNodeModules}`);
194
+
195
+ if (installedVersion === VERSION && hasNodeModules && !FORCE) {
196
+ log(` already up to date with deps, skipping`);
158
197
  note(
159
198
  `supipowers v${VERSION} is already installed and up to date.`,
160
199
  `Up to date (${platformDir})`,
@@ -162,9 +201,16 @@ function installToPlatform(platformDir: string, packageRoot: string): string {
162
201
  return extDir;
163
202
  }
164
203
 
204
+ if (installedVersion === VERSION && !hasNodeModules) {
205
+ log(` same version but node_modules missing — reinstalling deps`);
206
+ }
207
+ if (FORCE) {
208
+ log(` --force flag set, reinstalling`);
209
+ }
210
+
165
211
  const action = installedVersion ? "Updating" : "Installing";
166
212
  if (installedVersion) {
167
- note(`v${installedVersion} v${VERSION}`, `Updating supipowers (${platformDir})`);
213
+ note(`v${installedVersion} \u2192 v${VERSION}`, `Updating supipowers (${platformDir})`);
168
214
  }
169
215
 
170
216
  const s = spinner();
@@ -173,14 +219,40 @@ function installToPlatform(platformDir: string, packageRoot: string): string {
173
219
  try {
174
220
  // Clean previous installation to remove stale files
175
221
  if (existsSync(extDir)) {
222
+ log(` removing old extDir`);
176
223
  rmSync(extDir, { recursive: true });
177
224
  }
178
225
 
179
- // Copy extension (src/ + bin/ + package.json) ~/<platform>/agent/extensions/supipowers/
226
+ // Copy extension (src/ + bin/ + package.json) \u2192 ~/<platform>/agent/extensions/supipowers/
227
+ log(` creating extDir and copying files`);
180
228
  mkdirSync(extDir, { recursive: true });
181
229
  cpSync(join(packageRoot, "src"), join(extDir, "src"), { recursive: true });
182
230
  cpSync(join(packageRoot, "bin"), join(extDir, "bin"), { recursive: true });
183
231
  cpSync(join(packageRoot, "package.json"), join(extDir, "package.json"));
232
+ log(` files copied to ${extDir}`);
233
+
234
+ // Rewrite package.json for the installed extension.
235
+ // The npm-published package.json has bin, scripts, prepare, devDeps —
236
+ // all of which cause problems during `bun install` in the extension dir.
237
+ // We keep only what OMP needs (omp.extensions) and the runtime dependencies.
238
+ const sourcePkg = JSON.parse(readFileSync(join(extDir, "package.json"), "utf8"));
239
+ const runtimePkg = {
240
+ name: sourcePkg.name,
241
+ version: sourcePkg.version,
242
+ type: sourcePkg.type,
243
+ omp: sourcePkg.omp,
244
+ dependencies: {
245
+ // Only packages imported at runtime by src/ code:
246
+ // - config/schema.ts → @sinclair/typebox
247
+ // - commands/model.ts, model-picker.ts → @oh-my-pi/pi-ai
248
+ // - commands/model-picker.ts → @oh-my-pi/pi-tui
249
+ "@sinclair/typebox": "*",
250
+ "@oh-my-pi/pi-ai": "*",
251
+ "@oh-my-pi/pi-tui": "*",
252
+ },
253
+ };
254
+ writeFileSync(join(extDir, "package.json"), JSON.stringify(runtimePkg, null, 2));
255
+ log(` rewrote package.json: ${JSON.stringify(runtimePkg, null, 2)}`);
184
256
 
185
257
  // Copy skills → ~/<platform>/agent/skills/<skillname>/SKILL.md
186
258
  const skillsSource = join(packageRoot, "skills");
@@ -196,12 +268,44 @@ function installToPlatform(platformDir: string, packageRoot: string): string {
196
268
  }
197
269
  }
198
270
 
271
+ // Install runtime dependencies so the extension's imports resolve.
272
+ // Without node_modules/, external imports (@sinclair/typebox, @oh-my-pi/*)
273
+ // fail on systems where these packages aren't in Bun's global install.
274
+ log(` running: bun install (cwd=${extDir})`);
275
+ s.message("Installing extension dependencies...");
276
+ const install = run("bun", ["install"], { cwd: extDir });
277
+ log(` bun install exit code: ${install.status}`);
278
+ log(` bun install stdout: ${install.stdout ?? "(null)"}`);
279
+ log(` bun install stderr: ${install.stderr ?? "(null)"}`);
280
+ if (install.error) log(` bun install error: ${install.error.message}`);
281
+ if (install.status !== 0) {
282
+ // Non-fatal: the extension may still work if OMP provides the deps
283
+ // via its own module resolution (e.g. Bun global install on macOS).
284
+ note(
285
+ "Could not install extension dependencies.\n" +
286
+ "If /supi commands don't appear in OMP, run:\n" +
287
+ ` cd ~/${platformDir}/agent/extensions/supipowers && bun install`,
288
+ "Warning",
289
+ );
290
+ }
291
+
292
+ // Verify node_modules was created
293
+ const nmExists = existsSync(join(extDir, "node_modules"));
294
+ log(` node_modules exists after install: ${nmExists}`);
295
+ if (nmExists) {
296
+ try {
297
+ const nmContents = readdirSync(join(extDir, "node_modules"));
298
+ log(` node_modules top-level: ${nmContents.join(", ")}`);
299
+ } catch { /* ignore */ }
300
+ }
301
+
199
302
  s.stop(
200
303
  installedVersion
201
304
  ? `supipowers updated to v${VERSION} (${platformDir})`
202
305
  : `supipowers v${VERSION} installed (${platformDir})`,
203
306
  );
204
307
  } catch (err: unknown) {
308
+ log(` installToPlatform FAILED: ${err instanceof Error ? err.stack : String(err)}`);
205
309
  s.stop(`${action} failed (${platformDir})`);
206
310
  const message = err instanceof Error ? err.message : `Failed to copy files to ~/${platformDir}/agent/`;
207
311
  bail(message);
@@ -358,9 +462,14 @@ async function main(): Promise<void> {
358
462
  const piBin = findPiBinary();
359
463
  const ompBin = findOmpBinary();
360
464
 
465
+ log(`findPiBinary() => ${piBin ?? "null"}`);
466
+ log(`findOmpBinary() => ${ompBin ?? "null"}`);
467
+
361
468
  const piVer = piBin ? run(piBin, ["--version"]).stdout?.trim() || "unknown" : null;
362
469
  const ompVer = ompBin ? run(ompBin, ["--version"]).stdout?.trim() || "unknown" : null;
363
470
 
471
+ log(`piVer: ${piVer ?? "N/A"}, ompVer: ${ompVer ?? "N/A"}`);
472
+
364
473
  const detected: string[] = [];
365
474
  if (piBin) detected.push(`Pi ${piVer}`);
366
475
  if (ompBin) detected.push(`OMP ${ompVer}`);
@@ -429,6 +538,8 @@ async function main(): Promise<void> {
429
538
  // ── Step 3: Install supipowers to each chosen target ──────
430
539
 
431
540
  const packageRoot = resolve(__dirname, "..");
541
+ log(`packageRoot: ${packageRoot}`);
542
+ log(`targets: ${JSON.stringify(targets)}`);
432
543
 
433
544
  for (const target of targets) {
434
545
  installToPlatform(target.dir, packageRoot);
@@ -437,6 +548,10 @@ async function main(): Promise<void> {
437
548
  await installContextMode(target.dir);
438
549
  }
439
550
 
551
+ if (DEBUG) {
552
+ note(`Debug log written to:\n${LOG_FILE}`, "Debug");
553
+ }
554
+
440
555
  // ── Step 4: Unified dependency check (--skip-deps to skip) ──
441
556
 
442
557
  if (skipDeps) {
@@ -2,38 +2,42 @@
2
2
  # Install supipowers locally from the current working tree.
3
3
  # Usage: ./bin/local-install.sh
4
4
  #
5
- # This creates a global symlink so both the `supipowers` CLI and
6
- # the Pi/OMP extension resolve to your local source no publish needed.
7
- # Re-run after pulling new changes; the symlink stays valid.
5
+ # Creates a global symlink so `supipowers` CLI works, then runs the
6
+ # installer with --debug to deploy the extension and write a log file.
7
+ # Re-run after pulling new changes.
8
8
 
9
9
  set -euo pipefail
10
10
 
11
11
  SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
12
12
  PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
13
13
 
14
- echo " Installing supipowers locally from $PROJECT_DIR"
14
+ echo "-> Installing supipowers locally from $PROJECT_DIR"
15
15
 
16
16
  # 1. Install dependencies (fast no-op when lock is current)
17
- echo " Installing dependencies"
17
+ echo "-> Installing dependencies..."
18
18
  cd "$PROJECT_DIR"
19
19
  bun install --frozen-lockfile 2>/dev/null || bun install
20
20
 
21
21
  # 2. Create a global symlink via bun link
22
- # This registers the package globally so `supipowers` CLI works
23
- # and Pi/OMP can resolve it by name.
24
- echo "→ Linking supipowers globally…"
22
+ echo "-> Linking supipowers globally..."
25
23
  bun link
26
24
 
27
25
  # 3. Verify the link
28
26
  if command -v supipowers &>/dev/null; then
29
- echo " 'supipowers' CLI is available at $(which supipowers)"
27
+ echo "[OK] 'supipowers' CLI is available at $(which supipowers)"
30
28
  else
31
- echo " CLI not on PATH you may need to add bun's global bin to \$PATH:"
29
+ echo "[WARN] CLI not on PATH -- you may need to add bun's global bin to \$PATH:"
32
30
  echo " export PATH=\"\$HOME/.bun/bin:\$PATH\""
33
31
  fi
34
32
 
35
- # 4. Show version
33
+ # 4. Run the installer with --debug to deploy extension + write log
34
+ echo "-> Running installer (--debug mode)..."
35
+ bun run "$PROJECT_DIR/bin/install.ts" --debug --force
36
+
37
+ # 5. Show version and log location
36
38
  VERSION=$(node -e "console.log(require('$PROJECT_DIR/package.json').version)")
37
39
  echo ""
38
- echo " supipowers v${VERSION} installed locally (linked to $PROJECT_DIR)"
39
- echo " Any edits to src/ or skills/ take effect immediately — no rebuild needed."
40
+ echo "[OK] supipowers v${VERSION} installed locally"
41
+ if [ -f "$PROJECT_DIR/supipowers-install.log" ]; then
42
+ echo " Debug log: $PROJECT_DIR/supipowers-install.log"
43
+ fi
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "supipowers",
3
- "version": "1.2.3",
3
+ "version": "1.2.5",
4
4
  "description": "Workflow extension for OMP coding agents.",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -43,17 +43,26 @@
43
43
  },
44
44
  "peerDependencies": {
45
45
  "@oh-my-pi/pi-coding-agent": "*",
46
+ "@oh-my-pi/pi-ai": "*",
47
+ "@oh-my-pi/pi-tui": "*",
46
48
  "@sinclair/typebox": "*"
47
49
  },
48
50
  "peerDependenciesMeta": {
49
51
  "@oh-my-pi/pi-coding-agent": {
50
52
  "optional": true
51
53
  },
54
+ "@oh-my-pi/pi-ai": {
55
+ "optional": true
56
+ },
52
57
  "@oh-my-pi/pi-tui": {
53
58
  "optional": true
59
+ },
60
+ "@sinclair/typebox": {
61
+ "optional": true
54
62
  }
55
63
  },
56
64
  "devDependencies": {
65
+ "@oh-my-pi/pi-ai": "latest",
57
66
  "@oh-my-pi/pi-coding-agent": "latest",
58
67
  "@oh-my-pi/pi-tui": "latest",
59
68
  "@sinclair/typebox": "^0.34.48",
@@ -34,10 +34,10 @@ export async function checkBinary(
34
34
  exec: ExecFn,
35
35
  binary: string,
36
36
  ): Promise<{ installed: boolean; version?: string }> {
37
- // `which` is Unix-only; Windows uses `where` to locate executables
38
- const whichCmd = process.platform === "win32" ? "where" : "which";
39
- const which = await exec(whichCmd, [binary]);
40
- if (which.code !== 0) return { installed: false };
37
+ // Bun.which() is cross-platform (handles .cmd/.exe/.bat on Windows)
38
+ // and doesn't require shelling out to `which` (Unix) or `where` (Windows).
39
+ const found = Bun.which(binary);
40
+ if (!found) return { installed: false };
41
41
 
42
42
  const ver = await exec(binary, ["--version"]);
43
43
  const version = ver.code === 0 ? ver.stdout.trim().split("\n")[0] : undefined;