@lincoln504/pi-research 1.7.4 → 1.7.6
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/README.md +5 -5
- package/agent-skill/pi-research/SKILL.md +1 -1
- package/dist/cli-bin.mjs +122 -0
- package/dist/cli.mjs +44 -0
- package/docs/AGENT-SKILL.md +4 -1
- package/docs/ARCHITECTURE.md +1 -1
- package/docs/CHANGELOG.md +21 -0
- package/docs/CONFIGURATION.md +13 -0
- package/package.json +3 -2
- package/scripts/build.cjs +19 -0
- package/scripts/setup.cjs +39 -5
- package/src/cli-bin.mjs +122 -0
- package/src/cli.ts +75 -0
package/README.md
CHANGED
|
@@ -19,11 +19,11 @@
|
|
|
19
19
|
·
|
|
20
20
|
<a href="docs/ARCHITECTURE.md">Architecture</a>
|
|
21
21
|
·
|
|
22
|
-
<a href="docs/DOCUMENTATION-ES.md">ES</a>
|
|
22
|
+
<a href="https://github.com/Lincoln504/pi-research/blob/main/docs/DOCUMENTATION-ES.md">ES</a>
|
|
23
23
|
·
|
|
24
|
-
<a href="docs/DOCUMENTATION-ZH.md">中文研究</a>
|
|
24
|
+
<a href="https://github.com/Lincoln504/pi-research/blob/main/docs/DOCUMENTATION-ZH.md">中文研究</a>
|
|
25
25
|
·
|
|
26
|
-
<a href="docs/DOCUMENTATION-JA.md">日本語研究</a>
|
|
26
|
+
<a href="https://github.com/Lincoln504/pi-research/blob/main/docs/DOCUMENTATION-JA.md">日本語研究</a>
|
|
27
27
|
</p>
|
|
28
28
|
|
|
29
29
|
---
|
|
@@ -49,7 +49,7 @@ npm install -g @lincoln504/pi-research
|
|
|
49
49
|
pi-research skill install
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
-
No extra setup is needed. The package ships ready-made bindings for every platform and
|
|
52
|
+
No extra setup is needed. The package ships ready-made bindings for every platform and declares no build step, so there is nothing to approve or compile. The stealth browser (1GB+ installed) is fetched by the `postinstall` script when npm runs install scripts, which it does by default up to npm 11.18 — that is the bundled npm on the Node 22.22.2 floor this package requires, so expect the fetch during install. Newer npm (11.19+) skips install scripts by default, and the browser is then fetched lazily on the first scrape instead. Either way the first scrape takes a few minutes until it is in place. Set `PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1` to defer the fetch deliberately. Only Windows with npm older than 11.19 can fail when the install tries to compile from source. Upgrading npm fixes it.
|
|
53
53
|
|
|
54
54
|
The `@earendil-works/pi-*` host packages are **peer dependencies**. pi supplies its own copies when it loads this as an extension, and npm ≥7 installs them automatically for a standalone (global or project) install. An install that skips peers does not get them: `npm install --legacy-peer-deps`, yarn classic, and `pi install` itself (it passes that flag on purpose) are all in that group. The standalone engine and the agent-skill launcher then stop with a message naming the missing package; add it with:
|
|
55
55
|
|
|
@@ -63,7 +63,7 @@ In pi, the extension works out of the box on the session's model and pi's config
|
|
|
63
63
|
|
|
64
64
|
### Uninstall
|
|
65
65
|
|
|
66
|
-
`pi remove npm:@lincoln504/pi-research` removes the extension. `npm uninstall -g @lincoln504/pi-research` removes the standalone engine. npm 7+ no longer runs `preuninstall`, so nothing else is removed on its own: skill links into other agents (Claude Code, Codex, …), the state directory (`~/.pi/research/state`), and the cache (`~/.cache/pi-research`, including any downloaded embedding models) stay in place. Remove the skill links first with `pi-research skill uninstall` (or `/research-config` → Remove from External Agents)
|
|
66
|
+
`pi remove npm:@lincoln504/pi-research` removes the extension. `npm uninstall -g @lincoln504/pi-research` removes the standalone engine. npm 7+ no longer runs `preuninstall`, so nothing else is removed on its own: skill links into other agents (Claude Code, Codex, …), the state directory (`~/.pi/research/state`), and the cache (`~/.cache/pi-research`, including any downloaded embedding models) stay in place. Remove the skill links first with `pi-research skill uninstall` (or `/research-config` → Remove from External Agents), then remove the rest with `pi-research cleanup`: The shared stealth-browser cache (`~/.cache/camoufox`) is preserved unless `PI_RESEARCH_PURGE_BROWSERS=1`. See [AGENT-SKILL.md](docs/AGENT-SKILL.md#installation-flow) for the full picture.
|
|
67
67
|
|
|
68
68
|
### How it works
|
|
69
69
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: pi-research
|
|
3
3
|
description: "Multi-agent web research for any coding agent. Use for anything requiring current information from the internet — news, trends, product/service/company facts, statistics, people, places, events, APIs, library behavior, or 'what is X / how does X work' questions. Also use to search a local knowledge store of previously researched findings before doing live research. Powered by the pi-research engine (search, stealth scraping, security databases, Stack Exchange). Not for reading local files, running project commands, or analyzing this repository's own source."
|
|
4
4
|
license: MIT
|
|
5
|
-
metadata: { "author": "Lincoln Deen", "version": "1.7.
|
|
5
|
+
metadata: { "author": "Lincoln Deen", "version": "1.7.6", "package": "@lincoln504/pi-research", "engine": "pi-research-sdk", "openclaw": { "requires": { "bins": ["node"] } } }
|
|
6
6
|
allowed-tools: Bash(node:*)
|
|
7
7
|
---
|
|
8
8
|
|
package/dist/cli-bin.mjs
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* The installed `pi-research` bin.
|
|
4
|
+
*
|
|
5
|
+
* WHY THIS SHIM EXISTS INSTEAD OF POINTING `bin` STRAIGHT AT dist/cli.mjs
|
|
6
|
+
*
|
|
7
|
+
* dist/cli.mjs statically imports the `@earendil-works/pi-*` host packages, which
|
|
8
|
+
* are declared as PEER dependencies: pi supplies its own copies when it loads
|
|
9
|
+
* this package as an extension, and npm >= 7 resolves them automatically for a
|
|
10
|
+
* standalone (global or project) install. An install that skips peers leaves
|
|
11
|
+
* them absent — `npm install --legacy-peer-deps`, yarn classic, and a pnpm
|
|
12
|
+
* config with `auto-install-peers=false` all do that — and Node then dies at
|
|
13
|
+
* module load with a raw `ERR_MODULE_NOT_FOUND` stack before any of our code
|
|
14
|
+
* runs. The user sees an internal Node error instead of the one-line fix.
|
|
15
|
+
*
|
|
16
|
+
* The preflight cannot live inside the bundle: esbuild hoists the bundle's
|
|
17
|
+
* external `import` declarations to the very top of the output file, ahead of
|
|
18
|
+
* every statement in the program, so anything the bundle body does runs AFTER
|
|
19
|
+
* the failing import has already thrown. (Verified: a wrapper entry point whose
|
|
20
|
+
* first statement is the preflight still crashes, because the emitted file
|
|
21
|
+
* begins with `import "…/pi-ai";`.) It has to be a separate module, which is
|
|
22
|
+
* why this file is shipped unbundled and why `bin` points at it.
|
|
23
|
+
*
|
|
24
|
+
* The check uses `import.meta.resolve()`, NOT `createRequire().resolve()`: both
|
|
25
|
+
* host packages are ESM-only (`exports` maps with no `require` condition), so
|
|
26
|
+
* the CJS resolver fails on them with `ERR_PACKAGE_PATH_NOT_EXPORTED` even when
|
|
27
|
+
* they are correctly installed — a false negative that would break every
|
|
28
|
+
* healthy install. `import.meta.resolve()` uses the same ESM resolution the
|
|
29
|
+
* bundle itself uses, returns when the package is present, and throws
|
|
30
|
+
* `ERR_MODULE_NOT_FOUND` when it is not.
|
|
31
|
+
*
|
|
32
|
+
* Exit code 78 (EXIT.CONFIG) and the remedy text match the agent-skill
|
|
33
|
+
* launcher's `depMissing()` — one failure, one message, whichever entry point
|
|
34
|
+
* the user reached for.
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
import { existsSync } from 'node:fs';
|
|
38
|
+
import { fileURLToPath } from 'node:url';
|
|
39
|
+
|
|
40
|
+
const REQUIRED_PEERS = ['@earendil-works/pi-ai', '@earendil-works/pi-coding-agent'];
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Probe one peer dependency.
|
|
44
|
+
*
|
|
45
|
+
* 'ok' — resolves, and the file it resolves to exists
|
|
46
|
+
* 'missing' — does not resolve at all (the --legacy-peer-deps case)
|
|
47
|
+
* 'broken' — resolves (its package.json and exports map are readable) but the
|
|
48
|
+
* file those point at is not on disk
|
|
49
|
+
*
|
|
50
|
+
* The 'broken' case is checked with existsSync rather than by importing the
|
|
51
|
+
* module. Resolving is not loading: an interrupted install, a pruned
|
|
52
|
+
* node_modules or a bad package-cache restore leaves a package that resolves
|
|
53
|
+
* perfectly and then fails to load, which would pass this preflight and die
|
|
54
|
+
* inside cli.mjs with the same raw ERR_MODULE_NOT_FOUND this shim exists to
|
|
55
|
+
* replace. Importing it here instead would work, but it runs the host
|
|
56
|
+
* package's module side effects before the CLI proper has decided to start, so
|
|
57
|
+
* the cheap existence check is the right trade.
|
|
58
|
+
*
|
|
59
|
+
* @returns {'ok' | 'missing' | 'broken'}
|
|
60
|
+
*/
|
|
61
|
+
function probePeer(name) {
|
|
62
|
+
let resolved;
|
|
63
|
+
try {
|
|
64
|
+
resolved = import.meta.resolve(name);
|
|
65
|
+
} catch {
|
|
66
|
+
return 'missing';
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
return existsSync(fileURLToPath(resolved)) ? 'ok' : 'broken';
|
|
70
|
+
} catch {
|
|
71
|
+
// Resolved to something fileURLToPath cannot turn into a path (not a file:
|
|
72
|
+
// URL). It resolved, so treat it as present rather than inventing a failure.
|
|
73
|
+
return 'ok';
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const status = new Map(REQUIRED_PEERS.map((name) => [name, probePeer(name)]));
|
|
78
|
+
const missing = REQUIRED_PEERS.filter((name) => status.get(name) === 'missing');
|
|
79
|
+
const broken = REQUIRED_PEERS.filter((name) => status.get(name) === 'broken');
|
|
80
|
+
|
|
81
|
+
if (missing.length > 0 || broken.length > 0) {
|
|
82
|
+
// The remedy names the packages that are ACTUALLY absent. It used to hardcode
|
|
83
|
+
// `npm install -g @earendil-works/pi-coding-agent` with the comment "add just
|
|
84
|
+
// the missing host package", which was wrong whenever pi-ai was the missing
|
|
85
|
+
// one: the instruction named a package the user already had, and following it
|
|
86
|
+
// changed nothing. Reported by review 2026-10-01.
|
|
87
|
+
const needsInstall = [...missing, ...broken];
|
|
88
|
+
const lines = [
|
|
89
|
+
'',
|
|
90
|
+
'Error: pi-research cannot start — it is missing required runtime dependencies:',
|
|
91
|
+
...missing.map((name) => ` ${name}`),
|
|
92
|
+
...broken.map((name) => ` ${name} (installed, but its files are incomplete)`),
|
|
93
|
+
'',
|
|
94
|
+
'These are PEER dependencies. pi provides its own copies when it loads this',
|
|
95
|
+
'package as an extension, and npm >= 7 installs them automatically for a',
|
|
96
|
+
'standalone (global or project) install. This install skipped them, which is',
|
|
97
|
+
'what --legacy-peer-deps, yarn classic, and a pnpm config with',
|
|
98
|
+
'auto-install-peers=false do.',
|
|
99
|
+
'',
|
|
100
|
+
'Install the missing package(s), or reinstall this package, which resolves',
|
|
101
|
+
'them as peers:',
|
|
102
|
+
'',
|
|
103
|
+
` npm install -g ${needsInstall.join(' ')}`,
|
|
104
|
+
' npm install -g @lincoln504/pi-research',
|
|
105
|
+
'',
|
|
106
|
+
];
|
|
107
|
+
process.stderr.write(lines.join('\n'));
|
|
108
|
+
process.exit(78);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// dist/cli.mjs decides whether to run by comparing its own module URL against
|
|
112
|
+
// process.argv[1]. Because this shim is what the OS (or npm's Windows .cmd shim)
|
|
113
|
+
// actually invokes, argv[1] is THIS file; without the rewrite below that check
|
|
114
|
+
// would be false and the CLI would exit 0 having done nothing at all. Point it
|
|
115
|
+
// at the real entry point before importing it, so both sides resolve to the
|
|
116
|
+
// same realpath.
|
|
117
|
+
//
|
|
118
|
+
// fileURLToPath, not URL.pathname: on Windows the latter yields
|
|
119
|
+
// `/C:/…/cli.mjs`, which is not a path any filesystem call accepts.
|
|
120
|
+
process.argv[1] = fileURLToPath(new URL('./cli.mjs', import.meta.url));
|
|
121
|
+
|
|
122
|
+
await import('./cli.mjs');
|
package/dist/cli.mjs
CHANGED
|
@@ -11122,6 +11122,7 @@ var init_skill_installer = __esm({
|
|
|
11122
11122
|
import * as fs17 from "node:fs";
|
|
11123
11123
|
import * as os7 from "node:os";
|
|
11124
11124
|
import * as path19 from "node:path";
|
|
11125
|
+
import { spawnSync } from "node:child_process";
|
|
11125
11126
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
11126
11127
|
|
|
11127
11128
|
// src/core/service-initialization.ts
|
|
@@ -27790,6 +27791,30 @@ change the machine-wide defaults: set PI_RESEARCH_KNOWLEDGE_STORE_MODE=<mode> an
|
|
|
27790
27791
|
return reportError(err, "knowledge-config", kc.json);
|
|
27791
27792
|
}
|
|
27792
27793
|
}
|
|
27794
|
+
function cmdCleanup(c) {
|
|
27795
|
+
const script = fileURLToPath6(new URL("../scripts/cleanup.cjs", import.meta.url));
|
|
27796
|
+
if (!fs17.existsSync(script)) {
|
|
27797
|
+
toStderr(
|
|
27798
|
+
`
|
|
27799
|
+
Error: cleanup script not found at ${script}.
|
|
27800
|
+
Run it from the installed package instead: node <package-dir>/scripts/cleanup.cjs
|
|
27801
|
+
`
|
|
27802
|
+
);
|
|
27803
|
+
return EXIT.CONFIG;
|
|
27804
|
+
}
|
|
27805
|
+
const env = { ...process.env };
|
|
27806
|
+
if (c.purgeBrowsers) {
|
|
27807
|
+
env["PI_RESEARCH_PURGE_BROWSERS"] = "1";
|
|
27808
|
+
}
|
|
27809
|
+
const res = spawnSync(process.execPath, [script], { stdio: "inherit", env });
|
|
27810
|
+
if (res.error) {
|
|
27811
|
+
toStderr(`
|
|
27812
|
+
Error: could not run ${script}: ${res.error.message}
|
|
27813
|
+
`);
|
|
27814
|
+
return EXIT.SOFTWARE;
|
|
27815
|
+
}
|
|
27816
|
+
return res.status ?? EXIT.SOFTWARE;
|
|
27817
|
+
}
|
|
27793
27818
|
async function cmdSkill(s) {
|
|
27794
27819
|
const {
|
|
27795
27820
|
installSkill: installSkill2,
|
|
@@ -28043,6 +28068,12 @@ COMMANDS
|
|
|
28043
28068
|
skill uninstall Remove the skill from agents where pi-research installed it.
|
|
28044
28069
|
--dry-run | --json Plan only; JSON output.
|
|
28045
28070
|
|
|
28071
|
+
cleanup Remove what npm uninstall leaves behind: the skill links, the
|
|
28072
|
+
state tree (<config dir>/research/state) and the cache, including
|
|
28073
|
+
downloaded embedding models. Keeps config.env and the knowledge
|
|
28074
|
+
store database. --purge-browsers also deletes the shared
|
|
28075
|
+
stealth-browser cache (~/.cache/camoufox) other tools use.
|
|
28076
|
+
|
|
28046
28077
|
help, --help, -h Show this help.
|
|
28047
28078
|
--version, -v Print the pi-research version.
|
|
28048
28079
|
|
|
@@ -28199,6 +28230,17 @@ function parseArgs(argv) {
|
|
|
28199
28230
|
}
|
|
28200
28231
|
throw new UsageError(`unknown knowledge-config action "${action}". Use: show | set <none|project|global> | set retrieval <vector|bm25>.`);
|
|
28201
28232
|
}
|
|
28233
|
+
if (cmd === "cleanup") {
|
|
28234
|
+
let purgeBrowsers = false;
|
|
28235
|
+
for (const a of rest) {
|
|
28236
|
+
if (a === "--purge-browsers") purgeBrowsers = true;
|
|
28237
|
+
else if (a?.startsWith("--")) throw new UsageError(`unknown option for cleanup: ${a}`);
|
|
28238
|
+
else throw new UsageError(`unexpected argument "${a}" after "cleanup".`);
|
|
28239
|
+
}
|
|
28240
|
+
out.command = "cleanup";
|
|
28241
|
+
out.cleanup = { purgeBrowsers };
|
|
28242
|
+
return out;
|
|
28243
|
+
}
|
|
28202
28244
|
if (cmd === "skill") {
|
|
28203
28245
|
let json = false;
|
|
28204
28246
|
let dryRun = false;
|
|
@@ -28412,6 +28454,8 @@ Error: ${err.message}
|
|
|
28412
28454
|
return cmdKnowledgeConfig(parsed.knowledgeConfig);
|
|
28413
28455
|
case "skill":
|
|
28414
28456
|
return cmdSkill(parsed.skill);
|
|
28457
|
+
case "cleanup":
|
|
28458
|
+
return cmdCleanup(parsed.cleanup);
|
|
28415
28459
|
case "research":
|
|
28416
28460
|
return cmdResearch(parsed.research);
|
|
28417
28461
|
default:
|
package/docs/AGENT-SKILL.md
CHANGED
|
@@ -110,7 +110,10 @@ Install in External Agents. The installer:
|
|
|
110
110
|
> @lincoln504/pi-research` leaves the skill links, the state directory
|
|
111
111
|
> (`~/.pi/research/state`) and the cache directory (`~/.cache/pi-research`,
|
|
112
112
|
> including downloaded embedding models) in place. Run `pi-research skill uninstall`
|
|
113
|
-
> **before** removing the package to take the links with you.
|
|
113
|
+
> **before** removing the package to take the links with you. Then run
|
|
114
|
+
> `pi-research cleanup` to remove the state tree and the cache (it keeps `config.env`
|
|
115
|
+
> and the knowledge store database); add `--purge-browsers` to delete the shared
|
|
116
|
+
> `~/.cache/camoufox` cache as well.
|
|
114
117
|
|
|
115
118
|
Standalone (no pi extension). `pi-research skill install` and `pi-research skill uninstall`
|
|
116
119
|
do exactly the same from the CLI — same agent detection, same manifest, same
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -428,7 +428,7 @@ to support testing (mock replacement) and enforce init → use → dispose lifec
|
|
|
428
428
|
Pure ESM — the codebase is ES Modules (`"type": "module"`). Worker bundles are built with
|
|
429
429
|
esbuild (`npm run build:worker`) before integration tests or publishing.
|
|
430
430
|
|
|
431
|
-
Enforced boundaries — `docs/deps.svg` is regenerated on every push (madge), and
|
|
431
|
+
Enforced boundaries — [`docs/deps.svg`](https://github.com/Lincoln504/pi-research/blob/main/docs/deps.svg) is regenerated on every push (madge), and
|
|
432
432
|
architectural rules are enforced by dependency-cruiser
|
|
433
433
|
(`config/tooling/dependency-cruiser.cjs`).
|
|
434
434
|
|
package/docs/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,27 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.7.6] - 2026-10-01
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- **The missing-peer remedy names the package that is actually missing.** The 1.7.5 shim's bullet list correctly enumerated every absent `@earendil-works/pi-*` package, but the command underneath it was hardcoded to `npm install -g @earendil-works/pi-coding-agent` with the comment "add just the missing host package". When `pi-ai` (and not `pi-coding-agent`) was the absent one, the instruction named a package the user already had and following it changed nothing. The remedy is now built from the detected set, so both peers are named when both are missing and only the absent one is named otherwise. Caught in review; pinned by a test that asserts the present package is never the one the remedy suggests installing.
|
|
13
|
+
- **The bin preflight detects an install whose files are incomplete, not just an absent package.** It resolved each peer and treated a successful resolve as healthy. Resolving is not loading: an interrupted install, a pruned `node_modules` or a bad package-cache restore leaves `package.json` and its `exports` map intact with the file they point at gone, so the preflight passed it through and the bundle died on that import with the raw `ERR_MODULE_NOT_FOUND` the shim exists to replace. The preflight now also checks that the resolved file exists and reports `installed, but its files are incomplete` with the same exit 78. Checked with `existsSync` rather than by importing the module, which would run the host packages' side effects before the CLI has decided to start. Verified by reverting each half in isolation: without the `argv[1]` rewrite the stub engine exits 97, with a resolve-only preflight a deliberately truncated peer produces `ERR_MODULE_NOT_FOUND`, and the fixed shim produces exit 78.
|
|
14
|
+
- **`dist/cli-bin.mjs` ships executable.** It was published at mode 644 against 755 for `dist/cli.mjs`, because the build copies it with `copyFileSync`, which preserves the committed source mode. Every package manager chmods the declared `bin` target itself, so no install was broken, but the shipped artifact should not depend on that. The build now chmods 755 and the source is committed executable.
|
|
15
|
+
|
|
16
|
+
## [1.7.5] - 2026-10-01
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- **`pi-research cleanup` removes what `npm uninstall` leaves behind.** npm 7 dropped `preuninstall`, so uninstalling removes the package directory and nothing else: skill links in other agents, the state tree and the cache (downloaded embedding models included) all stay on disk. The deletion logic already existed in `scripts/cleanup.cjs` (shipped in the tarball, wired to the `preuninstall` and `cleanup` npm scripts), but reaching it meant knowing where your package manager put the package. The new subcommand delegates to that script rather than reimplementing it, so there is one deletion implementation and one set of ownership guards, prints `--purge-browsers` to also delete the shared `~/.cache/camoufox` cache, and exits with the script's status. Verified end to end against a redirected HOME: state and cache removed, `config.env` and `knowledge_db/` preserved, second run idempotent. Alongside it the uninstall story is now documented as commands rather than only as a list of what stays: README, `docs/AGENT-SKILL.md`, `docs/CONFIGURATION.md` (which also gained the three leftover rows it was missing: the embedding model cache, the camoufox cache, the skills manifest, and the debug log that nothing removes) and the ES/JA/ZH docs.
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
- **A silently dropped optional `@huggingface/transformers` subtree no longer fails the type-check gate, and both the release and CI type-check jobs now repair it.** `npm ci` (npm/cli#4828, the same bug the lancedb binding self-heal exists for) can drop the ENTIRE optional embedding tree without printing any `npm warn optional` line: the v1.7.4 release run installed 592 packages from the same lockfile that had installed 606 minutes earlier in CI, and its `npm run type-check` then failed with six `TS2307: Cannot find module '@huggingface/transformers'` errors, because `src/` imports that package's declarations unconditionally. Re-running the identical job installed 606 and passed, then published. The runtime tolerates the absence by design (the knowledge store disables cleanly, which is why the package is optional), but the type-check gate does not, so the failure was a delivery flake rather than a code defect. `scripts/ensure-native-deps.cjs` now reads the pin from `optionalDependencies` (one source of truth), verifies it resolves, and when it does not installs that exact version by name, which is not subject to the optional-dependency resolution bug. The repair uses `--no-save --no-package-lock` so package.json and the tracked lockfile stay untouched, emits a GitHub warning annotation so it is visible in the run summary, and exits non-zero if the repair does not take. Verified against a scratch tree with the subtree deleted: the package is restored at the pinned version, both manifest hashes are unchanged, and `npm run type-check` then passes. The CI type-check job now runs the self-heal too (it was exposed to the same flake and had no repair step), the lancedb install in the same script gained `--no-package-lock` for the same reason, and `test/unit/scripts/ensure-native-deps.test.ts` covers the pin lookup, the resolve check and the platform-package selection.
|
|
25
|
+
- **The `pi-research` bin reports a missing peer dependency instead of dying with a raw module error.** The CLI statically imports the `@earendil-works/pi-*` host packages, which are peer dependencies, so an install that skips peers (`npm install --legacy-peer-deps`, yarn classic, pnpm with `auto-install-peers=false`) left `pi-research` printing an internal `ERR_MODULE_NOT_FOUND` stack trace from inside Node. `bin` now points at a small unbundled shim (`src/cli-bin.mjs` → `dist/cli-bin.mjs`) that preflights both packages and exits 78 with the same remedy text and code the agent-skill launcher already prints, then loads the bundle. It cannot live inside the bundle: esbuild hoists the bundle's external imports to the top of the emitted file, ahead of every statement, so a wrapper entry point still crashed (verified). Two traps are pinned by tests: the check must use `import.meta.resolve()`, because `createRequire().resolve()` reports `ERR_PACKAGE_PATH_NOT_EXPORTED` for these ESM-only packages even when they are installed correctly, and the shim must rewrite `process.argv[1]` to the bundle path, because `cli.ts` runs `main()` only when argv[1] resolves to its own module file (otherwise the CLI exits 0 having done nothing). `scripts/verify-package.cjs` now also asserts that every `bin` target is shipped, so a shim dropped from `files[]` fails the publish gate instead of installing a command that does not exist.
|
|
26
|
+
- **The postinstall no longer starts a browser fetch it cannot finish, and `--system-deps` no longer installs an unpinned package.** `scripts/setup.cjs` used `npx playwright install-deps`, but `playwright` is not a dependency of this package (`playwright-core` is), so that resolved a different, unpinned package from the registry at install time; it now invokes the installed `playwright-core` CLI, which ships the same subcommand. When `CAMOUFOX_INSTALL_DIR` / `PLAYWRIGHT_BROWSERS_PATH` points at a path that exists but is not a usable directory (a plain file, no permission, a broken mount), the script used to log the error and then download the full 663MB browser anyway before failing; it now reports and skips the fetch, and the summary no longer claims the browser is "ready" when the path could not be read. Both paths still exit 0, because a failed install is worse than a browser fetched later.
|
|
27
|
+
- **Install claims and links that did not match reality are corrected.** The README said the package "runs no install scripts" and that the browser "downloads on first use". It declares three lifecycle scripts, and on npm up to 11.18 (the bundled npm on the Node 22.22.2 floor this package requires) the `postinstall` fetches that 663MB browser during install; only npm 11.19+ skips install scripts and defers the fetch to first use. The README, `docs/AGENT-SKILL.md` and the ES/JA/ZH docs now say which happens when, and how to defer it deliberately. The README's three translation links and `docs/ARCHITECTURE.md`'s `deps.svg` reference pointed at files that are not in the published tarball (the translations and the 660KB diagram are deliberately unshipped), so they are absolute repository URLs now and resolve from an installed package as well as from the npm page. `scripts/setup.cjs`'s header no longer claims it can never exit 1, which stopped being true when `PI_RESEARCH_STRICT_SETUP` (used by CI) was added.
|
|
28
|
+
|
|
8
29
|
## [1.7.4] - 2026-09-30
|
|
9
30
|
|
|
10
31
|
### Changed
|
package/docs/CONFIGURATION.md
CHANGED
|
@@ -363,7 +363,20 @@ All pi-research state lives under its own namespace, `~/.pi/research/`:
|
|
|
363
363
|
| `~/.pi/research/state/` | Active sessions, browser status, locks. |
|
|
364
364
|
| `~/.pi/research/knowledge_db/` | The knowledge store (LanceDB), unless `PI_RESEARCH_KNOWLEDGE_DIR` is set. |
|
|
365
365
|
| `~/.cache/pi-research/profiles/` | Transient browser profiles, unless `PI_RESEARCH_TMP_DIR` is set. Rooted at `$XDG_CACHE_HOME` when that is set. |
|
|
366
|
+
| `~/.cache/pi-research/models/` | Downloaded embedding models (the `@huggingface/transformers` backend). Re-downloaded on demand if removed. Rooted at `$XDG_CACHE_HOME` when that is set. |
|
|
366
367
|
| `~/.cache/pi-research/webgpu-viability.json` | Cached WebGPU-viability verdict (see the knowledge store doc). Rooted at `$XDG_CACHE_HOME` when that is set. |
|
|
368
|
+
| `~/.cache/camoufox/` | The stealth browser itself (1GB+). Shared with any other camoufox user on the machine, so it is never removed by default. |
|
|
369
|
+
| `~/.pi/research/installed-skills.json` | Manifest of the skill links/copies `pi-research skill install` created, so `skill uninstall` removes exactly those. |
|
|
370
|
+
| `$TMPDIR/pi-research.log` | Debug log, kept while `PI_RESEARCH_DEBUG=true`. Rotates at 10MB and keeps 10 archives. `PI_RESEARCH_LOG_PATH` relocates it. |
|
|
367
371
|
|
|
368
372
|
Paths can be relocated with `PI_RESEARCH_STATE_DIR`, `PI_RESEARCH_KNOWLEDGE_DIR`,
|
|
369
373
|
and `PI_RESEARCH_TMP_DIR`.
|
|
374
|
+
|
|
375
|
+
### What uninstalling leaves behind
|
|
376
|
+
|
|
377
|
+
`npm uninstall` removes the package directory and nothing else: npm 7 removed the
|
|
378
|
+
`preuninstall` lifecycle, so the script declared in `package.json` never runs.
|
|
379
|
+
`pi-research skill uninstall` removes the skill links, and `pi-research cleanup`
|
|
380
|
+
removes the state tree and the cache (embedding models included) while keeping
|
|
381
|
+
`config.env` and `knowledge_db/`. Add `--purge-browsers` to also delete the shared
|
|
382
|
+
`~/.cache/camoufox` cache. The debug log is not removed by either.
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lincoln504/pi-research",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.6",
|
|
4
4
|
"description": "Web research for your agents with smart and safe tooling + knowledge store",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
7
7
|
"main": "./src/index.ts",
|
|
8
8
|
"types": "./src/index.ts",
|
|
9
9
|
"bin": {
|
|
10
|
-
"pi-research": "./dist/cli.mjs"
|
|
10
|
+
"pi-research": "./dist/cli-bin.mjs"
|
|
11
11
|
},
|
|
12
12
|
"exports": {
|
|
13
13
|
".": "./src/index.ts",
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
},
|
|
17
17
|
"files": [
|
|
18
18
|
"src/",
|
|
19
|
+
"dist/cli-bin.mjs",
|
|
19
20
|
"dist/cli.mjs",
|
|
20
21
|
"dist/thread-worker.mjs",
|
|
21
22
|
"dist/pdf-extract-worker.mjs",
|
package/scripts/build.cjs
CHANGED
|
@@ -49,6 +49,24 @@ function copyWebGpuProbe() {
|
|
|
49
49
|
);
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
// The `pi-research` bin shim (src/cli-bin.mjs), copied unbundled next to the
|
|
53
|
+
// bundle it loads. It MUST NOT be bundled into dist/cli.mjs: its whole job is to
|
|
54
|
+
// run BEFORE that bundle's external peer-dependency imports are evaluated, and
|
|
55
|
+
// esbuild hoists those to the top of the emitted file. See the shim's own header
|
|
56
|
+
// for the full story and for why `bin` points here instead of at dist/cli.mjs.
|
|
57
|
+
function copyCliBin() {
|
|
58
|
+
fs.mkdirSync(p('dist'), { recursive: true });
|
|
59
|
+
fs.copyFileSync(p('src', 'cli-bin.mjs'), p('dist', 'cli-bin.mjs'));
|
|
60
|
+
// copyFileSync preserves the SOURCE mode. The source is committed executable
|
|
61
|
+
// (100755) so this is belt-and-braces, but nothing about the shipped bin should
|
|
62
|
+
// depend on the source file's mode or on the umask of whatever builds it. The
|
|
63
|
+
// bin is what the OS executes, so ship it executable like dist/cli.mjs: every
|
|
64
|
+
// package manager does its own chmod on the declared bin target, but nothing
|
|
65
|
+
// should depend on that. (Flagged in review 2026-10-01: the packed 1.7.5 tarball
|
|
66
|
+
// had dist/cli.mjs at 755 and dist/cli-bin.mjs at 644.)
|
|
67
|
+
fs.chmodSync(p('dist', 'cli-bin.mjs'), 0o755);
|
|
68
|
+
}
|
|
69
|
+
|
|
52
70
|
// Build the browser worker bundle. Factored out so the cli target can guarantee
|
|
53
71
|
// it exists when built in isolation (`build:cli`), not only via `build all`.
|
|
54
72
|
function buildWorker() {
|
|
@@ -146,6 +164,7 @@ const TARGETS = {
|
|
|
146
164
|
copyThreadWorker();
|
|
147
165
|
copyPdfWorker();
|
|
148
166
|
copyWebGpuProbe();
|
|
167
|
+
copyCliBin();
|
|
149
168
|
copyPrompts();
|
|
150
169
|
},
|
|
151
170
|
skill: () => {
|
package/scripts/setup.cjs
CHANGED
|
@@ -3,7 +3,13 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* pi-research postinstall setup.
|
|
5
5
|
* Installs Camoufox browser binaries.
|
|
6
|
-
*
|
|
6
|
+
*
|
|
7
|
+
* Exits 0 on EVERY failure path, because a non-zero exit from postinstall makes
|
|
8
|
+
* `npm install` fail and leaves the user without the package at all — the
|
|
9
|
+
* browser is re-fetchable on first use, a failed install is not. The single
|
|
10
|
+
* exception is PI_RESEARCH_STRICT_SETUP (set by this repo's CI), which restates
|
|
11
|
+
* the failure as exit 1 so an install regression fails the build loudly instead
|
|
12
|
+
* of hiding behind the graceful path.
|
|
7
13
|
*
|
|
8
14
|
* Environment:
|
|
9
15
|
* PLAYWRIGHT_BROWSERS_PATH - override browser install location
|
|
@@ -82,6 +88,10 @@ function camoufoxCachePath() {
|
|
|
82
88
|
}
|
|
83
89
|
|
|
84
90
|
let browsersInstalled = false;
|
|
91
|
+
// Set when a fetch cannot possibly succeed (an unusable configured install dir),
|
|
92
|
+
// so the download is skipped rather than run to completion and then fail. Kept
|
|
93
|
+
// separate from browsersInstalled so the summary below still tells the truth.
|
|
94
|
+
let fetchSkipped = false;
|
|
85
95
|
|
|
86
96
|
if (process.env.PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD === '1') {
|
|
87
97
|
console.log('pi-research: skipping browser download (PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1)');
|
|
@@ -98,7 +108,15 @@ if (process.env.PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD === '1') {
|
|
|
98
108
|
const installDeps = process.argv.includes('--system-deps') || process.env.PLAYWRIGHT_INSTALL_DEPS === 'true';
|
|
99
109
|
if (installDeps && isLinux) {
|
|
100
110
|
try {
|
|
101
|
-
|
|
111
|
+
// Use the installed playwright-core CLI, NOT `npx playwright`: `playwright`
|
|
112
|
+
// is not a dependency of this package (only `playwright-core` is), so
|
|
113
|
+
// `npx playwright` resolved a DIFFERENT, unpinned package from the registry
|
|
114
|
+
// at install time — an extra network fetch, an extra supply-chain surface,
|
|
115
|
+
// and a version that can disagree with the browser we drive. playwright-core
|
|
116
|
+
// ships the same `install-deps` subcommand (verified: its CLI lists it).
|
|
117
|
+
const playwrightCoreBin = path.join(projectRoot, 'node_modules', '.bin', isWindows ? 'playwright-core.cmd' : 'playwright-core');
|
|
118
|
+
const cli = existsSync(playwrightCoreBin) ? playwrightCoreBin : 'playwright-core';
|
|
119
|
+
execSync(`"${cli}" install-deps`, { stdio: 'inherit', env: { ...env, PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '0' } });
|
|
102
120
|
} catch (e) {
|
|
103
121
|
console.warn(`WARNING: could not install system dependencies. Run: sudo apt-get install -y libgbm1 libnss3 libatk1.0-0 libatk-bridge2.0-0 libcups2 libxkbcommon0 libxcomposite1\nReason: ${e instanceof Error ? e.message : String(e)}\n(These are the libraries headless camoufox needs. Xvfb is NOT required for the default headless mode — only add it if you opt into virtual-display mode with PI_RESEARCH_USE_XVFB=true.)`);
|
|
104
122
|
}
|
|
@@ -114,11 +132,23 @@ if (process.env.PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD === '1') {
|
|
|
114
132
|
console.log(`pi-research: Camoufox already installed at ${cachePath}. Skipping fetch.`);
|
|
115
133
|
}
|
|
116
134
|
} catch (e) {
|
|
117
|
-
|
|
135
|
+
// A configured install dir that exists but cannot be READ as a directory
|
|
136
|
+
// (a plain file, a broken symlink mount, no permission) makes the fetch
|
|
137
|
+
// below impossible: camoufox downloads the whole ~663MB archive and then
|
|
138
|
+
// fails to place it. Report and skip instead, which is what the user's
|
|
139
|
+
// install needs either way (the catch further down exits 0, and the
|
|
140
|
+
// browser is re-fetchable once the path is usable).
|
|
141
|
+
console.warn(`pi-research: cannot use the browser install directory ${cachePath}: ${e instanceof Error ? e.message : String(e)}`);
|
|
142
|
+
if (customCamoufoxDir()) {
|
|
143
|
+
console.warn('pi-research: skipping the browser fetch because CAMOUFOX_INSTALL_DIR / PLAYWRIGHT_BROWSERS_PATH points at a path that is not a usable directory. Fix or unset it, then run: npx camoufox-js fetch');
|
|
144
|
+
fetchSkipped = true;
|
|
145
|
+
}
|
|
118
146
|
}
|
|
119
147
|
}
|
|
120
148
|
|
|
121
|
-
if (
|
|
149
|
+
if (fetchSkipped) {
|
|
150
|
+
// Reported above; the fetch cannot work until the configured path is fixed.
|
|
151
|
+
} else if (!alreadyInstalled) {
|
|
122
152
|
try {
|
|
123
153
|
const bin = resolveCamoufoxBin();
|
|
124
154
|
// spawnSync with an ARGV ARRAY, never a shell string. `bin` is a filesystem
|
|
@@ -169,7 +199,11 @@ if (existsSync(cachePath)) {
|
|
|
169
199
|
const versions = readdirSync(cachePath).filter(f => statSync(path.join(cachePath, f)).isDirectory());
|
|
170
200
|
console.log(`pi-research: camoufox ready (${versions.join(', ') || 'installed'})`);
|
|
171
201
|
} catch (e) {
|
|
172
|
-
|
|
202
|
+
// Not "ready": the path exists but cannot be read as an install dir (a plain
|
|
203
|
+
// file, no permission, a broken mount). Claiming readiness here is how a
|
|
204
|
+
// broken CAMOUFOX_INSTALL_DIR turned into a mysterious "browser missing at
|
|
205
|
+
// runtime" much later. The fetch path above already printed the remedy.
|
|
206
|
+
console.warn(`pi-research: camoufox install path ${cachePath} could not be verified: ${e instanceof Error ? e.message : String(e)}`);
|
|
173
207
|
}
|
|
174
208
|
} else if (browsersInstalled) {
|
|
175
209
|
console.warn(`pi-research: camoufox binary not found at expected path ${cachePath}`);
|
package/src/cli-bin.mjs
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* The installed `pi-research` bin.
|
|
4
|
+
*
|
|
5
|
+
* WHY THIS SHIM EXISTS INSTEAD OF POINTING `bin` STRAIGHT AT dist/cli.mjs
|
|
6
|
+
*
|
|
7
|
+
* dist/cli.mjs statically imports the `@earendil-works/pi-*` host packages, which
|
|
8
|
+
* are declared as PEER dependencies: pi supplies its own copies when it loads
|
|
9
|
+
* this package as an extension, and npm >= 7 resolves them automatically for a
|
|
10
|
+
* standalone (global or project) install. An install that skips peers leaves
|
|
11
|
+
* them absent — `npm install --legacy-peer-deps`, yarn classic, and a pnpm
|
|
12
|
+
* config with `auto-install-peers=false` all do that — and Node then dies at
|
|
13
|
+
* module load with a raw `ERR_MODULE_NOT_FOUND` stack before any of our code
|
|
14
|
+
* runs. The user sees an internal Node error instead of the one-line fix.
|
|
15
|
+
*
|
|
16
|
+
* The preflight cannot live inside the bundle: esbuild hoists the bundle's
|
|
17
|
+
* external `import` declarations to the very top of the output file, ahead of
|
|
18
|
+
* every statement in the program, so anything the bundle body does runs AFTER
|
|
19
|
+
* the failing import has already thrown. (Verified: a wrapper entry point whose
|
|
20
|
+
* first statement is the preflight still crashes, because the emitted file
|
|
21
|
+
* begins with `import "…/pi-ai";`.) It has to be a separate module, which is
|
|
22
|
+
* why this file is shipped unbundled and why `bin` points at it.
|
|
23
|
+
*
|
|
24
|
+
* The check uses `import.meta.resolve()`, NOT `createRequire().resolve()`: both
|
|
25
|
+
* host packages are ESM-only (`exports` maps with no `require` condition), so
|
|
26
|
+
* the CJS resolver fails on them with `ERR_PACKAGE_PATH_NOT_EXPORTED` even when
|
|
27
|
+
* they are correctly installed — a false negative that would break every
|
|
28
|
+
* healthy install. `import.meta.resolve()` uses the same ESM resolution the
|
|
29
|
+
* bundle itself uses, returns when the package is present, and throws
|
|
30
|
+
* `ERR_MODULE_NOT_FOUND` when it is not.
|
|
31
|
+
*
|
|
32
|
+
* Exit code 78 (EXIT.CONFIG) and the remedy text match the agent-skill
|
|
33
|
+
* launcher's `depMissing()` — one failure, one message, whichever entry point
|
|
34
|
+
* the user reached for.
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
import { existsSync } from 'node:fs';
|
|
38
|
+
import { fileURLToPath } from 'node:url';
|
|
39
|
+
|
|
40
|
+
const REQUIRED_PEERS = ['@earendil-works/pi-ai', '@earendil-works/pi-coding-agent'];
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Probe one peer dependency.
|
|
44
|
+
*
|
|
45
|
+
* 'ok' — resolves, and the file it resolves to exists
|
|
46
|
+
* 'missing' — does not resolve at all (the --legacy-peer-deps case)
|
|
47
|
+
* 'broken' — resolves (its package.json and exports map are readable) but the
|
|
48
|
+
* file those point at is not on disk
|
|
49
|
+
*
|
|
50
|
+
* The 'broken' case is checked with existsSync rather than by importing the
|
|
51
|
+
* module. Resolving is not loading: an interrupted install, a pruned
|
|
52
|
+
* node_modules or a bad package-cache restore leaves a package that resolves
|
|
53
|
+
* perfectly and then fails to load, which would pass this preflight and die
|
|
54
|
+
* inside cli.mjs with the same raw ERR_MODULE_NOT_FOUND this shim exists to
|
|
55
|
+
* replace. Importing it here instead would work, but it runs the host
|
|
56
|
+
* package's module side effects before the CLI proper has decided to start, so
|
|
57
|
+
* the cheap existence check is the right trade.
|
|
58
|
+
*
|
|
59
|
+
* @returns {'ok' | 'missing' | 'broken'}
|
|
60
|
+
*/
|
|
61
|
+
function probePeer(name) {
|
|
62
|
+
let resolved;
|
|
63
|
+
try {
|
|
64
|
+
resolved = import.meta.resolve(name);
|
|
65
|
+
} catch {
|
|
66
|
+
return 'missing';
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
return existsSync(fileURLToPath(resolved)) ? 'ok' : 'broken';
|
|
70
|
+
} catch {
|
|
71
|
+
// Resolved to something fileURLToPath cannot turn into a path (not a file:
|
|
72
|
+
// URL). It resolved, so treat it as present rather than inventing a failure.
|
|
73
|
+
return 'ok';
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const status = new Map(REQUIRED_PEERS.map((name) => [name, probePeer(name)]));
|
|
78
|
+
const missing = REQUIRED_PEERS.filter((name) => status.get(name) === 'missing');
|
|
79
|
+
const broken = REQUIRED_PEERS.filter((name) => status.get(name) === 'broken');
|
|
80
|
+
|
|
81
|
+
if (missing.length > 0 || broken.length > 0) {
|
|
82
|
+
// The remedy names the packages that are ACTUALLY absent. It used to hardcode
|
|
83
|
+
// `npm install -g @earendil-works/pi-coding-agent` with the comment "add just
|
|
84
|
+
// the missing host package", which was wrong whenever pi-ai was the missing
|
|
85
|
+
// one: the instruction named a package the user already had, and following it
|
|
86
|
+
// changed nothing. Reported by review 2026-10-01.
|
|
87
|
+
const needsInstall = [...missing, ...broken];
|
|
88
|
+
const lines = [
|
|
89
|
+
'',
|
|
90
|
+
'Error: pi-research cannot start — it is missing required runtime dependencies:',
|
|
91
|
+
...missing.map((name) => ` ${name}`),
|
|
92
|
+
...broken.map((name) => ` ${name} (installed, but its files are incomplete)`),
|
|
93
|
+
'',
|
|
94
|
+
'These are PEER dependencies. pi provides its own copies when it loads this',
|
|
95
|
+
'package as an extension, and npm >= 7 installs them automatically for a',
|
|
96
|
+
'standalone (global or project) install. This install skipped them, which is',
|
|
97
|
+
'what --legacy-peer-deps, yarn classic, and a pnpm config with',
|
|
98
|
+
'auto-install-peers=false do.',
|
|
99
|
+
'',
|
|
100
|
+
'Install the missing package(s), or reinstall this package, which resolves',
|
|
101
|
+
'them as peers:',
|
|
102
|
+
'',
|
|
103
|
+
` npm install -g ${needsInstall.join(' ')}`,
|
|
104
|
+
' npm install -g @lincoln504/pi-research',
|
|
105
|
+
'',
|
|
106
|
+
];
|
|
107
|
+
process.stderr.write(lines.join('\n'));
|
|
108
|
+
process.exit(78);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// dist/cli.mjs decides whether to run by comparing its own module URL against
|
|
112
|
+
// process.argv[1]. Because this shim is what the OS (or npm's Windows .cmd shim)
|
|
113
|
+
// actually invokes, argv[1] is THIS file; without the rewrite below that check
|
|
114
|
+
// would be false and the CLI would exit 0 having done nothing at all. Point it
|
|
115
|
+
// at the real entry point before importing it, so both sides resolve to the
|
|
116
|
+
// same realpath.
|
|
117
|
+
//
|
|
118
|
+
// fileURLToPath, not URL.pathname: on Windows the latter yields
|
|
119
|
+
// `/C:/…/cli.mjs`, which is not a path any filesystem call accepts.
|
|
120
|
+
process.argv[1] = fileURLToPath(new URL('./cli.mjs', import.meta.url));
|
|
121
|
+
|
|
122
|
+
await import('./cli.mjs');
|
package/src/cli.ts
CHANGED
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
import * as fs from 'node:fs';
|
|
26
26
|
import * as os from 'node:os';
|
|
27
27
|
import * as path from 'node:path';
|
|
28
|
+
import { spawnSync } from 'node:child_process';
|
|
28
29
|
import { fileURLToPath } from 'node:url';
|
|
29
30
|
|
|
30
31
|
// pi-research internals (bundled; native/npm deps stay external).
|
|
@@ -989,6 +990,55 @@ export async function cmdKnowledgeConfig(kc: NonNullable<ParsedArgs['knowledgeCo
|
|
|
989
990
|
* clobbered, and every link/copy is manifest-tracked so uninstall removes exactly
|
|
990
991
|
* what was created.
|
|
991
992
|
*/
|
|
993
|
+
/**
|
|
994
|
+
* `pi-research cleanup` — the supported way to remove what `npm uninstall`
|
|
995
|
+
* leaves behind.
|
|
996
|
+
*
|
|
997
|
+
* npm removed `preuninstall` in npm 7, so an uninstall removes the package
|
|
998
|
+
* directory and nothing else: skill links in other agents, the state tree and
|
|
999
|
+
* the cache (downloaded embedding models included) all stay on disk. The
|
|
1000
|
+
* deletion logic already exists in `scripts/cleanup.cjs`, which ships in the
|
|
1001
|
+
* tarball and is wired to the `preuninstall` and `cleanup` npm scripts — but
|
|
1002
|
+
* reaching it means knowing where your package manager put the package.
|
|
1003
|
+
*
|
|
1004
|
+
* Delegates to that script rather than reimplementing it, deliberately: one
|
|
1005
|
+
* deletion implementation, one set of ownership guards (it removes only what it
|
|
1006
|
+
* can prove it created, and refuses to touch the shared camoufox cache unless
|
|
1007
|
+
* asked). spawnSync with an argv array and process.execPath, never a shell
|
|
1008
|
+
* string, so a package installed under a path containing shell metacharacters
|
|
1009
|
+
* cannot execute anything.
|
|
1010
|
+
*
|
|
1011
|
+
* Resolves the script relative to THIS module: `src/cli.ts` and the bundled
|
|
1012
|
+
* `dist/cli.mjs` are each one level below the package root, so `../scripts/`
|
|
1013
|
+
* is correct for a source checkout, a local install and a global install alike.
|
|
1014
|
+
*/
|
|
1015
|
+
function cmdCleanup(c: NonNullable<ParsedArgs['cleanup']>): number {
|
|
1016
|
+
const script = fileURLToPath(new URL('../scripts/cleanup.cjs', import.meta.url));
|
|
1017
|
+
if (!fs.existsSync(script)) {
|
|
1018
|
+
toStderr(
|
|
1019
|
+
`\nError: cleanup script not found at ${script}.\n` +
|
|
1020
|
+
'Run it from the installed package instead: node <package-dir>/scripts/cleanup.cjs\n',
|
|
1021
|
+
);
|
|
1022
|
+
return EXIT.CONFIG;
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
const env: NodeJS.ProcessEnv = { ...process.env };
|
|
1026
|
+
if (c.purgeBrowsers) {
|
|
1027
|
+
// Shared with every other camoufox user on the box, which is why it is not
|
|
1028
|
+
// the default. The script prints what it is about to delete.
|
|
1029
|
+
env['PI_RESEARCH_PURGE_BROWSERS'] = '1';
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
// stdio: 'inherit' — the script narrates each removal, and that narration is
|
|
1033
|
+
// the user's only confirmation of what was deleted.
|
|
1034
|
+
const res = spawnSync(process.execPath, [script], { stdio: 'inherit', env });
|
|
1035
|
+
if (res.error) {
|
|
1036
|
+
toStderr(`\nError: could not run ${script}: ${res.error.message}\n`);
|
|
1037
|
+
return EXIT.SOFTWARE;
|
|
1038
|
+
}
|
|
1039
|
+
return res.status ?? EXIT.SOFTWARE;
|
|
1040
|
+
}
|
|
1041
|
+
|
|
992
1042
|
async function cmdSkill(s: NonNullable<ParsedArgs['skill']>): Promise<number> {
|
|
993
1043
|
const {
|
|
994
1044
|
installSkill, uninstallSkill, skillInstallCandidates, skillUninstallCandidates,
|
|
@@ -1268,6 +1318,7 @@ interface ParsedArgs {
|
|
|
1268
1318
|
knowledgeConfig?: { action: 'show' | 'set' | 'set-retrieval'; mode?: 'none' | 'project' | 'global'; retrieval?: 'vector' | 'bm25'; json?: boolean };
|
|
1269
1319
|
status?: { json?: boolean };
|
|
1270
1320
|
skill?: { action: 'install' | 'uninstall' | 'status'; json?: boolean; dryRun?: boolean; copy?: boolean };
|
|
1321
|
+
cleanup?: { purgeBrowsers?: boolean };
|
|
1271
1322
|
configPath?: string;
|
|
1272
1323
|
json?: boolean;
|
|
1273
1324
|
}
|
|
@@ -1316,6 +1367,12 @@ COMMANDS
|
|
|
1316
1367
|
skill uninstall Remove the skill from agents where pi-research installed it.
|
|
1317
1368
|
--dry-run | --json Plan only; JSON output.
|
|
1318
1369
|
|
|
1370
|
+
cleanup Remove what npm uninstall leaves behind: the skill links, the
|
|
1371
|
+
state tree (<config dir>/research/state) and the cache, including
|
|
1372
|
+
downloaded embedding models. Keeps config.env and the knowledge
|
|
1373
|
+
store database. --purge-browsers also deletes the shared
|
|
1374
|
+
stealth-browser cache (~/.cache/camoufox) other tools use.
|
|
1375
|
+
|
|
1319
1376
|
help, --help, -h Show this help.
|
|
1320
1377
|
--version, -v Print the pi-research version.
|
|
1321
1378
|
|
|
@@ -1492,6 +1549,22 @@ export function parseArgs(argv: string[]): ParsedArgs {
|
|
|
1492
1549
|
throw new UsageError(`unknown knowledge-config action "${action}". Use: show | set <none|project|global> | set retrieval <vector|bm25>.`);
|
|
1493
1550
|
}
|
|
1494
1551
|
|
|
1552
|
+
if (cmd === 'cleanup') {
|
|
1553
|
+
// Wrapper for the shipped scripts/cleanup.cjs. npm 7+ never runs
|
|
1554
|
+
// `preuninstall`, so an uninstall leaves the state tree, the cache and any
|
|
1555
|
+
// skill links in other agents behind; this is the documented way to remove
|
|
1556
|
+
// them without locating the script inside node_modules or a global prefix.
|
|
1557
|
+
let purgeBrowsers = false;
|
|
1558
|
+
for (const a of rest) {
|
|
1559
|
+
if (a === '--purge-browsers') purgeBrowsers = true;
|
|
1560
|
+
else if (a?.startsWith('--')) throw new UsageError(`unknown option for cleanup: ${a}`);
|
|
1561
|
+
else throw new UsageError(`unexpected argument "${a}" after "cleanup".`);
|
|
1562
|
+
}
|
|
1563
|
+
out.command = 'cleanup';
|
|
1564
|
+
out.cleanup = { purgeBrowsers };
|
|
1565
|
+
return out;
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1495
1568
|
if (cmd === 'skill') {
|
|
1496
1569
|
let json = false;
|
|
1497
1570
|
let dryRun = false;
|
|
@@ -1847,6 +1920,8 @@ async function main(argv: string[]): Promise<number> {
|
|
|
1847
1920
|
return cmdKnowledgeConfig(parsed.knowledgeConfig!);
|
|
1848
1921
|
case 'skill':
|
|
1849
1922
|
return cmdSkill(parsed.skill!);
|
|
1923
|
+
case 'cleanup':
|
|
1924
|
+
return cmdCleanup(parsed.cleanup!);
|
|
1850
1925
|
case 'research':
|
|
1851
1926
|
return cmdResearch(parsed.research!);
|
|
1852
1927
|
default:
|