@aliou/pi-dev-kit 0.4.9 → 0.5.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.
- package/README.md +60 -0
- package/package.json +59 -2
- package/src/commands/index.ts +6 -0
- package/src/commands/update.ts +144 -0
- package/src/index.ts +8 -0
- package/src/prompts/setup-demo.md +35 -0
- package/src/skills/demo-setup/SKILL.md +217 -0
- package/src/skills/pi-extension/SKILL.md +140 -0
- package/src/skills/pi-extension/references/additional-apis.md +264 -0
- package/src/skills/pi-extension/references/commands.md +100 -0
- package/src/skills/pi-extension/references/components.md +166 -0
- package/src/skills/pi-extension/references/documentation.md +54 -0
- package/src/skills/pi-extension/references/hooks.md +244 -0
- package/src/skills/pi-extension/references/messages.md +169 -0
- package/src/skills/pi-extension/references/modes.md +156 -0
- package/src/skills/pi-extension/references/providers.md +134 -0
- package/src/skills/pi-extension/references/publish.md +139 -0
- package/src/skills/pi-extension/references/state.md +56 -0
- package/src/skills/pi-extension/references/structure.md +408 -0
- package/src/skills/pi-extension/references/testing.md +54 -0
- package/src/skills/pi-extension/references/tools.md +430 -0
- package/src/tools/changelog-tool.ts +596 -0
- package/src/tools/docs-tool.ts +240 -0
- package/src/tools/index.ts +12 -0
- package/src/tools/package-manager-tool.ts +223 -0
- package/src/tools/utils.ts +62 -0
- package/src/tools/version-tool.ts +77 -0
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
# Extension Structure
|
|
2
|
+
|
|
3
|
+
This covers the standalone repository structure for a Pi extension. This is the recommended layout for new extensions.
|
|
4
|
+
|
|
5
|
+
## Directory Layout
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
my-extension/
|
|
9
|
+
src/
|
|
10
|
+
index.ts # Entry point (default export)
|
|
11
|
+
config.ts # Config schema (types) + loader + defaults
|
|
12
|
+
client.ts # API client (if wrapping a third-party API)
|
|
13
|
+
tools/
|
|
14
|
+
my-tool.ts # One file per tool
|
|
15
|
+
commands/
|
|
16
|
+
my-command.ts # One file per command
|
|
17
|
+
components/
|
|
18
|
+
my-renderer.ts # Shared TUI components
|
|
19
|
+
providers/
|
|
20
|
+
index.ts # Provider registration
|
|
21
|
+
models.ts # Model definitions
|
|
22
|
+
utils/ # Internal helpers (matching, parsing, etc.)
|
|
23
|
+
my-helper.ts
|
|
24
|
+
package.json
|
|
25
|
+
tsconfig.json
|
|
26
|
+
biome.json # Linting/formatting
|
|
27
|
+
shell.nix # Nix dev environment
|
|
28
|
+
.changeset/
|
|
29
|
+
config.json # Changeset config for versioning
|
|
30
|
+
README.md
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Not every extension needs every directory. A simple extension with one tool might only have `src/index.ts` and `src/tools/my-tool.ts`.
|
|
34
|
+
|
|
35
|
+
### Organization principles
|
|
36
|
+
|
|
37
|
+
- **`index.ts` and `config.ts`** stay at root. These are the two core files every non-trivial extension has.
|
|
38
|
+
- **Tools, commands, components, providers, hooks** each get their own directory. One file per tool/command/component.
|
|
39
|
+
- **Config types live in `config.ts`**, not a separate `types.ts` or `config-schema.ts`. The config file exports both the types (raw and resolved) and the config loader instance.
|
|
40
|
+
- **Utility/helper files** go in `utils/`. This includes pattern matching, shell parsing, event helpers, migrations, etc. Anything that is not a tool, command, component, provider, or hook.
|
|
41
|
+
- **No separate `types.ts`** unless the extension has shared types unrelated to config (rare). Config types are the most common shared types, and they belong in `config.ts`.
|
|
42
|
+
|
|
43
|
+
## package.json
|
|
44
|
+
|
|
45
|
+
```json
|
|
46
|
+
{
|
|
47
|
+
"name": "@scope/pi-my-extension",
|
|
48
|
+
"version": "0.1.0",
|
|
49
|
+
"description": "Description of the extension",
|
|
50
|
+
"type": "module",
|
|
51
|
+
"license": "MIT",
|
|
52
|
+
"private": false,
|
|
53
|
+
"keywords": ["pi-package", "pi-extension", "pi"],
|
|
54
|
+
"repository": {
|
|
55
|
+
"type": "git",
|
|
56
|
+
"url": "https://github.com/your-org/pi-my-extension"
|
|
57
|
+
},
|
|
58
|
+
"publishConfig": {
|
|
59
|
+
"access": "public"
|
|
60
|
+
},
|
|
61
|
+
"files": ["src", "README.md"],
|
|
62
|
+
"pi": {
|
|
63
|
+
"extensions": ["./src/index.ts"],
|
|
64
|
+
"skills": ["./skills"],
|
|
65
|
+
"themes": ["./themes"],
|
|
66
|
+
"prompts": ["./prompts"],
|
|
67
|
+
"video": "https://example.com/demo.mp4"
|
|
68
|
+
},
|
|
69
|
+
"peerDependencies": {
|
|
70
|
+
"@mariozechner/pi-coding-agent": ">=CURRENT_VERSION",
|
|
71
|
+
"@mariozechner/pi-tui": ">=CURRENT_VERSION"
|
|
72
|
+
},
|
|
73
|
+
"peerDependenciesMeta": {
|
|
74
|
+
"@mariozechner/pi-coding-agent": { "optional": true },
|
|
75
|
+
"@mariozechner/pi-tui": { "optional": true }
|
|
76
|
+
},
|
|
77
|
+
"devDependencies": {
|
|
78
|
+
"@aliou/biome-plugins": "^0.3.0",
|
|
79
|
+
"@biomejs/biome": "^2.0.0",
|
|
80
|
+
"@changesets/cli": "^2.27.0",
|
|
81
|
+
"@mariozechner/pi-coding-agent": "CURRENT_VERSION",
|
|
82
|
+
"@mariozechner/pi-tui": "CURRENT_VERSION",
|
|
83
|
+
"@types/node": "^25.0.0",
|
|
84
|
+
"husky": "^9.0.0",
|
|
85
|
+
"typescript": "^5.8.0"
|
|
86
|
+
},
|
|
87
|
+
"scripts": {
|
|
88
|
+
"typecheck": "tsc --noEmit",
|
|
89
|
+
"lint": "biome check",
|
|
90
|
+
"format": "biome check --write",
|
|
91
|
+
"check:lockfile": "pnpm install --frozen-lockfile --ignore-scripts",
|
|
92
|
+
"prepare": "[ -d .git ] && husky || true",
|
|
93
|
+
"changeset": "changeset",
|
|
94
|
+
"version": "changeset version",
|
|
95
|
+
"release": "pnpm changeset publish"
|
|
96
|
+
},
|
|
97
|
+
"pnpm": {
|
|
98
|
+
"overrides": {
|
|
99
|
+
"@mariozechner/pi-ai": "$@mariozechner/pi-coding-agent",
|
|
100
|
+
"@mariozechner/pi-tui": "$@mariozechner/pi-coding-agent"
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
"packageManager": "pnpm@10.26.1"
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Replace `CURRENT_VERSION` with the actual installed version of pi (e.g., `0.52.7`).
|
|
108
|
+
|
|
109
|
+
Only include `pi` sub-fields that are actually used. `skills`, `themes`, `prompts`, and `video` are optional.
|
|
110
|
+
|
|
111
|
+
### Fields
|
|
112
|
+
|
|
113
|
+
**`pi` key**: Declares extension resources. All paths are relative to the package root.
|
|
114
|
+
|
|
115
|
+
| Field | Description |
|
|
116
|
+
|---|---|
|
|
117
|
+
| `extensions` | Array of entry point paths. Each is a TypeScript file with a default export function. |
|
|
118
|
+
| `skills` | Array of directories containing skill definitions. Optional. |
|
|
119
|
+
| `themes` | Array of directories containing theme files. Optional. |
|
|
120
|
+
| `prompts` | Array of directories containing prompt files. Optional. |
|
|
121
|
+
| `video` | URL to an `.mp4` demo video. Displayed on the pi website package listing. Not used by pi itself. Optional. |
|
|
122
|
+
|
|
123
|
+
**`peerDependencies`**: Declares the minimum pi version required. Both `@mariozechner/pi-coding-agent` and `@mariozechner/pi-tui` must be listed here as optional peers if your extension imports from either at runtime. Pi already ships these packages, so marking them as optional peers prevents npm from installing duplicate copies when a user installs your extension. Use `>=` with the current version when creating.
|
|
124
|
+
|
|
125
|
+
**`peerDependenciesMeta`**: Marks peer dependencies as optional. Without `optional: true`, npm 7+ auto-installs peers that are not already present, which defeats the purpose — Pi already provides them.
|
|
126
|
+
|
|
127
|
+
**`devDependencies`**: Same packages at exact pinned versions for local type checking. `pnpm install` in your repo installs peerDependencies automatically, so local development is unaffected.
|
|
128
|
+
|
|
129
|
+
**`scripts.prepare`**: The `[ -d .git ] && husky || true` guard prevents husky from running in consumer environments (including when Pi installs your package). Without this, `husky` runs on every `npm install` and fails with a non-zero exit code in environments without a `.git` directory.
|
|
130
|
+
|
|
131
|
+
**`scripts.check:lockfile`**: Verifies the lockfile is in sync with `package.json`. Run in CI to catch accidental lockfile drift.
|
|
132
|
+
|
|
133
|
+
**`pnpm.overrides`**: Ensures pi sub-packages resolve to the version bundled with pi-coding-agent, avoiding duplicate installations.
|
|
134
|
+
|
|
135
|
+
## tsconfig.json
|
|
136
|
+
|
|
137
|
+
```json
|
|
138
|
+
{
|
|
139
|
+
"compilerOptions": {
|
|
140
|
+
"target": "ES2022",
|
|
141
|
+
"module": "ESNext",
|
|
142
|
+
"moduleResolution": "bundler",
|
|
143
|
+
"strict": true,
|
|
144
|
+
"esModuleInterop": true,
|
|
145
|
+
"skipLibCheck": true,
|
|
146
|
+
"forceConsistentCasingInFileNames": true,
|
|
147
|
+
"resolveJsonModule": true,
|
|
148
|
+
"noEmit": true
|
|
149
|
+
},
|
|
150
|
+
"include": ["src/**/*"],
|
|
151
|
+
"exclude": ["node_modules"]
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Extensions are loaded directly by pi (no build step). `noEmit: true` means TypeScript is only used for type checking.
|
|
156
|
+
|
|
157
|
+
**Do not add `jsx` or `jsxImportSource` settings.** Although `src/components/` exists, pi-tui components are not React components. They implement the `Component` interface from `@mariozechner/pi-tui` and render to plain strings. No JSX transpilation is involved.
|
|
158
|
+
|
|
159
|
+
## biome.json
|
|
160
|
+
|
|
161
|
+
All extensions use Biome for linting and formatting. Canonical config:
|
|
162
|
+
|
|
163
|
+
```json
|
|
164
|
+
{
|
|
165
|
+
"$schema": "https://biomejs.dev/schemas/2.4.2/schema.json",
|
|
166
|
+
"plugins": [
|
|
167
|
+
"./node_modules/@aliou/biome-plugins/plugins/no-inline-imports.grit",
|
|
168
|
+
"./node_modules/@aliou/biome-plugins/plugins/no-js-import-extension.grit",
|
|
169
|
+
"./node_modules/@aliou/biome-plugins/plugins/no-emojis.grit"
|
|
170
|
+
],
|
|
171
|
+
"vcs": {
|
|
172
|
+
"enabled": true,
|
|
173
|
+
"clientKind": "git",
|
|
174
|
+
"useIgnoreFile": true
|
|
175
|
+
},
|
|
176
|
+
"files": {
|
|
177
|
+
"includes": ["**/*.ts", "**/*.json"],
|
|
178
|
+
"ignoreUnknown": true
|
|
179
|
+
},
|
|
180
|
+
"assist": {
|
|
181
|
+
"actions": {
|
|
182
|
+
"source": {
|
|
183
|
+
"organizeImports": "on"
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
"linter": {
|
|
188
|
+
"enabled": true,
|
|
189
|
+
"rules": {
|
|
190
|
+
"recommended": true
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
"formatter": {
|
|
194
|
+
"enabled": true,
|
|
195
|
+
"indentStyle": "space",
|
|
196
|
+
"indentWidth": 2
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
The `plugins` field requires Biome 2.x for GritQL plugin support. The `@aliou/biome-plugins` package has five plugins; three apply to pi extensions:
|
|
202
|
+
|
|
203
|
+
- `no-inline-imports`: Disallows `await import()` and `require()` inside functions. All imports must be static.
|
|
204
|
+
- `no-js-import-extension`: Disallows `.js` extensions in import paths (enforces the rule in Critical Rules).
|
|
205
|
+
- `no-emojis`: Disallows emoji characters in code and strings.
|
|
206
|
+
|
|
207
|
+
The other two (`no-interpolated-classname`, `phosphor-icon-suffix`) are specific to React and Phosphor icons and are not applicable.
|
|
208
|
+
|
|
209
|
+
## config.ts
|
|
210
|
+
|
|
211
|
+
Non-trivial extensions have a `config.ts` that defines the config schema, types, and loader instance. Use plain TypeScript interfaces with a raw/resolved two-type pattern. The raw type has all fields optional — only overrides are stored to disk. The resolved type has all fields required — defaults are merged in at load time.
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
import { ConfigLoader } from "@aliou/pi-utils-settings";
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Raw config shape (what gets saved to disk).
|
|
218
|
+
* All fields optional -- only overrides are stored.
|
|
219
|
+
*/
|
|
220
|
+
export interface MyExtensionConfig {
|
|
221
|
+
enabled?: boolean;
|
|
222
|
+
myOption?: string;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Resolved config (defaults merged in).
|
|
227
|
+
* All fields required.
|
|
228
|
+
*/
|
|
229
|
+
export interface ResolvedMyExtensionConfig {
|
|
230
|
+
enabled: boolean;
|
|
231
|
+
myOption: string;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const DEFAULTS: ResolvedMyExtensionConfig = {
|
|
235
|
+
enabled: true,
|
|
236
|
+
myOption: "default-value",
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Config loader instance.
|
|
241
|
+
* Config is stored at ~/.pi/agent/extensions/<name>.json
|
|
242
|
+
*/
|
|
243
|
+
export const configLoader = new ConfigLoader<
|
|
244
|
+
MyExtensionConfig,
|
|
245
|
+
ResolvedMyExtensionConfig
|
|
246
|
+
>("my-extension", DEFAULTS);
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
`ConfigLoader` comes from `@aliou/pi-utils-settings`, a standalone published package (source: `~/code/src/github.com/aliou/pi-extensions/packages/settings/`). It is listed as a regular dependency in `package.json`, not a peer dependency.
|
|
250
|
+
|
|
251
|
+
The name passed to `ConfigLoader` determines the filename: `"my-extension"` → `~/.pi/agent/extensions/my-extension.json`.
|
|
252
|
+
|
|
253
|
+
### Reading config
|
|
254
|
+
|
|
255
|
+
After calling `load()`, use `getConfig()` for the resolved config (defaults merged in) or `getRawConfig(scope)` for the raw config at a specific scope.
|
|
256
|
+
|
|
257
|
+
```typescript
|
|
258
|
+
await configLoader.load();
|
|
259
|
+
const config = configLoader.getConfig(); // ResolvedMyExtensionConfig
|
|
260
|
+
const raw = configLoader.getRawConfig("global"); // MyExtensionConfig | null
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### Saving config
|
|
264
|
+
|
|
265
|
+
Use `save(scope, config)` to persist changes. The scope must be one of the enabled scopes (`"global"`, `"local"`, or `"memory"`). After saving, the loader automatically reloads and re-merges.
|
|
266
|
+
|
|
267
|
+
```typescript
|
|
268
|
+
await configLoader.save("global", { myOption: "new-value" });
|
|
269
|
+
// configLoader.getConfig() now reflects the saved change
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
Memory scope is ephemeral -- it resets on reload and is not written to disk.
|
|
273
|
+
|
|
274
|
+
### Scopes and merge order
|
|
275
|
+
|
|
276
|
+
Default scopes are `["global", "local"]`. Merge priority (lowest to highest): defaults -> global -> local -> memory. Only overrides are stored to disk; missing fields fall back to defaults.
|
|
277
|
+
|
|
278
|
+
For extensions with migrations or multi-scope config (global + local + in-memory), pass an options object:
|
|
279
|
+
|
|
280
|
+
```typescript
|
|
281
|
+
export const configLoader = new ConfigLoader<MyExtensionConfig, ResolvedMyExtensionConfig>(
|
|
282
|
+
"my-extension",
|
|
283
|
+
DEFAULTS,
|
|
284
|
+
{
|
|
285
|
+
scopes: ["global", "local", "memory"],
|
|
286
|
+
migrations: [...],
|
|
287
|
+
},
|
|
288
|
+
);
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
## Entry Point (src/index.ts)
|
|
292
|
+
|
|
293
|
+
The entry point is a default export function that receives the `ExtensionAPI` object.
|
|
294
|
+
|
|
295
|
+
### Standard Pattern
|
|
296
|
+
|
|
297
|
+
```typescript
|
|
298
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
299
|
+
import { configLoader } from "./config";
|
|
300
|
+
import { registerCommands } from "./commands";
|
|
301
|
+
import { registerHooks } from "./hooks";
|
|
302
|
+
import { registerTools } from "./tools";
|
|
303
|
+
|
|
304
|
+
export default async function (pi: ExtensionAPI) {
|
|
305
|
+
await configLoader.load();
|
|
306
|
+
const config = configLoader.getConfig();
|
|
307
|
+
if (!config.enabled) return;
|
|
308
|
+
|
|
309
|
+
registerTools(pi);
|
|
310
|
+
registerCommands(pi);
|
|
311
|
+
registerHooks(pi);
|
|
312
|
+
}
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
### Acceptable Exceptions
|
|
316
|
+
|
|
317
|
+
Not all extensions follow the standard pattern exactly. These deviations are valid:
|
|
318
|
+
|
|
319
|
+
**No config**: Extensions that use environment variables exclusively and have no user-configurable settings skip config loading entirely. The entry point reads the env var directly and gates registration on its presence.
|
|
320
|
+
|
|
321
|
+
**API-key-first**: Extensions wrapping a third-party API check for the API key before loading config or registering anything. If the key is missing, notify the user and return early. Config loads after the key check. See the API Key Pattern section.
|
|
322
|
+
|
|
323
|
+
**No `enabled` check**: Extensions that are always active by design omit the `enabled` field and the early-return check. The entry point still loads config for other settings. Document this decision in `AGENTS.md`.
|
|
324
|
+
|
|
325
|
+
When deviating from the standard pattern, note the reason in the extension's `AGENTS.md`.
|
|
326
|
+
|
|
327
|
+
## API Key Pattern
|
|
328
|
+
|
|
329
|
+
If your extension wraps a third-party API that requires an API key:
|
|
330
|
+
|
|
331
|
+
```typescript
|
|
332
|
+
export default function (pi: ExtensionAPI) {
|
|
333
|
+
const apiKey = process.env.MY_API_KEY;
|
|
334
|
+
|
|
335
|
+
// Register provider unconditionally if it exists
|
|
336
|
+
// (provider handles missing key internally for model registration)
|
|
337
|
+
pi.registerProvider(myProvider);
|
|
338
|
+
|
|
339
|
+
// Only register tools that need the key
|
|
340
|
+
if (!apiKey) {
|
|
341
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
342
|
+
ctx.ui.notify("MY_API_KEY not set. Tools disabled.", "warning");
|
|
343
|
+
});
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
pi.registerTool(createMyTool(apiKey));
|
|
348
|
+
pi.registerCommand(createMyCommand(apiKey));
|
|
349
|
+
}
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
The principle: check for the API key before registering anything that requires it. If the extension also registers a provider, the provider can be registered regardless (it handles key presence internally for model listing).
|
|
353
|
+
|
|
354
|
+
## Imports
|
|
355
|
+
|
|
356
|
+
Do not use `.js` file extensions in imports. Use bare module paths:
|
|
357
|
+
|
|
358
|
+
```typescript
|
|
359
|
+
// Correct
|
|
360
|
+
import { myTool } from "./tools/my-tool";
|
|
361
|
+
import type { MyType } from "./types";
|
|
362
|
+
|
|
363
|
+
// Wrong
|
|
364
|
+
import { myTool } from "./tools/my-tool.js";
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
## Monorepo Variant
|
|
368
|
+
|
|
369
|
+
In a monorepo with pnpm workspaces, the structure differs slightly. There is no `src/` directory; the entry point and config live directly in the package root.
|
|
370
|
+
|
|
371
|
+
```
|
|
372
|
+
extensions/
|
|
373
|
+
my-extension/
|
|
374
|
+
index.ts # Entry point (no src/ directory)
|
|
375
|
+
config.ts # Config schema (types) + loader + defaults
|
|
376
|
+
commands/
|
|
377
|
+
settings-command.ts
|
|
378
|
+
hooks/
|
|
379
|
+
my-hook.ts
|
|
380
|
+
components/
|
|
381
|
+
my-editor.ts
|
|
382
|
+
utils/
|
|
383
|
+
matching.ts
|
|
384
|
+
shell-utils.ts
|
|
385
|
+
package.json
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
Key differences from standalone:
|
|
389
|
+
- Entry point directly in the package root (no `src/` directory).
|
|
390
|
+
- `"pi": { "extensions": ["./index.ts"] }` instead of `["./src/index.ts"]`.
|
|
391
|
+
- Uses `peerDependencies` (resolved by workspace root).
|
|
392
|
+
- Shared `tsconfig` from a workspace package.
|
|
393
|
+
- Same organization principles apply: config types in `config.ts`, helpers in `utils/`, one directory per feature category.
|
|
394
|
+
|
|
395
|
+
### Workspace dependencies
|
|
396
|
+
|
|
397
|
+
When an extension depends on another workspace package (e.g., `@aliou/pi-utils-settings`, `@aliou/pi-agent-kit`), use the `workspace:^` protocol instead of a version range:
|
|
398
|
+
|
|
399
|
+
```json
|
|
400
|
+
{
|
|
401
|
+
"dependencies": {
|
|
402
|
+
"@aliou/pi-utils-settings": "workspace:^",
|
|
403
|
+
"@aliou/sh": "^0.1.0"
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
Use `workspace:^` only for packages that live in this monorepo (under `packages/` or `extensions/`). External published packages like `@aliou/sh` keep regular version ranges.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Testing
|
|
2
|
+
|
|
3
|
+
Pi loads extensions directly from TypeScript source (no build step). Testing is done by running pi with the extension loaded.
|
|
4
|
+
|
|
5
|
+
## Local Development
|
|
6
|
+
|
|
7
|
+
During development, the extension is loaded from the local filesystem. Point pi to your extension's `package.json` directory:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# From within the extension directory
|
|
11
|
+
pi
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Pi reads the `pi.extensions` paths from `package.json` and loads the entry points.
|
|
15
|
+
|
|
16
|
+
## Type Checking
|
|
17
|
+
|
|
18
|
+
Run TypeScript type checking to catch errors without building:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pnpm tsc --noEmit
|
|
22
|
+
# or if configured in package.json:
|
|
23
|
+
pnpm typecheck
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Manual Testing Checklist
|
|
27
|
+
|
|
28
|
+
- [ ] Extension loads without errors.
|
|
29
|
+
- [ ] Tools appear in the tool list and work when called by the LLM.
|
|
30
|
+
- [ ] Commands appear in autocomplete and work when invoked.
|
|
31
|
+
- [ ] Custom renderers display correctly (both partial and final states).
|
|
32
|
+
- [ ] Missing API key shows a notification, not a crash.
|
|
33
|
+
- [ ] Works in Print mode (`pi -p "test message"`): no UI errors, graceful degradation.
|
|
34
|
+
- [ ] If using `ctx.ui.custom()`: RPC fallback is exercised (`custom()` returns undefined in RPC), and interactive close paths use explicit non-undefined sentinels (no accidental `done(undefined)` ambiguity).
|
|
35
|
+
|
|
36
|
+
## Testing Hooks
|
|
37
|
+
|
|
38
|
+
Test event hooks by triggering the relevant actions:
|
|
39
|
+
|
|
40
|
+
- `tool_call`: Have the LLM call a tool that your hook intercepts.
|
|
41
|
+
- `session_before_switch`: Create a new session or switch sessions.
|
|
42
|
+
- `input`: Type a message that matches your transform pattern.
|
|
43
|
+
- `before_agent_start`: Start any agent turn and verify system prompt modifications.
|
|
44
|
+
|
|
45
|
+
## Debugging
|
|
46
|
+
|
|
47
|
+
Extension errors are logged to the pi log file. Check the output for stack traces:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
# View pi logs
|
|
51
|
+
pi --log-level debug
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
If an extension fails to load, pi logs the error and continues without it.
|