pi-umbra 0.1.3 → 0.1.5
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 +33 -31
- package/node_modules/pi-umbra-help/package.json +4 -1
- package/node_modules/pi-umbra-inputbar/package.json +5 -1
- package/node_modules/pi-umbra-preview/package.json +4 -1
- package/node_modules/pi-umbra-rename/package.json +4 -1
- package/node_modules/pi-umbra-shimmer/README.md +31 -13
- package/node_modules/pi-umbra-shimmer/package.json +6 -2
- package/node_modules/pi-umbra-shimmer/patch.mjs +101 -0
- package/node_modules/pi-umbra-skill-matcher/README.md +41 -17
- package/node_modules/pi-umbra-skill-matcher/package.json +4 -2
- package/node_modules/pi-umbra-skill-matcher/patch.mjs +115 -0
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -1,31 +1,33 @@
|
|
|
1
|
-
# pi-umbra
|
|
2
|
-
|
|
3
|
-
Every small thing pi does not build in, in one install.
|
|
4
|
-
|
|
5
|
-
```sh
|
|
6
|
-
pi install npm:pi-umbra
|
|
7
|
-
```
|
|
8
|
-
|
|
9
|
-
One tarball. The nine packages below ship bundled inside it, so the install does not
|
|
10
|
-
depend on npm hoisting nine separate trees into the layout pi expects.
|
|
11
|
-
|
|
12
|
-
```
|
|
13
|
-
pi-umbra
|
|
14
|
-
├── pi-umbra-theme seven themes and the chrome around them
|
|
15
|
-
├── pi-umbra-ask the model asks you a question mid-turn
|
|
16
|
-
├── pi-umbra-copy-chat /umb-copy-chat
|
|
17
|
-
├── pi-umbra-help /umb-help, /umb-doctor
|
|
18
|
-
├── pi-umbra-inputbar the input box
|
|
19
|
-
├── pi-umbra-preview /umb-preview
|
|
20
|
-
├── pi-umbra-rename /umb-rename
|
|
21
|
-
├── pi-umbra-shimmer a shimmer through the working indicator
|
|
22
|
-
└── pi-umbra-skill-matcher skill completion mid-prompt
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
Install any of them on its own if you want less. Each package has its own README saying
|
|
26
|
-
what it solves.
|
|
27
|
-
|
|
28
|
-
`pi-umbra-shimmer` and `pi-umbra-skill-matcher`
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
1
|
+
# pi-umbra
|
|
2
|
+
|
|
3
|
+
Every small thing pi does not build in, in one install.
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
pi install npm:pi-umbra
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
One tarball. The nine packages below ship bundled inside it, so the install does not
|
|
10
|
+
depend on npm hoisting nine separate trees into the layout pi expects.
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
pi-umbra
|
|
14
|
+
├── pi-umbra-theme seven themes and the chrome around them
|
|
15
|
+
├── pi-umbra-ask the model asks you a question mid-turn
|
|
16
|
+
├── pi-umbra-copy-chat /umb-copy-chat
|
|
17
|
+
├── pi-umbra-help /umb-help, /umb-doctor
|
|
18
|
+
├── pi-umbra-inputbar the input box
|
|
19
|
+
├── pi-umbra-preview /umb-preview
|
|
20
|
+
├── pi-umbra-rename /umb-rename
|
|
21
|
+
├── pi-umbra-shimmer a shimmer through the working indicator
|
|
22
|
+
└── pi-umbra-skill-matcher skill completion mid-prompt
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Install any of them on its own if you want less. Each package has its own README saying
|
|
26
|
+
what it solves.
|
|
27
|
+
|
|
28
|
+
`pi-umbra-shimmer` and `pi-umbra-skill-matcher` reach past what the extension API
|
|
29
|
+
exposes, so each ships a `patch.mjs` that has to be run once after installing and again
|
|
30
|
+
after every pi update. Their own READMEs carry the command. Until it is run, those two
|
|
31
|
+
load and do nothing; the rest of the family is unaffected.
|
|
32
|
+
|
|
33
|
+
MIT.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-umbra-help",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Two commands over the pi-umbra family: /umb-help lists what is installed and how to configure it, /umb-doctor reports the faults that would otherwise stay silent.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -30,5 +30,8 @@
|
|
|
30
30
|
"extensions": [
|
|
31
31
|
"./extensions"
|
|
32
32
|
]
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"@earendil-works/pi-coding-agent": "*"
|
|
33
36
|
}
|
|
34
37
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-umbra-inputbar",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "pi's input bar, rebuilt: a chevron prompt, the session name on the border, a colour you pick, and a command to rename the session.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -29,5 +29,9 @@
|
|
|
29
29
|
"extensions": [
|
|
30
30
|
"./extensions"
|
|
31
31
|
]
|
|
32
|
+
},
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
35
|
+
"@earendil-works/pi-tui": "*"
|
|
32
36
|
}
|
|
33
37
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-umbra-preview",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Render a document with pandoc and open it in the browser, from inside pi.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -29,5 +29,8 @@
|
|
|
29
29
|
"extensions": [
|
|
30
30
|
"./extensions"
|
|
31
31
|
]
|
|
32
|
+
},
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"@earendil-works/pi-coding-agent": "*"
|
|
32
35
|
}
|
|
33
36
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-umbra-rename",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "One command that renames a pi session and the terminal window title together, which pi keeps behind two separate APIs.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -29,5 +29,8 @@
|
|
|
29
29
|
"extensions": [
|
|
30
30
|
"./extensions"
|
|
31
31
|
]
|
|
32
|
+
},
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"@earendil-works/pi-coding-agent": "*"
|
|
32
35
|
}
|
|
33
36
|
}
|
|
@@ -7,26 +7,44 @@ The highlight is not one colour. It travels from white down to the theme's own m
|
|
|
7
7
|
and back, blending the whole way rather than stepping, so the band changes tone while it
|
|
8
8
|
moves.
|
|
9
9
|
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
pi install npm:pi-umbra-shimmer
|
|
14
|
+
node ~/.pi/agent/npm/node_modules/pi-umbra-shimmer/patch.mjs
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Then restart pi.
|
|
18
|
+
|
|
19
|
+
## The second line is not optional
|
|
20
|
+
|
|
10
21
|
pi builds the working indicator with a colour function already in hand, and paints custom
|
|
11
|
-
spinner frames verbatim without running them through that step at all.
|
|
12
|
-
|
|
13
|
-
|
|
22
|
+
spinner frames verbatim without running them through that step at all. Neither point is
|
|
23
|
+
reachable from the extension API, so this package ships three patches against pi's built
|
|
24
|
+
bundle. Without them the extension loads and does nothing.
|
|
14
25
|
|
|
15
|
-
|
|
16
|
-
|
|
26
|
+
```sh
|
|
27
|
+
node ~/.pi/agent/npm/node_modules/pi-umbra-shimmer/patch.mjs --check
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`--check` writes nothing and exits 1 when a patch is missing. **Run it after every pi
|
|
31
|
+
update.** An update replaces the bundle, so all three patches disappear silently and the
|
|
32
|
+
shimmer stops without an error.
|
|
33
|
+
|
|
34
|
+
Nothing here is destructive. Each patch is additive, a patch whose text no longer matches
|
|
35
|
+
is reported rather than forced, and reinstalling pi returns the bundle to stock. The
|
|
36
|
+
patched behaviour is gated on the extension being loaded, so the patches alone change
|
|
37
|
+
nothing.
|
|
38
|
+
|
|
39
|
+
Set `PI_UMBRA_PI` if pi lives somewhere this script cannot find it.
|
|
17
40
|
|
|
18
41
|
```
|
|
19
42
|
pi-umbra-shimmer/
|
|
20
43
|
├── checks
|
|
21
44
|
│ └── umbra-shimmer.check.ts
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
## Install
|
|
27
|
-
|
|
28
|
-
```sh
|
|
29
|
-
pi install npm:pi-umbra-shimmer
|
|
45
|
+
├── extensions
|
|
46
|
+
│ └── umbra-shimmer.ts
|
|
47
|
+
└── patch.mjs
|
|
30
48
|
```
|
|
31
49
|
|
|
32
50
|
MIT.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-umbra-shimmer",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "A wave of colour running through pi's working indicator, over both the spinner and the text. Needs the umbra bundle patches; without them it does nothing.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -23,11 +23,15 @@
|
|
|
23
23
|
"extensions",
|
|
24
24
|
"checks",
|
|
25
25
|
"README.md",
|
|
26
|
-
"LICENSE"
|
|
26
|
+
"LICENSE",
|
|
27
|
+
"patch.mjs"
|
|
27
28
|
],
|
|
28
29
|
"pi": {
|
|
29
30
|
"extensions": [
|
|
30
31
|
"./extensions"
|
|
31
32
|
]
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"@earendil-works/pi-coding-agent": "*"
|
|
32
36
|
}
|
|
33
37
|
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// pi-umbra-shimmer needs 3 seams that pi's extension API does not expose, so it edits
|
|
2
|
+
// pi's built bundle. Run it after installing, and again after every pi update:
|
|
3
|
+
//
|
|
4
|
+
// node <this file> apply
|
|
5
|
+
// node <this file> --check report only, write nothing, exit 1 if anything is missing
|
|
6
|
+
//
|
|
7
|
+
// pi runs dist/bundle/cli.js, a self-contained bundle, and the chunk names are content-hashed,
|
|
8
|
+
// so every patch finds its own place by matching its own text rather than by line number.
|
|
9
|
+
//
|
|
10
|
+
// Nothing here is destructive. Each patch is additive, a patch whose text no longer matches is
|
|
11
|
+
// reported instead of forced, and reinstalling pi returns the bundle to stock. A pi upgrade
|
|
12
|
+
// replaces the bundle and drops every patch silently, which is what --check is for.
|
|
13
|
+
import { createRequire } from "node:module";
|
|
14
|
+
import { existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
15
|
+
import { homedir } from "node:os";
|
|
16
|
+
import { dirname, join } from "node:path";
|
|
17
|
+
|
|
18
|
+
// PI_UMBRA_PI points this at another pi tree. Otherwise pi is resolved from wherever this
|
|
19
|
+
// package was installed, since it is a peer dependency, and the bun global path is the last resort.
|
|
20
|
+
const resolvePi = () => {
|
|
21
|
+
if (process.env.PI_UMBRA_PI) return process.env.PI_UMBRA_PI;
|
|
22
|
+
try {
|
|
23
|
+
return dirname(createRequire(import.meta.url).resolve("@earendil-works/pi-coding-agent/package.json"));
|
|
24
|
+
} catch {
|
|
25
|
+
return join(homedir(), ".bun/install/global/node_modules/@earendil-works/pi-coding-agent");
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const BUNDLE = join(resolvePi(), "dist/bundle");
|
|
30
|
+
if (!existsSync(BUNDLE)) {
|
|
31
|
+
console.error(`no pi bundle at ${BUNDLE} - set PI_UMBRA_PI to your pi install`);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const PATCHES = [
|
|
36
|
+
{
|
|
37
|
+
// The indicator is built with a colour function already in hand - the editor embeds the
|
|
38
|
+
// working status, so pi hands it the editor border - and the fallback below never runs.
|
|
39
|
+
// Standing that function down when a shimmer is installed is what lets the fallback through.
|
|
40
|
+
reason: "a shimmer outranks the editor border",
|
|
41
|
+
from: `let colorFn=isWorkingStatusEditor(this.editor)?text=>(this.editor.borderColor??theme.getThinkingBorderColor(this.session.thinkingLevel||"off"))(text):void 0;`,
|
|
42
|
+
to: `let colorFn=globalThis.__umbraShimmer?void 0:isWorkingStatusEditor(this.editor)?text=>(this.editor.borderColor??theme.getThinkingBorderColor(this.session.thinkingLevel||"off"))(text):void 0;`,
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
// Custom frames are rendered verbatim, which is what kept the braille glyph out of the
|
|
46
|
+
// wave: the spinner colour function was skipped for them while the message kept being
|
|
47
|
+
// repainted every frame. Verbatim still holds when no shimmer is installed.
|
|
48
|
+
reason: "and paints the custom frames too",
|
|
49
|
+
from: `return this.renderIndicatorVerbatim?frame2:this.spinnerColorFn(frame2)`,
|
|
50
|
+
to: `return this.renderIndicatorVerbatim&&!globalThis.__umbraShimmer?frame2:this.spinnerColorFn(frame2)`,
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
// The working indicator paints its spinner with `accent` and its message with `muted`, both
|
|
54
|
+
// fixed at construction. A shimmer has to repaint every character on every frame, so the
|
|
55
|
+
// two colour functions become a lookup the shimmer extension can fill in. Nothing
|
|
56
|
+
// installed means pi's own two colours, unchanged.
|
|
57
|
+
reason: "the working indicator's colours can be taken over",
|
|
58
|
+
from: `super("working",ui,colorFn??(text=>theme.fg("accent",text)),colorFn??(text=>theme.fg("muted",text)),message,indicator)`,
|
|
59
|
+
to: `super("working",ui,colorFn??(text=>(globalThis.__umbraShimmer?.spinner??(t=>theme.fg("accent",t)))(text)),colorFn??(text=>(globalThis.__umbraShimmer?.message??(t=>theme.fg("muted",t)))(text)),message,indicator)`,
|
|
60
|
+
},
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
const files = [join(BUNDLE, "cli.js"), ...readdirSync(join(BUNDLE, "chunks")).map((name) => join(BUNDLE, "chunks", name))];
|
|
64
|
+
const CHECK = process.argv.includes("--check");
|
|
65
|
+
const unapplied = [];
|
|
66
|
+
const gone = [];
|
|
67
|
+
|
|
68
|
+
for (const patch of PATCHES) {
|
|
69
|
+
let done = false;
|
|
70
|
+
for (const path of files) {
|
|
71
|
+
const source = readFileSync(path, "utf8");
|
|
72
|
+
// A multi-line patch is written with newline escapes; match whatever line ending the file uses.
|
|
73
|
+
const eol = source.includes("\r\n") ? "\r\n" : "\n";
|
|
74
|
+
const from = patch.from.split("\n").join(eol);
|
|
75
|
+
const to = patch.to.split("\n").join(eol);
|
|
76
|
+
if (source.includes(to)) {
|
|
77
|
+
if (!CHECK) console.log(`already patched: ${patch.reason}`);
|
|
78
|
+
done = true;
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
if (!source.includes(from)) continue;
|
|
82
|
+
if (CHECK) {
|
|
83
|
+
unapplied.push(patch.reason);
|
|
84
|
+
done = true;
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
writeFileSync(path, source.replace(from, to));
|
|
88
|
+
console.log(`patched: ${patch.reason}`);
|
|
89
|
+
done = true;
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
// Neither the original text nor the patched text is there, so pi changed the code this
|
|
93
|
+
// patch names. Report it rather than force anything.
|
|
94
|
+
if (!done) gone.push(patch.reason);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
for (const reason of unapplied) console.log(`NOT APPLIED: ${reason}`);
|
|
98
|
+
for (const reason of gone) console.log(`SEAM GONE: ${reason}`);
|
|
99
|
+
const bad = unapplied.length + gone.length;
|
|
100
|
+
console.log(bad ? `${bad} of ${PATCHES.length} not in place` : `all ${PATCHES.length} patches applied`);
|
|
101
|
+
process.exit(bad ? 1 : 0);
|
|
@@ -3,34 +3,58 @@
|
|
|
3
3
|
Type `/huh` and reach `skill:huh`. Complete a skill anywhere in the line, not only at the
|
|
4
4
|
start of it.
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
```sh
|
|
9
|
+
pi install npm:pi-umbra-skill-matcher
|
|
10
|
+
node ~/.pi/agent/npm/node_modules/pi-umbra-skill-matcher/patch.mjs
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Then restart pi.
|
|
14
|
+
|
|
15
|
+
## What it fixes
|
|
16
|
+
|
|
17
|
+
pi lists every skill in the completion dropdown under its bare name, but only
|
|
18
|
+
`/skill:huh` runs one. So the dropdown offers `/huh`, you press enter, and pi answers
|
|
19
|
+
"Unknown command". The fuzzy matching is not the problem; the name it matches against is.
|
|
20
|
+
This wraps the built-in provider and rewrites every skill row to the form that dispatches,
|
|
21
|
+
then re-runs the match against the prefixed name so `/skill:hu` still finds it after the
|
|
22
|
+
built-in pass has given up.
|
|
12
23
|
|
|
13
24
|
It also fills the mid-sentence slash menu. pi gates that menu on the line starting with
|
|
14
|
-
`/`, so a `/name` written inside a sentence never reaches a provider at all.
|
|
15
|
-
gate needs a bundle patch, which is not published in this repo. What appears behind the
|
|
16
|
-
gate is decided here.
|
|
25
|
+
`/`, so a `/name` written inside a sentence never reaches a provider at all.
|
|
17
26
|
|
|
18
27
|
The mid-sentence match stays deliberately narrow, so a path keeps behaving like a path:
|
|
19
28
|
the token must be the last one on the line, hold no space, and follow real text ending in
|
|
20
29
|
whitespace. `src/foo` and an indented `/foo` both fail those tests and are left to pi.
|
|
21
30
|
|
|
31
|
+
## The second install line is not optional
|
|
32
|
+
|
|
33
|
+
Two of the four seams are past what the extension API exposes: the dispatch map that only
|
|
34
|
+
knows the prefixed name, and the gate that keeps the slash menu at the start of the line.
|
|
35
|
+
So this package ships four patches against pi's built bundle. Without them the extension
|
|
36
|
+
loads and does nothing.
|
|
37
|
+
|
|
38
|
+
```sh
|
|
39
|
+
node ~/.pi/agent/npm/node_modules/pi-umbra-skill-matcher/patch.mjs --check
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
`--check` writes nothing and exits 1 when a patch is missing. **Run it after every pi
|
|
43
|
+
update.** An update replaces the bundle, so all four patches disappear silently and `/huh`
|
|
44
|
+
goes back to "Unknown command".
|
|
45
|
+
|
|
46
|
+
Nothing here is destructive. Each patch is additive, a patch whose text no longer matches
|
|
47
|
+
is reported rather than forced, and reinstalling pi returns the bundle to stock.
|
|
48
|
+
|
|
49
|
+
Set `PI_UMBRA_PI` if pi lives somewhere this script cannot find it.
|
|
50
|
+
|
|
22
51
|
```
|
|
23
52
|
pi-umbra-skill-matcher/
|
|
24
53
|
├── checks
|
|
25
54
|
│ └── umbra-skill-matcher.check.ts
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
## Install
|
|
31
|
-
|
|
32
|
-
```sh
|
|
33
|
-
pi install npm:pi-umbra-skill-matcher
|
|
55
|
+
├── extensions
|
|
56
|
+
│ └── umbra-skill-matcher.ts
|
|
57
|
+
└── patch.mjs
|
|
34
58
|
```
|
|
35
59
|
|
|
36
60
|
MIT.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-umbra-skill-matcher",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Type /huh and reach skill:huh. pi lists every skill under its bare name, which is not the name that dispatches.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -21,7 +21,9 @@
|
|
|
21
21
|
"files": [
|
|
22
22
|
"extensions",
|
|
23
23
|
"checks",
|
|
24
|
-
"README.md"
|
|
24
|
+
"README.md",
|
|
25
|
+
"patch.mjs",
|
|
26
|
+
"LICENSE"
|
|
25
27
|
],
|
|
26
28
|
"pi": {
|
|
27
29
|
"extensions": [
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// pi-umbra-skill-matcher needs 4 seams that pi's extension API does not expose, so it edits
|
|
2
|
+
// pi's built bundle. Run it after installing, and again after every pi update:
|
|
3
|
+
//
|
|
4
|
+
// node <this file> apply
|
|
5
|
+
// node <this file> --check report only, write nothing, exit 1 if anything is missing
|
|
6
|
+
//
|
|
7
|
+
// pi runs dist/bundle/cli.js, a self-contained bundle, and the chunk names are content-hashed,
|
|
8
|
+
// so every patch finds its own place by matching its own text rather than by line number.
|
|
9
|
+
//
|
|
10
|
+
// Nothing here is destructive. Each patch is additive, a patch whose text no longer matches is
|
|
11
|
+
// reported instead of forced, and reinstalling pi returns the bundle to stock. A pi upgrade
|
|
12
|
+
// replaces the bundle and drops every patch silently, which is what --check is for.
|
|
13
|
+
import { createRequire } from "node:module";
|
|
14
|
+
import { existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
15
|
+
import { homedir } from "node:os";
|
|
16
|
+
import { dirname, join } from "node:path";
|
|
17
|
+
|
|
18
|
+
// PI_UMBRA_PI points this at another pi tree. Otherwise pi is resolved from wherever this
|
|
19
|
+
// package was installed, since it is a peer dependency, and the bun global path is the last resort.
|
|
20
|
+
const resolvePi = () => {
|
|
21
|
+
if (process.env.PI_UMBRA_PI) return process.env.PI_UMBRA_PI;
|
|
22
|
+
try {
|
|
23
|
+
return dirname(createRequire(import.meta.url).resolve("@earendil-works/pi-coding-agent/package.json"));
|
|
24
|
+
} catch {
|
|
25
|
+
return join(homedir(), ".bun/install/global/node_modules/@earendil-works/pi-coding-agent");
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const BUNDLE = join(resolvePi(), "dist/bundle");
|
|
30
|
+
if (!existsSync(BUNDLE)) {
|
|
31
|
+
console.error(`no pi bundle at ${BUNDLE} - set PI_UMBRA_PI to your pi install`);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const PATCHES = [
|
|
36
|
+
{
|
|
37
|
+
// A skill is reachable as /skill:huh only. The prefix is hardcoded at both sites below,
|
|
38
|
+
// with no setting for it. The dispatch map now holds each skill under both names, so
|
|
39
|
+
// /huh works and any /skill:huh already in muscle memory or in a doc keeps working.
|
|
40
|
+
// Only the bare name is offered in autocomplete, to keep the list one line per skill.
|
|
41
|
+
reason: "/huh instead of /skill:huh",
|
|
42
|
+
from: "let commandName=`skill:${skill.name}`;this.skillCommands.set(commandName,skill.filePath),skillCommandList.push({name:commandName,",
|
|
43
|
+
to: "let commandName=skill.name;this.skillCommands.set(`skill:${skill.name}`,skill.filePath),this.skillCommands.set(commandName,skill.filePath),skillCommandList.push({name:commandName,",
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
reason: "the command list names skills without the prefix too",
|
|
47
|
+
from: "skills=this._resourceLoader.getSkills().skills.map(skill=>({name:`skill:${skill.name}`,",
|
|
48
|
+
to: "skills=this._resourceLoader.getSkills().skills.map(skill=>({name:skill.name,",
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
// The slash menu only ever opens on a line that starts with "/": `isAtStartOfMessage`
|
|
52
|
+
// gates the keystroke and `isInSlashCommandContext` gates every letter after it. A
|
|
53
|
+
// "/name" written in the middle of a sentence therefore reaches no provider at all, and
|
|
54
|
+
// an extension cannot fix that - it is never asked. These two open the door;
|
|
55
|
+
// umbra-skill-matcher decides what stands behind it.
|
|
56
|
+
//
|
|
57
|
+
// `umbraMidSlash` is deliberately narrow, so a path keeps behaving like a path: the
|
|
58
|
+
// token must be the last one on the line, hold no space, and sit after real text that
|
|
59
|
+
// ends in whitespace. "src/foo" fails on the last clause, " /foo" on the one before it.
|
|
60
|
+
// `head!==head.trimEnd()` is how "ends in a space or a tab" is written without putting an
|
|
61
|
+
// escape inside a patch string.
|
|
62
|
+
reason: "a mid-sentence /name opens the slash menu",
|
|
63
|
+
from: `isInSlashCommandContext(textBeforeCursor){return this.isSlashMenuAllowed()&&textBeforeCursor.trimStart().startsWith("/")}`,
|
|
64
|
+
to: `isInSlashCommandContext(textBeforeCursor){return this.isSlashMenuAllowed()&&textBeforeCursor.trimStart().startsWith("/")||this.umbraMidSlash(textBeforeCursor)}umbraMidSlash(t){let i=t.lastIndexOf("/");if(i<1)return!1;if(t.slice(i+1).indexOf(" ")>=0)return!1;let head=t.slice(0,i);return head.trim()!==""&&head!==head.trimEnd()}`,
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
// The companion to the patch above: without it the menu opens on the second character
|
|
68
|
+
// rather than on the slash, because typing "/" is handled by its own branch that ends at
|
|
69
|
+
// `isAtStartOfMessage`. At that moment the token after the slash is still empty, which is
|
|
70
|
+
// exactly the shape `umbraMidSlash` accepts.
|
|
71
|
+
reason: "typing a mid-sentence slash opens the menu on the slash",
|
|
72
|
+
from: `char==="/"&&this.isAtStartOfMessage()`,
|
|
73
|
+
to: `char==="/"&&(this.isAtStartOfMessage()||this.umbraMidSlash((this.state.lines[this.state.cursorLine]||"").slice(0,this.state.cursorCol)))`,
|
|
74
|
+
},
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
const files = [join(BUNDLE, "cli.js"), ...readdirSync(join(BUNDLE, "chunks")).map((name) => join(BUNDLE, "chunks", name))];
|
|
78
|
+
const CHECK = process.argv.includes("--check");
|
|
79
|
+
const unapplied = [];
|
|
80
|
+
const gone = [];
|
|
81
|
+
|
|
82
|
+
for (const patch of PATCHES) {
|
|
83
|
+
let done = false;
|
|
84
|
+
for (const path of files) {
|
|
85
|
+
const source = readFileSync(path, "utf8");
|
|
86
|
+
// A multi-line patch is written with newline escapes; match whatever line ending the file uses.
|
|
87
|
+
const eol = source.includes("\r\n") ? "\r\n" : "\n";
|
|
88
|
+
const from = patch.from.split("\n").join(eol);
|
|
89
|
+
const to = patch.to.split("\n").join(eol);
|
|
90
|
+
if (source.includes(to)) {
|
|
91
|
+
if (!CHECK) console.log(`already patched: ${patch.reason}`);
|
|
92
|
+
done = true;
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
if (!source.includes(from)) continue;
|
|
96
|
+
if (CHECK) {
|
|
97
|
+
unapplied.push(patch.reason);
|
|
98
|
+
done = true;
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
writeFileSync(path, source.replace(from, to));
|
|
102
|
+
console.log(`patched: ${patch.reason}`);
|
|
103
|
+
done = true;
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
// Neither the original text nor the patched text is there, so pi changed the code this
|
|
107
|
+
// patch names. Report it rather than force anything.
|
|
108
|
+
if (!done) gone.push(patch.reason);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
for (const reason of unapplied) console.log(`NOT APPLIED: ${reason}`);
|
|
112
|
+
for (const reason of gone) console.log(`SEAM GONE: ${reason}`);
|
|
113
|
+
const bad = unapplied.length + gone.length;
|
|
114
|
+
console.log(bad ? `${bad} of ${PATCHES.length} not in place` : `all ${PATCHES.length} patches applied`);
|
|
115
|
+
process.exit(bad ? 1 : 0);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-umbra",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Every small thing pi does not build in, in one install: seven themes and the chrome around them, the input bar, session rename, a shimmer through the working indicator, mid-prompt skill completion, ask, copy-chat, preview and help.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -42,12 +42,12 @@
|
|
|
42
42
|
"pi-umbra-theme": "0.1.1",
|
|
43
43
|
"pi-umbra-ask": "0.1.1",
|
|
44
44
|
"pi-umbra-copy-chat": "0.1.1",
|
|
45
|
-
"pi-umbra-help": "0.1.
|
|
46
|
-
"pi-umbra-inputbar": "0.1.
|
|
47
|
-
"pi-umbra-preview": "0.1.
|
|
48
|
-
"pi-umbra-rename": "0.1.
|
|
49
|
-
"pi-umbra-shimmer": "0.1.
|
|
50
|
-
"pi-umbra-skill-matcher": "0.1.
|
|
45
|
+
"pi-umbra-help": "0.1.2",
|
|
46
|
+
"pi-umbra-inputbar": "0.1.2",
|
|
47
|
+
"pi-umbra-preview": "0.1.2",
|
|
48
|
+
"pi-umbra-rename": "0.1.2",
|
|
49
|
+
"pi-umbra-shimmer": "0.1.3",
|
|
50
|
+
"pi-umbra-skill-matcher": "0.1.2"
|
|
51
51
|
},
|
|
52
52
|
"bundledDependencies": [
|
|
53
53
|
"pi-umbra-theme",
|