@oh-my-pi/pi-coding-agent 1.337.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/CHANGELOG.md +1228 -0
- package/README.md +1041 -0
- package/docs/compaction.md +403 -0
- package/docs/custom-tools.md +541 -0
- package/docs/extension-loading.md +1004 -0
- package/docs/hooks.md +867 -0
- package/docs/rpc.md +1040 -0
- package/docs/sdk.md +994 -0
- package/docs/session-tree-plan.md +441 -0
- package/docs/session.md +240 -0
- package/docs/skills.md +290 -0
- package/docs/theme.md +637 -0
- package/docs/tree.md +197 -0
- package/docs/tui.md +341 -0
- package/examples/README.md +21 -0
- package/examples/custom-tools/README.md +124 -0
- package/examples/custom-tools/hello/index.ts +20 -0
- package/examples/custom-tools/question/index.ts +84 -0
- package/examples/custom-tools/subagent/README.md +172 -0
- package/examples/custom-tools/subagent/agents/planner.md +37 -0
- package/examples/custom-tools/subagent/agents/reviewer.md +35 -0
- package/examples/custom-tools/subagent/agents/scout.md +50 -0
- package/examples/custom-tools/subagent/agents/worker.md +24 -0
- package/examples/custom-tools/subagent/agents.ts +156 -0
- package/examples/custom-tools/subagent/commands/implement-and-review.md +10 -0
- package/examples/custom-tools/subagent/commands/implement.md +10 -0
- package/examples/custom-tools/subagent/commands/scout-and-plan.md +9 -0
- package/examples/custom-tools/subagent/index.ts +1002 -0
- package/examples/custom-tools/todo/index.ts +212 -0
- package/examples/hooks/README.md +56 -0
- package/examples/hooks/auto-commit-on-exit.ts +49 -0
- package/examples/hooks/confirm-destructive.ts +59 -0
- package/examples/hooks/custom-compaction.ts +116 -0
- package/examples/hooks/dirty-repo-guard.ts +52 -0
- package/examples/hooks/file-trigger.ts +41 -0
- package/examples/hooks/git-checkpoint.ts +53 -0
- package/examples/hooks/handoff.ts +150 -0
- package/examples/hooks/permission-gate.ts +34 -0
- package/examples/hooks/protected-paths.ts +30 -0
- package/examples/hooks/qna.ts +119 -0
- package/examples/hooks/snake.ts +343 -0
- package/examples/hooks/status-line.ts +40 -0
- package/examples/sdk/01-minimal.ts +22 -0
- package/examples/sdk/02-custom-model.ts +49 -0
- package/examples/sdk/03-custom-prompt.ts +44 -0
- package/examples/sdk/04-skills.ts +44 -0
- package/examples/sdk/05-tools.ts +90 -0
- package/examples/sdk/06-hooks.ts +61 -0
- package/examples/sdk/07-context-files.ts +36 -0
- package/examples/sdk/08-slash-commands.ts +42 -0
- package/examples/sdk/09-api-keys-and-oauth.ts +55 -0
- package/examples/sdk/10-settings.ts +38 -0
- package/examples/sdk/11-sessions.ts +48 -0
- package/examples/sdk/12-full-control.ts +95 -0
- package/examples/sdk/README.md +154 -0
- package/package.json +81 -0
- package/src/cli/args.ts +246 -0
- package/src/cli/file-processor.ts +72 -0
- package/src/cli/list-models.ts +104 -0
- package/src/cli/plugin-cli.ts +650 -0
- package/src/cli/session-picker.ts +41 -0
- package/src/cli.ts +10 -0
- package/src/commands/init.md +20 -0
- package/src/config.ts +159 -0
- package/src/core/agent-session.ts +1900 -0
- package/src/core/auth-storage.ts +236 -0
- package/src/core/bash-executor.ts +196 -0
- package/src/core/compaction/branch-summarization.ts +343 -0
- package/src/core/compaction/compaction.ts +742 -0
- package/src/core/compaction/index.ts +7 -0
- package/src/core/compaction/utils.ts +154 -0
- package/src/core/custom-tools/index.ts +21 -0
- package/src/core/custom-tools/loader.ts +248 -0
- package/src/core/custom-tools/types.ts +169 -0
- package/src/core/custom-tools/wrapper.ts +28 -0
- package/src/core/exec.ts +129 -0
- package/src/core/export-html/index.ts +211 -0
- package/src/core/export-html/template.css +781 -0
- package/src/core/export-html/template.html +54 -0
- package/src/core/export-html/template.js +1185 -0
- package/src/core/export-html/vendor/highlight.min.js +1213 -0
- package/src/core/export-html/vendor/marked.min.js +6 -0
- package/src/core/hooks/index.ts +16 -0
- package/src/core/hooks/loader.ts +312 -0
- package/src/core/hooks/runner.ts +434 -0
- package/src/core/hooks/tool-wrapper.ts +99 -0
- package/src/core/hooks/types.ts +773 -0
- package/src/core/index.ts +52 -0
- package/src/core/mcp/client.ts +158 -0
- package/src/core/mcp/config.ts +154 -0
- package/src/core/mcp/index.ts +45 -0
- package/src/core/mcp/loader.ts +68 -0
- package/src/core/mcp/manager.ts +181 -0
- package/src/core/mcp/tool-bridge.ts +148 -0
- package/src/core/mcp/transports/http.ts +316 -0
- package/src/core/mcp/transports/index.ts +6 -0
- package/src/core/mcp/transports/stdio.ts +252 -0
- package/src/core/mcp/types.ts +220 -0
- package/src/core/messages.ts +189 -0
- package/src/core/model-registry.ts +317 -0
- package/src/core/model-resolver.ts +393 -0
- package/src/core/plugins/doctor.ts +59 -0
- package/src/core/plugins/index.ts +38 -0
- package/src/core/plugins/installer.ts +189 -0
- package/src/core/plugins/loader.ts +338 -0
- package/src/core/plugins/manager.ts +672 -0
- package/src/core/plugins/parser.ts +105 -0
- package/src/core/plugins/paths.ts +32 -0
- package/src/core/plugins/types.ts +190 -0
- package/src/core/sdk.ts +760 -0
- package/src/core/session-manager.ts +1128 -0
- package/src/core/settings-manager.ts +443 -0
- package/src/core/skills.ts +437 -0
- package/src/core/slash-commands.ts +248 -0
- package/src/core/system-prompt.ts +439 -0
- package/src/core/timings.ts +25 -0
- package/src/core/tools/ask.ts +211 -0
- package/src/core/tools/bash-interceptor.ts +120 -0
- package/src/core/tools/bash.ts +250 -0
- package/src/core/tools/context.ts +32 -0
- package/src/core/tools/edit-diff.ts +475 -0
- package/src/core/tools/edit.ts +208 -0
- package/src/core/tools/exa/company.ts +59 -0
- package/src/core/tools/exa/index.ts +64 -0
- package/src/core/tools/exa/linkedin.ts +59 -0
- package/src/core/tools/exa/logger.ts +56 -0
- package/src/core/tools/exa/mcp-client.ts +368 -0
- package/src/core/tools/exa/render.ts +196 -0
- package/src/core/tools/exa/researcher.ts +90 -0
- package/src/core/tools/exa/search.ts +337 -0
- package/src/core/tools/exa/types.ts +168 -0
- package/src/core/tools/exa/websets.ts +248 -0
- package/src/core/tools/find.ts +261 -0
- package/src/core/tools/grep.ts +555 -0
- package/src/core/tools/index.ts +202 -0
- package/src/core/tools/ls.ts +140 -0
- package/src/core/tools/lsp/client.ts +605 -0
- package/src/core/tools/lsp/config.ts +147 -0
- package/src/core/tools/lsp/edits.ts +101 -0
- package/src/core/tools/lsp/index.ts +804 -0
- package/src/core/tools/lsp/render.ts +447 -0
- package/src/core/tools/lsp/rust-analyzer.ts +145 -0
- package/src/core/tools/lsp/types.ts +463 -0
- package/src/core/tools/lsp/utils.ts +486 -0
- package/src/core/tools/notebook.ts +229 -0
- package/src/core/tools/path-utils.ts +61 -0
- package/src/core/tools/read.ts +240 -0
- package/src/core/tools/renderers.ts +540 -0
- package/src/core/tools/task/agents.ts +153 -0
- package/src/core/tools/task/artifacts.ts +114 -0
- package/src/core/tools/task/bundled-agents/browser.md +71 -0
- package/src/core/tools/task/bundled-agents/explore.md +82 -0
- package/src/core/tools/task/bundled-agents/plan.md +54 -0
- package/src/core/tools/task/bundled-agents/reviewer.md +59 -0
- package/src/core/tools/task/bundled-agents/task.md +53 -0
- package/src/core/tools/task/bundled-commands/architect-plan.md +10 -0
- package/src/core/tools/task/bundled-commands/implement-with-critic.md +11 -0
- package/src/core/tools/task/bundled-commands/implement.md +11 -0
- package/src/core/tools/task/commands.ts +213 -0
- package/src/core/tools/task/discovery.ts +208 -0
- package/src/core/tools/task/executor.ts +367 -0
- package/src/core/tools/task/index.ts +388 -0
- package/src/core/tools/task/model-resolver.ts +115 -0
- package/src/core/tools/task/parallel.ts +38 -0
- package/src/core/tools/task/render.ts +232 -0
- package/src/core/tools/task/types.ts +99 -0
- package/src/core/tools/truncate.ts +265 -0
- package/src/core/tools/web-fetch.ts +2370 -0
- package/src/core/tools/web-search/auth.ts +193 -0
- package/src/core/tools/web-search/index.ts +537 -0
- package/src/core/tools/web-search/providers/anthropic.ts +198 -0
- package/src/core/tools/web-search/providers/exa.ts +302 -0
- package/src/core/tools/web-search/providers/perplexity.ts +195 -0
- package/src/core/tools/web-search/render.ts +182 -0
- package/src/core/tools/web-search/types.ts +180 -0
- package/src/core/tools/write.ts +99 -0
- package/src/index.ts +176 -0
- package/src/main.ts +464 -0
- package/src/migrations.ts +135 -0
- package/src/modes/index.ts +43 -0
- package/src/modes/interactive/components/armin.ts +382 -0
- package/src/modes/interactive/components/assistant-message.ts +86 -0
- package/src/modes/interactive/components/bash-execution.ts +196 -0
- package/src/modes/interactive/components/bordered-loader.ts +41 -0
- package/src/modes/interactive/components/branch-summary-message.ts +42 -0
- package/src/modes/interactive/components/compaction-summary-message.ts +45 -0
- package/src/modes/interactive/components/custom-editor.ts +122 -0
- package/src/modes/interactive/components/diff.ts +147 -0
- package/src/modes/interactive/components/dynamic-border.ts +25 -0
- package/src/modes/interactive/components/footer.ts +381 -0
- package/src/modes/interactive/components/hook-editor.ts +117 -0
- package/src/modes/interactive/components/hook-input.ts +64 -0
- package/src/modes/interactive/components/hook-message.ts +96 -0
- package/src/modes/interactive/components/hook-selector.ts +91 -0
- package/src/modes/interactive/components/model-selector.ts +247 -0
- package/src/modes/interactive/components/oauth-selector.ts +120 -0
- package/src/modes/interactive/components/plugin-settings.ts +479 -0
- package/src/modes/interactive/components/queue-mode-selector.ts +56 -0
- package/src/modes/interactive/components/session-selector.ts +204 -0
- package/src/modes/interactive/components/settings-selector.ts +453 -0
- package/src/modes/interactive/components/show-images-selector.ts +45 -0
- package/src/modes/interactive/components/theme-selector.ts +62 -0
- package/src/modes/interactive/components/thinking-selector.ts +64 -0
- package/src/modes/interactive/components/tool-execution.ts +675 -0
- package/src/modes/interactive/components/tree-selector.ts +866 -0
- package/src/modes/interactive/components/user-message-selector.ts +159 -0
- package/src/modes/interactive/components/user-message.ts +18 -0
- package/src/modes/interactive/components/visual-truncate.ts +50 -0
- package/src/modes/interactive/components/welcome.ts +183 -0
- package/src/modes/interactive/interactive-mode.ts +2516 -0
- package/src/modes/interactive/theme/dark.json +101 -0
- package/src/modes/interactive/theme/light.json +98 -0
- package/src/modes/interactive/theme/theme-schema.json +308 -0
- package/src/modes/interactive/theme/theme.ts +998 -0
- package/src/modes/print-mode.ts +128 -0
- package/src/modes/rpc/rpc-client.ts +527 -0
- package/src/modes/rpc/rpc-mode.ts +483 -0
- package/src/modes/rpc/rpc-types.ts +203 -0
- package/src/utils/changelog.ts +99 -0
- package/src/utils/clipboard.ts +265 -0
- package/src/utils/fuzzy.ts +108 -0
- package/src/utils/mime.ts +30 -0
- package/src/utils/shell.ts +276 -0
- package/src/utils/tools-manager.ts +274 -0
|
@@ -0,0 +1,1004 @@
|
|
|
1
|
+
# Extension Loading
|
|
2
|
+
|
|
3
|
+
Unified system for loading hooks, tools, skills, and themes from local files, directories, npm packages, and git repositories.
|
|
4
|
+
|
|
5
|
+
## Extension Types
|
|
6
|
+
|
|
7
|
+
| Type | Root Entry | Subdir Entry | Purpose |
|
|
8
|
+
|------|------------|--------------|---------|
|
|
9
|
+
| Hooks | `*.ts` / `*.js` | `index.ts` / `index.js` / package.json `main` | Event handlers for agent lifecycle |
|
|
10
|
+
| Tools | `*.ts` / `*.js` | `index.ts` / `index.js` / package.json `main` | Custom tools for the agent |
|
|
11
|
+
| Skills | `*.md` | `SKILL.md` | Context/instructions loaded into agent |
|
|
12
|
+
| Themes | `*.theme.json` | `*.theme.json` (recursive) | Color schemes for TUI |
|
|
13
|
+
|
|
14
|
+
**Note:** Themes use `*.theme.json` pattern scanned recursively at all levels, allowing flat theme packs without requiring subdirectories.
|
|
15
|
+
|
|
16
|
+
## Sources
|
|
17
|
+
|
|
18
|
+
Extensions can be loaded from:
|
|
19
|
+
|
|
20
|
+
### File Paths
|
|
21
|
+
```
|
|
22
|
+
./my-hook.ts
|
|
23
|
+
~/global-hook.ts
|
|
24
|
+
/absolute/path/hook.ts
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Directories
|
|
28
|
+
```
|
|
29
|
+
./my-hooks/
|
|
30
|
+
~/.pi/agent/hooks/
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### npm Packages
|
|
34
|
+
```
|
|
35
|
+
npm:package-name
|
|
36
|
+
npm:package-name@1.2.3
|
|
37
|
+
npm:package-name@latest
|
|
38
|
+
npm:@scope/package-name
|
|
39
|
+
npm:@scope/package-name@1.2.3
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Git Repositories
|
|
43
|
+
```
|
|
44
|
+
git:https://github.com/user/repo
|
|
45
|
+
git:https://github.com/user/repo@v1.0.0 # tag
|
|
46
|
+
git:https://github.com/user/repo@abc123f # commit
|
|
47
|
+
git:https://github.com/user/repo#branch # branch
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Storage Layout
|
|
51
|
+
|
|
52
|
+
### Permanent (settings.json)
|
|
53
|
+
```
|
|
54
|
+
~/.pi/agent/
|
|
55
|
+
hooks/
|
|
56
|
+
my-local-hook.ts # root-level file
|
|
57
|
+
complex-hook/ # directory with entry point
|
|
58
|
+
index.ts
|
|
59
|
+
utils.ts
|
|
60
|
+
npm/
|
|
61
|
+
my-hook@1.2.3/ # npm package
|
|
62
|
+
package.json
|
|
63
|
+
node_modules/
|
|
64
|
+
index.js
|
|
65
|
+
@scope/
|
|
66
|
+
scoped-hook@2.0.0/
|
|
67
|
+
...
|
|
68
|
+
git/
|
|
69
|
+
github.com/user/repo@v1.0.0/ # git repo
|
|
70
|
+
...
|
|
71
|
+
tools/
|
|
72
|
+
... (same structure)
|
|
73
|
+
skills/
|
|
74
|
+
... (same structure, but SKILL.md instead of index.ts)
|
|
75
|
+
themes/
|
|
76
|
+
dark.theme.json # root-level theme
|
|
77
|
+
light.theme.json
|
|
78
|
+
community-pack/ # theme pack (no entry point needed)
|
|
79
|
+
nord.theme.json
|
|
80
|
+
dracula.theme.json
|
|
81
|
+
npm/
|
|
82
|
+
cool-themes@1.0.0/
|
|
83
|
+
monokai.theme.json
|
|
84
|
+
solarized.theme.json
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Ephemeral (CLI flags)
|
|
88
|
+
```
|
|
89
|
+
/tmp/pi-extensions/
|
|
90
|
+
hooks/
|
|
91
|
+
npm/
|
|
92
|
+
my-hook@1.0.0/
|
|
93
|
+
git/
|
|
94
|
+
github.com/user/repo@v1.0.0/
|
|
95
|
+
tools/
|
|
96
|
+
...
|
|
97
|
+
skills/
|
|
98
|
+
...
|
|
99
|
+
themes/
|
|
100
|
+
...
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Temp directory persists until OS clears `/tmp/`. No re-download needed across sessions (usually).
|
|
104
|
+
|
|
105
|
+
## Entry Point Resolution
|
|
106
|
+
|
|
107
|
+
For each discovered directory, resolve entry point in order:
|
|
108
|
+
|
|
109
|
+
### Hooks & Tools
|
|
110
|
+
1. `index.ts` (if exists)
|
|
111
|
+
2. `index.js` (if exists)
|
|
112
|
+
3. `main` field in `package.json` (if exists)
|
|
113
|
+
|
|
114
|
+
### Skills
|
|
115
|
+
1. `SKILL.md` (required)
|
|
116
|
+
|
|
117
|
+
### Themes
|
|
118
|
+
Themes use recursive pattern matching instead of fixed entry points:
|
|
119
|
+
- Scan recursively for `*.theme.json` files at all levels
|
|
120
|
+
- Each matching file is a separate theme
|
|
121
|
+
- Path derived from filename (e.g., `dark.theme.json` → `dark`, `pack/nord.theme.json` → `pack/nord`)
|
|
122
|
+
|
|
123
|
+
## Scanning Algorithm
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
scan(baseDir, config):
|
|
127
|
+
results = []
|
|
128
|
+
|
|
129
|
+
for entry in baseDir:
|
|
130
|
+
skip if entry.name starts with "."
|
|
131
|
+
skip if entry.name == "node_modules"
|
|
132
|
+
skip if entry.name ends with ".installing"
|
|
133
|
+
|
|
134
|
+
if entry is file:
|
|
135
|
+
if matches rootPattern (e.g., *.ts, *.js, *.md, *.theme.json):
|
|
136
|
+
results.add(entry)
|
|
137
|
+
|
|
138
|
+
if entry is directory:
|
|
139
|
+
if config.recursivePattern:
|
|
140
|
+
# For themes: scan recursively for *.theme.json everywhere
|
|
141
|
+
results.addAll(scan(directory, config))
|
|
142
|
+
else if has entryPoint (index.ts, index.js, SKILL.md):
|
|
143
|
+
results.add(directory) # load as single extension
|
|
144
|
+
else:
|
|
145
|
+
results.addAll(scan(directory, config)) # recurse to find extensions
|
|
146
|
+
|
|
147
|
+
return results
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
**Default directories scanned (always, regardless of settings.json):**
|
|
151
|
+
- `~/.pi/agent/<type>/`
|
|
152
|
+
- `<cwd>/.pi/<type>/`
|
|
153
|
+
|
|
154
|
+
## Extension Packs
|
|
155
|
+
|
|
156
|
+
A key use case is pulling in a **pack** (collection) of extensions via a directory, npm package, or git repo, then filtering to a subset.
|
|
157
|
+
|
|
158
|
+
**Example: Skill pack**
|
|
159
|
+
```
|
|
160
|
+
npm:pi-skills@1.0.0 contains:
|
|
161
|
+
skills/
|
|
162
|
+
brave-search/SKILL.md
|
|
163
|
+
browser-tools/SKILL.md
|
|
164
|
+
transcribe/SKILL.md
|
|
165
|
+
youtube-transcript/SKILL.md
|
|
166
|
+
... (10+ skills)
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
You want only 2 of them:
|
|
170
|
+
```json
|
|
171
|
+
{
|
|
172
|
+
"skills": {
|
|
173
|
+
"paths": ["npm:pi-skills@1.0.0"],
|
|
174
|
+
"filter": ["brave-search", "youtube-transcript"]
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
**Example: Theme pack**
|
|
180
|
+
```
|
|
181
|
+
npm:community-themes@1.0.0 contains:
|
|
182
|
+
themes/
|
|
183
|
+
nord.theme.json
|
|
184
|
+
dracula.theme.json
|
|
185
|
+
solarized-dark.theme.json
|
|
186
|
+
solarized-light.theme.json
|
|
187
|
+
monokai.theme.json
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Exclude solarized variants:
|
|
191
|
+
```json
|
|
192
|
+
{
|
|
193
|
+
"themes": {
|
|
194
|
+
"paths": ["npm:community-themes@1.0.0"],
|
|
195
|
+
"filter": ["!solarized-*"]
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
**Example: Hook pack**
|
|
201
|
+
```
|
|
202
|
+
npm:audit-hooks@1.0.0 contains:
|
|
203
|
+
hooks/
|
|
204
|
+
file-audit/index.ts
|
|
205
|
+
command-audit/index.ts
|
|
206
|
+
network-audit/index.ts
|
|
207
|
+
debug-logger/index.ts
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
All except debug:
|
|
211
|
+
```json
|
|
212
|
+
{
|
|
213
|
+
"hooks": {
|
|
214
|
+
"paths": ["npm:audit-hooks@1.0.0"],
|
|
215
|
+
"filter": ["!debug-*"]
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## Filtering
|
|
221
|
+
|
|
222
|
+
Single filter array with `!` prefix for exclusion. Patterns are matched against extension paths (directory or filename without extension).
|
|
223
|
+
|
|
224
|
+
```json
|
|
225
|
+
{
|
|
226
|
+
"filter": ["pattern1", "pattern2", "!excluded-pattern"]
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
**Logic:**
|
|
231
|
+
1. Collect all patterns without `!` prefix → include patterns
|
|
232
|
+
2. Collect all patterns with `!` prefix → exclude patterns
|
|
233
|
+
3. If include patterns exist: start with extensions matching any include pattern
|
|
234
|
+
4. If no include patterns: start with all extensions
|
|
235
|
+
5. Remove extensions matching any exclude pattern
|
|
236
|
+
|
|
237
|
+
**Examples:**
|
|
238
|
+
```json
|
|
239
|
+
["brave-search"] // only brave-search
|
|
240
|
+
["brave-*", "docker"] // brave-search, brave-api, docker
|
|
241
|
+
["!transcribe"] // all except transcribe
|
|
242
|
+
["audit-*", "!audit-debug"] // audit-* except audit-debug
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Patterns are glob patterns matched against extension paths.
|
|
246
|
+
|
|
247
|
+
## CLI Arguments
|
|
248
|
+
|
|
249
|
+
### Adding Sources
|
|
250
|
+
```bash
|
|
251
|
+
pi --hook <path|npm:|git:> # add hook source (repeatable)
|
|
252
|
+
pi --tool <path|npm:|git:> # add custom tool source (repeatable)
|
|
253
|
+
pi --skill <path|npm:|git:> # add skill source (repeatable)
|
|
254
|
+
pi --theme <path|npm:|git:> # add theme source (repeatable)
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
**Installation locations for npm/git sources:**
|
|
258
|
+
|
|
259
|
+
| Source | Install location |
|
|
260
|
+
|--------|------------------|
|
|
261
|
+
| CLI flags | `/tmp/pi-extensions/<type>/npm/` or `git/` |
|
|
262
|
+
| Global settings (`~/.pi/agent/settings.json`) | `~/.pi/agent/<type>/npm/` or `git/` |
|
|
263
|
+
| Project settings (`<cwd>/.pi/settings.json`) | `<cwd>/.pi/<type>/npm/` or `git/` |
|
|
264
|
+
|
|
265
|
+
File/directory paths are used directly (no installation).
|
|
266
|
+
|
|
267
|
+
- **CLI = ephemeral**: cached in temp until OS clears `/tmp/`
|
|
268
|
+
- **Global settings = permanent**: installed to user's agent directory
|
|
269
|
+
- **Project settings = project-local**: installed to project's `.pi/` directory
|
|
270
|
+
|
|
271
|
+
Examples:
|
|
272
|
+
- `--hook npm:my-hook@1.0.0` → `/tmp/pi-extensions/hooks/npm/my-hook@1.0.0/`
|
|
273
|
+
- Global settings.json `npm:my-hook@1.0.0` → `~/.pi/agent/hooks/npm/my-hook@1.0.0/`
|
|
274
|
+
- Project settings.json `npm:my-hook@1.0.0` → `<cwd>/.pi/hooks/npm/my-hook@1.0.0/`
|
|
275
|
+
|
|
276
|
+
This encourages: try via CLI, if you like it, add to settings.json for permanent install.
|
|
277
|
+
|
|
278
|
+
### Filtering
|
|
279
|
+
```bash
|
|
280
|
+
pi --hooks "pattern1,pattern2,!excluded" # filter hooks
|
|
281
|
+
pi --custom-tools "pattern1,!excluded" # filter custom tools
|
|
282
|
+
pi --skills "pattern1,pattern2" # filter skills
|
|
283
|
+
pi --themes "pattern1" # filter themes
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### Disabling
|
|
287
|
+
```bash
|
|
288
|
+
pi --no-hooks # disable all hooks
|
|
289
|
+
pi --no-custom-tools # disable all custom tools
|
|
290
|
+
pi --no-skills # disable all skills (already exists)
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### Built-in Tools
|
|
294
|
+
```bash
|
|
295
|
+
pi --tools read,bash,edit,write # select which built-in tools to enable (unchanged)
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
## Settings Hierarchy
|
|
299
|
+
|
|
300
|
+
Extensions are configured in settings.json at two levels:
|
|
301
|
+
- **Global**: `~/.pi/agent/settings.json`
|
|
302
|
+
- **Project**: `<cwd>/.pi/settings.json`
|
|
303
|
+
|
|
304
|
+
**Merge behavior:**
|
|
305
|
+
- `paths`: **additive** - project paths are added to global paths
|
|
306
|
+
- `filter`: **override** - project filter replaces global filter if specified
|
|
307
|
+
|
|
308
|
+
**Example:**
|
|
309
|
+
```json
|
|
310
|
+
// Global: ~/.pi/agent/settings.json
|
|
311
|
+
{
|
|
312
|
+
"hooks": {
|
|
313
|
+
"paths": ["npm:audit-hooks@1.0.0"],
|
|
314
|
+
"filter": ["!debug-*"]
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Project: .pi/settings.json
|
|
319
|
+
{
|
|
320
|
+
"hooks": {
|
|
321
|
+
"paths": ["./project-hooks/"],
|
|
322
|
+
"filter": ["audit-*"] // overrides global filter
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Effective:
|
|
327
|
+
{
|
|
328
|
+
"hooks": {
|
|
329
|
+
"paths": ["npm:audit-hooks@1.0.0", "./project-hooks/"],
|
|
330
|
+
"filter": ["audit-*"]
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
## settings.json Structure
|
|
336
|
+
|
|
337
|
+
```json
|
|
338
|
+
{
|
|
339
|
+
"hooks": {
|
|
340
|
+
"paths": [
|
|
341
|
+
"./my-hooks/",
|
|
342
|
+
"npm:@scope/hook@1.0.0",
|
|
343
|
+
"git:https://github.com/user/hooks@v1.0.0"
|
|
344
|
+
],
|
|
345
|
+
"filter": ["audit-*", "!audit-debug"]
|
|
346
|
+
},
|
|
347
|
+
"tools": {
|
|
348
|
+
"paths": ["npm:cool-tools@2.0.0"],
|
|
349
|
+
"filter": ["!dangerous-tool"]
|
|
350
|
+
},
|
|
351
|
+
"skills": {
|
|
352
|
+
"paths": ["npm:pi-skills@1.0.0", "~/my-skills/"],
|
|
353
|
+
"filter": ["brave-search", "git-*", "!git-legacy"]
|
|
354
|
+
},
|
|
355
|
+
"themes": {
|
|
356
|
+
"paths": ["npm:community-themes@1.0.0"]
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
**Migration from current format:**
|
|
362
|
+
- `hooks: string[]` → `hooks.paths: string[]`
|
|
363
|
+
- `customTools: string[]` → `tools.paths: string[]`
|
|
364
|
+
- `skills.customDirectories` → `skills.paths`
|
|
365
|
+
- `skills.includeSkills` → `skills.filter` (patterns without `!`)
|
|
366
|
+
- `skills.ignoredSkills` → `skills.filter` (patterns with `!` prefix)
|
|
367
|
+
|
|
368
|
+
## Installation Flow
|
|
369
|
+
|
|
370
|
+
Target directory depends on source:
|
|
371
|
+
- **CLI flags**: `/tmp/pi-extensions/<type>/npm/` or `git/`
|
|
372
|
+
- **Global settings.json**: `~/.pi/agent/<type>/npm/` or `git/`
|
|
373
|
+
- **Project settings.json**: `<cwd>/.pi/<type>/npm/` or `git/`
|
|
374
|
+
|
|
375
|
+
### Atomic Installation
|
|
376
|
+
|
|
377
|
+
To prevent corrupted state from interrupted installs (Ctrl+C):
|
|
378
|
+
1. Install to `<target>.installing/` (temporary)
|
|
379
|
+
2. On success, atomically rename to `<target>/`
|
|
380
|
+
3. If interrupted, `<target>/` doesn't exist → next run retries cleanly
|
|
381
|
+
4. Scanner filters out `*.installing` directories (see Scanning Algorithm)
|
|
382
|
+
|
|
383
|
+
### npm Packages
|
|
384
|
+
1. Parse specifier: `npm:@scope/pkg@1.2.3` → name: `@scope/pkg`, version: `1.2.3`
|
|
385
|
+
2. Determine target dir based on source (CLI → temp, global → agent dir, project → cwd/.pi/)
|
|
386
|
+
3. If `<target>/` exists and has matching version in package.json → skip install
|
|
387
|
+
4. Otherwise:
|
|
388
|
+
- Remove stale `<target>.installing/` if exists
|
|
389
|
+
- `npm pack @scope/pkg@1.2.3` → download tarball
|
|
390
|
+
- Extract to `<target>.installing/`
|
|
391
|
+
- If `package.json` has `dependencies` → run `npm install`
|
|
392
|
+
- Rename `<target>.installing/` → `<target>/`
|
|
393
|
+
|
|
394
|
+
### Git Repositories
|
|
395
|
+
1. Parse specifier: `git:https://github.com/user/repo@v1.0.0`
|
|
396
|
+
2. Determine target dir based on source (CLI → temp, global → agent dir, project → cwd/.pi/)
|
|
397
|
+
3. If `<target>/` exists → skip clone
|
|
398
|
+
4. Otherwise:
|
|
399
|
+
- Remove stale `<target>.installing/` if exists
|
|
400
|
+
- `git clone <url>` to `<target>.installing/`
|
|
401
|
+
- `git checkout <tag|commit|branch>`
|
|
402
|
+
- If `package.json` has `dependencies` → run `npm install`
|
|
403
|
+
- Rename `<target>.installing/` → `<target>/`
|
|
404
|
+
|
|
405
|
+
## Extension Management Commands
|
|
406
|
+
|
|
407
|
+
### Install
|
|
408
|
+
|
|
409
|
+
Adds extension to settings.json and installs to disk.
|
|
410
|
+
|
|
411
|
+
```bash
|
|
412
|
+
pi install <type> <source> # global (default)
|
|
413
|
+
pi install <type> -p <source> # project-local
|
|
414
|
+
pi install <type> --project <source> # project-local
|
|
415
|
+
|
|
416
|
+
# Examples:
|
|
417
|
+
pi install hook npm:@scope/my-hook@1.0.0
|
|
418
|
+
# → adds to ~/.pi/agent/settings.json
|
|
419
|
+
# → installs to ~/.pi/agent/hooks/npm/@scope/my-hook@1.0.0/
|
|
420
|
+
|
|
421
|
+
pi install tool -p git:https://github.com/user/tool@v1.0.0
|
|
422
|
+
# → adds to <cwd>/.pi/settings.json
|
|
423
|
+
# → installs to <cwd>/.pi/tools/git/github.com/user/tool@v1.0.0/
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
### Remove
|
|
427
|
+
|
|
428
|
+
Removes extension from settings.json and deletes from disk.
|
|
429
|
+
|
|
430
|
+
```bash
|
|
431
|
+
pi remove <type> <name> # from global
|
|
432
|
+
pi remove <type> -p <name> # from project
|
|
433
|
+
|
|
434
|
+
# Examples:
|
|
435
|
+
pi remove hook my-hook # from ~/.pi/agent/settings.json + delete
|
|
436
|
+
pi remove skill -p brave-search # from <cwd>/.pi/settings.json + delete
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
### Update
|
|
440
|
+
|
|
441
|
+
Updates npm/git extensions to latest versions.
|
|
442
|
+
|
|
443
|
+
```bash
|
|
444
|
+
pi update # all (project + global)
|
|
445
|
+
pi update -p # project only
|
|
446
|
+
pi update <type>... # specific types
|
|
447
|
+
pi update -p <type>... # project, specific types
|
|
448
|
+
|
|
449
|
+
# Examples:
|
|
450
|
+
pi update # update everything
|
|
451
|
+
pi update hook tool # update hooks and tools
|
|
452
|
+
pi update -p skill # update project skills only
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
**Update behavior:**
|
|
456
|
+
- `npm:pkg@<version>`: check if newer version exists (e.g., `@latest` resolves to newer)
|
|
457
|
+
- `git:repo#branch`: `git pull`
|
|
458
|
+
- `git:repo@tag` or `git:repo@commit`: no-op (pinned)
|
|
459
|
+
- Local files/directories: no-op
|
|
460
|
+
|
|
461
|
+
## Loading Flow (Full)
|
|
462
|
+
|
|
463
|
+
1. **Collect sources:**
|
|
464
|
+
- Default directories: `~/.pi/agent/<type>/`, `./.pi/<type>/`
|
|
465
|
+
- settings.json `<type>.paths`
|
|
466
|
+
- CLI `--<type>` arguments
|
|
467
|
+
|
|
468
|
+
2. **Install remote sources:**
|
|
469
|
+
- Process `npm:` and `git:` specifiers
|
|
470
|
+
- Install to `~/.pi/agent/<type>/npm/` or `git/`
|
|
471
|
+
|
|
472
|
+
3. **Scan all sources:**
|
|
473
|
+
- Recursively discover extensions
|
|
474
|
+
- Compute relative path for each
|
|
475
|
+
|
|
476
|
+
4. **Apply filter:**
|
|
477
|
+
- Combine settings.json `<type>.filter` and CLI `--<type>s` patterns
|
|
478
|
+
- Filter by path (no loading yet)
|
|
479
|
+
|
|
480
|
+
5. **Load survivors:**
|
|
481
|
+
- Parse/execute only extensions that passed filter
|
|
482
|
+
- Validate (frontmatter, exports, schema)
|
|
483
|
+
- Report errors for invalid extensions
|
|
484
|
+
|
|
485
|
+
---
|
|
486
|
+
|
|
487
|
+
# Implementation Plan
|
|
488
|
+
|
|
489
|
+
## Overview
|
|
490
|
+
|
|
491
|
+
This implementation consolidates four separate loading systems (hooks, tools, skills, themes) into a unified extension loading framework with shared logic for source resolution, installation, scanning, filtering, and loading.
|
|
492
|
+
|
|
493
|
+
## New Files
|
|
494
|
+
|
|
495
|
+
### `src/core/extensions/types.ts`
|
|
496
|
+
Extension type definitions shared across all loaders.
|
|
497
|
+
|
|
498
|
+
```typescript
|
|
499
|
+
export type ExtensionType = "hooks" | "tools" | "skills" | "themes";
|
|
500
|
+
|
|
501
|
+
export interface ExtensionSource {
|
|
502
|
+
type: "file" | "directory" | "npm" | "git";
|
|
503
|
+
specifier: string; // original specifier from config/CLI
|
|
504
|
+
resolvedPath?: string; // resolved local path after install
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
export interface DiscoveredExtension {
|
|
508
|
+
path: string; // relative path (e.g., "brave-search", "npm/@scope/pkg@1.0.0")
|
|
509
|
+
absolutePath: string; // absolute filesystem path
|
|
510
|
+
entryPoint: string; // resolved entry point file
|
|
511
|
+
source: ExtensionSource;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
export interface ExtensionConfig {
|
|
515
|
+
paths?: string[];
|
|
516
|
+
filter?: string[];
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
export interface ExtensionTypeConfig {
|
|
520
|
+
rootPatterns: string[]; // e.g., ["*.ts", "*.js"]
|
|
521
|
+
subdirEntryPoints: string[]; // e.g., ["index.ts", "index.js"]
|
|
522
|
+
packageJsonFallback: boolean; // whether to check package.json main
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
export const EXTENSION_CONFIGS: Record<ExtensionType, ExtensionTypeConfig> = {
|
|
526
|
+
hooks: {
|
|
527
|
+
rootPatterns: ["*.ts", "*.js"],
|
|
528
|
+
subdirEntryPoints: ["index.ts", "index.js"],
|
|
529
|
+
packageJsonFallback: true,
|
|
530
|
+
recursivePattern: false,
|
|
531
|
+
},
|
|
532
|
+
tools: {
|
|
533
|
+
rootPatterns: ["*.ts", "*.js"],
|
|
534
|
+
subdirEntryPoints: ["index.ts", "index.js"],
|
|
535
|
+
packageJsonFallback: true,
|
|
536
|
+
recursivePattern: false,
|
|
537
|
+
},
|
|
538
|
+
skills: {
|
|
539
|
+
rootPatterns: ["*.md"],
|
|
540
|
+
subdirEntryPoints: ["SKILL.md"],
|
|
541
|
+
packageJsonFallback: false,
|
|
542
|
+
recursivePattern: false,
|
|
543
|
+
},
|
|
544
|
+
themes: {
|
|
545
|
+
rootPatterns: ["*.theme.json"],
|
|
546
|
+
subdirEntryPoints: [], // not used
|
|
547
|
+
packageJsonFallback: false,
|
|
548
|
+
recursivePattern: true, // scan for *.theme.json at all levels
|
|
549
|
+
},
|
|
550
|
+
};
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
### `src/core/extensions/source-resolver.ts`
|
|
554
|
+
Handles parsing and installing npm/git sources.
|
|
555
|
+
|
|
556
|
+
```typescript
|
|
557
|
+
export function parseSource(specifier: string): ExtensionSource;
|
|
558
|
+
export type InstallLocation = "cli" | "global" | "project";
|
|
559
|
+
|
|
560
|
+
export async function installSource(
|
|
561
|
+
source: ExtensionSource,
|
|
562
|
+
type: ExtensionType,
|
|
563
|
+
location: InstallLocation,
|
|
564
|
+
cwd: string, // needed for project-local installs
|
|
565
|
+
): Promise<string>;
|
|
566
|
+
export function isRemoteSource(specifier: string): boolean;
|
|
567
|
+
export function getInstallDir(type: ExtensionType, location: InstallLocation, cwd: string): string;
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
Key functions:
|
|
571
|
+
- `parseNpmSpecifier(spec)`: Parse `npm:@scope/pkg@1.2.3` → `{ name, version }`
|
|
572
|
+
- `parseGitSpecifier(spec)`: Parse `git:url@tag` or `git:url#branch`
|
|
573
|
+
- `installNpmPackage(name, version, targetDir)`: `npm pack` + extract + `npm install`
|
|
574
|
+
- `installGitRepo(url, ref, targetDir)`: `git clone` + checkout + `npm install`
|
|
575
|
+
- `getTargetDir(type, ephemeral)`: Returns temp dir or agent dir based on source
|
|
576
|
+
|
|
577
|
+
### `src/core/extensions/scanner.ts`
|
|
578
|
+
Unified recursive scanning for all extension types.
|
|
579
|
+
|
|
580
|
+
```typescript
|
|
581
|
+
export function scanDirectory(
|
|
582
|
+
baseDir: string,
|
|
583
|
+
config: ExtensionTypeConfig,
|
|
584
|
+
): DiscoveredExtension[];
|
|
585
|
+
|
|
586
|
+
export function resolveEntryPoint(
|
|
587
|
+
dir: string,
|
|
588
|
+
config: ExtensionTypeConfig,
|
|
589
|
+
): string | null;
|
|
590
|
+
|
|
591
|
+
export function getRelativePath(
|
|
592
|
+
absolutePath: string,
|
|
593
|
+
baseDir: string,
|
|
594
|
+
config: ExtensionTypeConfig,
|
|
595
|
+
): string; // strips entry point filename and extension
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
### `src/core/extensions/filter.ts`
|
|
599
|
+
Filter logic using glob patterns with `!` exclusion. Matches against `extension.path`.
|
|
600
|
+
|
|
601
|
+
```typescript
|
|
602
|
+
export function applyFilter(
|
|
603
|
+
extensions: DiscoveredExtension[],
|
|
604
|
+
patterns: string[],
|
|
605
|
+
): DiscoveredExtension[];
|
|
606
|
+
|
|
607
|
+
export function parseFilterPatterns(patterns: string[]): {
|
|
608
|
+
include: string[];
|
|
609
|
+
exclude: string[];
|
|
610
|
+
};
|
|
611
|
+
|
|
612
|
+
export function matchesPattern(path: string, pattern: string): boolean;
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
### `src/core/extensions/loader.ts`
|
|
616
|
+
Main entry point coordinating the full loading flow.
|
|
617
|
+
|
|
618
|
+
```typescript
|
|
619
|
+
export interface LoadExtensionsOptions {
|
|
620
|
+
type: ExtensionType;
|
|
621
|
+
cwd: string;
|
|
622
|
+
agentDir: string;
|
|
623
|
+
globalPaths: string[]; // from global settings.json → install to agentDir
|
|
624
|
+
projectPaths: string[]; // from project settings.json → install to cwd/.pi/
|
|
625
|
+
cliPaths: string[]; // from CLI flags → install to /tmp/
|
|
626
|
+
filter: string[]; // combined filter patterns
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
export interface LoadExtensionsResult<T> {
|
|
630
|
+
extensions: T[];
|
|
631
|
+
errors: Array<{ path: string; error: string }>;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
export async function discoverExtensions(
|
|
635
|
+
options: LoadExtensionsOptions,
|
|
636
|
+
): Promise<DiscoveredExtension[]>;
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
### `src/core/extensions/index.ts`
|
|
640
|
+
Public exports.
|
|
641
|
+
|
|
642
|
+
## Modified Files
|
|
643
|
+
|
|
644
|
+
### `src/config.ts`
|
|
645
|
+
|
|
646
|
+
Add directory getters:
|
|
647
|
+
|
|
648
|
+
```typescript
|
|
649
|
+
export function getHooksDir(): string {
|
|
650
|
+
return join(getAgentDir(), "hooks");
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
export function getSkillsDir(): string {
|
|
654
|
+
return join(getAgentDir(), "skills");
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
// getToolsDir() already exists
|
|
658
|
+
// getThemesDir() = bundled themes (in package)
|
|
659
|
+
// getCustomThemesDir() = ~/.pi/agent/themes/ (user themes) - already exists
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
### `src/core/settings-manager.ts`
|
|
663
|
+
|
|
664
|
+
Update `Settings` interface:
|
|
665
|
+
|
|
666
|
+
```typescript
|
|
667
|
+
// Old:
|
|
668
|
+
hooks?: string[];
|
|
669
|
+
customTools?: string[];
|
|
670
|
+
skills?: SkillsSettings;
|
|
671
|
+
|
|
672
|
+
// New:
|
|
673
|
+
hooks?: ExtensionConfig;
|
|
674
|
+
tools?: ExtensionConfig;
|
|
675
|
+
skills?: ExtensionConfig; // simplified from SkillsSettings
|
|
676
|
+
themes?: ExtensionConfig;
|
|
677
|
+
```
|
|
678
|
+
|
|
679
|
+
Add migration logic for old format:
|
|
680
|
+
|
|
681
|
+
```typescript
|
|
682
|
+
function migrateSettings(settings: unknown): Settings {
|
|
683
|
+
// Convert hooks: string[] → hooks: { paths: string[] }
|
|
684
|
+
// Convert customTools: string[] → tools: { paths: string[] }
|
|
685
|
+
// Convert skills.customDirectories → skills.paths
|
|
686
|
+
// Convert skills.includeSkills/ignoredSkills → skills.filter
|
|
687
|
+
}
|
|
688
|
+
```
|
|
689
|
+
|
|
690
|
+
Add unified getters:
|
|
691
|
+
|
|
692
|
+
```typescript
|
|
693
|
+
getExtensionConfig(type: ExtensionType): ExtensionConfig;
|
|
694
|
+
getExtensionPaths(type: ExtensionType): string[];
|
|
695
|
+
getExtensionFilter(type: ExtensionType): string[];
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
Update merge logic (paths are additive, filter overrides):
|
|
699
|
+
|
|
700
|
+
```typescript
|
|
701
|
+
function mergeExtensionConfig(global: ExtensionConfig, project: ExtensionConfig): ExtensionConfig {
|
|
702
|
+
return {
|
|
703
|
+
paths: [...(global.paths ?? []), ...(project.paths ?? [])], // additive
|
|
704
|
+
filter: project.filter ?? global.filter, // override
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
```
|
|
708
|
+
|
|
709
|
+
### `src/cli/args.ts`
|
|
710
|
+
|
|
711
|
+
Update `Args` interface:
|
|
712
|
+
|
|
713
|
+
```typescript
|
|
714
|
+
// Built-in tools (unchanged)
|
|
715
|
+
tools?: ToolName[]; // --tools read,bash,edit,write
|
|
716
|
+
|
|
717
|
+
// Source flags
|
|
718
|
+
hooks?: string[]; // --hook (existing, repeatable)
|
|
719
|
+
customTools?: string[]; // --tool (existing, repeatable)
|
|
720
|
+
skills?: string[]; // --skill (new, repeatable)
|
|
721
|
+
themes?: string[]; // --theme (new, repeatable)
|
|
722
|
+
|
|
723
|
+
// Filter flags
|
|
724
|
+
hooksFilter?: string[]; // --hooks "patterns"
|
|
725
|
+
customToolsFilter?: string[];// --custom-tools "patterns"
|
|
726
|
+
skillsFilter?: string[]; // --skills "patterns" (existing)
|
|
727
|
+
themesFilter?: string[]; // --themes "patterns"
|
|
728
|
+
|
|
729
|
+
// Disable flags
|
|
730
|
+
noHooks?: boolean; // --no-hooks
|
|
731
|
+
noCustomTools?: boolean; // --no-custom-tools
|
|
732
|
+
noSkills?: boolean; // --no-skills (existing)
|
|
733
|
+
```
|
|
734
|
+
|
|
735
|
+
Update argument parsing:
|
|
736
|
+
|
|
737
|
+
```typescript
|
|
738
|
+
// --tools (built-in tools, unchanged)
|
|
739
|
+
} else if (arg === "--tools" && i + 1 < args.length) {
|
|
740
|
+
// ... existing logic for built-in tools
|
|
741
|
+
|
|
742
|
+
// --tool (add custom tool source)
|
|
743
|
+
} else if (arg === "--tool" && i + 1 < args.length) {
|
|
744
|
+
result.customTools = result.customTools ?? [];
|
|
745
|
+
result.customTools.push(args[++i]);
|
|
746
|
+
|
|
747
|
+
// --custom-tools (filter custom tools)
|
|
748
|
+
} else if (arg === "--custom-tools" && i + 1 < args.length) {
|
|
749
|
+
result.customToolsFilter = args[++i].split(",").map(s => s.trim());
|
|
750
|
+
|
|
751
|
+
// --no-custom-tools
|
|
752
|
+
} else if (arg === "--no-custom-tools") {
|
|
753
|
+
result.noCustomTools = true;
|
|
754
|
+
|
|
755
|
+
// --skill (add source) - new
|
|
756
|
+
} else if (arg === "--skill" && i + 1 < args.length) {
|
|
757
|
+
result.skills = result.skills ?? [];
|
|
758
|
+
result.skills.push(args[++i]);
|
|
759
|
+
|
|
760
|
+
// --theme (add source) - new
|
|
761
|
+
} else if (arg === "--theme" && i + 1 < args.length) {
|
|
762
|
+
result.themes = result.themes ?? [];
|
|
763
|
+
result.themes.push(args[++i]);
|
|
764
|
+
|
|
765
|
+
// --themes (filter) - new
|
|
766
|
+
} else if (arg === "--themes" && i + 1 < args.length) {
|
|
767
|
+
result.themesFilter = args[++i].split(",").map(s => s.trim());
|
|
768
|
+
|
|
769
|
+
// --hooks (filter) - new
|
|
770
|
+
} else if (arg === "--hooks" && i + 1 < args.length) {
|
|
771
|
+
result.hooksFilter = args[++i].split(",").map(s => s.trim());
|
|
772
|
+
|
|
773
|
+
// --no-hooks - new
|
|
774
|
+
} else if (arg === "--no-hooks") {
|
|
775
|
+
result.noHooks = true;
|
|
776
|
+
```
|
|
777
|
+
|
|
778
|
+
Add subcommand handling for `pi install`, `pi remove`, `pi update`.
|
|
779
|
+
|
|
780
|
+
### `src/core/hooks/loader.ts`
|
|
781
|
+
|
|
782
|
+
Refactor to use extension system:
|
|
783
|
+
|
|
784
|
+
```typescript
|
|
785
|
+
import { discoverExtensions, type DiscoveredExtension } from "../extensions/index.js";
|
|
786
|
+
|
|
787
|
+
export async function discoverAndLoadHooks(
|
|
788
|
+
options: {
|
|
789
|
+
cwd: string;
|
|
790
|
+
agentDir?: string;
|
|
791
|
+
configuredPaths?: string[];
|
|
792
|
+
cliPaths?: string[];
|
|
793
|
+
filter?: string[];
|
|
794
|
+
}
|
|
795
|
+
): Promise<LoadHooksResult> {
|
|
796
|
+
const discovered = await discoverExtensions({
|
|
797
|
+
type: "hooks",
|
|
798
|
+
defaultDirs: [join(agentDir, "hooks"), join(cwd, ".pi", "hooks")],
|
|
799
|
+
configuredPaths: options.configuredPaths ?? [],
|
|
800
|
+
cliPaths: options.cliPaths ?? [],
|
|
801
|
+
filter: options.filter ?? [],
|
|
802
|
+
});
|
|
803
|
+
|
|
804
|
+
// Load each discovered hook using existing jiti logic
|
|
805
|
+
const results = await Promise.all(
|
|
806
|
+
discovered.map(ext => loadHook(ext.entryPoint, cwd))
|
|
807
|
+
);
|
|
808
|
+
|
|
809
|
+
// ... rest of existing logic
|
|
810
|
+
}
|
|
811
|
+
```
|
|
812
|
+
|
|
813
|
+
Remove duplicate code:
|
|
814
|
+
- `expandPath()` → use from extensions/source-resolver.ts
|
|
815
|
+
- `resolveHookPath()` → use from extensions/scanner.ts
|
|
816
|
+
- Discovery logic → use discoverExtensions()
|
|
817
|
+
|
|
818
|
+
### `src/core/custom-tools/loader.ts`
|
|
819
|
+
|
|
820
|
+
Same refactoring pattern as hooks/loader.ts.
|
|
821
|
+
|
|
822
|
+
### `src/core/skills.ts`
|
|
823
|
+
|
|
824
|
+
Refactor `loadSkills()` and `loadSkillsFromDir()`:
|
|
825
|
+
|
|
826
|
+
```typescript
|
|
827
|
+
export function loadSkills(options: LoadSkillsOptions): LoadSkillsResult {
|
|
828
|
+
const discovered = await discoverExtensions({
|
|
829
|
+
type: "skills",
|
|
830
|
+
defaultDirs: [
|
|
831
|
+
// existing default dirs
|
|
832
|
+
join(homedir(), ".codex", "skills"),
|
|
833
|
+
join(homedir(), ".claude", "skills"),
|
|
834
|
+
join(agentDir, "skills"),
|
|
835
|
+
join(cwd, ".pi", "skills"),
|
|
836
|
+
],
|
|
837
|
+
configuredPaths: options.paths ?? [],
|
|
838
|
+
cliPaths: options.cliPaths ?? [],
|
|
839
|
+
filter: options.filter ?? [],
|
|
840
|
+
});
|
|
841
|
+
|
|
842
|
+
// Load each discovered skill using existing parsing logic
|
|
843
|
+
// ...
|
|
844
|
+
}
|
|
845
|
+
```
|
|
846
|
+
|
|
847
|
+
Remove:
|
|
848
|
+
- `loadSkillsFromDirInternal()` recursive logic → use scanner.ts
|
|
849
|
+
- `matchesIncludePatterns()`/`matchesIgnorePatterns()` → use filter.ts
|
|
850
|
+
|
|
851
|
+
### `src/modes/interactive/theme/theme.ts`
|
|
852
|
+
|
|
853
|
+
Refactor `getAvailableThemes()` and `loadThemeJson()`:
|
|
854
|
+
|
|
855
|
+
```typescript
|
|
856
|
+
export function getAvailableThemes(): string[] {
|
|
857
|
+
const discovered = discoverExtensions({
|
|
858
|
+
type: "themes",
|
|
859
|
+
defaultDirs: [getThemesDir(), getCustomThemesDir()],
|
|
860
|
+
configuredPaths: settingsManager.getExtensionPaths("themes"),
|
|
861
|
+
cliPaths: [], // from args
|
|
862
|
+
filter: settingsManager.getExtensionFilter("themes"),
|
|
863
|
+
});
|
|
864
|
+
|
|
865
|
+
return discovered.map(ext => ext.path);
|
|
866
|
+
}
|
|
867
|
+
```
|
|
868
|
+
|
|
869
|
+
### `src/core/sdk.ts`
|
|
870
|
+
|
|
871
|
+
Update to pass new options structure:
|
|
872
|
+
|
|
873
|
+
```typescript
|
|
874
|
+
// Hooks
|
|
875
|
+
const { hooks, errors } = await discoverAndLoadHooks({
|
|
876
|
+
cwd,
|
|
877
|
+
agentDir,
|
|
878
|
+
configuredPaths: settingsManager.getExtensionPaths("hooks"),
|
|
879
|
+
cliPaths: options.additionalHookPaths,
|
|
880
|
+
filter: [...settingsManager.getExtensionFilter("hooks"), ...(options.hooksFilter ?? [])],
|
|
881
|
+
});
|
|
882
|
+
|
|
883
|
+
// Tools
|
|
884
|
+
const result = await discoverAndLoadCustomTools({
|
|
885
|
+
cwd,
|
|
886
|
+
agentDir,
|
|
887
|
+
configuredPaths: settingsManager.getExtensionPaths("tools"),
|
|
888
|
+
cliPaths: options.additionalToolPaths,
|
|
889
|
+
filter: [...settingsManager.getExtensionFilter("tools"), ...(options.toolsFilter ?? [])],
|
|
890
|
+
builtInToolNames: Object.keys(allTools),
|
|
891
|
+
});
|
|
892
|
+
```
|
|
893
|
+
|
|
894
|
+
### `src/modes/interactive/interactive-mode.ts`
|
|
895
|
+
|
|
896
|
+
Update skill loading to use new options structure.
|
|
897
|
+
|
|
898
|
+
## Migration & Backwards Compatibility
|
|
899
|
+
|
|
900
|
+
### Settings Migration
|
|
901
|
+
|
|
902
|
+
When loading settings.json, detect old format and migrate:
|
|
903
|
+
|
|
904
|
+
```typescript
|
|
905
|
+
// Old format detection
|
|
906
|
+
if (Array.isArray(settings.hooks)) {
|
|
907
|
+
settings.hooks = { paths: settings.hooks };
|
|
908
|
+
}
|
|
909
|
+
if (Array.isArray(settings.customTools)) {
|
|
910
|
+
settings.tools = { paths: settings.customTools };
|
|
911
|
+
delete settings.customTools;
|
|
912
|
+
}
|
|
913
|
+
if (settings.skills?.customDirectories) {
|
|
914
|
+
settings.skills.paths = settings.skills.customDirectories;
|
|
915
|
+
delete settings.skills.customDirectories;
|
|
916
|
+
}
|
|
917
|
+
// ... etc
|
|
918
|
+
```
|
|
919
|
+
|
|
920
|
+
### CLI Compatibility
|
|
921
|
+
|
|
922
|
+
- `--tools` with built-in tool names still works (detected by checking if values match known tool names)
|
|
923
|
+
- Alternatively, deprecation warning and suggest `--builtin-tools`
|
|
924
|
+
|
|
925
|
+
## Implementation Order
|
|
926
|
+
|
|
927
|
+
1. **Phase 1: Core extension framework**
|
|
928
|
+
- Create `src/core/extensions/` directory
|
|
929
|
+
- Implement types.ts, scanner.ts, filter.ts
|
|
930
|
+
- Unit tests for scanning and filtering
|
|
931
|
+
|
|
932
|
+
2. **Phase 2: Source resolution**
|
|
933
|
+
- Implement source-resolver.ts (npm + git)
|
|
934
|
+
- Add `npm pack` and `git clone` logic
|
|
935
|
+
- Unit tests for source parsing and installation
|
|
936
|
+
|
|
937
|
+
3. **Phase 3: Settings migration**
|
|
938
|
+
- Update settings-manager.ts with new types
|
|
939
|
+
- Add migration logic
|
|
940
|
+
- Update config.ts with new directory getters
|
|
941
|
+
|
|
942
|
+
4. **Phase 4: Refactor loaders**
|
|
943
|
+
- Refactor hooks/loader.ts
|
|
944
|
+
- Refactor custom-tools/loader.ts
|
|
945
|
+
- Refactor skills.ts
|
|
946
|
+
- Refactor theme.ts
|
|
947
|
+
- Remove duplicate code
|
|
948
|
+
|
|
949
|
+
5. **Phase 5: CLI updates**
|
|
950
|
+
- Add new flags to args.ts
|
|
951
|
+
- Update help text
|
|
952
|
+
- Add `pi install`, `pi remove`, `pi update` subcommands
|
|
953
|
+
|
|
954
|
+
6. **Phase 6: Integration**
|
|
955
|
+
- Update sdk.ts
|
|
956
|
+
- Update interactive-mode.ts
|
|
957
|
+
- End-to-end testing
|
|
958
|
+
|
|
959
|
+
7. **Phase 7: Documentation**
|
|
960
|
+
- Update README.md
|
|
961
|
+
- Update docs/hooks.md, docs/custom-tools.md
|
|
962
|
+
- Add examples for npm/git extensions
|
|
963
|
+
|
|
964
|
+
## Testing Strategy
|
|
965
|
+
|
|
966
|
+
### Unit Tests
|
|
967
|
+
- `test/extensions/scanner.test.ts`: Directory scanning, entry point resolution
|
|
968
|
+
- `test/extensions/filter.test.ts`: Pattern matching, include/exclude logic
|
|
969
|
+
- `test/extensions/source-resolver.test.ts`: npm/git specifier parsing
|
|
970
|
+
|
|
971
|
+
### Integration Tests
|
|
972
|
+
- `test/extensions/npm-install.test.ts`: Full npm package installation flow
|
|
973
|
+
- `test/extensions/git-clone.test.ts`: Full git repository cloning flow
|
|
974
|
+
- `test/extensions/loading.test.ts`: End-to-end extension discovery and loading
|
|
975
|
+
|
|
976
|
+
### Migration Tests
|
|
977
|
+
- `test/settings-migration.test.ts`: Old → new settings format conversion
|
|
978
|
+
|
|
979
|
+
## File Summary
|
|
980
|
+
|
|
981
|
+
### New Files (7)
|
|
982
|
+
- `src/core/extensions/types.ts`
|
|
983
|
+
- `src/core/extensions/source-resolver.ts`
|
|
984
|
+
- `src/core/extensions/scanner.ts`
|
|
985
|
+
- `src/core/extensions/filter.ts`
|
|
986
|
+
- `src/core/extensions/loader.ts`
|
|
987
|
+
- `src/core/extensions/index.ts`
|
|
988
|
+
- `docs/extension-loading.md` (this file)
|
|
989
|
+
|
|
990
|
+
### Modified Files (9)
|
|
991
|
+
- `src/config.ts` - add directory getters
|
|
992
|
+
- `src/core/settings-manager.ts` - new types, migration
|
|
993
|
+
- `src/cli/args.ts` - new flags, update parsing
|
|
994
|
+
- `src/core/hooks/loader.ts` - refactor to use extensions
|
|
995
|
+
- `src/core/custom-tools/loader.ts` - refactor to use extensions
|
|
996
|
+
- `src/core/skills.ts` - refactor to use extensions
|
|
997
|
+
- `src/modes/interactive/theme/theme.ts` - refactor to use extensions
|
|
998
|
+
- `src/core/sdk.ts` - update option passing
|
|
999
|
+
- `src/modes/interactive/interactive-mode.ts` - update skill loading
|
|
1000
|
+
|
|
1001
|
+
### Deleted Code (moved to extensions/)
|
|
1002
|
+
- Duplicate `expandPath()`, `normalizeUnicodeSpaces()` functions
|
|
1003
|
+
- Duplicate discovery/scanning logic
|
|
1004
|
+
- Duplicate path resolution logic
|