@mariozechner/pi-coding-agent 0.42.5 → 0.44.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 +52 -0
- package/README.md +16 -8
- package/dist/cli/list-models.d.ts.map +1 -1
- package/dist/cli/list-models.js +1 -1
- package/dist/cli/list-models.js.map +1 -1
- package/dist/cli/session-picker.d.ts +4 -2
- package/dist/cli/session-picker.d.ts.map +1 -1
- package/dist/cli/session-picker.js +3 -3
- package/dist/cli/session-picker.js.map +1 -1
- package/dist/core/agent-session.d.ts +19 -10
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +43 -18
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
- package/dist/core/compaction/branch-summarization.js +3 -1
- package/dist/core/compaction/branch-summarization.js.map +1 -1
- package/dist/core/extensions/index.d.ts +2 -2
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +8 -0
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +2 -2
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +11 -5
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +38 -17
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/footer-data-provider.d.ts.map +1 -1
- package/dist/core/footer-data-provider.js +10 -4
- package/dist/core/footer-data-provider.js.map +1 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/session-manager.d.ts +24 -4
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +179 -66
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +7 -3
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +15 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +13 -12
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/extension-editor.js +8 -8
- package/dist/modes/interactive/components/extension-editor.js.map +1 -1
- package/dist/modes/interactive/components/index.d.ts +1 -0
- package/dist/modes/interactive/components/index.d.ts.map +1 -1
- package/dist/modes/interactive/components/index.js +1 -0
- package/dist/modes/interactive/components/index.js.map +1 -1
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/model-selector.js +2 -3
- package/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/dist/modes/interactive/components/scoped-models-selector.d.ts +47 -0
- package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -0
- package/dist/modes/interactive/components/scoped-models-selector.js +241 -0
- package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -0
- package/dist/modes/interactive/components/session-selector.d.ts +17 -3
- package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/session-selector.js +192 -39
- package/dist/modes/interactive/components/session-selector.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +4 -2
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +14 -2
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/tree-selector.d.ts +2 -2
- package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/tree-selector.js +8 -7
- package/dist/modes/interactive/components/tree-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +7 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +263 -30
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/theme/theme.d.ts +1 -1
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +22 -8
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/modes/print-mode.d.ts.map +1 -1
- package/dist/modes/print-mode.js +9 -3
- package/dist/modes/print-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +4 -4
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +6 -6
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +18 -9
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +4 -4
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/docs/extensions.md +64 -10
- package/docs/rpc.md +10 -10
- package/docs/sdk.md +10 -5
- package/docs/session.md +13 -1
- package/docs/skills.md +27 -0
- package/docs/tree.md +9 -5
- package/docs/tui.md +3 -0
- package/examples/extensions/README.md +4 -3
- package/examples/extensions/confirm-destructive.ts +5 -5
- package/examples/extensions/dirty-repo-guard.ts +2 -2
- package/examples/extensions/git-checkpoint.ts +3 -3
- package/examples/extensions/handoff.ts +1 -1
- package/examples/extensions/model-status.ts +31 -0
- package/examples/extensions/notify.ts +25 -0
- package/examples/extensions/preset.ts +3 -3
- package/examples/extensions/todo.ts +1 -1
- package/examples/extensions/tools.ts +9 -8
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/examples/sdk/11-sessions.ts +1 -1
- package/package.json +4 -4
- package/dist/utils/fuzzy.d.ts +0 -7
- package/dist/utils/fuzzy.d.ts.map +0 -1
- package/dist/utils/fuzzy.js +0 -86
- package/dist/utils/fuzzy.js.map +0 -1
package/docs/skills.md
CHANGED
|
@@ -160,6 +160,7 @@ Configure skill loading in `~/.pi/agent/settings.json`:
|
|
|
160
160
|
"enableClaudeProject": true,
|
|
161
161
|
"enablePiUser": true,
|
|
162
162
|
"enablePiProject": true,
|
|
163
|
+
"enableSkillCommands": true,
|
|
163
164
|
"customDirectories": ["~/my-skills-repo"],
|
|
164
165
|
"ignoredSkills": ["deprecated-skill"],
|
|
165
166
|
"includeSkills": ["git-*", "docker"]
|
|
@@ -175,6 +176,7 @@ Configure skill loading in `~/.pi/agent/settings.json`:
|
|
|
175
176
|
| `enableClaudeProject` | `true` | Load from `<cwd>/.claude/skills/` |
|
|
176
177
|
| `enablePiUser` | `true` | Load from `~/.pi/agent/skills/` |
|
|
177
178
|
| `enablePiProject` | `true` | Load from `<cwd>/.pi/skills/` |
|
|
179
|
+
| `enableSkillCommands` | `true` | Register skills as `/skill:name` commands |
|
|
178
180
|
| `customDirectories` | `[]` | Additional directories to scan (supports `~` expansion) |
|
|
179
181
|
| `ignoredSkills` | `[]` | Glob patterns to exclude (e.g., `["deprecated-*", "test-skill"]`) |
|
|
180
182
|
| `includeSkills` | `[]` | Glob patterns to include (empty = all; e.g., `["git-*", "docker"]`) |
|
|
@@ -207,6 +209,31 @@ This overrides the `includeSkills` setting for the current session.
|
|
|
207
209
|
|
|
208
210
|
This is progressive disclosure: only descriptions are always in context, full instructions load on-demand.
|
|
209
211
|
|
|
212
|
+
## Skill Commands
|
|
213
|
+
|
|
214
|
+
Skills are automatically registered as slash commands with a `/skill:` prefix:
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
/skill:brave-search # Load and execute the brave-search skill
|
|
218
|
+
/skill:pdf-tools extract # Load skill with arguments
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Arguments after the command name are appended to the skill content as `User: <args>`.
|
|
222
|
+
|
|
223
|
+
Toggle skill commands via `/settings` or in `settings.json`:
|
|
224
|
+
|
|
225
|
+
```json
|
|
226
|
+
{
|
|
227
|
+
"skills": {
|
|
228
|
+
"enableSkillCommands": true
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
| Setting | Default | Description |
|
|
234
|
+
|---------|---------|-------------|
|
|
235
|
+
| `enableSkillCommands` | `true` | Register skills as `/skill:name` commands |
|
|
236
|
+
|
|
210
237
|
## Validation Warnings
|
|
211
238
|
|
|
212
239
|
Pi validates skills against the Agent Skills standard and warns (but still loads) non-compliant skills:
|
package/docs/tree.md
CHANGED
|
@@ -6,14 +6,14 @@ The `/tree` command provides tree-based navigation of the session history.
|
|
|
6
6
|
|
|
7
7
|
Sessions are stored as trees where each entry has an `id` and `parentId`. The "leaf" pointer tracks the current position. `/tree` lets you navigate to any point and optionally summarize the branch you're leaving.
|
|
8
8
|
|
|
9
|
-
### Comparison with `/
|
|
9
|
+
### Comparison with `/fork`
|
|
10
10
|
|
|
11
|
-
| Feature | `/
|
|
12
|
-
|
|
11
|
+
| Feature | `/fork` | `/tree` |
|
|
12
|
+
|---------|---------|---------|
|
|
13
13
|
| View | Flat list of user messages | Full tree structure |
|
|
14
14
|
| Action | Extracts path to **new session file** | Changes leaf in **same session** |
|
|
15
15
|
| Summary | Never | Optional (user prompted) |
|
|
16
|
-
| Events | `
|
|
16
|
+
| Events | `session_before_fork` / `session_fork` | `session_before_tree` / `session_tree` |
|
|
17
17
|
|
|
18
18
|
## Tree UI
|
|
19
19
|
|
|
@@ -66,7 +66,11 @@ If user selects the very first message (has no parent):
|
|
|
66
66
|
|
|
67
67
|
## Branch Summarization
|
|
68
68
|
|
|
69
|
-
When switching, user is
|
|
69
|
+
When switching branches, user is presented with three options:
|
|
70
|
+
|
|
71
|
+
1. **No summary** - Switch immediately without summarizing
|
|
72
|
+
2. **Summarize** - Generate a summary using the default prompt
|
|
73
|
+
3. **Summarize with custom prompt** - Opens an editor to enter additional focus instructions that are appended to the default summarization prompt
|
|
70
74
|
|
|
71
75
|
### What Gets Summarized
|
|
72
76
|
|
package/docs/tui.md
CHANGED
|
@@ -24,6 +24,8 @@ interface Component {
|
|
|
24
24
|
| `handleInput?(data)` | Receive keyboard input when component has focus. |
|
|
25
25
|
| `invalidate?()` | Clear cached render state. |
|
|
26
26
|
|
|
27
|
+
The TUI appends a full SGR reset and OSC 8 reset at the end of each rendered line. Styles do not carry across lines. If you emit multi-line text with styling, reapply styles per line or use `wrapTextWithAnsi()` so styles are preserved for each wrapped line.
|
|
28
|
+
|
|
27
29
|
## Using Components
|
|
28
30
|
|
|
29
31
|
**In hooks** via `ctx.ui.custom()`:
|
|
@@ -566,6 +568,7 @@ pi.registerCommand("settings", {
|
|
|
566
568
|
ctx.ui.notify(`${id} = ${newValue}`, "info");
|
|
567
569
|
},
|
|
568
570
|
() => done(undefined), // On close
|
|
571
|
+
{ enableSearch: true }, // Optional: enable fuzzy search by label
|
|
569
572
|
);
|
|
570
573
|
container.addChild(settingsList);
|
|
571
574
|
|
|
@@ -20,7 +20,7 @@ cp permission-gate.ts ~/.pi/agent/extensions/
|
|
|
20
20
|
|-----------|-------------|
|
|
21
21
|
| `permission-gate.ts` | Prompts for confirmation before dangerous bash commands (rm -rf, sudo, etc.) |
|
|
22
22
|
| `protected-paths.ts` | Blocks writes to protected paths (.env, .git/, node_modules/) |
|
|
23
|
-
| `confirm-destructive.ts` | Confirms before destructive session actions (clear, switch,
|
|
23
|
+
| `confirm-destructive.ts` | Confirms before destructive session actions (clear, switch, fork) |
|
|
24
24
|
| `dirty-repo-guard.ts` | Prevents session changes with uncommitted git changes |
|
|
25
25
|
|
|
26
26
|
### Custom Tools
|
|
@@ -48,12 +48,13 @@ cp permission-gate.ts ~/.pi/agent/extensions/
|
|
|
48
48
|
| `send-user-message.ts` | Demonstrates `pi.sendUserMessage()` for sending user messages from extensions |
|
|
49
49
|
| `timed-confirm.ts` | Demonstrates AbortSignal for auto-dismissing `ctx.ui.confirm()` and `ctx.ui.select()` dialogs |
|
|
50
50
|
| `modal-editor.ts` | Custom vim-like modal editor via `ctx.ui.setEditorComponent()` |
|
|
51
|
+
| `notify.ts` | Desktop notifications via OSC 777 when agent finishes (Ghostty, iTerm2, WezTerm) |
|
|
51
52
|
|
|
52
53
|
### Git Integration
|
|
53
54
|
|
|
54
55
|
| Extension | Description |
|
|
55
56
|
|-----------|-------------|
|
|
56
|
-
| `git-checkpoint.ts` | Creates git stash checkpoints at each turn for code restoration on
|
|
57
|
+
| `git-checkpoint.ts` | Creates git stash checkpoints at each turn for code restoration on fork |
|
|
57
58
|
| `auto-commit-on-exit.ts` | Auto-commits on exit using last assistant message for commit message |
|
|
58
59
|
|
|
59
60
|
### System Prompt & Compaction
|
|
@@ -129,7 +130,7 @@ action: Type.Union([Type.Literal("list"), Type.Literal("add")])
|
|
|
129
130
|
|
|
130
131
|
**State persistence via details:**
|
|
131
132
|
```typescript
|
|
132
|
-
// Store state in tool result details for proper
|
|
133
|
+
// Store state in tool result details for proper forking support
|
|
133
134
|
return {
|
|
134
135
|
content: [{ type: "text", text: "Done" }],
|
|
135
136
|
details: { todos: [...todos], nextId }, // Persisted in session
|
|
@@ -43,16 +43,16 @@ export default function (pi: ExtensionAPI) {
|
|
|
43
43
|
}
|
|
44
44
|
});
|
|
45
45
|
|
|
46
|
-
pi.on("
|
|
46
|
+
pi.on("session_before_fork", async (event, ctx) => {
|
|
47
47
|
if (!ctx.hasUI) return;
|
|
48
48
|
|
|
49
|
-
const choice = await ctx.ui.select(`
|
|
50
|
-
"Yes, create
|
|
49
|
+
const choice = await ctx.ui.select(`Fork from entry ${event.entryId.slice(0, 8)}?`, [
|
|
50
|
+
"Yes, create fork",
|
|
51
51
|
"No, stay in current session",
|
|
52
52
|
]);
|
|
53
53
|
|
|
54
|
-
if (choice !== "Yes, create
|
|
55
|
-
ctx.ui.notify("
|
|
54
|
+
if (choice !== "Yes, create fork") {
|
|
55
|
+
ctx.ui.notify("Fork cancelled", "info");
|
|
56
56
|
return { cancel: true };
|
|
57
57
|
}
|
|
58
58
|
});
|
|
@@ -50,7 +50,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
50
50
|
return checkDirtyRepo(pi, ctx, action);
|
|
51
51
|
});
|
|
52
52
|
|
|
53
|
-
pi.on("
|
|
54
|
-
return checkDirtyRepo(pi, ctx, "
|
|
53
|
+
pi.on("session_before_fork", async (_event, ctx) => {
|
|
54
|
+
return checkDirtyRepo(pi, ctx, "fork");
|
|
55
55
|
});
|
|
56
56
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Git Checkpoint Extension
|
|
3
3
|
*
|
|
4
|
-
* Creates git stash checkpoints at each turn so /
|
|
5
|
-
* When
|
|
4
|
+
* Creates git stash checkpoints at each turn so /fork can restore code state.
|
|
5
|
+
* When forking, offers to restore code to that point in history.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
@@ -26,7 +26,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
26
26
|
}
|
|
27
27
|
});
|
|
28
28
|
|
|
29
|
-
pi.on("
|
|
29
|
+
pi.on("session_before_fork", async (event, ctx) => {
|
|
30
30
|
const ref = checkpoints.get(event.entryId);
|
|
31
31
|
if (!ref) return;
|
|
32
32
|
|
|
@@ -125,7 +125,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
// Let user edit the generated prompt
|
|
128
|
-
const editedPrompt = await ctx.ui.editor("Edit handoff prompt
|
|
128
|
+
const editedPrompt = await ctx.ui.editor("Edit handoff prompt", result);
|
|
129
129
|
|
|
130
130
|
if (editedPrompt === undefined) {
|
|
131
131
|
ctx.ui.notify("Cancelled", "info");
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model status extension - shows model changes in the status bar.
|
|
3
|
+
*
|
|
4
|
+
* Demonstrates the `model_select` hook which fires when the model changes
|
|
5
|
+
* via /model command, Ctrl+P cycling, or session restore.
|
|
6
|
+
*
|
|
7
|
+
* Usage: pi -e ./model-status.ts
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
11
|
+
|
|
12
|
+
export default function (pi: ExtensionAPI) {
|
|
13
|
+
pi.on("model_select", async (event, ctx) => {
|
|
14
|
+
const { model, previousModel, source } = event;
|
|
15
|
+
|
|
16
|
+
// Format model identifiers
|
|
17
|
+
const next = `${model.provider}/${model.id}`;
|
|
18
|
+
const prev = previousModel ? `${previousModel.provider}/${previousModel.id}` : "none";
|
|
19
|
+
|
|
20
|
+
// Show notification on change
|
|
21
|
+
if (source !== "restore") {
|
|
22
|
+
ctx.ui.notify(`Model: ${next}`, "info");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Update status bar with current model
|
|
26
|
+
ctx.ui.setStatus("model", `🤖 ${model.id}`);
|
|
27
|
+
|
|
28
|
+
// Log change details (visible in debug output)
|
|
29
|
+
console.log(`[model_select] ${prev} → ${next} (${source})`);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Desktop Notification Extension
|
|
3
|
+
*
|
|
4
|
+
* Sends a native desktop notification when the agent finishes and is waiting for input.
|
|
5
|
+
* Uses OSC 777 escape sequence - no external dependencies.
|
|
6
|
+
*
|
|
7
|
+
* Supported terminals: Ghostty, iTerm2, WezTerm, rxvt-unicode
|
|
8
|
+
* Not supported: Kitty (uses OSC 99), Terminal.app, Windows Terminal, Alacritty
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Send a desktop notification via OSC 777 escape sequence.
|
|
15
|
+
*/
|
|
16
|
+
function notify(title: string, body: string): void {
|
|
17
|
+
// OSC 777 format: ESC ] 777 ; notify ; title ; body BEL
|
|
18
|
+
process.stdout.write(`\x1b]777;notify;${title};${body}\x07`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export default function (pi: ExtensionAPI) {
|
|
22
|
+
pi.on("agent_end", async () => {
|
|
23
|
+
notify("Pi", "Ready for input");
|
|
24
|
+
});
|
|
25
|
+
}
|
|
@@ -133,9 +133,9 @@ export default function presetExtension(pi: ExtensionAPI) {
|
|
|
133
133
|
|
|
134
134
|
// Apply tools if specified
|
|
135
135
|
if (preset.tools && preset.tools.length > 0) {
|
|
136
|
-
const
|
|
137
|
-
const validTools = preset.tools.filter((t) =>
|
|
138
|
-
const invalidTools = preset.tools.filter((t) => !
|
|
136
|
+
const allToolNames = pi.getAllTools().map((t) => t.name);
|
|
137
|
+
const validTools = preset.tools.filter((t) => allToolNames.includes(t));
|
|
138
|
+
const invalidTools = preset.tools.filter((t) => !allToolNames.includes(t));
|
|
139
139
|
|
|
140
140
|
if (invalidTools.length > 0) {
|
|
141
141
|
ctx.ui.notify(`Preset "${name}": Unknown tools: ${invalidTools.join(", ")}`, "warning");
|
|
@@ -131,7 +131,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
131
131
|
// Reconstruct state on session events
|
|
132
132
|
pi.on("session_start", async (_event, ctx) => reconstructState(ctx));
|
|
133
133
|
pi.on("session_switch", async (_event, ctx) => reconstructState(ctx));
|
|
134
|
-
pi.on("
|
|
134
|
+
pi.on("session_fork", async (_event, ctx) => reconstructState(ctx));
|
|
135
135
|
pi.on("session_tree", async (_event, ctx) => reconstructState(ctx));
|
|
136
136
|
|
|
137
137
|
// Register the todo tool for the LLM
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* 2. Use /tools to open the tool selector
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
12
|
+
import type { ExtensionAPI, ExtensionContext, ToolInfo } from "@mariozechner/pi-coding-agent";
|
|
13
13
|
import { getSettingsListTheme } from "@mariozechner/pi-coding-agent";
|
|
14
14
|
import { Container, type SettingItem, SettingsList } from "@mariozechner/pi-tui";
|
|
15
15
|
|
|
@@ -21,7 +21,7 @@ interface ToolsState {
|
|
|
21
21
|
export default function toolsExtension(pi: ExtensionAPI) {
|
|
22
22
|
// Track enabled tools
|
|
23
23
|
let enabledTools: Set<string> = new Set();
|
|
24
|
-
let allTools:
|
|
24
|
+
let allTools: ToolInfo[] = [];
|
|
25
25
|
|
|
26
26
|
// Persist current state
|
|
27
27
|
function persistState() {
|
|
@@ -54,7 +54,8 @@ export default function toolsExtension(pi: ExtensionAPI) {
|
|
|
54
54
|
|
|
55
55
|
if (savedTools) {
|
|
56
56
|
// Restore saved tool selection (filter to only tools that still exist)
|
|
57
|
-
|
|
57
|
+
const allToolNames = allTools.map((t) => t.name);
|
|
58
|
+
enabledTools = new Set(savedTools.filter((t: string) => allToolNames.includes(t)));
|
|
58
59
|
applyTools();
|
|
59
60
|
} else {
|
|
60
61
|
// No saved state - sync with currently active tools
|
|
@@ -72,9 +73,9 @@ export default function toolsExtension(pi: ExtensionAPI) {
|
|
|
72
73
|
await ctx.ui.custom((tui, theme, _kb, done) => {
|
|
73
74
|
// Build settings items for each tool
|
|
74
75
|
const items: SettingItem[] = allTools.map((tool) => ({
|
|
75
|
-
id: tool,
|
|
76
|
-
label: tool,
|
|
77
|
-
currentValue: enabledTools.has(tool) ? "enabled" : "disabled",
|
|
76
|
+
id: tool.name,
|
|
77
|
+
label: tool.name,
|
|
78
|
+
currentValue: enabledTools.has(tool.name) ? "enabled" : "disabled",
|
|
78
79
|
values: ["enabled", "disabled"],
|
|
79
80
|
}));
|
|
80
81
|
|
|
@@ -138,8 +139,8 @@ export default function toolsExtension(pi: ExtensionAPI) {
|
|
|
138
139
|
restoreFromBranch(ctx);
|
|
139
140
|
});
|
|
140
141
|
|
|
141
|
-
// Restore state after
|
|
142
|
-
pi.on("
|
|
142
|
+
// Restore state after forking
|
|
143
|
+
pi.on("session_fork", async (_event, ctx) => {
|
|
143
144
|
restoreFromBranch(ctx);
|
|
144
145
|
});
|
|
145
146
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-extension-with-deps",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "pi-extension-with-deps",
|
|
9
|
-
"version": "1.
|
|
9
|
+
"version": "1.8.0",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"ms": "^2.1.3"
|
|
12
12
|
},
|
|
@@ -26,7 +26,7 @@ if (modelFallbackMessage) console.log("Note:", modelFallbackMessage);
|
|
|
26
26
|
console.log("Continued session:", continued.sessionFile);
|
|
27
27
|
|
|
28
28
|
// List and open specific session
|
|
29
|
-
const sessions = SessionManager.list(process.cwd());
|
|
29
|
+
const sessions = await SessionManager.list(process.cwd());
|
|
30
30
|
console.log(`\nFound ${sessions.length} sessions:`);
|
|
31
31
|
for (const info of sessions.slice(0, 3)) {
|
|
32
32
|
console.log(` ${info.id.slice(0, 8)}... - "${info.firstMessage.slice(0, 30)}..."`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mariozechner/pi-coding-agent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.44.0",
|
|
4
4
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"piConfig": {
|
|
@@ -39,9 +39,9 @@
|
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"@mariozechner/clipboard": "^0.3.0",
|
|
42
|
-
"@mariozechner/pi-agent-core": "^0.
|
|
43
|
-
"@mariozechner/pi-ai": "^0.
|
|
44
|
-
"@mariozechner/pi-tui": "^0.
|
|
42
|
+
"@mariozechner/pi-agent-core": "^0.44.0",
|
|
43
|
+
"@mariozechner/pi-ai": "^0.44.0",
|
|
44
|
+
"@mariozechner/pi-tui": "^0.44.0",
|
|
45
45
|
"chalk": "^5.5.0",
|
|
46
46
|
"cli-highlight": "^2.1.11",
|
|
47
47
|
"diff": "^8.0.2",
|
package/dist/utils/fuzzy.d.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
export interface FuzzyMatch {
|
|
2
|
-
matches: boolean;
|
|
3
|
-
score: number;
|
|
4
|
-
}
|
|
5
|
-
export declare function fuzzyMatch(query: string, text: string): FuzzyMatch;
|
|
6
|
-
export declare function fuzzyFilter<T>(items: T[], query: string, getText: (item: T) => string): T[];
|
|
7
|
-
//# sourceMappingURL=fuzzy.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"fuzzy.d.ts","sourceRoot":"","sources":["../../src/utils/fuzzy.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,UAAU;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACd;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,UAAU,CAoDlE;AAID,wBAAgB,WAAW,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,GAAG,CAAC,EAAE,CA2C3F","sourcesContent":["// Fuzzy search. Matches if all query characters appear in order (not necessarily consecutive).\n// Lower score = better match.\n\nexport interface FuzzyMatch {\n\tmatches: boolean;\n\tscore: number;\n}\n\nexport function fuzzyMatch(query: string, text: string): FuzzyMatch {\n\tconst queryLower = query.toLowerCase();\n\tconst textLower = text.toLowerCase();\n\n\tif (queryLower.length === 0) {\n\t\treturn { matches: true, score: 0 };\n\t}\n\n\tif (queryLower.length > textLower.length) {\n\t\treturn { matches: false, score: 0 };\n\t}\n\n\tlet queryIndex = 0;\n\tlet score = 0;\n\tlet lastMatchIndex = -1;\n\tlet consecutiveMatches = 0;\n\n\tfor (let i = 0; i < textLower.length && queryIndex < queryLower.length; i++) {\n\t\tif (textLower[i] === queryLower[queryIndex]) {\n\t\t\tconst isWordBoundary = i === 0 || /[\\s\\-_./]/.test(textLower[i - 1]!);\n\n\t\t\t// Reward consecutive character matches (e.g., typing \"foo\" matches \"foobar\" better than \"f_o_o\")\n\t\t\tif (lastMatchIndex === i - 1) {\n\t\t\t\tconsecutiveMatches++;\n\t\t\t\tscore -= consecutiveMatches * 5;\n\t\t\t} else {\n\t\t\t\tconsecutiveMatches = 0;\n\t\t\t\t// Penalize gaps between matched characters\n\t\t\t\tif (lastMatchIndex >= 0) {\n\t\t\t\t\tscore += (i - lastMatchIndex - 1) * 2;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Reward matches at word boundaries (start of words are more likely intentional targets)\n\t\t\tif (isWordBoundary) {\n\t\t\t\tscore -= 10;\n\t\t\t}\n\n\t\t\t// Slight penalty for matches later in the string (prefer earlier matches)\n\t\t\tscore += i * 0.1;\n\n\t\t\tlastMatchIndex = i;\n\t\t\tqueryIndex++;\n\t\t}\n\t}\n\n\t// Not all query characters were found in order\n\tif (queryIndex < queryLower.length) {\n\t\treturn { matches: false, score: 0 };\n\t}\n\n\treturn { matches: true, score };\n}\n\n// Filter and sort items by fuzzy match quality (best matches first)\n// Supports space-separated tokens: all tokens must match, sorted by match count then score\nexport function fuzzyFilter<T>(items: T[], query: string, getText: (item: T) => string): T[] {\n\tif (!query.trim()) {\n\t\treturn items;\n\t}\n\n\t// Split query into tokens\n\tconst tokens = query\n\t\t.trim()\n\t\t.split(/\\s+/)\n\t\t.filter((t) => t.length > 0);\n\n\tif (tokens.length === 0) {\n\t\treturn items;\n\t}\n\n\tconst results: { item: T; totalScore: number }[] = [];\n\n\tfor (const item of items) {\n\t\tconst text = getText(item);\n\t\tlet totalScore = 0;\n\t\tlet allMatch = true;\n\n\t\t// Check each token against the text - ALL must match\n\t\tfor (const token of tokens) {\n\t\t\tconst match = fuzzyMatch(token, text);\n\t\t\tif (match.matches) {\n\t\t\t\ttotalScore += match.score;\n\t\t\t} else {\n\t\t\t\tallMatch = false;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\t// Only include if all tokens match\n\t\tif (allMatch) {\n\t\t\tresults.push({ item, totalScore });\n\t\t}\n\t}\n\n\t// Sort by score (asc, lower is better)\n\tresults.sort((a, b) => a.totalScore - b.totalScore);\n\n\treturn results.map((r) => r.item);\n}\n"]}
|
package/dist/utils/fuzzy.js
DELETED
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
// Fuzzy search. Matches if all query characters appear in order (not necessarily consecutive).
|
|
2
|
-
// Lower score = better match.
|
|
3
|
-
export function fuzzyMatch(query, text) {
|
|
4
|
-
const queryLower = query.toLowerCase();
|
|
5
|
-
const textLower = text.toLowerCase();
|
|
6
|
-
if (queryLower.length === 0) {
|
|
7
|
-
return { matches: true, score: 0 };
|
|
8
|
-
}
|
|
9
|
-
if (queryLower.length > textLower.length) {
|
|
10
|
-
return { matches: false, score: 0 };
|
|
11
|
-
}
|
|
12
|
-
let queryIndex = 0;
|
|
13
|
-
let score = 0;
|
|
14
|
-
let lastMatchIndex = -1;
|
|
15
|
-
let consecutiveMatches = 0;
|
|
16
|
-
for (let i = 0; i < textLower.length && queryIndex < queryLower.length; i++) {
|
|
17
|
-
if (textLower[i] === queryLower[queryIndex]) {
|
|
18
|
-
const isWordBoundary = i === 0 || /[\s\-_./]/.test(textLower[i - 1]);
|
|
19
|
-
// Reward consecutive character matches (e.g., typing "foo" matches "foobar" better than "f_o_o")
|
|
20
|
-
if (lastMatchIndex === i - 1) {
|
|
21
|
-
consecutiveMatches++;
|
|
22
|
-
score -= consecutiveMatches * 5;
|
|
23
|
-
}
|
|
24
|
-
else {
|
|
25
|
-
consecutiveMatches = 0;
|
|
26
|
-
// Penalize gaps between matched characters
|
|
27
|
-
if (lastMatchIndex >= 0) {
|
|
28
|
-
score += (i - lastMatchIndex - 1) * 2;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
// Reward matches at word boundaries (start of words are more likely intentional targets)
|
|
32
|
-
if (isWordBoundary) {
|
|
33
|
-
score -= 10;
|
|
34
|
-
}
|
|
35
|
-
// Slight penalty for matches later in the string (prefer earlier matches)
|
|
36
|
-
score += i * 0.1;
|
|
37
|
-
lastMatchIndex = i;
|
|
38
|
-
queryIndex++;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
// Not all query characters were found in order
|
|
42
|
-
if (queryIndex < queryLower.length) {
|
|
43
|
-
return { matches: false, score: 0 };
|
|
44
|
-
}
|
|
45
|
-
return { matches: true, score };
|
|
46
|
-
}
|
|
47
|
-
// Filter and sort items by fuzzy match quality (best matches first)
|
|
48
|
-
// Supports space-separated tokens: all tokens must match, sorted by match count then score
|
|
49
|
-
export function fuzzyFilter(items, query, getText) {
|
|
50
|
-
if (!query.trim()) {
|
|
51
|
-
return items;
|
|
52
|
-
}
|
|
53
|
-
// Split query into tokens
|
|
54
|
-
const tokens = query
|
|
55
|
-
.trim()
|
|
56
|
-
.split(/\s+/)
|
|
57
|
-
.filter((t) => t.length > 0);
|
|
58
|
-
if (tokens.length === 0) {
|
|
59
|
-
return items;
|
|
60
|
-
}
|
|
61
|
-
const results = [];
|
|
62
|
-
for (const item of items) {
|
|
63
|
-
const text = getText(item);
|
|
64
|
-
let totalScore = 0;
|
|
65
|
-
let allMatch = true;
|
|
66
|
-
// Check each token against the text - ALL must match
|
|
67
|
-
for (const token of tokens) {
|
|
68
|
-
const match = fuzzyMatch(token, text);
|
|
69
|
-
if (match.matches) {
|
|
70
|
-
totalScore += match.score;
|
|
71
|
-
}
|
|
72
|
-
else {
|
|
73
|
-
allMatch = false;
|
|
74
|
-
break;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
// Only include if all tokens match
|
|
78
|
-
if (allMatch) {
|
|
79
|
-
results.push({ item, totalScore });
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
// Sort by score (asc, lower is better)
|
|
83
|
-
results.sort((a, b) => a.totalScore - b.totalScore);
|
|
84
|
-
return results.map((r) => r.item);
|
|
85
|
-
}
|
|
86
|
-
//# sourceMappingURL=fuzzy.js.map
|
package/dist/utils/fuzzy.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"fuzzy.js","sourceRoot":"","sources":["../../src/utils/fuzzy.ts"],"names":[],"mappings":"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","sourcesContent":["// Fuzzy search. Matches if all query characters appear in order (not necessarily consecutive).\n// Lower score = better match.\n\nexport interface FuzzyMatch {\n\tmatches: boolean;\n\tscore: number;\n}\n\nexport function fuzzyMatch(query: string, text: string): FuzzyMatch {\n\tconst queryLower = query.toLowerCase();\n\tconst textLower = text.toLowerCase();\n\n\tif (queryLower.length === 0) {\n\t\treturn { matches: true, score: 0 };\n\t}\n\n\tif (queryLower.length > textLower.length) {\n\t\treturn { matches: false, score: 0 };\n\t}\n\n\tlet queryIndex = 0;\n\tlet score = 0;\n\tlet lastMatchIndex = -1;\n\tlet consecutiveMatches = 0;\n\n\tfor (let i = 0; i < textLower.length && queryIndex < queryLower.length; i++) {\n\t\tif (textLower[i] === queryLower[queryIndex]) {\n\t\t\tconst isWordBoundary = i === 0 || /[\\s\\-_./]/.test(textLower[i - 1]!);\n\n\t\t\t// Reward consecutive character matches (e.g., typing \"foo\" matches \"foobar\" better than \"f_o_o\")\n\t\t\tif (lastMatchIndex === i - 1) {\n\t\t\t\tconsecutiveMatches++;\n\t\t\t\tscore -= consecutiveMatches * 5;\n\t\t\t} else {\n\t\t\t\tconsecutiveMatches = 0;\n\t\t\t\t// Penalize gaps between matched characters\n\t\t\t\tif (lastMatchIndex >= 0) {\n\t\t\t\t\tscore += (i - lastMatchIndex - 1) * 2;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Reward matches at word boundaries (start of words are more likely intentional targets)\n\t\t\tif (isWordBoundary) {\n\t\t\t\tscore -= 10;\n\t\t\t}\n\n\t\t\t// Slight penalty for matches later in the string (prefer earlier matches)\n\t\t\tscore += i * 0.1;\n\n\t\t\tlastMatchIndex = i;\n\t\t\tqueryIndex++;\n\t\t}\n\t}\n\n\t// Not all query characters were found in order\n\tif (queryIndex < queryLower.length) {\n\t\treturn { matches: false, score: 0 };\n\t}\n\n\treturn { matches: true, score };\n}\n\n// Filter and sort items by fuzzy match quality (best matches first)\n// Supports space-separated tokens: all tokens must match, sorted by match count then score\nexport function fuzzyFilter<T>(items: T[], query: string, getText: (item: T) => string): T[] {\n\tif (!query.trim()) {\n\t\treturn items;\n\t}\n\n\t// Split query into tokens\n\tconst tokens = query\n\t\t.trim()\n\t\t.split(/\\s+/)\n\t\t.filter((t) => t.length > 0);\n\n\tif (tokens.length === 0) {\n\t\treturn items;\n\t}\n\n\tconst results: { item: T; totalScore: number }[] = [];\n\n\tfor (const item of items) {\n\t\tconst text = getText(item);\n\t\tlet totalScore = 0;\n\t\tlet allMatch = true;\n\n\t\t// Check each token against the text - ALL must match\n\t\tfor (const token of tokens) {\n\t\t\tconst match = fuzzyMatch(token, text);\n\t\t\tif (match.matches) {\n\t\t\t\ttotalScore += match.score;\n\t\t\t} else {\n\t\t\t\tallMatch = false;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\t// Only include if all tokens match\n\t\tif (allMatch) {\n\t\t\tresults.push({ item, totalScore });\n\t\t}\n\t}\n\n\t// Sort by score (asc, lower is better)\n\tresults.sort((a, b) => a.totalScore - b.totalScore);\n\n\treturn results.map((r) => r.item);\n}\n"]}
|