@aliou/pi-dev-kit 0.6.5 → 0.7.0

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.
@@ -1,15 +1,16 @@
1
1
  # Providers
2
2
 
3
- Providers add LLM backends to pi. They connect pi to model APIs, proxies, gateways, and custom streaming implementations.
3
+ Providers add or override LLM backends through `pi.registerProvider(name, config)`. Use them for proxies, custom endpoints, OAuth/SSO, and custom streaming APIs.
4
4
 
5
- This reference tracks the current `pi.registerProvider(name, config)` API from pi-mono. For full provider details and advanced examples, also read pi-mono `packages/coding-agent/docs/custom-provider.md`.
5
+ Read Pi `docs/custom-provider.md` and `docs/models.md` before implementing a provider.
6
6
 
7
- ## Registration
7
+ ## Quick Registration
8
8
 
9
9
  ```typescript
10
- import type { ExtensionAPI, ProviderConfig } from "@mariozechner/pi-coding-agent";
10
+ import type { ExtensionAPI, ProviderConfig } from "@earendil-works/pi-coding-agent";
11
11
 
12
- const myProviderConfig: ProviderConfig = {
12
+ const myProvider: ProviderConfig = {
13
+ name: "My Provider",
13
14
  baseUrl: "https://api.example.com/v1",
14
15
  apiKey: "MY_API_KEY",
15
16
  api: "openai-completions",
@@ -26,134 +27,292 @@ const myProviderConfig: ProviderConfig = {
26
27
  ],
27
28
  };
28
29
 
29
- export default function (pi: ExtensionAPI) {
30
- pi.registerProvider("my-provider", myProviderConfig);
30
+ export default function providersExtension(pi: ExtensionAPI) {
31
+ pi.registerProvider("my-provider", myProvider);
31
32
  }
32
33
  ```
33
34
 
34
- ## Common Registration Patterns
35
+ Use legacy `@mariozechner/*` imports only when the target `@earendil-works/*` package is not available yet.
35
36
 
36
- ### Override an existing provider
37
+ ## Async Model Discovery
37
38
 
38
- ```typescript
39
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
39
+ If models come from a remote endpoint, fetch them in an async extension factory, not `session_start`. Pi waits for the factory before startup continues, so models are available during startup and `pi --list-models`.
40
40
 
41
- export default function (pi: ExtensionAPI) {
42
- pi.registerProvider("anthropic", {
43
- baseUrl: "https://proxy.example.com",
41
+ ```typescript
42
+ export default async function providersExtension(pi: ExtensionAPI) {
43
+ const response = await fetch("http://localhost:1234/v1/models");
44
+ const payload = (await response.json()) as {
45
+ data: Array<{ id: string; name?: string; context_window?: number; max_tokens?: number }>;
46
+ };
47
+
48
+ pi.registerProvider("local-openai", {
49
+ name: "Local OpenAI",
50
+ baseUrl: "http://localhost:1234/v1",
51
+ apiKey: "LOCAL_OPENAI_API_KEY",
52
+ api: "openai-completions",
53
+ models: payload.data.map((model) => ({
54
+ id: model.id,
55
+ name: model.name ?? model.id,
56
+ reasoning: false,
57
+ input: ["text"],
58
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
59
+ contextWindow: model.context_window ?? 128000,
60
+ maxTokens: model.max_tokens ?? 4096,
61
+ })),
44
62
  });
45
63
  }
46
64
  ```
47
65
 
48
- Use this when you want to keep the built-in provider and model list, but change the endpoint and/or headers.
66
+ ## Override Existing Providers
49
67
 
50
- ### Register a new provider
68
+ When only `baseUrl` and/or `headers` are provided, Pi keeps built-in models and auth.
51
69
 
52
70
  ```typescript
53
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
54
-
55
- export default function (pi: ExtensionAPI) {
56
- pi.registerProvider("my-provider", {
57
- baseUrl: "https://api.example.com/v1",
58
- apiKey: "MY_API_KEY",
59
- api: "openai-completions",
60
- models: [
61
- {
62
- id: "my-model",
63
- name: "My Model",
64
- reasoning: false,
65
- input: ["text"],
66
- cost: { input: 0.5, output: 1.5, cacheRead: 0, cacheWrite: 0 },
67
- contextWindow: 128000,
68
- maxTokens: 8192,
69
- },
70
- ],
71
- });
72
- }
71
+ pi.registerProvider("anthropic", {
72
+ baseUrl: "https://proxy.example.com",
73
+ });
74
+
75
+ pi.registerProvider("openai", {
76
+ headers: {
77
+ "X-Custom-Header": "MY_HEADER_ENV_OR_LITERAL",
78
+ },
79
+ });
73
80
  ```
74
81
 
75
- ### Unregister a provider
82
+ ## Unregister Providers
76
83
 
77
84
  ```typescript
78
85
  pi.unregisterProvider("my-provider");
79
86
  ```
80
87
 
81
- This takes effect immediately at runtime.
88
+ Unregistering removes dynamic models, API key fallback, OAuth registration, and stream handlers. Built-in behavior overridden by that provider is restored.
82
89
 
83
90
  ## ProviderConfig Fields
84
91
 
85
- | Field | Type | Description |
86
- |---|---|---|
87
- | `baseUrl` | `string` | Base URL for the provider or proxy. |
88
- | `headers` | `Record<string, string>` | Optional static headers to add to requests. |
89
- | `apiKey` | `string` | Environment variable name containing the API key. |
90
- | `api` | `"openai-completions" \| "openai-responses"` | Compatibility mode for request/response handling. |
91
- | `models` | `ProviderModelConfig[]` | Model definitions exposed by this provider. |
92
- | `streamSimple` | `function` | Optional custom streaming implementation for non-standard APIs. |
93
- | `oauth` | `object` | Optional OAuth config for providers that need browser-based auth. |
92
+ | Field | Notes |
93
+ |---|---|
94
+ | `name` | Display name for `/login` and UI. |
95
+ | `baseUrl` | Endpoint URL. Required when defining models. |
96
+ | `apiKey` | API key, env var name, or auth value. Required unless `oauth` handles auth. |
97
+ | `api` | API type. Required at provider or model level when defining models. |
98
+ | `headers` | Static custom headers. Values can be env var names. |
99
+ | `authHeader` | When `true`, Pi sends `Authorization: Bearer <key>`. |
100
+ | `models` | Replaces registered models for this provider when provided. |
101
+ | `oauth` | Adds `/login` support. |
102
+ | `streamSimple` | Custom streaming implementation for non-standard APIs. |
103
+
104
+ Supported API types include:
105
+
106
+ - `anthropic-messages`
107
+ - `openai-completions`
108
+ - `openai-responses`
109
+ - `azure-openai-responses`
110
+ - `openai-codex-responses`
111
+ - `mistral-conversations`
112
+ - `google-generative-ai`
113
+ - `google-vertex`
114
+ - `bedrock-converse-stream`
115
+
116
+ Prefer a built-in API type. Use `streamSimple` only when the upstream API cannot be adapted with config and compatibility flags.
117
+
118
+ ## Model Fields
119
+
120
+ | Field | Required | Notes |
121
+ |---|---:|---|
122
+ | `id` | Yes | Model ID sent to the API. |
123
+ | `name` | Yes for provider extensions | Human-readable display name. |
124
+ | `api` | No | Model-level API override. |
125
+ | `baseUrl` | No | Model-level endpoint override. |
126
+ | `reasoning` | Yes | Whether extended thinking is supported. |
127
+ | `thinkingLevelMap` | No | Maps Pi thinking levels to provider-specific values; `null` hides unsupported levels. |
128
+ | `input` | Yes | `Array<"text" | "image">`. |
129
+ | `cost` | Yes | Per-million token cost: `{ input, output, cacheRead, cacheWrite }`. |
130
+ | `contextWindow` | Yes | Context window in tokens. |
131
+ | `maxTokens` | Yes | Maximum output tokens. |
132
+ | `headers` | No | Model-specific headers. |
133
+ | `compat` | No | Provider compatibility flags. |
134
+
135
+ ### Thinking level map
136
+
137
+ Use model-level `thinkingLevelMap`; do not use older `compat.reasoningEffortMap`.
94
138
 
95
- Use the built-in OpenAI-compatible path when possible. Reach for `streamSimple` only when the upstream API is not compatible enough.
139
+ ```typescript
140
+ {
141
+ id: "custom-reasoner",
142
+ name: "Custom Reasoner",
143
+ reasoning: true,
144
+ thinkingLevelMap: {
145
+ minimal: null,
146
+ low: null,
147
+ medium: null,
148
+ high: "default",
149
+ xhigh: "max",
150
+ },
151
+ input: ["text"],
152
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
153
+ contextWindow: 128000,
154
+ maxTokens: 8192,
155
+ }
156
+ ```
157
+
158
+ ## Compatibility Flags
96
159
 
97
- ## Model Definition
160
+ For OpenAI-compatible servers, use `compat` instead of custom streaming when possible.
98
161
 
99
- The exact model type has more fields, but these are the ones you will usually need:
162
+ ```typescript
163
+ compat: {
164
+ supportsDeveloperRole: false,
165
+ supportsReasoningEffort: false,
166
+ supportsUsageInStreaming: false,
167
+ maxTokensField: "max_tokens",
168
+ requiresToolResultName: true,
169
+ thinkingFormat: "qwen-chat-template",
170
+ cacheControlFormat: "anthropic",
171
+ }
172
+ ```
100
173
 
101
- | Field | Type | Description |
102
- |---|---|---|
103
- | `id` | `string` | Model identifier within the provider config. |
104
- | `name` | `string` | Display name shown in model selection UI. |
105
- | `reasoning` | `boolean` | Whether the model is a reasoning model. |
106
- | `input` | `Array<"text" \| "image" \| "audio" \| "pdf">` | Input modalities supported by the model. |
107
- | `cost` | `object` | `{ input, output, cacheRead, cacheWrite }` cost values. |
108
- | `contextWindow` | `number` | Maximum context window. |
109
- | `maxTokens` | `number` | Maximum output tokens. |
174
+ Common flags:
110
175
 
111
- ## API Key Gating
176
+ - `supportsDeveloperRole`
177
+ - `supportsReasoningEffort`
178
+ - `supportsUsageInStreaming`
179
+ - `maxTokensField`
180
+ - `requiresToolResultName`
181
+ - `requiresAssistantAfterToolResult`
182
+ - `requiresThinkingAsText`
183
+ - `requiresReasoningContentOnAssistantMessages`
184
+ - `thinkingFormat`
185
+ - `cacheControlFormat`
186
+ - `supportsStrictMode`
187
+ - `supportsLongCacheRetention`
188
+ - `openRouterRouting`
189
+ - `vercelGatewayRouting`
112
190
 
113
- Provider registration and extension tool registration are separate concerns.
191
+ For Anthropic-compatible proxies, check `supportsEagerToolInputStreaming` and `supportsLongCacheRetention` in Pi docs.
114
192
 
115
- For providers:
116
- - Register the provider with `pi.registerProvider(name, config)`.
117
- - Point `apiKey` at the environment variable name that holds the credential.
118
- - If the provider should exist even when tools are disabled, still register it.
193
+ ## OAuth Providers
119
194
 
120
- For tools and commands that require the same credential:
121
- - Gate those registrations separately in your extension entry point.
195
+ OAuth integrates with `/login`.
122
196
 
123
197
  ```typescript
124
- export default function (pi: ExtensionAPI) {
125
- pi.registerProvider("my-provider", {
126
- baseUrl: "https://api.example.com/v1",
127
- apiKey: "MY_API_KEY",
128
- api: "openai-completions",
129
- models: [
130
- {
131
- id: "my-model",
132
- name: "My Model",
133
- reasoning: false,
134
- input: ["text"],
135
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
136
- contextWindow: 128000,
137
- maxTokens: 4096,
138
- },
139
- ],
140
- });
198
+ import type { OAuthCredentials, OAuthLoginCallbacks } from "@earendil-works/pi-ai";
199
+
200
+ pi.registerProvider("corporate-ai", {
201
+ name: "Corporate AI",
202
+ baseUrl: "https://ai.corp.com/v1",
203
+ api: "openai-responses",
204
+ models,
205
+ oauth: {
206
+ name: "Corporate AI (SSO)",
207
+ async login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {
208
+ callbacks.onAuth({ url: "https://sso.corp.com/authorize" });
209
+ const code = await callbacks.onPrompt({ message: "Enter code:" });
210
+ return exchangeCodeForTokens(code);
211
+ },
212
+ async refreshToken(credentials) {
213
+ return refreshTokens(credentials.refresh);
214
+ },
215
+ getApiKey(credentials) {
216
+ return credentials.access;
217
+ },
218
+ modifyModels(models, credentials) {
219
+ return models;
220
+ },
221
+ },
222
+ });
223
+ ```
224
+
225
+ Use `modifyModels` when subscription, tenant, or region information in credentials changes model endpoints or availability.
141
226
 
142
- if (!process.env.MY_API_KEY) return;
227
+ ## API Key Gating
228
+
229
+ Provider registration and tool registration are separate.
230
+
231
+ - Register providers even when the API key is absent; Pi resolves auth and can show login/setup UI.
232
+ - Gate tools and commands that directly call the same API.
233
+
234
+ ```typescript
235
+ export default function extension(pi: ExtensionAPI) {
236
+ pi.registerProvider("my-provider", providerConfig);
143
237
 
144
- pi.registerTool(mySearchTool);
145
- pi.registerCommand("quota", { handler: showQuota });
238
+ if (!process.env.MY_API_KEY) {
239
+ pi.on("session_start", (_event, ctx) => {
240
+ ctx.ui.notify("MY_API_KEY not set. my-provider tools disabled.", "warning");
241
+ });
242
+ return;
243
+ }
244
+
245
+ pi.registerTool(createSearchTool(process.env.MY_API_KEY));
246
+ }
247
+ ```
248
+
249
+ Missing keys should disable affected tools gracefully, not crash extension loading.
250
+
251
+ ## Custom Streaming
252
+
253
+ Use `streamSimple` only for non-standard APIs. Follow Pi provider implementations and tests.
254
+
255
+ Basic pattern:
256
+
257
+ ```typescript
258
+ import {
259
+ calculateCost,
260
+ createAssistantMessageEventStream,
261
+ type AssistantMessage,
262
+ type Context,
263
+ type Model,
264
+ type SimpleStreamOptions,
265
+ } from "@earendil-works/pi-ai";
266
+
267
+ function streamMyProvider(model: Model<any>, context: Context, options?: SimpleStreamOptions) {
268
+ const stream = createAssistantMessageEventStream();
269
+
270
+ (async () => {
271
+ const output: AssistantMessage = {
272
+ role: "assistant",
273
+ content: [],
274
+ api: model.api,
275
+ provider: model.provider,
276
+ model: model.id,
277
+ usage: {
278
+ input: 0,
279
+ output: 0,
280
+ cacheRead: 0,
281
+ cacheWrite: 0,
282
+ totalTokens: 0,
283
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
284
+ },
285
+ stopReason: "stop",
286
+ timestamp: Date.now(),
287
+ };
288
+
289
+ try {
290
+ stream.push({ type: "start", partial: output });
291
+ // Push text/thinking/toolcall events as data arrives.
292
+ calculateCost(model, output.usage);
293
+ stream.push({ type: "done", reason: "stop", message: output });
294
+ stream.end();
295
+ } catch (error) {
296
+ output.stopReason = options?.signal?.aborted ? "aborted" : "error";
297
+ output.errorMessage = error instanceof Error ? error.message : String(error);
298
+ stream.push({ type: "error", reason: output.stopReason, error: output });
299
+ stream.end();
300
+ }
301
+ })();
302
+
303
+ return stream;
146
304
  }
147
305
  ```
148
306
 
149
- That pattern keeps provider setup accurate while still hiding tools that cannot work without credentials.
307
+ Test custom providers against streaming, abort, usage, unicode, tool call, and context-overflow cases.
150
308
 
151
- ## When to read the upstream docs
309
+ ## Checklist
152
310
 
153
- Also read pi-mono `packages/coding-agent/docs/custom-provider.md` when you need:
154
- - custom streaming via `streamSimple`
155
- - OAuth support
156
- - proxying existing providers
157
- - header injection
158
- - provider teardown with `pi.unregisterProvider()`
159
- - advanced model config details
311
+ - [ ] Provider uses `pi.registerProvider(name, config)`.
312
+ - [ ] Dynamic model discovery happens in an async factory.
313
+ - [ ] Existing provider overrides do not redefine models unless needed.
314
+ - [ ] New models include current fields and `thinkingLevelMap` when relevant.
315
+ - [ ] Compatibility flags are used before custom streaming.
316
+ - [ ] OAuth providers implement login, refresh, getApiKey, and optional modifyModels.
317
+ - [ ] Tools needing the same credential are gated separately.
318
+ - [ ] Missing credentials produce a notification, not a crash.
@@ -1,12 +1,10 @@
1
1
  # Publishing
2
2
 
3
- Extensions are published to npm and installed with `pi install`.
3
+ Pi packages are published to npm or installed from git/local paths with `pi install`.
4
4
 
5
5
  ## Package Setup
6
6
 
7
- The `package.json` must have the `pi` key declaring extension resources. See `references/structure.md` for the full template.
8
-
9
- Key fields for publishing:
7
+ A publishable package needs Pi metadata and discoverability fields.
10
8
 
11
9
  ```json
12
10
  {
@@ -15,20 +13,42 @@ Key fields for publishing:
15
13
  "type": "module",
16
14
  "license": "MIT",
17
15
  "private": false,
16
+ "keywords": ["pi-package", "pi-extension", "pi"],
18
17
  "publishConfig": { "access": "public" },
19
18
  "files": ["src", "README.md"],
20
19
  "pi": {
21
- "extensions": ["./src/index.ts"]
20
+ "extensions": ["./src/tools/index.ts", "./src/commands/index.ts"]
22
21
  },
23
22
  "peerDependencies": {
24
- "@mariozechner/pi-coding-agent": ">=0.51.0"
23
+ "@earendil-works/pi-coding-agent": "*",
24
+ "typebox": "*"
25
+ },
26
+ "peerDependenciesMeta": {
27
+ "@earendil-works/pi-coding-agent": { "optional": true },
28
+ "typebox": { "optional": true }
25
29
  }
26
30
  }
27
31
  ```
28
32
 
33
+ Use the legacy `@mariozechner/*` namespace only while the target Pi packages are not published under `@earendil-works/*`.
34
+
35
+ Pi core packages imported at runtime belong in optional `peerDependencies` with `"*"`. Keep exact target versions in `devDependencies` for local type checking. Third-party runtime packages belong in `dependencies`.
36
+
37
+ ## Installation Specs
38
+
39
+ Users can install with:
40
+
41
+ ```bash
42
+ pi install npm:@scope/pi-my-extension
43
+ pi install git:github.com/org/pi-my-extension@v1.0.0
44
+ pi install ./relative/path/to/package
45
+ ```
46
+
47
+ By default, global installs write to `~/.pi/agent/settings.json`; `-l` writes project settings.
48
+
29
49
  ## Versioning with Changesets
30
50
 
31
- Use [changesets](https://github.com/changesets/changesets) for versioning and changelogs.
51
+ Use Changesets for versioning and changelogs.
32
52
 
33
53
  ### `.changeset/config.json`
34
54
 
@@ -48,92 +68,86 @@ Use [changesets](https://github.com/changesets/changesets) for versioning and ch
48
68
 
49
69
  ### Creating a changeset
50
70
 
51
- The interactive CLI (`pnpm changeset`) is not available in headless environments. Write the file directly instead. Create `.changeset/<descriptive-name>.md`:
71
+ In headless agent sessions, write the file directly instead of running the interactive CLI.
52
72
 
53
73
  ```md
54
74
  ---
55
75
  "@scope/pi-my-extension": patch
56
76
  ---
57
77
 
58
- Description of what changed and why it matters to users.
78
+ Describe the user-visible change.
59
79
  ```
60
80
 
61
- Bump types: `patch` for fixes and internal changes, `minor` for new user-facing features, `major` for breaking changes.
81
+ Use:
82
+
83
+ - `patch` for fixes and internal compatibility updates.
84
+ - `minor` for new user-facing features.
85
+ - `major` for breaking changes.
62
86
 
63
- Commit the changeset file alongside the changes it describes. Multiple changeset files can coexist — they are all consumed together on the next release.
87
+ Commit the changeset with the change it describes.
64
88
 
65
- ### Releasing manually (no CI)
89
+ ### Manual release
66
90
 
67
91
  ```bash
68
- pnpm changeset version # consumes .changeset/*.md, bumps version, updates CHANGELOG.md
69
- pnpm changeset publish # publishes to npm
92
+ pnpm changeset version
93
+ pnpm changeset publish
70
94
  ```
71
95
 
72
96
  ## GitHub Actions Automation
73
97
 
74
- The recommended setup uses a single `publish.yml` workflow that runs on every push to `main`. It handles two cases automatically:
75
-
76
- - **Pending changesets present**: opens (or updates) a version PR titled `Updating @scope/pi-my-extension to version X.Y.Z`.
77
- - **Version PR merged**: publishes the package to npm and creates a GitHub release with a matching git tag.
78
-
79
- Copy `.github/workflows/publish.yml` from `pi-extension-template` into the new repo. It uses `changesets/action@v1` under the hood.
98
+ Use the template publish workflow from `pi-extension-template`.
80
99
 
81
- The workflow requires two secrets, configured in the repo's GitHub settings under **Settings → Secrets and variables → Actions**:
100
+ It handles:
82
101
 
83
- - `GITHUB_TOKEN` automatically provided by GitHub Actions, no setup needed.
84
- - `NPM_TOKEN` an npm automation token with publish access to the `@scope` org. Create one at npmjs.com under **Access Tokens → Generate New Token → Automation**. Add it as a repository secret named `NPM_TOKEN`. Without this, the publish step will fail silently on the version PR merge.
102
+ - Pending changesets: opens or updates a version PR.
103
+ - Version PR merged: publishes to npm and creates a GitHub release.
85
104
 
86
- The workflow also sets `NPM_CONFIG_PROVENANCE=true`, which links the published package to the GitHub Actions run for supply chain transparency (requires the `id-token: write` permission, already included in the template).
105
+ Required secrets:
87
106
 
88
- ## First-time Setup for a New Package
107
+ - `GITHUB_TOKEN`: provided by GitHub Actions.
108
+ - `NPM_TOKEN`: npm automation token with publish access.
89
109
 
90
- Before the workflow can publish a package that has never been on npm:
110
+ Keep `NPM_CONFIG_PROVENANCE=true` and `id-token: write` for provenance.
91
111
 
92
- 1. Make sure `"private": false` and `"publishConfig": { "access": "public" }` are in `package.json`.
93
- 2. Add the `NPM_TOKEN` secret to the repo (see above).
94
- 3. The first time the version PR is merged, the workflow publishes the package. npm will create the package entry automatically — no manual `npm publish` needed.
112
+ ## First Publish
95
113
 
96
- If the package name is scoped (e.g., `@aliou/pi-my-extension`) and the scope is new to your npm account, you may need to create the scope first at npmjs.com or run `npm publish --access public` once manually to register it.
114
+ Before first publish:
97
115
 
98
- ## Installation
99
-
100
- Users install extensions with:
101
-
102
- ```bash
103
- pi install @scope/pi-my-extension
104
- ```
116
+ 1. Set `"private": false`.
117
+ 2. Set `"publishConfig": { "access": "public" }` for scoped public packages.
118
+ 3. Add `NPM_TOKEN` to repo secrets.
119
+ 4. Merge the first Changesets version PR.
105
120
 
106
- Pi reads the `pi` key from the package's `package.json` to discover extensions, skills, themes, and prompts.
121
+ npm creates the package entry on first publish. If the npm scope is new to your account, you may need to create the scope or publish once manually with `--access public`.
107
122
 
108
- ## Dependency Management in Monorepos
123
+ ## Monorepo Dependency Rules
109
124
 
110
- If publishing from a monorepo that contains both public and private packages:
125
+ Public packages cannot depend on private workspace packages. Users installing from npm will not have those private packages.
111
126
 
112
- **Critical rule**: Public packages cannot depend on private workspace packages. This will break when users try to install your package from npm.
127
+ When adding a dependency:
113
128
 
114
- In the pi-extensions monorepo, this is enforced by:
115
- - Pre-commit hook that blocks commits with invalid dependencies
116
- - CI check that prevents merging bad dependencies
117
- - `pnpm run check:public-deps` validates all dependencies
129
+ 1. If it is a public workspace package, use `workspace:^` in the monorepo and make sure it is published.
130
+ 2. If it is private, do not depend on it from a public package.
131
+ 3. For external packages, use a normal npm range.
118
132
 
119
- When adding a workspace dependency to a `package.json`:
120
- 1. Check if the dependency is public (`"private": false` or `"publishConfig": { "access": "public" }`).
121
- 2. If the dependency is private, either make it public, make your package private, or remove the dependency.
133
+ Run the repo's public-dependency check when available, for example `pnpm run check:public-deps`.
122
134
 
123
135
  ## Pre-publish Checklist
124
136
 
125
- - [ ] `"private": false` is set.
126
- - [ ] `"publishConfig": { "access": "public" }` is set.
127
- - [ ] `"files"` lists only what users need (`["src", "README.md"]`).
128
- - [ ] `peerDependencies` version range is correct (`>=` minimum supported version).
129
- - [ ] `@mariozechner/pi-tui` is in `peerDependencies` with `optional: true` in `peerDependenciesMeta` if imported at runtime.
130
- - [ ] `prepare` script is `[ -d .git ] && husky || true`, not bare `husky`.
131
- - [ ] `check:lockfile` script is present.
132
- - [ ] `description` is clear and concise.
133
- - [ ] `pi.extensions` paths are correct.
134
- - [ ] `NPM_TOKEN` secret is set on the GitHub repo.
135
- - [ ] `.github/workflows/publish.yml` is present.
136
- - [ ] If in a monorepo: no dependency on private workspace packages (`pnpm run check:public-deps` if available).
137
- - [ ] README documents what the extension does, required environment variables, and available tools/commands.
138
- - [ ] If wrapping a third-party API: extension handles missing API key gracefully (notification, not crash).
137
+ - [ ] `private` is `false`.
138
+ - [ ] `publishConfig.access` is `public` for scoped public packages.
139
+ - [ ] `keywords` includes `pi-package`.
140
+ - [ ] `files` lists only shipped files users need.
141
+ - [ ] `pi.extensions`, `pi.skills`, `pi.prompts`, and `pi.themes` paths are correct.
142
+ - [ ] Demo `pi.video` or `pi.image` metadata is present when available.
143
+ - [ ] Imported Pi core packages are optional peers with `"*"`.
144
+ - [ ] Imported Pi core packages are exact dev dependencies for type checking.
145
+ - [ ] Third-party runtime packages are in `dependencies`.
146
+ - [ ] No private workspace dependencies in public packages.
147
+ - [ ] `prepare` is `[ -d .git ] && husky || true`, not bare `husky`.
148
+ - [ ] `check:lockfile` exists.
149
+ - [ ] README documents setup, tools, commands, providers, env vars, and limitations.
150
+ - [ ] Missing API keys are handled with notifications or disabled features, not crashes.
139
151
  - [ ] `pnpm typecheck` and `pnpm lint` pass.
152
+ - [ ] `.github/workflows/publish.yml` is present if using CI publish.
153
+ - [ ] `NPM_TOKEN` is configured before relying on CI publish.