pi-all-tools 1.0.7 → 1.1.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 +18 -2
- package/extensions/default-tools.ts +87 -3
- package/package.json +16 -2
- package/test/default-tools.test.ts +100 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# pi-all-tools
|
|
2
2
|
|
|
3
|
-
Ensure 3 additional Pi tools are always available: `find`, `grep`, `ls
|
|
3
|
+
Ensure 3 additional Pi tools are always available: `find`, `grep`, `ls`.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -10,4 +10,20 @@ pi install npm:pi-all-tools
|
|
|
10
10
|
|
|
11
11
|
## What it does
|
|
12
12
|
|
|
13
|
-
Pi 默认自带 4 个工具 (read, bash, edit, write)。此扩展在每次 agent
|
|
13
|
+
Pi 默认自带 4 个工具 (read, bash, edit, write)。此扩展在每次 agent 启动时补齐 `find`, `grep`, `ls`,共 7 个工具可用,不覆盖其他扩展注册的工具。
|
|
14
|
+
|
|
15
|
+
## Pi 1.1+:改用原生 defaultTools
|
|
16
|
+
|
|
17
|
+
Pi 1.1 给 `defaultTools` 和 `--tools` 加了 `+name` / `-name` 增量语法,这件事可以不再依赖扩展:
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
{
|
|
21
|
+
"defaultTools": ["+find", "+grep", "+ls"]
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**本扩展在 Pi 1.1+ 上会让位给这个设置**:某个工具如果被 `defaultTools` 明确写成 `-find`,扩展不会再把它加回来(`-name` 的移除按官方语义应当保持);没有被提到的工具仍然照旧补齐。所以:
|
|
26
|
+
|
|
27
|
+
- 已经装了本扩展的用户,升级 Pi 后行为不变,无需改配置。
|
|
28
|
+
- 想单独禁用某个工具,写 `{"defaultTools": ["-find"]}` 即可,扩展不会覆盖。
|
|
29
|
+
- 全新环境建议直接用上面的原生配置,本扩展可以不安。
|
|
@@ -1,9 +1,93 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* pi-all-tools — keep find, grep and ls available.
|
|
3
|
+
*
|
|
4
|
+
* Pi 1.1 added `+name`/`-name` entries to `defaultTools` and `--tools`, so the
|
|
5
|
+
* tool set can be adjusted in configuration without an extension:
|
|
6
|
+
*
|
|
7
|
+
* { "defaultTools": ["+find", "+grep", "+ls"] }
|
|
8
|
+
*
|
|
9
|
+
* This package predates that. It still ensures the three tools, but on Pi that
|
|
10
|
+
* reads those increments it defers to what the setting says about each one:
|
|
11
|
+
*
|
|
12
|
+
* { "defaultTools": ["-find"] }
|
|
13
|
+
*
|
|
14
|
+
* now means find stays off, instead of being re-added on the next agent start.
|
|
15
|
+
* A tool the setting never mentions is added exactly as it always was, so an
|
|
16
|
+
* existing install keeps its 7-tool set with no configuration change.
|
|
17
|
+
*
|
|
18
|
+
* Ponytail: local typed interface keeps zero deps.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/** The tools this package has always ensured are present. */
|
|
22
|
+
const WANTED = ["find", "grep", "ls"];
|
|
23
|
+
|
|
24
|
+
interface PiLike {
|
|
25
|
+
on: (event: string, cb: () => void | Promise<void>) => void;
|
|
26
|
+
getActiveTools?: () => string[] | undefined;
|
|
27
|
+
setActiveTools?: (tools: string[]) => void;
|
|
28
|
+
getSettings?: () => { defaultTools?: string[] } | undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* What `defaultTools` says about one tool, once the `+name`/`-name` grammar is
|
|
33
|
+
* understood:
|
|
34
|
+
*
|
|
35
|
+
* - `added` — named plainly or with `+`; the person wants it on.
|
|
36
|
+
* - `removed` — named with `-`; this extension must not put it back, because a
|
|
37
|
+
* `-name` removal is documented to stick across reloads.
|
|
38
|
+
* - `unset` — not named; the extension may add it as before.
|
|
39
|
+
*
|
|
40
|
+
* A plain allowlist that simply omits the tool is not read as a removal: that
|
|
41
|
+
* is the older replace-the-defaults form this package never honoured anyway.
|
|
42
|
+
*/
|
|
43
|
+
export function statedIntent(defaultTools: string[] | undefined, tool: string): "added" | "removed" | "unset" {
|
|
44
|
+
if (!Array.isArray(defaultTools)) return "unset";
|
|
45
|
+
for (const entry of defaultTools) {
|
|
46
|
+
if (typeof entry !== "string") continue;
|
|
47
|
+
if (entry === tool || entry === `+${tool}`) return "added";
|
|
48
|
+
if (entry === `-${tool}`) return "removed";
|
|
49
|
+
}
|
|
50
|
+
return "unset";
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Whether Pi reads `+name`/`-name` increments in `defaultTools`.
|
|
55
|
+
*
|
|
56
|
+
* `getSettings` exposes the resolved settings; only Pi 1.1+ pairs it with the
|
|
57
|
+
* increment grammar. Asking also covers the case where settings exist but carry
|
|
58
|
+
* no `defaultTools` key at all, which reads as `unset` either way.
|
|
59
|
+
*/
|
|
60
|
+
function supportsIncrements(pi: PiLike): boolean {
|
|
61
|
+
try {
|
|
62
|
+
return typeof pi.getSettings === "function" && pi.getSettings() !== undefined;
|
|
63
|
+
} catch {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export default function (pi: PiLike) {
|
|
69
|
+
// Resolved once: a session cannot change which Pi grammar it runs.
|
|
70
|
+
const respectsIncrements = supportsIncrements(pi);
|
|
71
|
+
|
|
3
72
|
pi.on("before_agent_start", async () => {
|
|
4
73
|
try {
|
|
74
|
+
if (typeof pi.getActiveTools !== "function" || typeof pi.setActiveTools !== "function") return;
|
|
5
75
|
const current = pi.getActiveTools() ?? [];
|
|
6
|
-
|
|
76
|
+
|
|
77
|
+
let defaultTools: string[] | undefined;
|
|
78
|
+
if (respectsIncrements) {
|
|
79
|
+
try {
|
|
80
|
+
defaultTools = pi.getSettings?.()?.defaultTools;
|
|
81
|
+
} catch {
|
|
82
|
+
defaultTools = undefined;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const missing = WANTED.filter((tool) => {
|
|
87
|
+
if (current.includes(tool)) return false;
|
|
88
|
+
// A natively-removed tool stays removed; anything else is added.
|
|
89
|
+
return statedIntent(defaultTools, tool) !== "removed";
|
|
90
|
+
});
|
|
7
91
|
if (missing.length > 0) pi.setActiveTools([...current, ...missing]);
|
|
8
92
|
} catch {
|
|
9
93
|
// Extension should never crash Pi — silent no-op on failure
|
package/package.json
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-all-tools",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Enable all 7 built-in Pi tools by default (read, bash, edit, write, grep, find, ls)",
|
|
5
|
+
"type": "module",
|
|
5
6
|
"license": "MIT",
|
|
6
|
-
"repository":
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/RetiredPhysicist/pi-plugins.git",
|
|
10
|
+
"directory": "packages/pi-all-tools"
|
|
11
|
+
},
|
|
7
12
|
"keywords": [
|
|
8
13
|
"pi-package",
|
|
9
14
|
"pi",
|
|
@@ -14,5 +19,14 @@
|
|
|
14
19
|
"extensions": [
|
|
15
20
|
"./extensions"
|
|
16
21
|
]
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"test": "node --import tsx --test test/*.test.ts"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"tsx": "^4.23.15"
|
|
28
|
+
},
|
|
29
|
+
"peerDependencies": {
|
|
30
|
+
"@earendil-works/pi-coding-agent": "^1.1.0"
|
|
17
31
|
}
|
|
18
32
|
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { describe, it } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import register, { statedIntent } from "../extensions/default-tools.ts";
|
|
4
|
+
|
|
5
|
+
/** A minimal stand-in for the Pi extension API. */
|
|
6
|
+
function harness(options: {
|
|
7
|
+
active?: string[];
|
|
8
|
+
defaultTools?: string[];
|
|
9
|
+
hasSettings?: boolean;
|
|
10
|
+
} = {}) {
|
|
11
|
+
const handlers = new Map<string, () => void | Promise<void>>();
|
|
12
|
+
const pi = {
|
|
13
|
+
on(event: string, cb: () => void | Promise<void>) {
|
|
14
|
+
handlers.set(event, cb);
|
|
15
|
+
},
|
|
16
|
+
getActiveTools: () => options.active ?? [],
|
|
17
|
+
setActiveTools(tools: string[]) {
|
|
18
|
+
options.active = tools;
|
|
19
|
+
},
|
|
20
|
+
...(options.hasSettings === false
|
|
21
|
+
? {}
|
|
22
|
+
: { getSettings: () => (options.defaultTools ? { defaultTools: options.defaultTools } : {}) }),
|
|
23
|
+
};
|
|
24
|
+
register(pi as never);
|
|
25
|
+
return {
|
|
26
|
+
active: () => options.active ?? [],
|
|
27
|
+
async start() {
|
|
28
|
+
const handler = handlers.get("before_agent_start");
|
|
29
|
+
if (handler) await handler();
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
describe("statedIntent", () => {
|
|
35
|
+
it("reads a plain name and +name as added", () => {
|
|
36
|
+
assert.equal(statedIntent(["find"], "find"), "added");
|
|
37
|
+
assert.equal(statedIntent(["+find"], "find"), "added");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("reads -name as removed", () => {
|
|
41
|
+
assert.equal(statedIntent(["-find"], "find"), "removed");
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("treats an absent or unnamed tool as unset", () => {
|
|
45
|
+
assert.equal(statedIntent(undefined, "find"), "unset");
|
|
46
|
+
assert.equal(statedIntent([], "find"), "unset");
|
|
47
|
+
assert.equal(statedIntent(["+grep"], "find"), "unset");
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("ignores non-string entries", () => {
|
|
51
|
+
assert.equal(statedIntent([1 as never, "-find"], "find"), "removed");
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe("pi-all-tools extension", () => {
|
|
56
|
+
it("adds the missing tools on an unconfigured install", async () => {
|
|
57
|
+
const pi = harness({ active: ["read", "bash", "edit", "write"] });
|
|
58
|
+
await pi.start();
|
|
59
|
+
assert.deepEqual(pi.active(), ["read", "bash", "edit", "write", "find", "grep", "ls"]);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("leaves the active set alone when nothing is missing", async () => {
|
|
63
|
+
const pi = harness({ active: ["read", "find", "grep", "ls"] });
|
|
64
|
+
await pi.start();
|
|
65
|
+
assert.deepEqual(pi.active(), ["read", "find", "grep", "ls"]);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("respects a native removal instead of re-adding the tool", async () => {
|
|
69
|
+
const pi = harness({ active: ["read", "grep"], defaultTools: ["-find"] });
|
|
70
|
+
await pi.start();
|
|
71
|
+
// find stays removed; grep and ls are still filled in.
|
|
72
|
+
assert.deepEqual(pi.active(), ["read", "grep", "ls"]);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("still adds a tool the setting never mentions", async () => {
|
|
76
|
+
const pi = harness({ active: ["read"], defaultTools: ["+grep"] });
|
|
77
|
+
await pi.start();
|
|
78
|
+
assert.deepEqual(pi.active(), ["read", "find", "grep", "ls"]);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("falls back to the legacy behavior when settings are unavailable", async () => {
|
|
82
|
+
const pi = harness({ active: ["read"], hasSettings: false });
|
|
83
|
+
await pi.start();
|
|
84
|
+
assert.deepEqual(pi.active(), ["read", "find", "grep", "ls"]);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("never throws when the active-tool API is missing", async () => {
|
|
88
|
+
const handlers = new Map<string, () => void | Promise<void>>();
|
|
89
|
+
const pi = {
|
|
90
|
+
on(event: string, cb: () => void | Promise<void>) {
|
|
91
|
+
handlers.set(event, cb);
|
|
92
|
+
},
|
|
93
|
+
getSettings: () => ({}),
|
|
94
|
+
};
|
|
95
|
+
register(pi as never);
|
|
96
|
+
const handler = handlers.get("before_agent_start");
|
|
97
|
+
assert.ok(handler);
|
|
98
|
+
await handler();
|
|
99
|
+
});
|
|
100
|
+
});
|