diff options
| author | user <user@clank> | 2026-07-22 20:33:39 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-08-12 19:34:35 +0200 |
| commit | 1e34da3f4819ecc3d460e52a3a022d6f195ebb96 (patch) | |
| tree | 017358f75feace52133ad2805ae7a93f787d0fab /pi-ext | |
| parent | slop-tool-boxes: pulse the border of in-progress (pending) tools (diff) | |
| download | sl0ppi-1e34da3f4819ecc3d460e52a3a022d6f195ebb96.tar.gz sl0ppi-1e34da3f4819ecc3d460e52a3a022d6f195ebb96.tar.xz sl0ppi-1e34da3f4819ecc3d460e52a3a022d6f195ebb96.zip | |
dev.sh: also load globally-installed pi packages (subagents, goal, ...)
-ne disables package extension discovery, so pi-subagents' subagent tool never
loaded under dev.sh. Add scripts/resolve-package-extensions.mjs to resolve each
settings.json package's extension entry (file/index-dir/discovery-dir, mirroring
pi's loader) and re-add them via -e. Package skills/prompts already load under
-ne. Opt out per package with PI_DEV_SKIP_PACKAGES.
Diffstat (limited to 'pi-ext')
| -rwxr-xr-x | pi-ext/dev.sh | 11 | ||||
| -rwxr-xr-x | pi-ext/scripts/resolve-package-extensions.mjs | 106 |
2 files changed, 117 insertions, 0 deletions
diff --git a/pi-ext/dev.sh b/pi-ext/dev.sh index dddc026..08ff321 100755 --- a/pi-ext/dev.sh +++ b/pi-ext/dev.sh @@ -22,4 +22,15 @@ if [ "${#args[@]}" -eq 1 ]; then exit 1 fi +# -ne disables package discovery too, so globally-installed pi packages +# (pi-subagents, pi-goal, pi-autoresearch, ...) wouldn't load their tools. Re-add +# each package's extension entry via -e so they work alongside our dev copies. +# Their skills/prompts still load (separate -ns/-np gates, which we don't pass). +# Skip some with e.g. PI_DEV_SKIP_PACKAGES="pi-goal,pi-autoresearch". +if command -v node >/dev/null 2>&1; then + while IFS= read -r extpath; do + [ -n "$extpath" ] && args+=(-e "$extpath") + done < <(node "$(dirname "$0")/scripts/resolve-package-extensions.mjs" 2>/dev/null || true) +fi + exec pi "${args[@]}" "$@" diff --git a/pi-ext/scripts/resolve-package-extensions.mjs b/pi-ext/scripts/resolve-package-extensions.mjs new file mode 100755 index 0000000..4ba070d --- /dev/null +++ b/pi-ext/scripts/resolve-package-extensions.mjs @@ -0,0 +1,106 @@ +#!/usr/bin/env node +/** + * Print absolute extension entry files for the globally-installed pi packages + * listed in ~/.pi/agent/settings.json `packages`, one per line. + * + * dev.sh runs pi with -ne (--no-extensions), which disables ALL extension + * discovery — including npm packages like pi-subagents — so their tools/commands + * don't load. (Package *skills* and *prompts* still load, since those use the + * separate -ns/-np gates.) This resolver lets dev.sh re-add just the package + * extension modules via -e, without also pulling in the global-dir copies of our + * own in-development extensions (which we deliberately isolate). + * + * Skip specific packages with PI_DEV_SKIP_PACKAGES="pi-goal,pi-autoresearch". + * + * Resolution mirrors pi's loader: a package.json "pi.extensions" entry may point + * at a file, an index.ts/js directory, or a discovery directory of *.ts/*.js + * files and index-bearing subdirs. + */ +import { readFileSync, existsSync, statSync, readdirSync } from "node:fs"; +import { join, resolve } from "node:path"; +import { homedir } from "node:os"; + +const AGENT = join(homedir(), ".pi", "agent"); +const NODE_MODULES = join(AGENT, "npm", "node_modules"); +const SETTINGS = join(AGENT, "settings.json"); + +const readJSON = (p) => { + try { + return JSON.parse(readFileSync(p, "utf8")); + } catch { + return null; + } +}; + +const isExtFile = (n) => n.endsWith(".ts") || n.endsWith(".js"); +const out = new Set(); + +function piField(pkg) { + if (!pkg) return null; + // Extensions live under "pi.extensions" (a couple of packages nest under a + // key matching their own name); we only need the extensions array. + return pkg.pi ?? null; +} + +function addDir(dir) { + const field = piField(readJSON(join(dir, "package.json"))); + if (field && Array.isArray(field.extensions)) { + for (const e of field.extensions) addEntry(dir, e); + return; + } + for (const idx of ["index.ts", "index.js"]) { + if (existsSync(join(dir, idx))) { + out.add(join(dir, idx)); + return; + } + } + // Discovery directory: direct *.ts/*.js files + index-bearing subdirs. + let entries = []; + try { + entries = readdirSync(dir, { withFileTypes: true }); + } catch { + return; + } + for (const ent of entries) { + const p = join(dir, ent.name); + if ((ent.isFile() || ent.isSymbolicLink()) && isExtFile(ent.name)) { + out.add(p); + } else if (ent.isDirectory()) { + for (const idx of ["index.ts", "index.js"]) { + if (existsSync(join(p, idx))) { + out.add(join(p, idx)); + break; + } + } + } + } +} + +function addEntry(base, entry) { + const abs = resolve(base, entry); + if (!existsSync(abs)) return; + if (statSync(abs).isDirectory()) addDir(abs); + else if (isExtFile(abs)) out.add(abs); +} + +const settings = readJSON(SETTINGS) ?? {}; +const packages = Array.isArray(settings.packages) ? settings.packages : []; +const skip = new Set( + (process.env.PI_DEV_SKIP_PACKAGES ?? "") + .split(",") + .map((s) => s.trim()) + .filter(Boolean), +); + +for (const spec of packages) { + if (!spec.startsWith("npm:")) continue; // git: packages resolve elsewhere + const name = spec.slice(4).replace(/@[^/@]+$/, ""); // strip trailing @version + if (skip.has(name)) continue; + const dir = join(NODE_MODULES, name); + if (!existsSync(dir)) continue; + const field = piField(readJSON(join(dir, "package.json"))); + const entries = field && Array.isArray(field.extensions) ? field.extensions : ["."]; + for (const e of entries) addEntry(dir, e); +} + +for (const p of out) console.log(p); |
