localpi 0.6.0 → 0.6.2

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
@@ -4,7 +4,7 @@
4
4
  <img src="assets/cover.svg" alt="localpi: an unopinionated Pi distribution that makes it easy to test and work with small local models on constrained systems" width="880">
5
5
  </p>
6
6
 
7
- Localpi is an unopinionated Pi distribution that makes it easy to test and work with small local models on constrained systems.
7
+ Localpi is an unopinionated [Pi](https://pi.dev) distribution that makes it easy to test and work with small local models on constrained systems.
8
8
 
9
9
  By default, Localpi discovers available local providers, lets you choose when more than one model is loaded, points Pi at the selected model, and writes Pi config for the other discovered models so `/model` can switch among them during the session.
10
10
 
@@ -216,9 +216,10 @@ Approval still works: the adapter forwards Pi's extension dialogs to the ACP cli
216
216
  asks before a tool call runs. The adapter does not pass `--no-extensions`, so Pi extension discovery
217
217
  stays on.
218
218
 
219
- Pi has no ACP mode of its own. ACP support comes from `pi-acp` (MIT), which `package.json` pins to
220
- an exact version, and localpi never vendors its source. Set `LOCALPI_ACP_ADAPTER` to a path to run a
221
- different adapter build.
219
+ Pi has no ACP mode of its own. ACP support comes from
220
+ [`pi-acp`](https://github.com/svkozak/pi-acp) (MIT), which `package.json` pins to an exact version,
221
+ and localpi never vendors its source. Set `LOCALPI_ACP_ADAPTER` to a path to run a different adapter
222
+ build.
222
223
 
223
224
  ## Continue On Truncation
224
225
 
@@ -523,6 +524,7 @@ demowall record --session demowall-<timestamp> --out demo.mp4 --seconds 60
523
524
  - `--max-tokens <n>`: generated model max output tokens
524
525
  - `--continue-on-truncation <n>`: continue a reply cut off by the output limit, up to `n` times. Off by default, and `LOCALPI_CONTINUE_ON_TRUNCATION=<n>` sets the same limit
525
526
  - `--base-url <url>`: OpenAI-compatible endpoint for LM Studio or custom endpoints
527
+ - `--api-key <value>`: Pi provider API key for this run. A literal value, an environment reference such as `${NAME}`, or a `!command`. Default: `local`. `LOCALPI_API_KEY` sets the same value
526
528
  - `--server-command <path>`: `llama-server` executable path
527
529
  - `--llama-server <path>`: alias for `--server-command`
528
530
  - `--host <host>`: managed `llama-server` host. Default: `127.0.0.1`
@@ -582,6 +584,7 @@ explicit `PI_OFFLINE=0` or `PI_OFFLINE=1` always wins.
582
584
  - `LOCALPI_MODEL`
583
585
  - `LOCALPI_PROVIDER`
584
586
  - `LOCALPI_BASE_URL`
587
+ - `LOCALPI_API_KEY`
585
588
  - `LOCALPI_PROVIDERS_FILE`
586
589
  - `LOCALPI_MODEL_PROFILE`
587
590
  - `LOCALPI_MODEL_REASONING`
@@ -669,6 +672,20 @@ Model capability profiles can fill in metadata that OpenAI-compatible servers do
669
672
 
670
673
  `LOCALPAGER_AGENT_PROFILE`, `LOCALPAGER_AGENT_REASONING`, and `LOCALPAGER_AGENT_THINKING_FORMAT` are accepted as aliases so LocalPager Agent can pass the same profile metadata through to localpi.
671
674
 
675
+ ## Related Projects
676
+
677
+ - [Pi](https://pi.dev) is the coding agent this distribution launches. Pi owns model detection, the
678
+ tool loop, streaming, slash commands, and the session. Its source is
679
+ [earendil-works/pi](https://github.com/earendil-works/pi).
680
+ - [pi-factory](https://github.com/osolmaz/pi-factory) builds the Pi launch plan, the launch
681
+ environment, and the runtime config a session uses.
682
+ - [pi-demo-mode](https://github.com/osolmaz/pi-demo-mode) is the shared extension behind demo mode.
683
+ - [pi-acp](https://github.com/svkozak/pi-acp) is the adapter that ACP mode pins, for the
684
+ [Agent Client Protocol](https://agentclientprotocol.com). Localpi keeps no copy of its source.
685
+ - Inference stacks: [llama.cpp](https://github.com/ggml-org/llama.cpp) and its `llama-server`,
686
+ [LM Studio](https://lmstudio.ai), [vLLM](https://github.com/vllm-project/vllm),
687
+ [SGLang](https://github.com/sgl-project/sglang), and [Ollama](https://ollama.com).
688
+
672
689
  ## Development
673
690
 
674
691
  ```bash
@@ -19,6 +19,7 @@ export function defaultOptions() {
19
19
  return {
20
20
  runtime: parseRuntime(envString("LOCALPI_RUNTIME", "auto")),
21
21
  baseUrl: envOptionalBaseUrl("LOCALPI_BASE_URL"),
22
+ apiKey: envString("LOCALPI_API_KEY", "local"),
22
23
  model: process.env["LOCALPI_MODEL"],
23
24
  provider: process.env["LOCALPI_PROVIDER"],
24
25
  customProviderId: envString("LOCALPI_PROVIDER_ID", "local-openai"),
@@ -100,6 +101,8 @@ export function usage() {
100
101
  " --provider <id> catalog provider id to use",
101
102
  " --model <alias|id|path> model alias, backend id, or GGUF path",
102
103
  " --base-url <url> OpenAI-compatible endpoint",
104
+ " --api-key <value> Pi provider API key: a literal, ${NAME}, or !command",
105
+ " (LOCALPI_API_KEY=<value>, default: local)",
103
106
  " --ctx <n> model context window",
104
107
  " --context-window <n> alias for --ctx",
105
108
  " --max-tokens <n> generated model max output tokens",
@@ -189,6 +192,7 @@ const booleanFlagUpdaters = {
189
192
  const valueFlagUpdaters = {
190
193
  "--runtime": (options, value) => ({ ...options, runtime: parseRuntime(value) }),
191
194
  "--base-url": (options, value) => ({ ...options, baseUrl: normalizeBaseUrl(value) }),
195
+ "--api-key": (options, value) => ({ ...options, apiKey: value }),
192
196
  "--model": (options, value) => ({ ...options, model: value }),
193
197
  "--provider": (options, value) => ({ ...options, provider: value }),
194
198
  "--provider-id": (options, value) => ({ ...options, customProviderId: value }),
@@ -86,7 +86,7 @@ function providersForConnection(options, connection) {
86
86
  id: group.providerId,
87
87
  baseUrl: group.baseUrl,
88
88
  api: "openai-completions",
89
- apiKey: "local",
89
+ apiKey: options.apiKey,
90
90
  compat: {
91
91
  supportsDeveloperRole: false,
92
92
  supportsReasoningEffort: false
@@ -1,6 +1,6 @@
1
1
  export function startupModelSelectorExtensionSource(options) {
2
2
  const startupModelsSource = JSON.stringify(options.models);
3
- return `import type { ExtensionAPI, SettingsManager } from "@earendil-works/pi-coding-agent";
3
+ return `import type { ExtensionAPI, ModelRegistry, ModelRuntime } from "@earendil-works/pi-coding-agent";
4
4
  import { ModelSelectorComponent } from "@earendil-works/pi-coding-agent";
5
5
 
6
6
  type SelectedModel = Parameters<ExtensionAPI["setModel"]>[0];
@@ -22,20 +22,16 @@ export default function localpiStartupModelSelector(pi: ExtensionAPI): void {
22
22
  const scopedModels = selectableModels.map((model) => ({ model }));
23
23
 
24
24
  opened = true;
25
- const selected = await ctx.ui.custom<SelectedModel | undefined>((tui, _theme, _keybindings, done) => {
26
- const settings = {
27
- setDefaultModelAndProvider: () => {}
28
- } as unknown as SettingsManager;
29
- return new ModelSelectorComponent(
25
+ const selected = await ctx.ui.custom<SelectedModel | undefined>((tui, _theme, _keybindings, done) =>
26
+ new ModelSelectorComponent(
30
27
  tui,
31
28
  ctx.model,
32
- settings,
33
- startupModelRegistry(ctx.modelRegistry) as typeof ctx.modelRegistry,
29
+ startupModelRuntime(ctx.modelRegistry),
34
30
  scopedModels,
35
- (model) => done(model),
31
+ (model: SelectedModel) => done(model),
36
32
  () => done(undefined)
37
- );
38
- });
33
+ )
34
+ );
39
35
 
40
36
  if (selected === undefined) {
41
37
  return;
@@ -54,21 +50,20 @@ function startupAvailableModels(registry: {
54
50
  return registry.getAvailable().filter((model) => startupModelKeys.has(modelKey(model)));
55
51
  }
56
52
 
57
- function startupModelRegistry(registry: {
58
- refresh(): void;
59
- getError(): string | undefined;
60
- getAvailable(): SelectedModel[];
61
- find(provider: string, modelId: string): SelectedModel | undefined;
62
- }): typeof registry {
63
- return {
64
- refresh: () => registry.refresh(),
65
- getError: () => registry.getError(),
66
- getAvailable: () => startupAvailableModels(registry),
67
- find: (provider, modelId) => {
53
+ // The selector reads a ModelRuntime, which extensions cannot reach. It only calls
54
+ // getAvailableSnapshot, getModel, getError, and refresh, so this view answers
55
+ // those from the registry and hides every model outside the startup list.
56
+ function startupModelRuntime(registry: ModelRegistry): ModelRuntime {
57
+ const view = {
58
+ getAvailableSnapshot: () => startupAvailableModels(registry),
59
+ getModel: (provider: string, modelId: string) => {
68
60
  const model = registry.find(provider, modelId);
69
61
  return model !== undefined && startupModelKeys.has(modelKey(model)) ? model : undefined;
70
- }
62
+ },
63
+ getError: () => registry.getError(),
64
+ refresh: (options?: Parameters<ModelRegistry["refresh"]>[0]) => registry.refresh(options)
71
65
  };
66
+ return view as unknown as ModelRuntime;
72
67
  }
73
68
 
74
69
  function modelKey(model: { readonly provider: string; readonly id: string }): string {
@@ -110,6 +110,7 @@ Localpi:
110
110
  - can use `--provider <id>` to name the generated Pi provider
111
111
  - uses `/v1/models` for discovery
112
112
  - avoids assuming it can start, stop, or unload the backend
113
+ - writes the `--api-key` value as the generated Pi provider key, exactly as given, so an environment reference such as `${HF_TOKEN}` or a `!command` keeps the secret out of the Pi config. The default is `local`, which local engines accept and ignore
113
114
 
114
115
  ### Configured Providers
115
116
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "localpi",
3
- "version": "0.6.0",
3
+ "version": "0.6.2",
4
4
  "description": "Swiss army knife for running Pi with local inference engines.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -41,7 +41,7 @@
41
41
  "check": "npm run format && npm run lint && npm run typecheck && npm test && npm run build"
42
42
  },
43
43
  "devDependencies": {
44
- "@earendil-works/pi-coding-agent": "^0.87.0",
44
+ "@earendil-works/pi-coding-agent": "^0.87.1",
45
45
  "@eslint/js": "^9.0.0",
46
46
  "@stryker-mutator/core": "^9.6.1",
47
47
  "@stryker-mutator/typescript-checker": "^9.6.1",