@aliou/pi-dev-kit 0.6.4 → 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.
- package/README.md +2 -2
- package/package.json +15 -10
- package/src/commands/index.ts +5 -1
- package/src/commands/update.ts +139 -91
- package/src/skills/pi-extension/SKILL.md +108 -131
- package/src/skills/pi-extension/references/additional-apis.md +256 -173
- package/src/skills/pi-extension/references/commands.md +113 -33
- package/src/skills/pi-extension/references/components.md +267 -102
- package/src/skills/pi-extension/references/hooks.md +229 -156
- package/src/skills/pi-extension/references/messages.md +97 -92
- package/src/skills/pi-extension/references/modes.md +80 -90
- package/src/skills/pi-extension/references/providers.md +255 -96
- package/src/skills/pi-extension/references/publish.md +76 -62
- package/src/skills/pi-extension/references/state.md +80 -33
- package/src/skills/pi-extension/references/structure.md +156 -245
- package/src/skills/pi-extension/references/testing.md +1 -1
- package/src/skills/pi-extension/references/tools.md +212 -816
- package/src/tools/changelog-tool.ts +237 -230
- package/src/tools/docs-tool.ts +127 -130
- package/src/tools/index.ts +5 -1
- package/src/tools/package-manager-tool.ts +152 -147
- package/src/tools/utils.ts +33 -23
- package/src/tools/version-tool.ts +51 -51
- package/src/index.ts +0 -8
|
@@ -1,54 +1,59 @@
|
|
|
1
1
|
# Extension Structure
|
|
2
2
|
|
|
3
|
-
This
|
|
3
|
+
This is the recommended standalone repository layout for Pi extension packages.
|
|
4
4
|
|
|
5
5
|
## Directory Layout
|
|
6
6
|
|
|
7
7
|
```
|
|
8
8
|
my-extension/
|
|
9
9
|
src/
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
config.ts
|
|
11
|
+
client.ts # API/domain client, no Pi imports when possible
|
|
12
|
+
manager.ts # Core/domain logic, no Pi imports when possible
|
|
13
13
|
tools/
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
create.ts
|
|
19
|
-
list.ts
|
|
20
|
-
show.ts
|
|
21
|
-
render.ts # Separate render module (when rendering is complex)
|
|
22
|
-
types.ts # Serialized types for tool details
|
|
14
|
+
index.ts # Tool entry point, default export registers tools
|
|
15
|
+
actions/ # Optional action modules for multi-action tools
|
|
16
|
+
render.ts # Optional complex rendering
|
|
17
|
+
types.ts # Optional tool params/details types
|
|
23
18
|
commands/
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
19
|
+
index.ts # Command entry point
|
|
20
|
+
components/ # Command-specific TUI components
|
|
21
|
+
hooks/
|
|
22
|
+
index.ts # Event hook entry point
|
|
27
23
|
providers/
|
|
28
|
-
index.ts # Provider
|
|
29
|
-
models.ts
|
|
30
|
-
|
|
31
|
-
|
|
24
|
+
index.ts # Provider entry point
|
|
25
|
+
models.ts
|
|
26
|
+
components/ # Shared TUI components only when genuinely shared
|
|
27
|
+
utils/ # Parsing, matching, migrations, small helpers
|
|
32
28
|
package.json
|
|
33
29
|
tsconfig.json
|
|
34
|
-
biome.json
|
|
35
|
-
shell.nix
|
|
36
|
-
.changeset/
|
|
37
|
-
config.json # Changeset config for versioning
|
|
30
|
+
biome.json
|
|
31
|
+
shell.nix
|
|
32
|
+
.changeset/config.json
|
|
38
33
|
README.md
|
|
39
34
|
```
|
|
40
35
|
|
|
41
|
-
Not every extension needs every directory. A
|
|
36
|
+
Not every extension needs every directory. A one-tool extension can be `src/tools/index.ts` plus `src/config.ts`.
|
|
37
|
+
|
|
38
|
+
## Organization Rules
|
|
39
|
+
|
|
40
|
+
- Each feature directory is its own Pi entry point: `tools/index.ts`, `commands/index.ts`, `hooks/index.ts`, `providers/index.ts`.
|
|
41
|
+
- List those entry points directly in `package.json` `pi.extensions`.
|
|
42
|
+
- Avoid a root `src/index.ts` that imports and registers everything in new code.
|
|
43
|
+
- Keep `config.ts` at the root and shared by entry points.
|
|
44
|
+
- Keep config types in `config.ts`, not `types.ts`.
|
|
45
|
+
- Put domain logic in Pi-free modules such as `client.ts` and `manager.ts`; tools and commands should be thin wrappers.
|
|
46
|
+
- Components are support modules, not Pi entry points.
|
|
47
|
+
- Multi-action tools get a directory under `tools/`.
|
|
48
|
+
- Use `utils/` for generic helpers that are not tools, commands, hooks, providers, or components.
|
|
49
|
+
|
|
50
|
+
## Package Namespace
|
|
42
51
|
|
|
43
|
-
|
|
52
|
+
Pi core packages are migrating from `@mariozechner/*` to `@earendil-works/*`.
|
|
44
53
|
|
|
45
|
-
-
|
|
46
|
-
-
|
|
47
|
-
-
|
|
48
|
-
- **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.
|
|
49
|
-
- **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`.
|
|
50
|
-
- **Multi-action tools** get their own directory under `tools/`. The tool registration + rendering lives in `index.ts`, each action gets its own file in `actions/`, and complex rendering logic goes in `render.ts`. Serialized types for tool details go in `types.ts`.
|
|
51
|
-
- **Core/domain logic** lives in dedicated modules at the `src/` root (`client.ts`, `manager.ts`). These contain the business logic, are testable without the Pi framework, and don't import from `@mariozechner/pi-coding-agent`. Tools are thin wrappers that call these modules and format results.
|
|
54
|
+
- Use `@earendil-works/*` once the target packages are published.
|
|
55
|
+
- Keep `@mariozechner/*` for projects that still target a legacy Pi package namespace.
|
|
56
|
+
- Do not mix namespaces unless you are intentionally doing a staged migration.
|
|
52
57
|
|
|
53
58
|
## package.json
|
|
54
59
|
|
|
@@ -70,35 +75,40 @@ Not every extension needs every directory. A simple extension with one tool migh
|
|
|
70
75
|
},
|
|
71
76
|
"files": ["src", "README.md"],
|
|
72
77
|
"pi": {
|
|
73
|
-
"extensions": [
|
|
78
|
+
"extensions": [
|
|
79
|
+
"./src/tools/index.ts",
|
|
80
|
+
"./src/commands/index.ts",
|
|
81
|
+
"./src/hooks/index.ts",
|
|
82
|
+
"./src/providers/index.ts"
|
|
83
|
+
],
|
|
74
84
|
"skills": ["./skills"],
|
|
75
85
|
"themes": ["./themes"],
|
|
76
86
|
"prompts": ["./prompts"],
|
|
77
87
|
"video": "https://example.com/demo.mp4"
|
|
78
88
|
},
|
|
89
|
+
"dependencies": {},
|
|
79
90
|
"peerDependencies": {
|
|
80
|
-
"@
|
|
81
|
-
"@
|
|
82
|
-
"@
|
|
83
|
-
"
|
|
91
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
92
|
+
"@earendil-works/pi-ai": "*",
|
|
93
|
+
"@earendil-works/pi-tui": "*",
|
|
94
|
+
"typebox": "*"
|
|
84
95
|
},
|
|
85
96
|
"peerDependenciesMeta": {
|
|
86
|
-
"@
|
|
87
|
-
"@
|
|
88
|
-
"@
|
|
89
|
-
"
|
|
97
|
+
"@earendil-works/pi-coding-agent": { "optional": true },
|
|
98
|
+
"@earendil-works/pi-ai": { "optional": true },
|
|
99
|
+
"@earendil-works/pi-tui": { "optional": true },
|
|
100
|
+
"typebox": { "optional": true }
|
|
90
101
|
},
|
|
91
102
|
"devDependencies": {
|
|
92
|
-
"@
|
|
93
|
-
"@biomejs/biome": "^2.0.0",
|
|
103
|
+
"@biomejs/biome": "^2.3.0",
|
|
94
104
|
"@changesets/cli": "^2.27.0",
|
|
95
|
-
"@
|
|
96
|
-
"@
|
|
97
|
-
"@
|
|
98
|
-
"
|
|
105
|
+
"@earendil-works/pi-ai": "CURRENT_VERSION",
|
|
106
|
+
"@earendil-works/pi-coding-agent": "CURRENT_VERSION",
|
|
107
|
+
"@earendil-works/pi-tui": "CURRENT_VERSION",
|
|
108
|
+
"typebox": "1.1.24",
|
|
99
109
|
"@types/node": "^25.0.0",
|
|
100
110
|
"husky": "^9.0.0",
|
|
101
|
-
"typescript": "^5.
|
|
111
|
+
"typescript": "^5.9.0"
|
|
102
112
|
},
|
|
103
113
|
"scripts": {
|
|
104
114
|
"typecheck": "tsc --noEmit",
|
|
@@ -112,48 +122,36 @@ Not every extension needs every directory. A simple extension with one tool migh
|
|
|
112
122
|
},
|
|
113
123
|
"pnpm": {
|
|
114
124
|
"overrides": {
|
|
115
|
-
"@
|
|
116
|
-
"@
|
|
125
|
+
"@earendil-works/pi-ai": "$@earendil-works/pi-coding-agent",
|
|
126
|
+
"@earendil-works/pi-tui": "$@earendil-works/pi-coding-agent"
|
|
117
127
|
}
|
|
118
128
|
},
|
|
119
129
|
"packageManager": "pnpm@10.26.1"
|
|
120
130
|
}
|
|
121
131
|
```
|
|
122
132
|
|
|
123
|
-
Replace `CURRENT_VERSION` with the
|
|
124
|
-
|
|
125
|
-
Only include `pi` sub-fields that are actually used. `skills`, `themes`, `prompts`, and `video` are optional.
|
|
126
|
-
|
|
127
|
-
### Fields
|
|
128
|
-
|
|
129
|
-
**`pi` key**: Declares extension resources. All paths are relative to the package root.
|
|
130
|
-
|
|
131
|
-
| Field | Description |
|
|
132
|
-
|---|---|
|
|
133
|
-
| `extensions` | Array of entry point paths. Each is a TypeScript file with a default export function. |
|
|
134
|
-
| `skills` | Array of directories containing skill definitions. Optional. |
|
|
135
|
-
| `themes` | Array of directories containing theme files. Optional. |
|
|
136
|
-
| `prompts` | Array of directories containing prompt files. Optional. |
|
|
137
|
-
| `video` | URL to an `.mp4` demo video. Displayed on the pi website package listing. Not used by pi itself. Optional. |
|
|
133
|
+
Replace `CURRENT_VERSION` with the exact target Pi version for local type checking. If the target version is only available under the legacy namespace, use the matching `@mariozechner/*` package names consistently.
|
|
138
134
|
|
|
139
|
-
|
|
135
|
+
Only include `pi` sub-fields you actually use. `skills`, `themes`, `prompts`, `video`, and `image` are optional.
|
|
140
136
|
|
|
141
|
-
|
|
142
|
-
- `@mariozechner/pi-tui` — TUI components
|
|
143
|
-
- `@mariozechner/pi-ai` — AI utilities (`StringEnum`, etc.)
|
|
144
|
-
- `@sinclair/typebox` — schema definitions for tool parameters and related types
|
|
137
|
+
### Dependency Rules
|
|
145
138
|
|
|
146
|
-
|
|
139
|
+
Pi provides these runtime packages to extensions:
|
|
147
140
|
|
|
148
|
-
|
|
141
|
+
- `@earendil-works/pi-coding-agent` / legacy `@mariozechner/pi-coding-agent`
|
|
142
|
+
- `@earendil-works/pi-agent-core` / legacy `@mariozechner/pi-agent-core`
|
|
143
|
+
- `@earendil-works/pi-ai` / legacy `@mariozechner/pi-ai`
|
|
144
|
+
- `@earendil-works/pi-tui` / legacy `@mariozechner/pi-tui`
|
|
145
|
+
- `typebox`
|
|
149
146
|
|
|
150
|
-
|
|
147
|
+
For any of these that you import:
|
|
151
148
|
|
|
152
|
-
|
|
149
|
+
- Put them in `peerDependencies` with `"*"`.
|
|
150
|
+
- Mark them `optional: true` in `peerDependenciesMeta`.
|
|
151
|
+
- Put exact target versions in `devDependencies` for type checking.
|
|
152
|
+
- Do not bundle them.
|
|
153
153
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
**`pnpm.overrides`**: Ensures pi sub-packages resolve to the version bundled with pi-coding-agent, avoiding duplicate installations.
|
|
154
|
+
Third-party runtime packages that Pi does not provide belong in `dependencies`. If bundling another Pi package's resources into your tarball, add it to `dependencies` and `bundledDependencies`, then reference resources through `node_modules/...` paths.
|
|
157
155
|
|
|
158
156
|
## tsconfig.json
|
|
159
157
|
|
|
@@ -175,13 +173,11 @@ List any of these you import at runtime in `peerDependencies` as optional peers.
|
|
|
175
173
|
}
|
|
176
174
|
```
|
|
177
175
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
**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.
|
|
176
|
+
Pi loads TypeScript directly through jiti. No build step is needed. Do not add JSX settings; Pi TUI components are not React components.
|
|
181
177
|
|
|
182
178
|
## biome.json
|
|
183
179
|
|
|
184
|
-
|
|
180
|
+
Use Biome 2.x. If the project uses `@aliou/biome-plugins`, enable the Pi-relevant plugins.
|
|
185
181
|
|
|
186
182
|
```json
|
|
187
183
|
{
|
|
@@ -221,34 +217,18 @@ All extensions use Biome for linting and formatting. Canonical config:
|
|
|
221
217
|
}
|
|
222
218
|
```
|
|
223
219
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
- `no-inline-imports`: Disallows `await import()` and `require()` inside functions. All imports must be static.
|
|
227
|
-
- `no-js-import-extension`: Disallows `.js` extensions in import paths (enforces the rule in Critical Rules).
|
|
228
|
-
- `no-emojis`: Disallows emoji characters in code and strings.
|
|
229
|
-
|
|
230
|
-
The other two (`no-interpolated-classname`, `phosphor-icon-suffix`) are specific to React and Phosphor icons and are not applicable.
|
|
220
|
+
## Config Pattern
|
|
231
221
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
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.
|
|
222
|
+
Use plain TypeScript interfaces with raw/resolved types. Do not use TypeBox for config types.
|
|
235
223
|
|
|
236
224
|
```typescript
|
|
237
225
|
import { ConfigLoader } from "@aliou/pi-utils-settings";
|
|
238
226
|
|
|
239
|
-
/**
|
|
240
|
-
* Raw config shape (what gets saved to disk).
|
|
241
|
-
* All fields optional -- only overrides are stored.
|
|
242
|
-
*/
|
|
243
227
|
export interface MyExtensionConfig {
|
|
244
228
|
enabled?: boolean;
|
|
245
229
|
myOption?: string;
|
|
246
230
|
}
|
|
247
231
|
|
|
248
|
-
/**
|
|
249
|
-
* Resolved config (defaults merged in).
|
|
250
|
-
* All fields required.
|
|
251
|
-
*/
|
|
252
232
|
export interface ResolvedMyExtensionConfig {
|
|
253
233
|
enabled: boolean;
|
|
254
234
|
myOption: string;
|
|
@@ -259,68 +239,22 @@ const DEFAULTS: ResolvedMyExtensionConfig = {
|
|
|
259
239
|
myOption: "default-value",
|
|
260
240
|
};
|
|
261
241
|
|
|
262
|
-
/**
|
|
263
|
-
* Config loader instance.
|
|
264
|
-
* Config is stored at ~/.pi/agent/extensions/<name>.json
|
|
265
|
-
*/
|
|
266
|
-
export const configLoader = new ConfigLoader<
|
|
267
|
-
MyExtensionConfig,
|
|
268
|
-
ResolvedMyExtensionConfig
|
|
269
|
-
>("my-extension", DEFAULTS);
|
|
270
|
-
```
|
|
271
|
-
|
|
272
|
-
`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.
|
|
273
|
-
|
|
274
|
-
The name passed to `ConfigLoader` determines the filename: `"my-extension"` → `~/.pi/agent/extensions/my-extension.json`.
|
|
275
|
-
|
|
276
|
-
### Reading config
|
|
277
|
-
|
|
278
|
-
After calling `load()`, use `getConfig()` for the resolved config (defaults merged in) or `getRawConfig(scope)` for the raw config at a specific scope.
|
|
279
|
-
|
|
280
|
-
```typescript
|
|
281
|
-
await configLoader.load();
|
|
282
|
-
const config = configLoader.getConfig(); // ResolvedMyExtensionConfig
|
|
283
|
-
const raw = configLoader.getRawConfig("global"); // MyExtensionConfig | null
|
|
284
|
-
```
|
|
285
|
-
|
|
286
|
-
### Saving config
|
|
287
|
-
|
|
288
|
-
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.
|
|
289
|
-
|
|
290
|
-
```typescript
|
|
291
|
-
await configLoader.save("global", { myOption: "new-value" });
|
|
292
|
-
// configLoader.getConfig() now reflects the saved change
|
|
293
|
-
```
|
|
294
|
-
|
|
295
|
-
Memory scope is ephemeral -- it resets on reload and is not written to disk.
|
|
296
|
-
|
|
297
|
-
### Scopes and merge order
|
|
298
|
-
|
|
299
|
-
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.
|
|
300
|
-
|
|
301
|
-
For extensions with migrations or multi-scope config (global + local + in-memory), pass an options object:
|
|
302
|
-
|
|
303
|
-
```typescript
|
|
304
242
|
export const configLoader = new ConfigLoader<MyExtensionConfig, ResolvedMyExtensionConfig>(
|
|
305
243
|
"my-extension",
|
|
306
244
|
DEFAULTS,
|
|
307
|
-
{
|
|
308
|
-
scopes: ["global", "local", "memory"],
|
|
309
|
-
migrations: [...],
|
|
310
|
-
},
|
|
311
245
|
);
|
|
312
246
|
```
|
|
313
247
|
|
|
314
|
-
|
|
248
|
+
After `await configLoader.load()`, use `configLoader.getConfig()` for resolved config and `getRawConfig(scope)` for a scope's raw overrides.
|
|
315
249
|
|
|
316
|
-
|
|
250
|
+
### Scopes and Migrations
|
|
317
251
|
|
|
318
252
|
```typescript
|
|
319
253
|
import { ConfigLoader, type Migration, buildSchemaUrl } from "@aliou/pi-utils-settings";
|
|
320
254
|
import pkg from "../package.json" with { type: "json" };
|
|
321
255
|
|
|
322
|
-
const
|
|
323
|
-
name: "legacy-
|
|
256
|
+
const migration: Migration<MyConfig> = {
|
|
257
|
+
name: "legacy-key-to-workspaces",
|
|
324
258
|
shouldRun: (config) => Boolean(config.apiKey && !config.workspaces),
|
|
325
259
|
run: (config) => {
|
|
326
260
|
const migrated = structuredClone(config);
|
|
@@ -330,30 +264,22 @@ const legacyMigration: Migration<MyExtensionConfig> = {
|
|
|
330
264
|
},
|
|
331
265
|
};
|
|
332
266
|
|
|
333
|
-
const schemaUrl = buildSchemaUrl(pkg.name, pkg.version);
|
|
334
|
-
|
|
335
267
|
export const configLoader = new ConfigLoader<MyConfig, ResolvedMyConfig>(
|
|
336
268
|
"my-extension",
|
|
337
269
|
DEFAULTS,
|
|
338
270
|
{
|
|
339
|
-
|
|
340
|
-
|
|
271
|
+
scopes: ["global", "local", "memory"],
|
|
272
|
+
schemaUrl: buildSchemaUrl(pkg.name, pkg.version),
|
|
273
|
+
migrations: [migration],
|
|
341
274
|
},
|
|
342
275
|
);
|
|
343
276
|
```
|
|
344
277
|
|
|
345
|
-
|
|
346
|
-
- `name`: unique identifier for idempotency
|
|
347
|
-
- `shouldRun(config)`: predicate that returns true if migration is needed
|
|
348
|
-
- `run(config)`: returns the migrated config (must not mutate the input)
|
|
349
|
-
|
|
350
|
-
### JSON Schema for Config Validation
|
|
351
|
-
|
|
352
|
-
Use `buildSchemaUrl(pkg.name, pkg.version)` from `@aliou/pi-utils-settings` to generate a schema URL. Config files get a `$schema` field pointing to the published schema, enabling editor validation and autocompletion.
|
|
278
|
+
Migrations must be named, idempotent, and must not mutate their input.
|
|
353
279
|
|
|
354
280
|
## Settings Command
|
|
355
281
|
|
|
356
|
-
|
|
282
|
+
Use `registerSettingsCommand` from `@aliou/pi-utils-settings` for configurable extensions.
|
|
357
283
|
|
|
358
284
|
```typescript
|
|
359
285
|
import { registerSettingsCommand, type SettingsSection } from "@aliou/pi-utils-settings";
|
|
@@ -363,7 +289,9 @@ registerSettingsCommand<MyConfig, ResolvedMyConfig>(pi, {
|
|
|
363
289
|
commandDescription: "Configure my extension",
|
|
364
290
|
title: "My Extension Settings",
|
|
365
291
|
configStore: configLoader,
|
|
366
|
-
onSave: () => {
|
|
292
|
+
onSave: () => {
|
|
293
|
+
// Invalidate caches.
|
|
294
|
+
},
|
|
367
295
|
buildSections: (tabConfig, resolved, ctx): SettingsSection[] => [
|
|
368
296
|
{
|
|
369
297
|
label: "General",
|
|
@@ -381,137 +309,109 @@ registerSettingsCommand<MyConfig, ResolvedMyConfig>(pi, {
|
|
|
381
309
|
});
|
|
382
310
|
```
|
|
383
311
|
|
|
384
|
-
For
|
|
312
|
+
For onboarding credentials, use the `Wizard` component from `@aliou/pi-utils-settings`.
|
|
385
313
|
|
|
386
|
-
|
|
314
|
+
## Entry Point Pattern
|
|
387
315
|
|
|
388
|
-
|
|
316
|
+
Each feature entry point loads config, checks `enabled`, then registers its feature.
|
|
389
317
|
|
|
390
318
|
```typescript
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
{ label: "Scope", build: (ctx) => new ScopeStep(state, ctx) },
|
|
400
|
-
],
|
|
401
|
-
onComplete: async () => { /* save config */ },
|
|
402
|
-
onCancel: () => done(false),
|
|
319
|
+
// src/tools/index.ts
|
|
320
|
+
import type { AgentToolResult, ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
321
|
+
import { defineTool } from "@earendil-works/pi-coding-agent";
|
|
322
|
+
import { type Static, Type } from "typebox";
|
|
323
|
+
import { configLoader } from "../config";
|
|
324
|
+
|
|
325
|
+
const parameters = Type.Object({
|
|
326
|
+
query: Type.String({ description: "Search query" }),
|
|
403
327
|
});
|
|
404
|
-
```
|
|
405
|
-
|
|
406
|
-
Each step receives a `WizardStepContext` with `markComplete()`/`markIncomplete()` to control navigation gates. See `pi-linear/src/commands/auth-wizard.ts` for a full example with async validation and spinner.
|
|
407
328
|
|
|
408
|
-
|
|
329
|
+
type MyToolParams = Static<typeof parameters>;
|
|
409
330
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
331
|
+
interface MyToolDetails {
|
|
332
|
+
results: string[];
|
|
333
|
+
}
|
|
413
334
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
335
|
+
const myTool = defineTool({
|
|
336
|
+
name: "my_tool",
|
|
337
|
+
label: "My Tool",
|
|
338
|
+
description: "Search for items",
|
|
339
|
+
parameters,
|
|
340
|
+
async execute(_toolCallId, params, signal): Promise<AgentToolResult<MyToolDetails>> {
|
|
341
|
+
const results = await search(params.query, { signal });
|
|
342
|
+
return {
|
|
343
|
+
content: [{ type: "text", text: JSON.stringify(results) }],
|
|
344
|
+
details: { results },
|
|
345
|
+
};
|
|
346
|
+
},
|
|
347
|
+
});
|
|
420
348
|
|
|
421
|
-
export default async function (pi: ExtensionAPI) {
|
|
349
|
+
export default async function toolsExtension(pi: ExtensionAPI) {
|
|
422
350
|
await configLoader.load();
|
|
423
351
|
const config = configLoader.getConfig();
|
|
424
352
|
if (!config.enabled) return;
|
|
425
353
|
|
|
426
|
-
|
|
427
|
-
registerCommands(pi);
|
|
428
|
-
registerHooks(pi);
|
|
354
|
+
pi.registerTool(myTool);
|
|
429
355
|
}
|
|
430
356
|
```
|
|
431
357
|
|
|
432
|
-
### Acceptable
|
|
433
|
-
|
|
434
|
-
Not all extensions follow the standard pattern exactly. These deviations are valid:
|
|
435
|
-
|
|
436
|
-
**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.
|
|
358
|
+
### Acceptable Deviations
|
|
437
359
|
|
|
438
|
-
|
|
360
|
+
Document deviations in `AGENTS.md`.
|
|
439
361
|
|
|
440
|
-
**No
|
|
441
|
-
|
|
442
|
-
|
|
362
|
+
- **No config**: no user-configurable settings.
|
|
363
|
+
- **API-key-first**: check required API key before loading config or registering API-dependent features.
|
|
364
|
+
- **No enabled toggle**: extension is always active by design.
|
|
365
|
+
- **Shared bootstrap**: multiple entry points call a shared setup helper.
|
|
443
366
|
|
|
444
367
|
## API Key Pattern
|
|
445
368
|
|
|
446
|
-
If your extension wraps a third-party API that requires an API key:
|
|
447
|
-
|
|
448
369
|
```typescript
|
|
449
|
-
export default function (pi: ExtensionAPI) {
|
|
370
|
+
export default function toolsExtension(pi: ExtensionAPI) {
|
|
450
371
|
const apiKey = process.env.MY_API_KEY;
|
|
451
372
|
|
|
452
|
-
// Register provider unconditionally if it exists
|
|
453
|
-
// (provider handles missing key internally for model registration)
|
|
454
|
-
pi.registerProvider(myProvider);
|
|
455
|
-
|
|
456
|
-
// Only register tools that need the key
|
|
457
373
|
if (!apiKey) {
|
|
458
|
-
pi.on("session_start",
|
|
459
|
-
ctx.ui.notify("MY_API_KEY not set.
|
|
374
|
+
pi.on("session_start", (_event, ctx) => {
|
|
375
|
+
ctx.ui.notify("MY_API_KEY not set. my-extension tools disabled.", "warning");
|
|
460
376
|
});
|
|
461
377
|
return;
|
|
462
378
|
}
|
|
463
379
|
|
|
464
380
|
pi.registerTool(createMyTool(apiKey));
|
|
465
|
-
pi.registerCommand(createMyCommand(apiKey));
|
|
466
381
|
}
|
|
467
382
|
```
|
|
468
383
|
|
|
469
|
-
|
|
384
|
+
Providers are different: register providers even if a key may be absent, because Pi handles auth resolution and login UI.
|
|
470
385
|
|
|
471
386
|
## Imports
|
|
472
387
|
|
|
473
|
-
Do not use `.js` file extensions in imports.
|
|
388
|
+
Do not use `.js` file extensions in TypeScript imports.
|
|
474
389
|
|
|
475
390
|
```typescript
|
|
476
391
|
// Correct
|
|
477
392
|
import { myTool } from "./tools/my-tool";
|
|
478
|
-
import type { MyType } from "./types";
|
|
479
393
|
|
|
480
394
|
// Wrong
|
|
481
395
|
import { myTool } from "./tools/my-tool.js";
|
|
482
396
|
```
|
|
483
397
|
|
|
398
|
+
Do not use inline dynamic imports unless there is a documented reason.
|
|
399
|
+
|
|
484
400
|
## Monorepo Variant
|
|
485
401
|
|
|
486
|
-
In
|
|
402
|
+
In pnpm workspaces, package roots may not have `src/`.
|
|
487
403
|
|
|
488
404
|
```
|
|
489
|
-
extensions/
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
my-hook.ts
|
|
497
|
-
components/
|
|
498
|
-
my-editor.ts
|
|
499
|
-
utils/
|
|
500
|
-
matching.ts
|
|
501
|
-
shell-utils.ts
|
|
502
|
-
package.json
|
|
405
|
+
extensions/my-extension/
|
|
406
|
+
index.ts
|
|
407
|
+
config.ts
|
|
408
|
+
commands/
|
|
409
|
+
hooks/
|
|
410
|
+
components/
|
|
411
|
+
package.json
|
|
503
412
|
```
|
|
504
413
|
|
|
505
|
-
|
|
506
|
-
- Entry point directly in the package root (no `src/` directory).
|
|
507
|
-
- `"pi": { "extensions": ["./index.ts"] }` instead of `["./src/index.ts"]`.
|
|
508
|
-
- Uses `peerDependencies` (resolved by workspace root).
|
|
509
|
-
- Shared `tsconfig` from a workspace package.
|
|
510
|
-
- Same organization principles apply: config types in `config.ts`, helpers in `utils/`, one directory per feature category.
|
|
511
|
-
|
|
512
|
-
### Workspace dependencies
|
|
513
|
-
|
|
514
|
-
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:
|
|
414
|
+
Use workspace protocol only for local workspace packages.
|
|
515
415
|
|
|
516
416
|
```json
|
|
517
417
|
{
|
|
@@ -522,4 +422,15 @@ When an extension depends on another workspace package (e.g., `@aliou/pi-utils-s
|
|
|
522
422
|
}
|
|
523
423
|
```
|
|
524
424
|
|
|
525
|
-
|
|
425
|
+
Do not publish packages that depend on private workspace packages.
|
|
426
|
+
|
|
427
|
+
## Checklist
|
|
428
|
+
|
|
429
|
+
- [ ] One entry point per feature directory.
|
|
430
|
+
- [ ] No root fan-out registrar in new code.
|
|
431
|
+
- [ ] Pi core imports are optional peers with `"*"` and exact dev deps.
|
|
432
|
+
- [ ] Third-party runtime packages are in `dependencies`.
|
|
433
|
+
- [ ] Config uses raw/resolved TypeScript interfaces.
|
|
434
|
+
- [ ] Settings use `registerSettingsCommand` when configurable.
|
|
435
|
+
- [ ] API-key-missing path notifies and disables affected features.
|
|
436
|
+
- [ ] No `.js` suffixes in TypeScript imports.
|
|
@@ -44,7 +44,7 @@ Test event hooks by triggering the relevant actions:
|
|
|
44
44
|
|
|
45
45
|
## Unit Testing Core Logic
|
|
46
46
|
|
|
47
|
-
The core/lib pattern makes domain logic testable without the Pi framework. Extract business logic into modules that don't import from `@mariozechner/pi-coding-agent` and test them directly.
|
|
47
|
+
The core/lib pattern makes domain logic testable without the Pi framework. Extract business logic into modules that don't import from Pi core packages (`@earendil-works/pi-coding-agent` or legacy `@mariozechner/pi-coding-agent`) and test them directly.
|
|
48
48
|
|
|
49
49
|
### Testable core modules
|
|
50
50
|
|