@lincoln504/pi-research 1.0.2 → 1.0.3

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 CHANGED
@@ -63,7 +63,7 @@ pi install npm:@lincoln504/pi-research
63
63
 
64
64
  See [Agent skill](docs/AGENT-SKILL.md) or [SDK](docs/SDK.md) for standalone (no pi extension) installation.
65
65
 
66
- Then configure a model and key — set `PI_RESEARCH_MODEL` (e.g. `openai/gpt-4o`) plus `PI_RESEARCH_API_KEY` and `PI_RESEARCH_PROVIDER`, either as environment variables or in `~/.pi/research/config.env`. See [Configuration](docs/CONFIGURATION.md). (The pi extension needs no key — it uses pi's own auth.)
66
+ The pi extension works out of the box — it runs on your pi session's model and pi's own configuration. Standalone use (agent skill / CLI / SDK) requires a configured model: set `PI_RESEARCH_MODEL` (e.g. `openai/gpt-4o`) as an environment variable or in `~/.pi/research/config.env`, plus `PI_RESEARCH_API_KEY` if you don't use pi. See [Configuration](docs/CONFIGURATION.md).
67
67
 
68
68
  The first install pulls the stealth browser engine, which takes a few minutes.
69
69
 
package/dist/cli.mjs CHANGED
@@ -22000,14 +22000,15 @@ async function _doInit(options = {}) {
22000
22000
  }
22001
22001
  globalApiKey = options.apiKey || process.env["PI_RESEARCH_API_KEY"];
22002
22002
  let parsedProvider = options.provider || process.env["PI_RESEARCH_PROVIDER"];
22003
- if (!parsedProvider && options.model) {
22004
- if (typeof options.model === "string") {
22005
- const slashIdx = options.model.indexOf("/");
22003
+ const effectiveModel = options.model ?? globalConfig.RESEARCH_MODEL;
22004
+ if (!parsedProvider && effectiveModel) {
22005
+ if (typeof effectiveModel === "string") {
22006
+ const slashIdx = effectiveModel.indexOf("/");
22006
22007
  if (slashIdx > 0) {
22007
- parsedProvider = options.model.slice(0, slashIdx);
22008
+ parsedProvider = effectiveModel.slice(0, slashIdx);
22008
22009
  }
22009
- } else if (options.model.provider) {
22010
- parsedProvider = options.model.provider;
22010
+ } else if (effectiveModel.provider) {
22011
+ parsedProvider = effectiveModel.provider;
22011
22012
  }
22012
22013
  }
22013
22014
  if (globalApiKey && !parsedProvider) {
@@ -22015,7 +22016,16 @@ async function _doInit(options = {}) {
22015
22016
  }
22016
22017
  globalRegistry = buildModelRegistry(globalApiKey, parsedProvider);
22017
22018
  try {
22018
- globalModel = resolveModel(globalRegistry, typeof options.model === "string" ? options.model : void 0, parsedProvider, globalApiKey);
22019
+ if (effectiveModel && typeof effectiveModel === "object" && effectiveModel.id) {
22020
+ globalModel = effectiveModel;
22021
+ } else {
22022
+ globalModel = resolveModel(
22023
+ globalRegistry,
22024
+ typeof effectiveModel === "string" ? effectiveModel : void 0,
22025
+ parsedProvider,
22026
+ globalApiKey
22027
+ );
22028
+ }
22019
22029
  globalContainer = getServiceContainer();
22020
22030
  registerCoreServices(globalContainer);
22021
22031
  registerInfrastructureServices(globalContainer);
@@ -22630,6 +22640,18 @@ function detectCredentials() {
22630
22640
  };
22631
22641
  }
22632
22642
  if (piAuthPresent) {
22643
+ if (!model) {
22644
+ return {
22645
+ source: "pi-config",
22646
+ apiKeyConfigured: true,
22647
+ provider,
22648
+ model,
22649
+ modelFrom,
22650
+ piAuthPresent,
22651
+ piModelsPresent,
22652
+ problem: 'pi credentials were found (auth.json), but no research model is configured. The standalone CLI / agent skill run only on an explicitly configured model \u2014 they do not follow the model selected inside the pi extension. Set PI_RESEARCH_MODEL to a "provider/model-id" (in the environment, or in config.env / cli.env below).'
22653
+ };
22654
+ }
22633
22655
  return {
22634
22656
  source: "pi-config",
22635
22657
  apiKeyConfigured: true,
@@ -22659,14 +22681,14 @@ function configBlock(det, extraNote) {
22659
22681
  lines.push(` \u2022 base config file: ${paths.configEnv}`);
22660
22682
  lines.push(` \u2022 cli overlay: ${paths.cliIfaceEnv} (optional; overrides base for this CLI / agent skill)`);
22661
22683
  lines.push(` \u2022 env vars: PI_RESEARCH_API_KEY / PI_RESEARCH_PROVIDER / PI_RESEARCH_MODEL`);
22662
- lines.push(` \u2022 pi auth storage: ${paths.piAuth}`);
22663
- lines.push(` \u2022 pi models: ${paths.piModels}`);
22684
+ lines.push(` \u2022 pi config (keys): ${paths.piAuth}`);
22685
+ lines.push(` \u2022 pi config (models): ${paths.piModels}`);
22664
22686
  lines.push("");
22665
22687
  lines.push("Detected:");
22666
22688
  lines.push(` \u2022 credential source: ${det.source}`);
22667
22689
  lines.push(` \u2022 api key configured: ${det.apiKeyConfigured}`);
22668
- lines.push(` \u2022 provider: ${det.provider ?? "(unset)"}`);
22669
- lines.push(` \u2022 model: ${det.model ?? "(unset)"} [from: ${det.modelFrom}]`);
22690
+ lines.push(` \u2022 provider: ${det.provider ?? "(unset \u2014 inferred from a provider/model-id model)"}`);
22691
+ lines.push(` \u2022 model: ${det.model ? `${det.model} [from: ${det.modelFrom}]` : "(not set \u2014 set PI_RESEARCH_MODEL, required)"}`);
22670
22692
  lines.push(` \u2022 pi auth.json: ${det.piAuthPresent ? "present" : "absent"}`);
22671
22693
  lines.push(` \u2022 pi models.json: ${det.piModelsPresent ? "present" : "absent"}`);
22672
22694
  if (extraNote) lines.push(` \u2022 note: ${extraNote}`);
@@ -22923,8 +22945,9 @@ uninstall: ${BINARY_NAME} skill uninstall
22923
22945
  } catch (err) {
22924
22946
  return reportError(err, "skill install", s.json);
22925
22947
  }
22948
+ const skillModel = process.env["PI_RESEARCH_MODEL"] ?? getConfig(process.cwd(), "cli").RESEARCH_MODEL;
22926
22949
  if (s.json) {
22927
- toStdout(pretty({ command: "skill", action: "install", dryRun: !!s.dryRun, results: results2 }));
22950
+ toStdout(pretty({ command: "skill", action: "install", dryRun: !!s.dryRun, results: results2, modelConfigured: Boolean(skillModel) }));
22928
22951
  } else {
22929
22952
  toStdout(`pi-research agent skill \u2014 ${s.dryRun ? "install (dry run)" : "install"}
22930
22953
 
@@ -22934,6 +22957,15 @@ uninstall: ${BINARY_NAME} skill uninstall
22934
22957
  toStdout(`
22935
22958
  The skill activates automatically \u2014 ask the agent to research something.
22936
22959
  `);
22960
+ if (!skillModel) {
22961
+ toStdout(
22962
+ `
22963
+ Required: set PI_RESEARCH_MODEL=provider/model-id in ${resolvedConfigPaths().configEnv}
22964
+ (or as an env var) \u2014 the skill runs only on this configured model, not your pi
22965
+ session model. Verify with: ${BINARY_NAME} status
22966
+ `
22967
+ );
22968
+ }
22937
22969
  }
22938
22970
  return results2.some((r) => r.status === "error") ? EXIT.SOFTWARE : EXIT.OK;
22939
22971
  }
@@ -23091,11 +23123,15 @@ COMMANDS
23091
23123
  CONFIGURE
23092
23124
  Credentials are resolved in this order (first match wins):
23093
23125
 
23094
- env vars: PI_RESEARCH_API_KEY PI_RESEARCH_PROVIDER PI_RESEARCH_MODEL
23095
- cli overlay: ${p.cliIfaceEnv} (optional; this CLI / agent skill only)
23096
- base config: ${p.configEnv}
23097
- pi auth: ${p.piAuth}
23098
- pi models: ${p.piModels}
23126
+ env vars: PI_RESEARCH_API_KEY PI_RESEARCH_PROVIDER PI_RESEARCH_MODEL
23127
+ cli overlay: ${p.cliIfaceEnv} (optional; this CLI / agent skill only)
23128
+ base config: ${p.configEnv}
23129
+ pi config (keys): ${p.piAuth}
23130
+ pi config (models): ${p.piModels}
23131
+
23132
+ A model is REQUIRED: set PI_RESEARCH_MODEL to a "provider/model-id". This CLI and
23133
+ the agent skill run only on that configured model \u2014 they do not follow the model
23134
+ selected inside the pi extension.
23099
23135
 
23100
23136
  The pi extension has its own optional overlay file:
23101
23137
  pi extension: ${p.piIfaceEnv}
@@ -10,7 +10,8 @@ pi-research software.
10
10
  Already running the `pi` extension (`pi install npm:@lincoln504/pi-research`)? You
11
11
  already have the engine — install the skill into your other agents with
12
12
  `/research-config` → Install in External Agents (see [Installation flow](#installation-flow))
13
- and skip the rest.
13
+ and skip the install commands below. The model step still applies to you: the
14
+ skill runs on its own configured `PI_RESEARCH_MODEL`, not your pi session's model.
14
15
 
15
16
  For standalone use without pi, install the engine globally, then link the skill into
16
17
  every coding agent detected on this machine:
@@ -24,6 +25,18 @@ pi-research skill install # link the skill into Claude, Codex, Op
24
25
  different skill in the slot, and records what it created so `pi-research skill uninstall`
25
26
  removes exactly that. Run `pi-research skill status` to see where it is installed.
26
27
 
28
+ Then configure the model research runs on — the skill and standalone CLI use only
29
+ this explicitly configured model (they never follow the model selected inside the
30
+ pi extension) and refuse to start without one:
31
+
32
+ ```sh
33
+ # ~/.pi/research/config.env (or export as an env var)
34
+ PI_RESEARCH_MODEL=provider/model-id
35
+ ```
36
+
37
+ If you use `pi`, the API key comes from your pi configuration (`~/.pi/agent/auth.json`) automatically; otherwise
38
+ also set `PI_RESEARCH_API_KEY` (same file or env var). See [Configuration](CONFIGURATION.md).
39
+
27
40
  On Windows, run `pi-research` from `cmd` or use `pi-research.cmd`: PowerShell's default
28
41
  execution policy (`Restricted`) blocks npm's `.ps1` shims ("running scripts is
29
42
  disabled"); or run `Set-ExecutionPolicy -Scope CurrentUser RemoteSigned` once.
@@ -114,8 +127,8 @@ Manual. Symlink the directory into any agent's skills folder yourself:
114
127
  ### Prerequisites
115
128
 
116
129
  - Node.js >= 22.19.0
117
- - `pi-research` installed where the launcher can find it, plus a model + API key.
118
- See [Configuration](CONFIGURATION.md).
130
+ - `pi-research` installed where the launcher can find it, plus a configured model
131
+ (`PI_RESEARCH_MODEL`) + API key. See [Configuration](CONFIGURATION.md).
119
132
 
120
133
  ```bash
121
134
  npm install -g @lincoln504/pi-research
@@ -75,7 +75,7 @@ Research
75
75
  | `PI_RESEARCH_RETRY_DELAY_MS` | `2000` | 100–10000 | Base delay between retries. |
76
76
  | `PI_RESEARCH_WORKER_THREADS` | `4` | 1–10 | Browser worker processes. Higher = more throughput, more CPU/RAM. |
77
77
  | `PI_RESEARCH_WORKER_CONCURRENCY` | `2` | 1–10 | Tasks per worker process. |
78
- | `PI_RESEARCH_MODEL` | _(session model)_ | — | Model override for researcher sub-agents (deep and quick) and knowledge synthesis. In the pi extension the coordinator and evaluator keep using the session model; on the standalone CLI/SDK, when no `--model`/`model` option is given, this also selects the session model itself. Accepts `provider/id` or a bare model id. |
78
+ | `PI_RESEARCH_MODEL` | _(pi: session model; CLI/skill: required)_ | — | The model research runs on. **Required for the standalone CLI / agent skill** — they use only this configured model (never the model selected inside the pi extension) and refuse to start without one. On the SDK it selects the session model when no `model` option is given. In the pi extension it overrides researcher sub-agents and knowledge synthesis, while the coordinator and evaluator keep using the session model. Accepts `provider/id` or a bare model id. |
79
79
  | `PI_RESEARCH_DISABLED_TOOLS` | _(none)_ | — | Comma-separated research tools to disable for a run (`search`, `scrape`, `security_search`, `stackexchange`, `youtube_transcript`, `grep`). Removed from every researcher's toolset and named in the coordinator/evaluator prompt. |
80
80
  | `PI_RESEARCH_REPORT_EXPORT_ENABLED` (TUI) | `false` | — | Front-ends write a Markdown report to disk and surface its path. |
81
81
  | `PI_RESEARCH_REPORT_EXPORT_DIR` | _(smart cwd)_ | — | Pin exported reports to a fixed directory, bypassing the cwd-relative resolution. Useful for the agent skill, which runs from the host agent's arbitrary directory. |
@@ -128,11 +128,11 @@ See the [knowledge store doc](KNOWLEDGE-STORE.md) for what each value does.
128
128
  | `PI_RESEARCH_EMBEDDING_MODEL_INIT_TIMEOUT_MS` | `300000` | 10000–600000 | Embedding-model initialization timeout (first-time download can be slow). |
129
129
  | `PI_RESEARCH_WEBGPU_REPROBE` | _(unset)_ | — | Set `1` to discard the cached WebGPU-viability verdict and probe again on next use. |
130
130
 
131
- API keys (all optional)
131
+ API keys
132
132
 
133
133
  | Variable | Description |
134
134
  |----------|-------------|
135
- | `PI_RESEARCH_API_KEY` / `PI_RESEARCH_PROVIDER` | Explicit LLM credentials for SDK / CLI mode (not needed when using pi's own auth). Provider is required alongside the key. |
135
+ | `PI_RESEARCH_API_KEY` / `PI_RESEARCH_PROVIDER` | Explicit LLM credentials for SDK / CLI mode (not needed when pi's configuration — `~/.pi/agent/auth.json` — supplies the key). On the CLI / agent skill both can also live in `config.env` / `cli.env`. The provider is required alongside the key, or inferred from a `provider/model-id` `PI_RESEARCH_MODEL`. |
136
136
  | `STACKEXCHANGE_API_KEY` | Raises the Stack Exchange tool's limit from 300/day to 10,000/day. Obtain at <https://stackapps.com/apps/oauth>. |
137
137
  | `GITHUB_TOKEN` | Raises the security tool's GitHub Advisory limit from 60/hr to 5000/hr (any default-scope token). |
138
138
  | `NVD_API_KEY` | Raises the security tool's NVD limit ~10× and tightens request spacing. Request at <https://nvd.nist.gov/developers/request-an-api-key>. Recommended when using severity-filtered security searches: those issue a second (CVSS v2) NVD query to catch v2-only CVEs, which roughly doubles request time against the 6 s/request unauthenticated throttle. |
package/docs/SDK.md CHANGED
@@ -13,6 +13,13 @@ run the `pi` extension, which keeps its own private copy your scripts can't impo
13
13
  npm install @lincoln504/pi-research
14
14
  ```
15
15
 
16
+ Then pick the model: pass `model` to `initResearchSDK`, or set `PI_RESEARCH_MODEL`
17
+ (env or `~/.pi/research/config.env`). The SDK never follows the model selected
18
+ inside the pi extension; only when neither is set does it fall back to the first
19
+ available model in your pi registry. The API key comes from your pi
20
+ configuration (`~/.pi/agent/auth.json`) automatically, or from the `apiKey`
21
+ option / `PI_RESEARCH_API_KEY` env var.
22
+
16
23
  `src/sdk.ts` is a library for scripts, CI, and custom tooling. It is configured
17
24
  from code, not from a global overlay file — there is no `sdk.env`. It reads the
18
25
  base `~/.pi/research/config.env` as a baseline, and everything is
@@ -83,7 +90,7 @@ extension and the CLI / agent skill do it when `PI_RESEARCH_REPORT_EXPORT_ENABLE
83
90
 
84
91
  | Option | Description |
85
92
  |--------|-------------|
86
- | `model` | `"provider/id"` string or a `Model` object. Omit to use the first available pi model. |
93
+ | `model` | `"provider/id"` string or a `Model` object. Omit to use the configured `PI_RESEARCH_MODEL`; only when neither is set does the SDK fall back to the first available pi model. |
87
94
  | `apiKey` / `provider` | Explicit credentials (provider required with apiKey). |
88
95
  | `config` | `Partial<Config>` overrides, applied over the base/defaults. |
89
96
  | `ignoreGlobalConfig` | Skip `config.env` entirely — defaults + `process.env` + `config` only. |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lincoln504/pi-research",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "A set of web research tools (search, scrape, and others) and research agent system that uses them safely and efficiently.",
5
5
  "type": "module",
6
6
  "private": false,
@@ -39,8 +39,8 @@ model/key is missing.
39
39
  ### Prerequisites
40
40
 
41
41
  - Node.js >= 22.19.0
42
- - `pi-research` installed somewhere the launcher can find it, and a model +
43
- API key configured. See [`references/configuration.md`](references/configuration.md).
42
+ - `pi-research` installed somewhere the launcher can find it, and a configured
43
+ model (`PI_RESEARCH_MODEL`) + API key. See [`references/configuration.md`](references/configuration.md).
44
44
 
45
45
  ```bash
46
46
  npm install -g @lincoln504/pi-research
@@ -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.0.2", "package": "@lincoln504/pi-research", "engine": "pi-research-sdk", "openclaw": { "requires": { "bins": ["node"] } } }
5
+ metadata: { "author": "Lincoln Deen", "version": "1.0.3", "package": "@lincoln504/pi-research", "engine": "pi-research-sdk", "openclaw": { "requires": { "bins": ["node"] } } }
6
6
  allowed-tools: Bash(node:*)
7
7
  ---
8
8
 
@@ -91,10 +91,13 @@ On exit 78 the engine prints exact configure locations to stderr — relay verba
91
91
  ```
92
92
  config file: ~/.pi/research/config.env
93
93
  env vars: PI_RESEARCH_API_KEY PI_RESEARCH_PROVIDER PI_RESEARCH_MODEL
94
- pi auth: ~/.pi/agent/auth.json
95
- pi models: ~/.pi/agent/models.json
94
+ pi config (keys): ~/.pi/agent/auth.json (supplies the API key only)
95
+ pi config (models): ~/.pi/agent/models.json
96
96
  ```
97
97
 
98
+ `PI_RESEARCH_MODEL` (a `provider/model-id`) is always required — pi's configuration
99
+ supplies only the key, and the skill never uses the model selected inside the pi extension.
100
+
98
101
  Run `status` for the resolved paths on this machine. Full reference:
99
102
  [`references/configuration.md`](references/configuration.md).
100
103
 
@@ -1,6 +1,8 @@
1
1
  ## Configuration
2
2
 
3
- The skill drives the **pi-research** engine, which needs a model + API key.
3
+ The skill drives the **pi-research** engine, which needs an explicitly configured
4
+ model (`PI_RESEARCH_MODEL`) + API key — it runs only on that configured model and
5
+ never follows the model selected inside the pi extension.
4
6
  Run `node "<SKILL_DIR>/scripts/run.mjs" status` to print what's detected and where
5
7
  to configure (no research runs).
6
8
 
@@ -13,7 +15,8 @@ to configure (no research runs).
13
15
  export PI_RESEARCH_MODEL=openai/gpt-4o # provider/model-id
14
16
  ```
15
17
  2. **Global config** `~/.pi/research/config.env` (same KEY=VALUE keys; read at startup).
16
- 3. **pi auth** (if you use `pi`): `~/.pi/agent/auth.json` + `~/.pi/agent/models.json`.
18
+ 3. **pi configuration** (if you use `pi`): the API key comes from `~/.pi/agent/auth.json`
19
+ automatically — `PI_RESEARCH_MODEL` must still be set (env or `config.env`).
17
20
 
18
21
  Real env vars always win. Optional CLI-only overlay `~/.pi/research/cli.env` layers
19
22
  over `config.env` for the CLI only. Precedence:
@@ -23,7 +26,7 @@ over `config.env` for the CLI only. Precedence:
23
26
 
24
27
  | Variable | Default | Description |
25
28
  |----------|---------|-------------|
26
- | `PI_RESEARCH_MODEL` | session model | Model override for all research sub-tasks. |
29
+ | `PI_RESEARCH_MODEL` | _(required)_ | The `provider/model-id` all research runs on. |
27
30
  | `PI_RESEARCH_DEFAULT_RESEARCH_DEPTH` | `1` | Default depth (1–3) when `--depth` omitted. |
28
31
  | `PI_RESEARCH_MAX_RESEARCHERS` | `3` | Parallel researchers (1–5). |
29
32
  | `PI_RESEARCH_TIMEOUT_MS` | `300000` | Per-researcher timeout (180000–1800000). |
@@ -70,7 +73,7 @@ model/key; message lists the fix) · `70` runtime error (network/provider/intern
70
73
  `export PI_RESEARCH_BIN=/path/to/pi-research/dist/cli.mjs` (exact CLI entry;
71
74
  checked first — also the escape hatch for unusual Windows shim layouts).
72
75
  - **"No model or API key" (78 from engine):** set the three env vars (or config /
73
- pi auth), then re-run `status`.
76
+ pi's configuration), then re-run `status`.
74
77
  - **Rate limit (70):** provider returned 429 — wait and retry.
75
78
  - **GPU/embedding errors:** set `PI_RESEARCH_EMBEDDING_DEVICE=cpu` (headless/no-GPU).
76
79
 
@@ -139,10 +139,11 @@ function notInstalled() {
139
139
  " # or, with pi: pi install npm:@lincoln504/pi-research",
140
140
  " # or point at a copy: export PI_RESEARCH_PATH=/path/to/pi-research",
141
141
  "",
142
- "After installing, configure a model + API key. Locations:",
142
+ "After installing, configure the model (PI_RESEARCH_MODEL is required) + API key.",
143
+ "Locations:",
143
144
  ` \u2022 env vars: PI_RESEARCH_API_KEY / PI_RESEARCH_PROVIDER / PI_RESEARCH_MODEL`,
144
145
  ` \u2022 global config file: ${join(home, ".pi", "research", "config.env")}`,
145
- ` \u2022 pi auth storage: ${join(home, ".pi", "agent", "auth.json")}`,
146
+ ` \u2022 pi config (keys): ${join(home, ".pi", "agent", "auth.json")} (supplies the API key only)`,
146
147
  "",
147
148
  "Run `status` once installed to verify detection.",
148
149
  ""
package/src/cli.ts CHANGED
@@ -196,7 +196,7 @@ export interface ResolvedConfigPaths {
196
196
  cliIfaceEnv: string;
197
197
  /** Per-interface overlay for the pi extension (~/.pi/research/pi.env). */
198
198
  piIfaceEnv: string;
199
- /** pi auth storage (~/.pi/agent/auth.json). */
199
+ /** pi's key file (~/.pi/agent/auth.json), part of pi's configuration. */
200
200
  piAuth: string;
201
201
  /** pi model definitions (~/.pi/agent/models.json). */
202
202
  piModels: string;
@@ -300,9 +300,29 @@ function detectCredentials(): CredentialDetection {
300
300
  };
301
301
  }
302
302
 
303
- // --- pi auth storage path --------------------------------------------------
303
+ // --- pi configuration path (key from auth.json) -----------------------------
304
304
  if (piAuthPresent) {
305
- // The SDK will resolve a model from the registry (models.json + PI_RESEARCH_MODEL).
305
+ // Keys come from auth.json, but the MODEL must be configured explicitly:
306
+ // the standalone CLI / agent skill run only on the configured model — they
307
+ // never follow the model selected inside the pi extension, and silently
308
+ // falling back to the registry's first authed entry picks a model nobody
309
+ // chose. Fail fast here (pre-flight, before any SDK init).
310
+ if (!model) {
311
+ return {
312
+ source: 'pi-config',
313
+ apiKeyConfigured: true,
314
+ provider,
315
+ model,
316
+ modelFrom,
317
+ piAuthPresent,
318
+ piModelsPresent,
319
+ problem:
320
+ 'pi credentials were found (auth.json), but no research model is configured. ' +
321
+ 'The standalone CLI / agent skill run only on an explicitly configured model — they do not ' +
322
+ 'follow the model selected inside the pi extension. Set PI_RESEARCH_MODEL to a ' +
323
+ '"provider/model-id" (in the environment, or in config.env / cli.env below).',
324
+ };
325
+ }
306
326
  return {
307
327
  source: 'pi-config',
308
328
  apiKeyConfigured: true, // key lives in auth.json; SDK reads it
@@ -342,14 +362,14 @@ function configBlock(det: CredentialDetection, extraNote?: string): string {
342
362
  lines.push(` • base config file: ${paths.configEnv}`);
343
363
  lines.push(` • cli overlay: ${paths.cliIfaceEnv} (optional; overrides base for this CLI / agent skill)`);
344
364
  lines.push(` • env vars: PI_RESEARCH_API_KEY / PI_RESEARCH_PROVIDER / PI_RESEARCH_MODEL`);
345
- lines.push(` • pi auth storage: ${paths.piAuth}`);
346
- lines.push(` • pi models: ${paths.piModels}`);
365
+ lines.push(` • pi config (keys): ${paths.piAuth}`);
366
+ lines.push(` • pi config (models): ${paths.piModels}`);
347
367
  lines.push('');
348
368
  lines.push('Detected:');
349
369
  lines.push(` • credential source: ${det.source}`);
350
370
  lines.push(` • api key configured: ${det.apiKeyConfigured}`);
351
- lines.push(` • provider: ${det.provider ?? '(unset)'}`);
352
- lines.push(` • model: ${det.model ?? '(unset)'} [from: ${det.modelFrom}]`);
371
+ lines.push(` • provider: ${det.provider ?? '(unset — inferred from a provider/model-id model)'}`);
372
+ lines.push(` • model: ${det.model ? `${det.model} [from: ${det.modelFrom}]` : '(not set — set PI_RESEARCH_MODEL, required)'}`);
353
373
  lines.push(` • pi auth.json: ${det.piAuthPresent ? 'present' : 'absent'}`);
354
374
  lines.push(` • pi models.json: ${det.piModelsPresent ? 'present' : 'absent'}`);
355
375
  if (extraNote) lines.push(` • note: ${extraNote}`);
@@ -657,12 +677,23 @@ async function cmdSkill(s: NonNullable<ParsedArgs['skill']>): Promise<number> {
657
677
  } catch (err) {
658
678
  return reportError(err, 'skill install', s.json);
659
679
  }
680
+ // The skill runs only on an explicitly configured model — if none is set yet,
681
+ // say so right here, at the moment of install (mirrors the TUI install action).
682
+ const skillModel =
683
+ process.env['PI_RESEARCH_MODEL'] ?? getConfig(process.cwd(), 'cli').RESEARCH_MODEL;
660
684
  if (s.json) {
661
- toStdout(pretty({ command: 'skill', action: 'install', dryRun: !!s.dryRun, results }));
685
+ toStdout(pretty({ command: 'skill', action: 'install', dryRun: !!s.dryRun, results, modelConfigured: Boolean(skillModel) }));
662
686
  } else {
663
687
  toStdout(`pi-research agent skill — ${s.dryRun ? 'install (dry run)' : 'install'}\n\n`);
664
688
  for (const r of results) toStdout(` ${r.tool.padEnd(10)} ${describeInstall(r)}\n`);
665
689
  toStdout(`\nThe skill activates automatically — ask the agent to research something.\n`);
690
+ if (!skillModel) {
691
+ toStdout(
692
+ `\nRequired: set PI_RESEARCH_MODEL=provider/model-id in ${resolvedConfigPaths().configEnv}\n` +
693
+ `(or as an env var) — the skill runs only on this configured model, not your pi\n` +
694
+ `session model. Verify with: ${BINARY_NAME} status\n`
695
+ );
696
+ }
666
697
  }
667
698
  return results.some((r) => r.status === 'error') ? EXIT.SOFTWARE : EXIT.OK;
668
699
  }
@@ -842,11 +873,15 @@ COMMANDS
842
873
  CONFIGURE
843
874
  Credentials are resolved in this order (first match wins):
844
875
 
845
- env vars: PI_RESEARCH_API_KEY PI_RESEARCH_PROVIDER PI_RESEARCH_MODEL
846
- cli overlay: ${p.cliIfaceEnv} (optional; this CLI / agent skill only)
847
- base config: ${p.configEnv}
848
- pi auth: ${p.piAuth}
849
- pi models: ${p.piModels}
876
+ env vars: PI_RESEARCH_API_KEY PI_RESEARCH_PROVIDER PI_RESEARCH_MODEL
877
+ cli overlay: ${p.cliIfaceEnv} (optional; this CLI / agent skill only)
878
+ base config: ${p.configEnv}
879
+ pi config (keys): ${p.piAuth}
880
+ pi config (models): ${p.piModels}
881
+
882
+ A model is REQUIRED: set PI_RESEARCH_MODEL to a "provider/model-id". This CLI and
883
+ the agent skill run only on that configured model — they do not follow the model
884
+ selected inside the pi extension.
850
885
 
851
886
  The pi extension has its own optional overlay file:
852
887
  pi extension: ${p.piIfaceEnv}
@@ -23,7 +23,7 @@ import {
23
23
  } from '@earendil-works/pi-tui';
24
24
  import { setInteractiveTuiActive, initGlobalTuiController } from './tui/tui-controller.ts';
25
25
  import { normalizeSessionId } from './orchestration/session-state.ts';
26
- import { getConfig, saveConfig, resetConfig, getDbDir } from './config.ts';
26
+ import { getConfig, saveConfig, resetConfig, getDbDir, getGlobalEnvFilePath } from './config.ts';
27
27
  import { healthRegistry } from './healthcheck/index.ts';
28
28
  import { getService, clearService, tryGetServiceContainerFromCtx } from './core/service-registry.ts';
29
29
  import { ServiceNames, IKnowledgeStoreService } from './core/service-interfaces.ts';
@@ -648,6 +648,19 @@ async function installSkillAction(ctx: ExtensionContext, pi: ExtensionAPI): Prom
648
648
  }
649
649
  lines.push('', 'The skill activates automatically — just ask the agent to pi-research something.');
650
650
 
651
+ // The skill runs only on an explicitly configured model (it never follows the
652
+ // pi session model) — if none is configured yet, say so right here, at the
653
+ // moment of install, mirroring the CLI's `skill install` output.
654
+ const skillModel = process.env['PI_RESEARCH_MODEL'] ?? getConfig(ctx.cwd, 'cli').RESEARCH_MODEL;
655
+ if (!skillModel) {
656
+ lines.push(
657
+ '',
658
+ '**Required:** set `PI_RESEARCH_MODEL=provider/model-id` in ' +
659
+ `\`${getGlobalEnvFilePath()}\` (or as an env var) — the skill runs only on ` +
660
+ 'this configured model, not your pi session model.'
661
+ );
662
+ }
663
+
651
664
  pi.sendMessage({ customType: 'skill-install-result', content: lines.join('\n'), display: true });
652
665
  if (ctx.hasUI) {
653
666
  if (ok > 0) ctx.ui.notify(`Skill installed for ${ok} coding agent${ok === 1 ? '' : 's'}.`, 'info');
package/src/sdk.ts CHANGED
@@ -144,17 +144,21 @@ function _removeSignalHandlers(): void {
144
144
  * Options for initializing the Research SDK
145
145
  */
146
146
  export interface ResearchSDKOptions {
147
- /**
147
+ /**
148
148
  * The LLM model to use for coordination, planning, and evaluation.
149
- * If a string, it must match "provider/id" (e.g. "openai/gpt-4o").
150
- * If omitted, the first available model in pi's configuration will be used.
149
+ * If a string, it must match "provider/id" (e.g. "openai/gpt-4o"); a Model
150
+ * object is used as-is. If omitted, the configured RESEARCH_MODEL
151
+ * (PI_RESEARCH_MODEL / config.env, or the `config` option) is used; only
152
+ * when neither is set does the SDK fall back to the first available model
153
+ * in pi's configuration. The SDK never follows the model selected
154
+ * interactively inside the pi extension.
151
155
  */
152
156
  model?: string | Model<any>;
153
157
 
154
158
  /**
155
159
  * Optional explicit API key for the model.
156
160
  * If provided, `provider` must also be set.
157
- * If omitted, pi's global auth storage (~/.pi/agent/auth.json) is used.
161
+ * If omitted, the key from pi's configuration (~/.pi/agent/auth.json) is used.
158
162
  */
159
163
  apiKey?: string;
160
164
 
@@ -232,15 +236,18 @@ async function _doInit(options: ResearchSDKOptions = {}): Promise<void> {
232
236
  globalApiKey = options.apiKey || process.env['PI_RESEARCH_API_KEY'];
233
237
  let parsedProvider = options.provider || process.env['PI_RESEARCH_PROVIDER'];
234
238
 
235
- // Infer provider from model if not explicitly provided
236
- if (!parsedProvider && options.model) {
237
- if (typeof options.model === 'string') {
238
- const slashIdx = options.model.indexOf('/');
239
+ // Infer provider from the effective model if not explicitly provided. The
240
+ // effective model is the explicit option first, else the configured
241
+ // RESEARCH_MODEL — the same precedence the resolve below uses.
242
+ const effectiveModel = options.model ?? globalConfig.RESEARCH_MODEL;
243
+ if (!parsedProvider && effectiveModel) {
244
+ if (typeof effectiveModel === 'string') {
245
+ const slashIdx = effectiveModel.indexOf('/');
239
246
  if (slashIdx > 0) {
240
- parsedProvider = options.model.slice(0, slashIdx);
247
+ parsedProvider = effectiveModel.slice(0, slashIdx);
241
248
  }
242
- } else if ((options.model as any).provider) {
243
- parsedProvider = (options.model as any).provider;
249
+ } else if ((effectiveModel as any).provider) {
250
+ parsedProvider = (effectiveModel as any).provider;
244
251
  }
245
252
  }
246
253
 
@@ -252,8 +259,23 @@ async function _doInit(options: ResearchSDKOptions = {}): Promise<void> {
252
259
  globalRegistry = sharedBuildModelRegistry(globalApiKey, parsedProvider);
253
260
 
254
261
  try {
255
- // Resolve model using unified logic.
256
- globalModel = resolveModel(globalRegistry, typeof options.model === 'string' ? options.model : undefined, parsedProvider, globalApiKey);
262
+ // Resolve the model. Precedence: explicit option (a Model object is used
263
+ // as-is; it need not exist in the registry) → configured RESEARCH_MODEL →
264
+ // provider default → registry fallback. Seeding the resolver with the
265
+ // configured model is what keeps the coordinator (which runs on ctx.model)
266
+ // on the SAME model as the researchers/synthesis (which read
267
+ // RESEARCH_MODEL themselves) — without it a configured model would govern
268
+ // only the sub-agents and planning would silently run on the fallback.
269
+ if (effectiveModel && typeof effectiveModel === 'object' && (effectiveModel as any).id) {
270
+ globalModel = effectiveModel as Model<any>;
271
+ } else {
272
+ globalModel = resolveModel(
273
+ globalRegistry,
274
+ typeof effectiveModel === 'string' ? effectiveModel : undefined,
275
+ parsedProvider,
276
+ globalApiKey
277
+ );
278
+ }
257
279
 
258
280
  // Use the global container to ensure internal services can resolve dependencies
259
281
  globalContainer = getServiceContainer();