@aaroncql/pim-agent 0.0.1 → 0.2.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 +94 -66
- package/bin/pim.ts +55 -3
- package/package.json +20 -5
- package/src/extensions/_init/index.ts +3 -2
- package/src/extensions/apply-patch/coordinator.ts +49 -0
- package/src/extensions/apply-patch/executor.ts +566 -0
- package/src/extensions/apply-patch/index.ts +74 -0
- package/src/extensions/apply-patch/matcher.ts +66 -0
- package/src/extensions/apply-patch/model.ts +34 -0
- package/src/extensions/apply-patch/parser.ts +381 -0
- package/src/extensions/apply-patch/render.ts +261 -0
- package/src/extensions/apply-patch/schema.ts +43 -0
- package/src/extensions/apply-patch/types.ts +30 -0
- package/src/extensions/bash/index.ts +3 -3
- package/src/extensions/edit/index.ts +2 -1
- package/src/extensions/glob/index.ts +3 -1
- package/src/extensions/glob/schema.ts +2 -1
- package/src/extensions/grep/index.ts +3 -1
- package/src/extensions/grep/render.ts +18 -4
- package/src/extensions/grep/schema.ts +1 -1
- package/src/extensions/read/index.ts +36 -9
- package/src/extensions/read/render.ts +31 -3
- package/src/extensions/subagent/index.ts +4 -1
- package/src/extensions/todo/index.ts +4 -3
- package/src/extensions/web-search/index.ts +2 -1
- package/src/extensions/write/index.ts +2 -1
- package/src/shared/PatchSummary.ts +82 -0
- package/src/telegram/Renderer.ts +190 -4
- package/src/extensions/bash/capture.test.ts +0 -126
- package/src/extensions/bash/format.test.ts +0 -240
- package/src/extensions/bash/run.test.ts +0 -262
- package/src/extensions/command-picker/ranker.test.ts +0 -46
- package/src/extensions/edit/edit.test.ts +0 -285
- package/src/extensions/file-picker/catalog.test.ts +0 -263
- package/src/extensions/file-picker/index.test.ts +0 -168
- package/src/extensions/file-picker/ranker.test.ts +0 -94
- package/src/extensions/footer/git.test.ts +0 -76
- package/src/extensions/footer/index.test.ts +0 -161
- package/src/extensions/footer/segments.test.ts +0 -164
- package/src/extensions/glob/glob.test.ts +0 -171
- package/src/extensions/glob/index.test.ts +0 -68
- package/src/extensions/glob/render.test.ts +0 -126
- package/src/extensions/grep/grep.test.ts +0 -387
- package/src/extensions/grep/index.test.ts +0 -68
- package/src/extensions/grep/render.test.ts +0 -269
- package/src/extensions/read/read.test.ts +0 -177
- package/src/extensions/read/render.test.ts +0 -61
- package/src/extensions/subagent/index.test.ts +0 -44
- package/src/extensions/subagent/render.test.ts +0 -292
- package/src/extensions/subagent/subagent.test.ts +0 -315
- package/src/extensions/system-prompt/prompt.test.ts +0 -64
- package/src/extensions/todo/index.test.ts +0 -244
- package/src/extensions/todo/render.test.ts +0 -180
- package/src/extensions/todo/todo.test.ts +0 -222
- package/src/extensions/tps/index.test.ts +0 -254
- package/src/extensions/web-fetch/WebViewMarkdownSnapshot.test.ts +0 -119
- package/src/extensions/web-fetch/fetch.test.ts +0 -244
- package/src/extensions/web-fetch/render.test.ts +0 -56
- package/src/extensions/web-search/ExaMcpClient.test.ts +0 -143
- package/src/extensions/web-search/render.test.ts +0 -21
- package/src/extensions/web-search/search.test.ts +0 -53
- package/src/extensions/working-indicator/index.test.ts +0 -21
- package/src/extensions/write/render.test.ts +0 -64
- package/src/extensions/write/write.test.ts +0 -108
- package/src/shared/DiffLines.test.ts +0 -193
- package/src/shared/DiffRenderer.test.ts +0 -206
- package/src/shared/EditMatcher.test.ts +0 -123
- package/src/shared/FileScanner.test.ts +0 -158
- package/src/shared/FuzzyMatcher.test.ts +0 -114
- package/src/shared/GitignoreFilter.test.ts +0 -64
- package/src/shared/Lines.test.ts +0 -25
- package/src/shared/McpClient.test.ts +0 -235
- package/src/shared/OutputBudget.test.ts +0 -99
- package/src/shared/Paths.test.ts +0 -51
- package/src/shared/PimSettings.test.ts +0 -90
- package/src/shared/Renderer.test.ts +0 -190
- package/src/shared/SpillCache.test.ts +0 -94
- package/src/shared/Tools.test.ts +0 -392
- package/src/telegram/Config.test.ts +0 -275
- package/src/telegram/Markdown.test.ts +0 -143
- package/src/telegram/Renderer.test.ts +0 -216
- package/src/telegram/SessionRegistry.test.ts +0 -89
- package/src/telegram/TaskScheduler.test.ts +0 -278
- package/src/telegram/TaskTool.test.ts +0 -179
package/README.md
CHANGED
|
@@ -3,26 +3,29 @@
|
|
|
3
3
|
|
|
4
4
|
_**Pim is to Pi what Vim is to Vi.**_
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
A Bun-native extension pack for [Pi](https://pi.dev/): web access, subagents, revamped core tools, ANSI-compatible themes, fzf-style completions, Telegram mode, and more. Preliminary score of [37.8% on Terminal-Bench 2.0](#terminal-bench-20) with locally hosted Qwen3.6-35B, rivalling Claude Code + Sonnet 4.5.
|
|
7
7
|
|
|
8
8
|
- [Quick Start](#quick-start)
|
|
9
|
+
- [Enabling/Disabling Extensions](#enablingdisabling-extensions)
|
|
9
10
|
- [API Keys (Optional)](#api-keys-optional)
|
|
10
|
-
- [Recommended Settings (Optional)](#recommended-settings-optional)
|
|
11
|
+
- [Recommended Pi Settings (Optional)](#recommended-pi-settings-optional)
|
|
12
|
+
- [Why Pim?](#why-pim)
|
|
13
|
+
- [Lean System Prompt](#lean-system-prompt)
|
|
14
|
+
- [Model-Aware Tools](#model-aware-tools)
|
|
15
|
+
- [Terminal-Bench 2.0](#terminal-bench-20)
|
|
11
16
|
- [Agent Tools](#agent-tools)
|
|
12
17
|
- [Terminal UI](#terminal-ui)
|
|
13
18
|
- [Telegram Bot](#telegram-bot)
|
|
14
19
|
- [Setup](#setup)
|
|
15
20
|
- [Commands](#commands)
|
|
16
21
|
- [Features](#features)
|
|
17
|
-
- [Why Pim?](#why-pim)
|
|
18
|
-
- [Harness Design](#harness-design)
|
|
19
|
-
- [Terminal-Bench 2.0](#terminal-bench-20)
|
|
20
22
|
- [Developing](#developing)
|
|
21
23
|
|
|
24
|
+

|
|
25
|
+
|
|
22
26
|
## Quick Start
|
|
23
27
|
|
|
24
|
-
|
|
25
|
-
> The following instructions assume you have [Pi](https://pi.dev/docs/latest/quickstart) and [Bun](https://bun.com/docs/installation) already installed. If not, install them first (_or ask your agent to do it for you_). For all things related to Pi, refer to [Pi's comprehensive docs](https://pi.dev/docs/latest).
|
|
28
|
+
Ensure that you have [Pi](https://pi.dev/docs/latest/quickstart) and [Bun](https://bun.com/docs/installation) already installed. If not, install them first (_or ask your agent to do it for you_). For all things related to Pi, refer to [Pi's comprehensive docs](https://pi.dev/docs/latest).
|
|
26
29
|
|
|
27
30
|
```sh
|
|
28
31
|
# First, install Pim as a Pi extension:
|
|
@@ -35,11 +38,24 @@ bun install -g @aaroncql/pim-agent
|
|
|
35
38
|
pim
|
|
36
39
|
```
|
|
37
40
|
|
|
38
|
-
|
|
41
|
+
> [!IMPORTANT]
|
|
42
|
+
> **Use `pim` instead of `pi` after installing Pim.** The `pim` command is a drop-in replacement for `pi` that [runs Pi via Bun](./bin/pim.ts), enabling Bun-specific APIs. Existing Pi behaviour and extensions should continue to work normally.
|
|
43
|
+
|
|
44
|
+
If `pim` cannot locate Pi, make sure `pi` is on your `PATH`, or set:
|
|
45
|
+
|
|
46
|
+
```sh
|
|
47
|
+
PIM_PI_CLI=/path/to/pi/dist/cli.js pim
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Enabling/Disabling Extensions
|
|
51
|
+
|
|
52
|
+
Pim ships a collection of extensions which are all enabled by default. To disable specific ones that don't suit your needs, run `pim config` and toggle them there.
|
|
53
|
+
|
|
54
|
+
Some Pim extensions can be toggled directly within the TUI as well: `/powerline` for the Git-aware powerline footer, `/tps` for inference speed reporting.
|
|
39
55
|
|
|
40
56
|
### API Keys (Optional)
|
|
41
57
|
|
|
42
|
-
Pim's web tools use [Exa](https://exa.ai) for searching the web and [Jina](https://jina.ai/reader/) for fetching websites as Markdown.
|
|
58
|
+
Pim's web tools use [Exa](https://exa.ai) for searching the web and [Jina](https://jina.ai/reader/) for fetching websites as Markdown. These tools still work without API keys, but are subject to the following rate limits (as of May 2026):
|
|
43
59
|
|
|
44
60
|
- Exa - 1,000 requests per month
|
|
45
61
|
- Jina - 20 requests per minute
|
|
@@ -63,7 +79,7 @@ Environment variables override `settings.json` when present:
|
|
|
63
79
|
EXA_API_KEY='api_key_here' JINA_API_KEY='api_key_here' pim
|
|
64
80
|
```
|
|
65
81
|
|
|
66
|
-
### Recommended Settings (Optional)
|
|
82
|
+
### Recommended Pi Settings (Optional)
|
|
67
83
|
|
|
68
84
|
Add the following settings to your `~/.pi/agent/settings.json` for the best experience with Pim:
|
|
69
85
|
|
|
@@ -77,10 +93,69 @@ Add the following settings to your `~/.pi/agent/settings.json` for the best expe
|
|
|
77
93
|
}
|
|
78
94
|
```
|
|
79
95
|
|
|
96
|
+
## Why Pim?
|
|
97
|
+
|
|
98
|
+
Pim's philosophy is **opinionated but minimal**. Its goal is to improve the out-of-the-box experience for both users and agents, without sacrificing composability with other Pi extensions.
|
|
99
|
+
|
|
100
|
+
### Lean System Prompt
|
|
101
|
+
|
|
102
|
+
Pim's system prompt is just **~3K tokens** despite exposing 10+ tools, far leaner than alternatives like OpenCode (~10K) or Hermes (~16K).
|
|
103
|
+
|
|
104
|
+
This is achieved by having tool descriptions focus on _how_ to use each tool instead of prescribing _when_, since models already appear to internally encode when tools are needed, and prompting them to call tools can [suppress both necessary and unnecessary calls](https://arxiv.org/abs/2605.09252).
|
|
105
|
+
|
|
106
|
+
### Model-Aware Tools
|
|
107
|
+
|
|
108
|
+
LLMs are [increasingly post-trained](https://openai.com/index/introducing-codex) for specific agent harnesses, making tool schemas part of the model's learned interface. For text-file editing, Anthropic models are trained to use [string replacement operations](https://platform.claude.com/docs/en/agents-and-tools/tool-use/text-editor-tool), while OpenAI models use [V4A patch operations](https://developers.openai.com/api/docs/guides/tools-apply-patch).
|
|
109
|
+
|
|
110
|
+
Pim keeps the active toolset model-aware instead of assuming one tool fits every LLM. It dynamically exposes the tools best suited to the selected model, giving each model the interface that best matches its learned behaviour while keeping the prompt lean.
|
|
111
|
+
|
|
112
|
+
### Terminal-Bench 2.0
|
|
113
|
+
|
|
114
|
+
| ID | Pim Version | LLM / Model | Results |
|
|
115
|
+
| --- | --- | --- | --- |
|
|
116
|
+
| [r1](./benchmarks/terminal_bench_2/results/r1/) | [`21d084d1`](https://github.com/AaronCQL/pim-agent/tree/21d084d1) | `Qwen3.6-35B-A3B-UD-Q6_K_XL.gguf` | **41.6%** (37/89) |
|
|
117
|
+
| [r2](./benchmarks/terminal_bench_2/results/r2/) | [`bfd792cf`](https://github.com/AaronCQL/pim-agent/tree/bfd792cf) | `Qwen3.6-35B-A3B-UD-Q6_K_XL.gguf` | **36.0%** (32/89) |
|
|
118
|
+
| [r3](./benchmarks/terminal_bench_2/results/r3/) | [`cd52f3a4`](https://github.com/AaronCQL/pim-agent/tree/cd52f3a4) | `Qwen3.6-35B-A3B-UD-Q6_K_XL.gguf` | **36.0%** (32/89) |
|
|
119
|
+
|
|
120
|
+
Preliminary aggregate score of **37.8%** from 3 independent runs. Each ran on an incremental build of Pim, though changes between runs were minor and none were tuned to the benchmark. Pim's `subagent` tool was disabled for all runs to keep each trial single-agent.
|
|
121
|
+
|
|
122
|
+
On average, Pim solves **~54% more tasks** than [little-coder](https://github.com/itayinbarr/little-coder) with the same Qwen3.6-35B model (37.8% vs 24.6%). This also places Pim in a similar tier to Claude Code + Sonnet 4.5 (40.1%), and above Codex + GPT-5-Mini (31.9%).
|
|
123
|
+
|
|
124
|
+
The Qwen3.6-35B model is hosted via llama.cpp on an M4 Pro 48GB MacBook, with the following config:
|
|
125
|
+
|
|
126
|
+
```sh
|
|
127
|
+
llama-server \
|
|
128
|
+
-c 131072 \
|
|
129
|
+
-ngl 99 \
|
|
130
|
+
--slot-save-path /tmp/llama-slots \
|
|
131
|
+
--flash-attn on \
|
|
132
|
+
--cache-type-k q8_0 \
|
|
133
|
+
--cache-type-v q8_0 \
|
|
134
|
+
--jinja \
|
|
135
|
+
--temp 0.6 \
|
|
136
|
+
--top-p 0.95 \
|
|
137
|
+
--top-k 20 \
|
|
138
|
+
--min-p 0.0 \
|
|
139
|
+
--presence-penalty 0.0 \
|
|
140
|
+
--repeat-penalty 1.0 \
|
|
141
|
+
--reasoning-budget 16384 \
|
|
142
|
+
--reasoning-budget-message "Alright, I've thought enough. Let me take the next concrete step now — either a tool call or a final answer — and refine based on what I learn." \
|
|
143
|
+
-np 1
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
_Note 1_: results are preliminary as only 3 independent full runs were conducted; Terminal-Bench 2.0 requires 5 independent full runs under a fixed configuration for an official score.
|
|
147
|
+
|
|
148
|
+
_Note 2_: the gap with little-coder may be partly explained by different inference configs (128K context vs 32K, Q6_K_XL vs Q4_K_M, higher thinking budget, etc.).
|
|
149
|
+
|
|
150
|
+
_Note 3_: in r1 and r3, the `code-from-image` trial was counted as non-passing because Qwen autonomously searched for the answer online after legitimately trying for a while.
|
|
151
|
+
|
|
152
|
+
_Note 4_: see the [`benchmarks/terminal_bench_2`](./benchmarks/terminal_bench_2/) dir for breakdown of results and reproduction steps.
|
|
153
|
+
|
|
80
154
|
## Agent Tools
|
|
81
155
|
|
|
82
|
-
Pim revamps Pi's default tools (`bash`, `read`, `write`, `edit`) and
|
|
156
|
+
Pim revamps Pi's default tools (`bash`, `read`, `write`, `edit`) so they produce consistent behaviour and output, cross-reference each other where useful, and render uniformly in the TUI. It also adds:
|
|
83
157
|
|
|
158
|
+
- **`apply_patch`** - V4A patch editing, dynamically exposed instead of `edit` for OpenAI models
|
|
84
159
|
- **`glob`** - file enumeration by glob pattern, sorted newest-first, respects `.gitignore`
|
|
85
160
|
- **`grep`** - regex search across files with context lines, multiline matching, respects `.gitignore`
|
|
86
161
|
- **`web_search`** - search the web via [Exa](https://exa.ai) with ranked results and snippets
|
|
@@ -92,11 +167,11 @@ Pim revamps Pi's default tools (`bash`, `read`, `write`, `edit`) and adds the fo
|
|
|
92
167
|
|
|
93
168
|
Pim also ships with quality of life improvements for the TUI:
|
|
94
169
|
|
|
95
|
-
- **ANSI-compatible themes** - `pim-light` and `pim-dark` themes
|
|
170
|
+
- **ANSI-compatible themes** - `pim-light` and `pim-dark` themes which adapt to your terminal's colour scheme
|
|
96
171
|
- **fzf-style autocomplete** - `@path` file picker and `/command` picker with fuzzy search
|
|
97
|
-
- **Git-aware powerline footer** -
|
|
172
|
+
- **Git-aware powerline footer** - cwd, git branch and states, context usage, model and session cost (toggle with `/powerline`)
|
|
98
173
|
- **TPS reporting** - per-cycle decode/prefill rate, TTFT, and cache read tokens (toggle with `/tps`)
|
|
99
|
-
- **Concise tool
|
|
174
|
+
- **Concise tool UI** - minimal one-liner title across all tool calls, `Ctrl+O` to toggle full details
|
|
100
175
|
|
|
101
176
|
## Telegram Bot
|
|
102
177
|
|
|
@@ -149,58 +224,11 @@ For development, run standalone with `pim --mode telegram` instead.
|
|
|
149
224
|
|
|
150
225
|
### Features
|
|
151
226
|
|
|
152
|
-
- **Scheduled tasks** - your bot can create one-time, interval, or cron-based tasks that fire automatically; ask your bot to schedule something.
|
|
153
|
-
- **
|
|
154
|
-
- **
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
### Harness Design
|
|
159
|
-
|
|
160
|
-
Pim overrides Pi's default tools (`bash`, `read`, `write`, `edit`) so that all tools produce consistent, structured output for the model, cross-reference each other where useful, and render uniformly in the TUI.
|
|
161
|
-
|
|
162
|
-
The system prompt is also kept as minimal as possible: at just ~3K tokens despite having 10+ tools (vs OpenCode's ~10K, Hermes' ~16K), with tool descriptions focusing on _how_ to use each tool instead of prescribing _when_. The rationale is that models already appear to internally encode when tools are needed, and prompting them to call tools can [suppress both necessary and unnecessary calls](https://arxiv.org/abs/2605.09252).
|
|
163
|
-
|
|
164
|
-
### Terminal-Bench 2.0
|
|
165
|
-
|
|
166
|
-
Preliminary results from two full runs:
|
|
167
|
-
|
|
168
|
-
| ID | Pim Version | LLM / Model | Results |
|
|
169
|
-
| --- | --- | --- | --- |
|
|
170
|
-
| [r1](./benchmarks/terminal_bench_2/results/r1/) | [`21d084d1`](https://github.com/AaronCQL/pim-agent/tree/21d084d1) | `Qwen3.6-35B-A3B-UD-Q6_K_XL.gguf` | **41.6%** (37/89) |
|
|
171
|
-
| [r2](./benchmarks/terminal_bench_2/results/r2/) | [`bfd792cf`](https://github.com/AaronCQL/pim-agent/tree/bfd792cf) | `Qwen3.6-35B-A3B-UD-Q6_K_XL.gguf` | **36.0%** (32/89) |
|
|
172
|
-
|
|
173
|
-
Comparing against the same Qwen3.6-35B model, Pim solves up to **70% more tasks** than [little-coder](https://github.com/itayinbarr/little-coder) (41.6% vs 24.6%). This puts Pim, with a locally hosted model, in a similar tier to Claude Code + Sonnet 4.5 (40.1%) and above Codex + GPT-5-Mini (31.9%).
|
|
174
|
-
|
|
175
|
-
The Qwen3.6-35B model is hosted via llama.cpp on an M4 Pro 48GB MacBook, with the following config:
|
|
176
|
-
|
|
177
|
-
```sh
|
|
178
|
-
llama-server \
|
|
179
|
-
-c 131072 \
|
|
180
|
-
-ngl 99 \
|
|
181
|
-
--slot-save-path /tmp/llama-slots \
|
|
182
|
-
--flash-attn on \
|
|
183
|
-
--cache-type-k q8_0 \
|
|
184
|
-
--cache-type-v q8_0 \
|
|
185
|
-
--jinja \
|
|
186
|
-
--temp 0.6 \
|
|
187
|
-
--top-p 0.95 \
|
|
188
|
-
--top-k 20 \
|
|
189
|
-
--min-p 0.0 \
|
|
190
|
-
--presence-penalty 0.0 \
|
|
191
|
-
--repeat-penalty 1.0 \
|
|
192
|
-
--reasoning-budget 16384 \
|
|
193
|
-
--reasoning-budget-message "Alright, I've thought enough. Let me take the next concrete step now — either a tool call or a final answer — and refine based on what I learn." \
|
|
194
|
-
-np 1
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
_Note 1_: results are preliminary as only 2 independent full runs were conducted; Terminal-Bench 2.0 requires 5 independent full runs for an official score.
|
|
198
|
-
|
|
199
|
-
_Note 2_: the gap with little-coder may be partly explained by different inference configs (128K context vs 32K, Q6_K_XL vs Q4_K_M, higher thinking budget, etc.).
|
|
200
|
-
|
|
201
|
-
_Note 3_: in r1, the recorded result is actually 1 higher at 38/89. However, the `code-from-image` trial was excluded because Qwen autonomously searched for the answer online after 27 legitimate turns (see line 822 in [trajectory.json](benchmarks/terminal_bench_2/results/r1/code-from-image/trajectory.json)).
|
|
202
|
-
|
|
203
|
-
_Note 4_: see the [`benchmarks/terminal_bench_2`](./benchmarks/terminal_bench_2/) dir for breakdown of results and reproduction steps.
|
|
227
|
+
- ⏰ **Scheduled tasks** - your bot can create one-time, interval, or cron-based tasks that fire automatically; ask your bot to schedule something.
|
|
228
|
+
- 👀 **Live progress logs** - use `/logs` to choose what you see while the agent works: final replies, tool use, intermediate text, or thinking.
|
|
229
|
+
- 📝 **Markdown formatting** - replies render Markdown out of the box, including tables converted to vertical lists for Telegram.
|
|
230
|
+
- 📎 **Rich media** - send photos, documents, videos, audio, and voice messages directly in chat; your bot can also send files back to you.
|
|
231
|
+
- 🧵 **Thread-specific prompts** - each chat (or thread) gets its own session and optional instructions; ask your bot to modify its instructions.
|
|
204
232
|
|
|
205
233
|
## Developing
|
|
206
234
|
|
package/bin/pim.ts
CHANGED
|
@@ -1,9 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
+
import { realpath } from "node:fs/promises";
|
|
2
3
|
import { dirname, join } from "node:path";
|
|
3
4
|
|
|
4
5
|
const PI_PACKAGE = "@earendil-works/pi-coding-agent";
|
|
5
6
|
|
|
6
|
-
function findPiCli(): string {
|
|
7
|
+
async function findPiCli(): Promise<string> {
|
|
8
|
+
const envCli = await resolveEnvPiCli();
|
|
9
|
+
if (envCli) {
|
|
10
|
+
return envCli;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const pathCli = await resolvePathPiCli();
|
|
14
|
+
if (pathCli) {
|
|
15
|
+
return pathCli;
|
|
16
|
+
}
|
|
17
|
+
|
|
7
18
|
const globalCli = resolveGlobalPiCli();
|
|
8
19
|
if (globalCli) {
|
|
9
20
|
return globalCli;
|
|
@@ -15,11 +26,52 @@ function findPiCli(): string {
|
|
|
15
26
|
} catch {
|
|
16
27
|
throw new Error(
|
|
17
28
|
`Pim could not locate ${PI_PACKAGE}.\n` +
|
|
18
|
-
`Install
|
|
29
|
+
`Install Pi from https://pi.dev/docs/latest/quickstart, or set PIM_PI_CLI=/path/to/cli.js`
|
|
19
30
|
);
|
|
20
31
|
}
|
|
21
32
|
}
|
|
22
33
|
|
|
34
|
+
async function resolveEnvPiCli(): Promise<string | null> {
|
|
35
|
+
const candidate = process.env["PIM_PI_CLI"]?.trim();
|
|
36
|
+
if (!candidate) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
return (await isFile(candidate)) ? candidate : null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function resolvePathPiCli(): Promise<string | null> {
|
|
43
|
+
const piBin = Bun.which("pi");
|
|
44
|
+
if (!piBin) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const cliPath = await resolveRealPath(piBin);
|
|
49
|
+
const pkgPath = join(dirname(cliPath), "..", "package.json");
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
const pkg = (await Bun.file(pkgPath).json()) as { readonly name?: string };
|
|
53
|
+
return pkg.name === PI_PACKAGE ? cliPath : null;
|
|
54
|
+
} catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function resolveRealPath(path: string): Promise<string> {
|
|
60
|
+
try {
|
|
61
|
+
return await realpath(path);
|
|
62
|
+
} catch {
|
|
63
|
+
return path;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function isFile(path: string): Promise<boolean> {
|
|
68
|
+
try {
|
|
69
|
+
return (await Bun.file(path).stat()).isFile();
|
|
70
|
+
} catch {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
23
75
|
function resolveGlobalPiCli(): string | null {
|
|
24
76
|
const result = Bun.spawnSync({ cmd: ["bun", "pm", "-g", "bin"] });
|
|
25
77
|
if (result.exitCode !== 0) {
|
|
@@ -78,7 +130,7 @@ if (mode === "telegram") {
|
|
|
78
130
|
process.exit(0);
|
|
79
131
|
}
|
|
80
132
|
|
|
81
|
-
const piCli = findPiCli();
|
|
133
|
+
const piCli = await findPiCli();
|
|
82
134
|
const proc = Bun.spawn({
|
|
83
135
|
cmd: [process.execPath, piCli, ...cliArgs],
|
|
84
136
|
stdio: [
|
package/package.json
CHANGED
|
@@ -1,14 +1,28 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aaroncql/pim-agent",
|
|
3
|
-
"version": "0.0
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "A Bun-native extension pack for Pi: web access, subagents, revamped core tools, ANSI-compatible themes, fzf-style completions, Telegram mode, and more.",
|
|
5
|
+
"license": "MIT",
|
|
5
6
|
"type": "module",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"pi-package",
|
|
9
|
+
"agent"
|
|
10
|
+
],
|
|
11
|
+
"homepage": "https://github.com/AaronCQL/pim-agent#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/AaronCQL/pim-agent/issues"
|
|
14
|
+
},
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/AaronCQL/pim-agent.git"
|
|
18
|
+
},
|
|
6
19
|
"bin": {
|
|
7
20
|
"pim": "bin/pim.ts"
|
|
8
21
|
},
|
|
9
22
|
"files": [
|
|
10
23
|
"bin/",
|
|
11
|
-
"src/"
|
|
24
|
+
"src/",
|
|
25
|
+
"!src/**/*.test.ts"
|
|
12
26
|
],
|
|
13
27
|
"engines": {
|
|
14
28
|
"bun": ">=1.2.7"
|
|
@@ -19,14 +33,15 @@
|
|
|
19
33
|
],
|
|
20
34
|
"themes": [
|
|
21
35
|
"./src/themes"
|
|
22
|
-
]
|
|
36
|
+
],
|
|
37
|
+
"image": "https://raw.githubusercontent.com/AaronCQL/pim-agent/refs/heads/main/assets/demo.webp"
|
|
23
38
|
},
|
|
24
39
|
"scripts": {
|
|
25
40
|
"dev": "bun link && pim",
|
|
26
41
|
"typecheck": "tsgo --noEmit",
|
|
27
42
|
"test": "bun test src --only-failures",
|
|
28
43
|
"lint": "oxlint . --fix",
|
|
29
|
-
"format": "prettier --write --list-different package.json tsconfig.json .oxlintrc.json .prettierrc.json \"src/**/*.ts\" \"bin/**/*.ts\"
|
|
44
|
+
"format": "prettier --write --list-different package.json tsconfig.json .oxlintrc.json .prettierrc.json \"src/**/*.ts\" \"bin/**/*.ts\"",
|
|
30
45
|
"check": "bun run typecheck && bun run test && bun run lint && bun run format"
|
|
31
46
|
},
|
|
32
47
|
"peerDependencies": {
|
|
@@ -21,8 +21,9 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
21
21
|
if (typeof Bun === "undefined") {
|
|
22
22
|
throw new Error(
|
|
23
23
|
"Pim requires the Bun runtime.\n" +
|
|
24
|
-
"Install
|
|
25
|
-
"Then run: pim"
|
|
24
|
+
"Install the Pim launcher: bun install -g @aaroncql/pim-agent\n" +
|
|
25
|
+
"Then run: pim\n" +
|
|
26
|
+
"If pim cannot locate Pi, ensure `pi` is on PATH or set PIM_PI_CLI=/path/to/cli.js"
|
|
26
27
|
);
|
|
27
28
|
}
|
|
28
29
|
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
const EDIT_TOOL = "edit";
|
|
2
|
+
const APPLY_PATCH_TOOL = "apply_patch";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Pure reconcile over the active-tool list. Single-slot swap:
|
|
6
|
+
* - If neither `edit` nor `apply_patch` is active, no-op (respect user opt-out).
|
|
7
|
+
* - Otherwise keep exactly one in the same position: `apply_patch` when the
|
|
8
|
+
* model is GPT/Codex-family, else `edit`. All other active tools are
|
|
9
|
+
* preserved in order.
|
|
10
|
+
*
|
|
11
|
+
* Returns the same array reference when nothing changes so callers can skip the
|
|
12
|
+
* prompt-rebuilding `setActiveTools` call.
|
|
13
|
+
*/
|
|
14
|
+
export function computeActiveTools(
|
|
15
|
+
active: readonly string[],
|
|
16
|
+
isGpt: boolean
|
|
17
|
+
): readonly string[] {
|
|
18
|
+
const hasEdit = active.includes(EDIT_TOOL);
|
|
19
|
+
const hasApplyPatch = active.includes(APPLY_PATCH_TOOL);
|
|
20
|
+
|
|
21
|
+
if (!hasEdit && !hasApplyPatch) {
|
|
22
|
+
return active;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const desired = isGpt ? APPLY_PATCH_TOOL : EDIT_TOOL;
|
|
26
|
+
const drop = isGpt ? EDIT_TOOL : APPLY_PATCH_TOOL;
|
|
27
|
+
|
|
28
|
+
if (active.includes(desired) && !active.includes(drop)) {
|
|
29
|
+
return active;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const result: string[] = [];
|
|
33
|
+
let placed = false;
|
|
34
|
+
for (const tool of active) {
|
|
35
|
+
if (tool === EDIT_TOOL || tool === APPLY_PATCH_TOOL) {
|
|
36
|
+
if (!placed) {
|
|
37
|
+
result.push(desired);
|
|
38
|
+
placed = true;
|
|
39
|
+
}
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
result.push(tool);
|
|
43
|
+
}
|
|
44
|
+
if (!placed) {
|
|
45
|
+
result.push(desired);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return result;
|
|
49
|
+
}
|