@anton-kochev/pi-extensions 0.1.0 → 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 +16 -17
- package/package.json +15 -4
- package/squiggle/README.md +6 -3
- package/squiggle/extensions/squiggle.ts +71 -9
package/README.md
CHANGED
|
@@ -1,32 +1,22 @@
|
|
|
1
1
|
# pi-extensions
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@anton-kochev/pi-extensions)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Pi extensions for personal use.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
```yaml
|
|
10
|
-
pi:
|
|
11
|
-
extensions:
|
|
12
|
-
squiggle: "git:https://github.com/anton-kochev/pi-extensions.git#main"
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
Pithos's entrypoint passes this to `pi install`, which clones the repo, runs `npm install`, and registers the extensions declared in the root `pi.extensions` manifest.
|
|
16
|
-
|
|
17
|
-
## Install directly with pi
|
|
7
|
+
## Install
|
|
18
8
|
|
|
19
9
|
```bash
|
|
20
|
-
pi install
|
|
10
|
+
pi install npm:@anton-kochev/pi-extensions
|
|
21
11
|
```
|
|
22
12
|
|
|
23
|
-
|
|
13
|
+
Or pin to a specific version:
|
|
24
14
|
|
|
25
15
|
```bash
|
|
26
|
-
pi install
|
|
16
|
+
pi install npm:@anton-kochev/pi-extensions@<version>
|
|
27
17
|
```
|
|
28
18
|
|
|
29
|
-
## Extensions
|
|
19
|
+
## Extensions
|
|
30
20
|
|
|
31
21
|
- [`squiggle/`](./squiggle) — quietly polish grammar and spelling in user prompts.
|
|
32
22
|
|
|
@@ -39,3 +29,12 @@ pi install -l ./squiggle
|
|
|
39
29
|
```
|
|
40
30
|
|
|
41
31
|
Each subdirectory has its own `package.json` so individual extensions remain installable in isolation.
|
|
32
|
+
|
|
33
|
+
## Release
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npm version patch # or minor/major
|
|
37
|
+
git push --follow-tags
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Trusted publishing handles the rest — the workflow at `.github/workflows/publish.yml` fires on tag push and publishes to npm via OIDC.
|
package/package.json
CHANGED
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@anton-kochev/pi-extensions",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Pi extensions.",
|
|
5
|
-
"keywords": [
|
|
5
|
+
"keywords": [
|
|
6
|
+
"pi-package"
|
|
7
|
+
],
|
|
6
8
|
"license": "MIT",
|
|
7
9
|
"type": "module",
|
|
8
|
-
"
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/anton-kochev/pi-extensions.git"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"squiggle",
|
|
16
|
+
"README.md"
|
|
17
|
+
],
|
|
9
18
|
"publishConfig": {
|
|
10
19
|
"access": "public"
|
|
11
20
|
},
|
|
@@ -14,6 +23,8 @@
|
|
|
14
23
|
"@earendil-works/pi-coding-agent": "*"
|
|
15
24
|
},
|
|
16
25
|
"pi": {
|
|
17
|
-
"extensions": [
|
|
26
|
+
"extensions": [
|
|
27
|
+
"./squiggle/extensions"
|
|
28
|
+
]
|
|
18
29
|
}
|
|
19
30
|
}
|
package/squiggle/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Quietly polish grammar and spelling in your pi prompts.
|
|
4
4
|
|
|
5
|
-
The extension intercepts user input, corrects spelling and grammar using a configured model, shows a colored diff, and submits the corrected prompt automatically without confirmation. Named after the red squiggle from your favorite spell-checker.
|
|
5
|
+
The extension intercepts user input, shows a `squiggling...` spinner while processing, corrects spelling and grammar using a configured model, shows a colored diff, and submits the corrected prompt automatically without confirmation. Named after the red squiggle from your favorite spell-checker.
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
@@ -68,14 +68,17 @@ SQUIGGLE_MODEL=openai-codex/gpt-5.4-mini pi
|
|
|
68
68
|
SQUIGGLE_MAX_CHARS=1000 pi
|
|
69
69
|
```
|
|
70
70
|
|
|
71
|
-
##
|
|
71
|
+
## Commands
|
|
72
72
|
|
|
73
73
|
Inside pi:
|
|
74
74
|
|
|
75
75
|
```text
|
|
76
|
-
/squiggle
|
|
76
|
+
/squiggle toggle # switch between on/off
|
|
77
|
+
/squiggle-status # show status
|
|
77
78
|
```
|
|
78
79
|
|
|
80
|
+
The toggle state is saved in the current pi session and overrides `.pi/squiggle.json` and environment configuration for that session.
|
|
81
|
+
|
|
79
82
|
## Notes
|
|
80
83
|
|
|
81
84
|
This package imports pi runtime packages as peer dependencies:
|
|
@@ -4,23 +4,44 @@ import { complete, type UserMessage } from "@earendil-works/pi-ai";
|
|
|
4
4
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
5
5
|
|
|
6
6
|
export default function squiggle(pi: ExtensionAPI) {
|
|
7
|
+
let runtimeMode: SquiggleConfig["mode"] | undefined;
|
|
8
|
+
|
|
9
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
10
|
+
runtimeMode = restoreRuntimeMode(ctx);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
pi.registerCommand("squiggle", {
|
|
14
|
+
description: "Toggle squiggle on/off",
|
|
15
|
+
handler: async (args, ctx) => {
|
|
16
|
+
const command = args.trim().toLowerCase();
|
|
17
|
+
if (command !== "toggle") {
|
|
18
|
+
ctx.ui.notify("Usage: /squiggle toggle", "warning");
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const config = loadEffectiveConfig(ctx.cwd, runtimeMode);
|
|
23
|
+
runtimeMode = config.mode === "on" ? "off" : "on";
|
|
24
|
+
persistRuntimeMode(pi, runtimeMode);
|
|
25
|
+
ctx.ui.notify(formatStatus(ctx, loadEffectiveConfig(ctx.cwd, runtimeMode)), "info");
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
|
|
7
29
|
pi.registerCommand("squiggle-status", {
|
|
8
30
|
description: "Show whether squiggle is loaded",
|
|
9
31
|
handler: async (_args, ctx) => {
|
|
10
|
-
|
|
11
|
-
const model = selectCorrectionModel(ctx, config);
|
|
12
|
-
ctx.ui.notify(`squiggle is loaded (${config.mode}, ${formatModel(model)}).`, "info");
|
|
32
|
+
ctx.ui.notify(formatStatus(ctx, loadEffectiveConfig(ctx.cwd, runtimeMode)), "info");
|
|
13
33
|
},
|
|
14
34
|
});
|
|
15
35
|
|
|
16
36
|
pi.on("input", async (event, ctx) => {
|
|
17
37
|
if (event.source === "extension") return { action: "continue" };
|
|
18
38
|
|
|
19
|
-
const config =
|
|
39
|
+
const config = loadEffectiveConfig(ctx.cwd, runtimeMode);
|
|
20
40
|
if (config.mode === "off") return { action: "continue" };
|
|
21
41
|
if (!event.text.trim()) return { action: "continue" };
|
|
22
42
|
|
|
23
|
-
const
|
|
43
|
+
const stopIndicator = startSquiggleIndicator(ctx);
|
|
44
|
+
const corrected = await correctWithModel(event.text, ctx, config).finally(stopIndicator);
|
|
24
45
|
if (!corrected || corrected === event.text) return { action: "continue" };
|
|
25
46
|
|
|
26
47
|
if (ctx.hasUI) ctx.ui.notify(formatColoredDiff(event.text, corrected), "info");
|
|
@@ -100,6 +121,28 @@ function loadConfig(cwd: string): SquiggleConfig {
|
|
|
100
121
|
};
|
|
101
122
|
}
|
|
102
123
|
|
|
124
|
+
function loadEffectiveConfig(cwd: string, runtimeMode: SquiggleConfig["mode"] | undefined): SquiggleConfig {
|
|
125
|
+
const config = loadConfig(cwd);
|
|
126
|
+
return { ...config, mode: runtimeMode ?? config.mode };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function restoreRuntimeMode(ctx: ExtensionContext): SquiggleConfig["mode"] | undefined {
|
|
130
|
+
for (const entry of [...ctx.sessionManager.getEntries()].reverse()) {
|
|
131
|
+
if (entry.type !== "custom" || entry.customType !== "squiggle-mode") continue;
|
|
132
|
+
const data = (entry as { data?: { mode?: unknown } }).data;
|
|
133
|
+
return normalizeMode(data?.mode);
|
|
134
|
+
}
|
|
135
|
+
return undefined;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function persistRuntimeMode(pi: ExtensionAPI, mode: SquiggleConfig["mode"]): void {
|
|
139
|
+
pi.appendEntry("squiggle-mode", { mode });
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function formatStatus(ctx: ExtensionContext, config: SquiggleConfig): string {
|
|
143
|
+
return `squiggle is ${config.mode} (${formatModel(selectCorrectionModel(ctx, config))}).`;
|
|
144
|
+
}
|
|
145
|
+
|
|
103
146
|
function readConfigFile(cwd: string): Partial<SquiggleConfig> {
|
|
104
147
|
const path = join(cwd, ".pi", "squiggle.json");
|
|
105
148
|
if (!existsSync(path)) return {};
|
|
@@ -143,27 +186,46 @@ function formatModel(model: ReturnType<typeof selectCorrectionModel>): string {
|
|
|
143
186
|
return model ? `${model.provider}/${model.id}` : "no model";
|
|
144
187
|
}
|
|
145
188
|
|
|
189
|
+
function startSquiggleIndicator(ctx: ExtensionContext): () => void {
|
|
190
|
+
if (!ctx.hasUI) return () => {};
|
|
191
|
+
|
|
192
|
+
const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
193
|
+
let frame = 0;
|
|
194
|
+
let timer: ReturnType<typeof setInterval> | undefined;
|
|
195
|
+
|
|
196
|
+
const render = () => {
|
|
197
|
+
const theme = ctx.ui.theme;
|
|
198
|
+
ctx.ui.setStatus("squiggle", theme.fg("accent", frames[frame]!) + theme.fg("dim", " squiggling..."));
|
|
199
|
+
frame = (frame + 1) % frames.length;
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
render();
|
|
203
|
+
timer = setInterval(render, 120);
|
|
204
|
+
|
|
205
|
+
return () => {
|
|
206
|
+
if (timer) clearInterval(timer);
|
|
207
|
+
ctx.ui.setStatus("squiggle", undefined);
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
146
211
|
type DiffOp = {
|
|
147
212
|
type: "same" | "add" | "remove";
|
|
148
213
|
text: string;
|
|
149
214
|
};
|
|
150
215
|
|
|
151
216
|
function formatColoredDiff(before: string, after: string): string {
|
|
152
|
-
const dim = "\x1b[90;3m";
|
|
153
217
|
const same = "\x1b[90;3m";
|
|
154
218
|
const added = "\x1b[32;3m";
|
|
155
219
|
const removed = "\x1b[31;3m";
|
|
156
220
|
const reset = "\x1b[0m";
|
|
157
221
|
|
|
158
|
-
|
|
222
|
+
return diffChars(before.trim(), after.trim())
|
|
159
223
|
.map((op) => {
|
|
160
224
|
if (op.type === "add") return `${added}${op.text}${reset}`;
|
|
161
225
|
if (op.type === "remove") return `${removed}${op.text}${reset}`;
|
|
162
226
|
return `${same}${op.text}${reset}`;
|
|
163
227
|
})
|
|
164
228
|
.join("");
|
|
165
|
-
|
|
166
|
-
return `${dim}squiggle:${reset}\n${rendered}`;
|
|
167
229
|
}
|
|
168
230
|
|
|
169
231
|
function diffChars(before: string, after: string): DiffOp[] {
|