@wierdbytes/pi-facelift 0.6.6 → 0.7.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 +4 -0
- package/README.md +11 -1
- package/config.test.ts +50 -17
- package/config.ts +17 -1
- package/index.ts +41 -0
- package/package.json +1 -1
- package/working-indicator.test.ts +135 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.7.0
|
|
4
|
+
|
|
5
|
+
- **Change:** still accent `●` instead of pi's animated spinner in the `Working...` line — no ~12×/s terminal repaints while the model streams; `animateWorkingIndicator` in `/facelift` brings the animation back.
|
|
6
|
+
|
|
3
7
|
## 0.6.6
|
|
4
8
|
|
|
5
9
|
- **Fix:** prompt-cache warm-ups no longer inflate `worked` time / deflate `tps` or start a phantom working timer while idle.
|
package/README.md
CHANGED
|
@@ -62,6 +62,11 @@ tool output.
|
|
|
62
62
|
(`0h12m0s` → `12m0s`); sub-minute values keep one decimal (`45.2s`).
|
|
63
63
|
|
|
64
64
|
Toggle via `showWorkingTime` in `/facelift`.
|
|
65
|
+
- **still working indicator** — pi's braille spinner in front of the
|
|
66
|
+
`Working...` line is replaced with a still accent `●`, so the terminal
|
|
67
|
+
isn't repainted ~12×/s while the model streams (GPU-rendered hosts such
|
|
68
|
+
as Herdr redraw the whole window for every frame). Turn the animation
|
|
69
|
+
back on via `animateWorkingIndicator` in `/facelift`.
|
|
65
70
|
|
|
66
71
|
## Install
|
|
67
72
|
|
|
@@ -128,7 +133,11 @@ Current schema:
|
|
|
128
133
|
// each run's model time (`worked`, tools excluded) and wall-clock time
|
|
129
134
|
// (`total`, retries + tools + overhead) as a muted line in chat
|
|
130
135
|
// history. Default true.
|
|
131
|
-
"showWorkingTime": true
|
|
136
|
+
"showWorkingTime": true,
|
|
137
|
+
|
|
138
|
+
// Animate the spinner in front of the "Working..." line (pi's default,
|
|
139
|
+
// redrawn every 80ms). Default false: a still accent "●".
|
|
140
|
+
"animateWorkingIndicator": false
|
|
132
141
|
}
|
|
133
142
|
```
|
|
134
143
|
|
|
@@ -147,6 +156,7 @@ icons, image protocols) or boot-time toggles:
|
|
|
147
156
|
| `FACELIFT_IMAGE_PROTOCOL` | auto | Force `kitty` / `iterm2` / `none`. Auto-detected from `$TERM_PROGRAM` etc. |
|
|
148
157
|
| `DIFF_LAYOUT` | (none) | One-shot override for `diffLayout` on first-run seeding. |
|
|
149
158
|
| `FACELIFT_SHOW_WORKING_TIME` | `true` | One-shot override for `showWorkingTime` on first-run seeding (`0`/`off` ⇒ off). |
|
|
159
|
+
| `FACELIFT_ANIMATE_WORKING` | `false` | Default for `animateWorkingIndicator` when the config doesn't set it (`1`/`on` ⇒ animated). |
|
|
150
160
|
|
|
151
161
|
## Development
|
|
152
162
|
|
package/config.test.ts
CHANGED
|
@@ -40,6 +40,8 @@ beforeEach(() => {
|
|
|
40
40
|
delete process.env.DIFF_LAYOUT;
|
|
41
41
|
savedEnv.FACELIFT_SHOW_WORKING_TIME = process.env.FACELIFT_SHOW_WORKING_TIME;
|
|
42
42
|
delete process.env.FACELIFT_SHOW_WORKING_TIME;
|
|
43
|
+
savedEnv.FACELIFT_ANIMATE_WORKING = process.env.FACELIFT_ANIMATE_WORKING;
|
|
44
|
+
delete process.env.FACELIFT_ANIMATE_WORKING;
|
|
43
45
|
});
|
|
44
46
|
|
|
45
47
|
afterEach(() => {
|
|
@@ -48,29 +50,32 @@ afterEach(() => {
|
|
|
48
50
|
if (savedEnv.FACELIFT_SHOW_WORKING_TIME !== undefined)
|
|
49
51
|
process.env.FACELIFT_SHOW_WORKING_TIME = savedEnv.FACELIFT_SHOW_WORKING_TIME;
|
|
50
52
|
else delete process.env.FACELIFT_SHOW_WORKING_TIME;
|
|
53
|
+
if (savedEnv.FACELIFT_ANIMATE_WORKING !== undefined)
|
|
54
|
+
process.env.FACELIFT_ANIMATE_WORKING = savedEnv.FACELIFT_ANIMATE_WORKING;
|
|
55
|
+
else delete process.env.FACELIFT_ANIMATE_WORKING;
|
|
51
56
|
rmSync(dir, { recursive: true, force: true });
|
|
52
57
|
});
|
|
53
58
|
|
|
54
59
|
describe("envDefaults", () => {
|
|
55
60
|
it("returns 'consistent' when DIFF_LAYOUT is unset", () => {
|
|
56
|
-
expect(envDefaults()).toEqual({ diffLayout: "consistent", showWorkingTime: true });
|
|
61
|
+
expect(envDefaults()).toEqual({ diffLayout: "consistent", showWorkingTime: true, animateWorkingIndicator: false });
|
|
57
62
|
});
|
|
58
63
|
|
|
59
64
|
for (const layout of VALID_DIFF_LAYOUTS) {
|
|
60
65
|
it(`honours DIFF_LAYOUT=${layout}`, () => {
|
|
61
66
|
process.env.DIFF_LAYOUT = layout;
|
|
62
|
-
expect(envDefaults()).toEqual({ diffLayout: layout, showWorkingTime: true });
|
|
67
|
+
expect(envDefaults()).toEqual({ diffLayout: layout, showWorkingTime: true, animateWorkingIndicator: false });
|
|
63
68
|
});
|
|
64
69
|
}
|
|
65
70
|
|
|
66
71
|
it("ignores invalid DIFF_LAYOUT values", () => {
|
|
67
72
|
process.env.DIFF_LAYOUT = "rainbow";
|
|
68
|
-
expect(envDefaults()).toEqual({ diffLayout: "consistent", showWorkingTime: true });
|
|
73
|
+
expect(envDefaults()).toEqual({ diffLayout: "consistent", showWorkingTime: true, animateWorkingIndicator: false });
|
|
69
74
|
});
|
|
70
75
|
|
|
71
76
|
it("is case-insensitive on DIFF_LAYOUT", () => {
|
|
72
77
|
process.env.DIFF_LAYOUT = "SPLIT";
|
|
73
|
-
expect(envDefaults()).toEqual({ diffLayout: "split", showWorkingTime: true });
|
|
78
|
+
expect(envDefaults()).toEqual({ diffLayout: "split", showWorkingTime: true, animateWorkingIndicator: false });
|
|
74
79
|
});
|
|
75
80
|
|
|
76
81
|
it("defaults showWorkingTime to true", () => {
|
|
@@ -90,15 +95,43 @@ describe("envDefaults", () => {
|
|
|
90
95
|
});
|
|
91
96
|
});
|
|
92
97
|
|
|
98
|
+
describe("animateWorkingIndicator", () => {
|
|
99
|
+
it("defaults to false (a still indicator)", () => {
|
|
100
|
+
expect(envDefaults().animateWorkingIndicator).toBe(false);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("honours FACELIFT_ANIMATE_WORKING=1/on/true to animate", () => {
|
|
104
|
+
for (const v of ["1", "on", "true"]) {
|
|
105
|
+
process.env.FACELIFT_ANIMATE_WORKING = v;
|
|
106
|
+
expect(envDefaults().animateWorkingIndicator).toBe(true);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("persists animateWorkingIndicator=true through save/load", () => {
|
|
111
|
+
saveConfig({ diffLayout: "split", showWorkingTime: true, animateWorkingIndicator: true }, configFile);
|
|
112
|
+
expect(loadConfig(configFile)).toEqual({ diffLayout: "split", showWorkingTime: true, animateWorkingIndicator: true });
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("sanitises a non-boolean value back to the default", () => {
|
|
116
|
+
writeFileSync(configFile, JSON.stringify({ diffLayout: "split", animateWorkingIndicator: "yes" }));
|
|
117
|
+
expect(loadConfig(configFile).animateWorkingIndicator).toBe(false);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("keeps older config files (no field) on the default", () => {
|
|
121
|
+
writeFileSync(configFile, JSON.stringify({ diffLayout: "split", showWorkingTime: false }));
|
|
122
|
+
expect(loadConfig(configFile)).toEqual({ diffLayout: "split", showWorkingTime: false, animateWorkingIndicator: false });
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
93
126
|
describe("showWorkingTime round-trip", () => {
|
|
94
127
|
it("persists showWorkingTime=false through save/load", () => {
|
|
95
|
-
saveConfig({ diffLayout: "consistent", showWorkingTime: false }, configFile);
|
|
96
|
-
expect(loadConfig(configFile)).toEqual({ diffLayout: "consistent", showWorkingTime: false });
|
|
128
|
+
saveConfig({ diffLayout: "consistent", showWorkingTime: false, animateWorkingIndicator: false }, configFile);
|
|
129
|
+
expect(loadConfig(configFile)).toEqual({ diffLayout: "consistent", showWorkingTime: false, animateWorkingIndicator: false });
|
|
97
130
|
});
|
|
98
131
|
|
|
99
132
|
it("sanitises a non-boolean showWorkingTime back to the default", () => {
|
|
100
133
|
writeFileSync(configFile, JSON.stringify({ diffLayout: "split", showWorkingTime: "nope" }));
|
|
101
|
-
expect(loadConfig(configFile)).toEqual({ diffLayout: "split", showWorkingTime: true });
|
|
134
|
+
expect(loadConfig(configFile)).toEqual({ diffLayout: "split", showWorkingTime: true, animateWorkingIndicator: false });
|
|
102
135
|
});
|
|
103
136
|
});
|
|
104
137
|
|
|
@@ -107,7 +140,7 @@ describe("loadConfig + saveConfig round-trip", () => {
|
|
|
107
140
|
it(`persists diffLayout=${layout}`, () => {
|
|
108
141
|
saveConfig({ diffLayout: layout } as WierdFaceliftConfig, configFile);
|
|
109
142
|
expect(existsSync(configFile)).toBe(true);
|
|
110
|
-
expect(loadConfig(configFile)).toEqual({ diffLayout: layout, showWorkingTime: true });
|
|
143
|
+
expect(loadConfig(configFile)).toEqual({ diffLayout: layout, showWorkingTime: true, animateWorkingIndicator: false });
|
|
111
144
|
});
|
|
112
145
|
}
|
|
113
146
|
|
|
@@ -116,23 +149,23 @@ describe("loadConfig + saveConfig round-trip", () => {
|
|
|
116
149
|
configFile,
|
|
117
150
|
JSON.stringify({ diffLayout: "consistent", iconMode: "rainbow", junk: 42 }),
|
|
118
151
|
);
|
|
119
|
-
expect(loadConfig(configFile)).toEqual({ diffLayout: "consistent", showWorkingTime: true });
|
|
152
|
+
expect(loadConfig(configFile)).toEqual({ diffLayout: "consistent", showWorkingTime: true, animateWorkingIndicator: false });
|
|
120
153
|
});
|
|
121
154
|
|
|
122
155
|
it("rejects invalid diffLayout strings and falls back to default", () => {
|
|
123
156
|
writeFileSync(configFile, JSON.stringify({ diffLayout: "tutti-frutti" }));
|
|
124
|
-
expect(loadConfig(configFile)).toEqual({ diffLayout: "consistent", showWorkingTime: true });
|
|
157
|
+
expect(loadConfig(configFile)).toEqual({ diffLayout: "consistent", showWorkingTime: true, animateWorkingIndicator: false });
|
|
125
158
|
});
|
|
126
159
|
|
|
127
160
|
it("returns defaults when the file is missing (no write)", () => {
|
|
128
161
|
expect(existsSync(configFile)).toBe(false);
|
|
129
|
-
expect(loadConfig(configFile)).toEqual({ diffLayout: "consistent", showWorkingTime: true });
|
|
162
|
+
expect(loadConfig(configFile)).toEqual({ diffLayout: "consistent", showWorkingTime: true, animateWorkingIndicator: false });
|
|
130
163
|
expect(existsSync(configFile)).toBe(false);
|
|
131
164
|
});
|
|
132
165
|
|
|
133
166
|
it("returns defaults when the file is malformed JSON", () => {
|
|
134
167
|
writeFileSync(configFile, "{ not valid json");
|
|
135
|
-
expect(loadConfig(configFile)).toEqual({ diffLayout: "consistent", showWorkingTime: true });
|
|
168
|
+
expect(loadConfig(configFile)).toEqual({ diffLayout: "consistent", showWorkingTime: true, animateWorkingIndicator: false });
|
|
136
169
|
});
|
|
137
170
|
|
|
138
171
|
it("writes a trailing newline", () => {
|
|
@@ -145,22 +178,22 @@ describe("loadOrInitConfig", () => {
|
|
|
145
178
|
it("seeds a missing file from env defaults", () => {
|
|
146
179
|
expect(existsSync(configFile)).toBe(false);
|
|
147
180
|
const cfg = loadOrInitConfig(configFile);
|
|
148
|
-
expect(cfg).toEqual({ diffLayout: "consistent", showWorkingTime: true });
|
|
181
|
+
expect(cfg).toEqual({ diffLayout: "consistent", showWorkingTime: true, animateWorkingIndicator: false });
|
|
149
182
|
expect(existsSync(configFile)).toBe(true);
|
|
150
|
-
expect(JSON.parse(readFileSync(configFile, "utf-8"))).toEqual({ diffLayout: "consistent", showWorkingTime: true });
|
|
183
|
+
expect(JSON.parse(readFileSync(configFile, "utf-8"))).toEqual({ diffLayout: "consistent", showWorkingTime: true, animateWorkingIndicator: false });
|
|
151
184
|
});
|
|
152
185
|
|
|
153
186
|
it("respects DIFF_LAYOUT when seeding", () => {
|
|
154
187
|
process.env.DIFF_LAYOUT = "unified";
|
|
155
188
|
const cfg = loadOrInitConfig(configFile);
|
|
156
|
-
expect(cfg).toEqual({ diffLayout: "unified", showWorkingTime: true });
|
|
157
|
-
expect(JSON.parse(readFileSync(configFile, "utf-8"))).toEqual({ diffLayout: "unified", showWorkingTime: true });
|
|
189
|
+
expect(cfg).toEqual({ diffLayout: "unified", showWorkingTime: true, animateWorkingIndicator: false });
|
|
190
|
+
expect(JSON.parse(readFileSync(configFile, "utf-8"))).toEqual({ diffLayout: "unified", showWorkingTime: true, animateWorkingIndicator: false });
|
|
158
191
|
});
|
|
159
192
|
|
|
160
193
|
it("does not overwrite an existing file", () => {
|
|
161
194
|
writeFileSync(configFile, JSON.stringify({ diffLayout: "split" }));
|
|
162
195
|
process.env.DIFF_LAYOUT = "unified"; // would override on a fresh seed
|
|
163
196
|
const cfg = loadOrInitConfig(configFile);
|
|
164
|
-
expect(cfg).toEqual({ diffLayout: "split", showWorkingTime: true });
|
|
197
|
+
expect(cfg).toEqual({ diffLayout: "split", showWorkingTime: true, animateWorkingIndicator: false });
|
|
165
198
|
});
|
|
166
199
|
});
|
package/config.ts
CHANGED
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
* extension.
|
|
11
11
|
* - Saves write atomically (one JSON.stringify + trailing newline).
|
|
12
12
|
*
|
|
13
|
-
* The knobs we expose today are `diffLayout
|
|
13
|
+
* The knobs we expose today are `diffLayout`, `showWorkingTime` and
|
|
14
|
+
* `animateWorkingIndicator`, and
|
|
14
15
|
* the schema is designed to accept more fields (icon mode, max preview
|
|
15
16
|
* lines, etc.) without breaking older config files.
|
|
16
17
|
*/
|
|
@@ -58,6 +59,18 @@ export interface WierdFaceliftConfig {
|
|
|
58
59
|
* (`0`/`false`/`off` to disable).
|
|
59
60
|
*/
|
|
60
61
|
showWorkingTime: boolean;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Animate the spinner in front of the streaming "Working…" line (pi's
|
|
65
|
+
* default: a braille spinner redrawn every 80ms). Off ⇒ a still accent
|
|
66
|
+
* `●` instead, so an idle-looking terminal isn't repainted 12×/s while
|
|
67
|
+
* the model streams — GPU-rendered hosts (e.g. Herdr) redraw the whole
|
|
68
|
+
* window for each of those frames.
|
|
69
|
+
*
|
|
70
|
+
* Defaults to `false`; override with env `FACELIFT_ANIMATE_WORKING`
|
|
71
|
+
* (`1`/`true`/`on` to animate).
|
|
72
|
+
*/
|
|
73
|
+
animateWorkingIndicator: boolean;
|
|
61
74
|
}
|
|
62
75
|
|
|
63
76
|
function isDiffLayout(value: unknown): value is DiffLayoutPreference {
|
|
@@ -88,6 +101,7 @@ export function envDefaults(): WierdFaceliftConfig {
|
|
|
88
101
|
return {
|
|
89
102
|
diffLayout: fromEnv ?? DEFAULT_DIFF_LAYOUT,
|
|
90
103
|
showWorkingTime: parseBool(process.env.FACELIFT_SHOW_WORKING_TIME, true),
|
|
104
|
+
animateWorkingIndicator: parseBool(process.env.FACELIFT_ANIMATE_WORKING, false),
|
|
91
105
|
};
|
|
92
106
|
}
|
|
93
107
|
|
|
@@ -98,6 +112,7 @@ function sanitize(raw: unknown): WierdFaceliftConfig {
|
|
|
98
112
|
const cfg: WierdFaceliftConfig = { ...defaults };
|
|
99
113
|
if (isDiffLayout(obj.diffLayout)) cfg.diffLayout = obj.diffLayout;
|
|
100
114
|
if (typeof obj.showWorkingTime === "boolean") cfg.showWorkingTime = obj.showWorkingTime;
|
|
115
|
+
if (typeof obj.animateWorkingIndicator === "boolean") cfg.animateWorkingIndicator = obj.animateWorkingIndicator;
|
|
101
116
|
return cfg;
|
|
102
117
|
}
|
|
103
118
|
|
|
@@ -143,6 +158,7 @@ export function saveConfig(
|
|
|
143
158
|
const out: Record<string, unknown> = {
|
|
144
159
|
diffLayout: cfg.diffLayout,
|
|
145
160
|
showWorkingTime: cfg.showWorkingTime,
|
|
161
|
+
animateWorkingIndicator: cfg.animateWorkingIndicator,
|
|
146
162
|
};
|
|
147
163
|
writeFileSync(path, `${JSON.stringify(out, null, 2)}\n`, "utf-8");
|
|
148
164
|
return path;
|
package/index.ts
CHANGED
|
@@ -1258,6 +1258,31 @@ export default function piFaceliftExtension(pi: PiFaceliftApi, deps?: PiFacelift
|
|
|
1258
1258
|
if (entry) pi.appendEntry(WORKING_TIME_ENTRY, entry);
|
|
1259
1259
|
});
|
|
1260
1260
|
|
|
1261
|
+
// ===================================================================
|
|
1262
|
+
// Working indicator — a still `●` instead of pi's 80ms spinner
|
|
1263
|
+
// -------------------------------------------------------------------
|
|
1264
|
+
// pi's braille spinner repaints the terminal ~12×/s for as long as the
|
|
1265
|
+
// model streams; a GPU-rendered host (Herdr) redraws its whole window
|
|
1266
|
+
// for every one of those frames. A single frame starts no animation
|
|
1267
|
+
// timer at all. pi resets the indicator on every reload / session
|
|
1268
|
+
// switch, so it is applied on each `session_start` (and at once when
|
|
1269
|
+
// the setting changes).
|
|
1270
|
+
// ===================================================================
|
|
1271
|
+
|
|
1272
|
+
const applyWorkingIndicator = (ctx: SessionContextLike): void => {
|
|
1273
|
+
try {
|
|
1274
|
+
if (!ctx.hasUI || typeof ctx.ui.setWorkingIndicator !== "function") return;
|
|
1275
|
+
if (faceliftConfig.animateWorkingIndicator) ctx.ui.setWorkingIndicator();
|
|
1276
|
+
else ctx.ui.setWorkingIndicator({ frames: [ctx.ui.theme.fg("accent", "●")] });
|
|
1277
|
+
} catch {
|
|
1278
|
+
// Stale ctx or an older pi without the API: keep pi's default.
|
|
1279
|
+
}
|
|
1280
|
+
};
|
|
1281
|
+
|
|
1282
|
+
pi.on("session_start", (_event, ctx) => {
|
|
1283
|
+
applyWorkingIndicator(ctx);
|
|
1284
|
+
});
|
|
1285
|
+
|
|
1261
1286
|
// ===================================================================
|
|
1262
1287
|
// read — syntax-highlighted file content
|
|
1263
1288
|
// ===================================================================
|
|
@@ -1866,6 +1891,15 @@ export default function piFaceliftExtension(pi: PiFaceliftApi, deps?: PiFacelift
|
|
|
1866
1891
|
value: faceliftConfig.showWorkingTime,
|
|
1867
1892
|
default: true,
|
|
1868
1893
|
},
|
|
1894
|
+
{
|
|
1895
|
+
key: "animateWorkingIndicator",
|
|
1896
|
+
type: "boolean",
|
|
1897
|
+
label: "Animated spinner",
|
|
1898
|
+
description:
|
|
1899
|
+
"Animate the spinner in front of the \u201cWorking\u2026\u201d line (pi\u2019s default, redrawn every 80ms). Off shows a still \u25cf, so the terminal isn\u2019t repainted 12\u00d7/s while the model streams \u2014 cheaper in GPU-rendered hosts such as Herdr.",
|
|
1900
|
+
value: faceliftConfig.animateWorkingIndicator,
|
|
1901
|
+
default: false,
|
|
1902
|
+
},
|
|
1869
1903
|
];
|
|
1870
1904
|
await openSettingsModal(extCtx, {
|
|
1871
1905
|
title: "@wierdbytes/pi-facelift",
|
|
@@ -1879,6 +1913,11 @@ export default function piFaceliftExtension(pi: PiFaceliftApi, deps?: PiFacelift
|
|
|
1879
1913
|
faceliftConfig = { ...faceliftConfig, showWorkingTime: value as boolean };
|
|
1880
1914
|
persistFaceliftConfig(extCtx);
|
|
1881
1915
|
}
|
|
1916
|
+
if (key === "animateWorkingIndicator") {
|
|
1917
|
+
faceliftConfig = { ...faceliftConfig, animateWorkingIndicator: value as boolean };
|
|
1918
|
+
persistFaceliftConfig(extCtx);
|
|
1919
|
+
applyWorkingIndicator(extCtx);
|
|
1920
|
+
}
|
|
1882
1921
|
},
|
|
1883
1922
|
});
|
|
1884
1923
|
};
|
|
@@ -1888,6 +1927,7 @@ export default function piFaceliftExtension(pi: PiFaceliftApi, deps?: PiFacelift
|
|
|
1888
1927
|
`config: ${getFaceliftConfigPath()}`,
|
|
1889
1928
|
`diffLayout: ${faceliftConfig.diffLayout}`,
|
|
1890
1929
|
`workingTime: ${faceliftConfig.showWorkingTime ? "on" : "off"}`,
|
|
1930
|
+
`spinner: ${faceliftConfig.animateWorkingIndicator ? "animated" : "still"}`,
|
|
1891
1931
|
];
|
|
1892
1932
|
extCtx.ui.notify(`@wierdbytes/pi-facelift\n${lines.join("\n")}`, "info");
|
|
1893
1933
|
};
|
|
@@ -1895,6 +1935,7 @@ export default function piFaceliftExtension(pi: PiFaceliftApi, deps?: PiFacelift
|
|
|
1895
1935
|
const resetFaceliftConfig = (extCtx: ExtensionContext): void => {
|
|
1896
1936
|
faceliftConfig = faceliftEnvDefaults();
|
|
1897
1937
|
persistFaceliftConfig(extCtx);
|
|
1938
|
+
applyWorkingIndicator(extCtx);
|
|
1898
1939
|
extCtx.ui.notify("pi-facelift: config reset to defaults.", "info");
|
|
1899
1940
|
};
|
|
1900
1941
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wierdbytes/pi-facelift",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Cosmetic facelift for built-in pi tool output — open-right rounded frames, syntax-highlighted reads, status-aware bash summaries, find/grep.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the still working indicator: on `session_start` facelift
|
|
3
|
+
* replaces pi's 80ms braille spinner with a single accent `●` frame (a
|
|
4
|
+
* single frame starts no animation timer), unless the config asks for
|
|
5
|
+
* the animated default (`animateWorkingIndicator: true`).
|
|
6
|
+
*
|
|
7
|
+
* The extension reads its config from `${PI_AGENT_DIR}/wierd-facelift/`,
|
|
8
|
+
* so each test points PI_AGENT_DIR at a temp dir with the config it needs.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
12
|
+
|
|
13
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
14
|
+
import { tmpdir } from "node:os";
|
|
15
|
+
import { join } from "node:path";
|
|
16
|
+
|
|
17
|
+
import piFaceliftExtension from "./index.ts";
|
|
18
|
+
|
|
19
|
+
class MockText {
|
|
20
|
+
constructor(
|
|
21
|
+
private text = "",
|
|
22
|
+
_x = 0,
|
|
23
|
+
_y = 0,
|
|
24
|
+
) {}
|
|
25
|
+
setText(value: string) {
|
|
26
|
+
this.text = value;
|
|
27
|
+
}
|
|
28
|
+
getText() {
|
|
29
|
+
return this.text;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let dir: string;
|
|
34
|
+
let savedAgentDir: string | undefined;
|
|
35
|
+
let savedAnimate: string | undefined;
|
|
36
|
+
|
|
37
|
+
beforeEach(() => {
|
|
38
|
+
dir = mkdtempSync(join(tmpdir(), "facelift-indicator-"));
|
|
39
|
+
savedAgentDir = process.env.PI_AGENT_DIR;
|
|
40
|
+
savedAnimate = process.env.FACELIFT_ANIMATE_WORKING;
|
|
41
|
+
process.env.PI_AGENT_DIR = dir;
|
|
42
|
+
delete process.env.FACELIFT_ANIMATE_WORKING;
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
afterEach(() => {
|
|
46
|
+
if (savedAgentDir !== undefined) process.env.PI_AGENT_DIR = savedAgentDir;
|
|
47
|
+
else delete process.env.PI_AGENT_DIR;
|
|
48
|
+
if (savedAnimate !== undefined) process.env.FACELIFT_ANIMATE_WORKING = savedAnimate;
|
|
49
|
+
else delete process.env.FACELIFT_ANIMATE_WORKING;
|
|
50
|
+
rmSync(dir, { recursive: true, force: true });
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
function writeConfig(config: Record<string, unknown>) {
|
|
54
|
+
mkdirSync(join(dir, "wierd-facelift"), { recursive: true });
|
|
55
|
+
writeFileSync(join(dir, "wierd-facelift", "config.json"), JSON.stringify(config));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function loadExtension(ui: Record<string, unknown> = {}, hasUI = true) {
|
|
59
|
+
const handlers = new Map<string, (event: unknown, ctx: unknown) => void>();
|
|
60
|
+
const pi = {
|
|
61
|
+
registerTool: () => {},
|
|
62
|
+
registerCommand: () => {},
|
|
63
|
+
on: (name: string, h: (event: unknown, ctx: unknown) => void) => handlers.set(name, h),
|
|
64
|
+
registerEntryRenderer: () => {},
|
|
65
|
+
appendEntry: () => {},
|
|
66
|
+
};
|
|
67
|
+
piFaceliftExtension(pi as any, {
|
|
68
|
+
sdk: {
|
|
69
|
+
createReadToolDefinition: (_cwd: string) => ({
|
|
70
|
+
name: "mock",
|
|
71
|
+
description: "mock",
|
|
72
|
+
parameters: { type: "object", properties: {} },
|
|
73
|
+
execute: async () => ({ content: [{ type: "text", text: "" }] }),
|
|
74
|
+
}),
|
|
75
|
+
getAgentDir: () => dir,
|
|
76
|
+
},
|
|
77
|
+
TextComponent: MockText as any,
|
|
78
|
+
});
|
|
79
|
+
const setWorkingIndicator = vi.fn();
|
|
80
|
+
const ctx = {
|
|
81
|
+
hasUI,
|
|
82
|
+
ui: {
|
|
83
|
+
setWorkingMessage: vi.fn(),
|
|
84
|
+
setWorkingIndicator,
|
|
85
|
+
theme: { fg: (token: string, text: string) => `<${token}>${text}</${token}>` },
|
|
86
|
+
...ui,
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
const fire = (name: string) => handlers.get(name)?.({}, ctx);
|
|
90
|
+
return { fire, setWorkingIndicator, ctx };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
describe("working indicator", () => {
|
|
94
|
+
it("sets a single still accent frame on session_start by default", () => {
|
|
95
|
+
const { fire, setWorkingIndicator } = loadExtension();
|
|
96
|
+
fire("session_start");
|
|
97
|
+
expect(setWorkingIndicator).toHaveBeenCalledTimes(1);
|
|
98
|
+
expect(setWorkingIndicator).toHaveBeenCalledWith({ frames: ["<accent>●</accent>"] });
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("keeps an older config file (no field) on the still indicator", () => {
|
|
102
|
+
writeConfig({ diffLayout: "split" });
|
|
103
|
+
const { fire, setWorkingIndicator } = loadExtension();
|
|
104
|
+
fire("session_start");
|
|
105
|
+
expect(setWorkingIndicator).toHaveBeenCalledWith({ frames: ["<accent>●</accent>"] });
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("restores pi's animated default when animateWorkingIndicator is on", () => {
|
|
109
|
+
writeConfig({ diffLayout: "split", animateWorkingIndicator: true });
|
|
110
|
+
const { fire, setWorkingIndicator } = loadExtension();
|
|
111
|
+
fire("session_start");
|
|
112
|
+
expect(setWorkingIndicator).toHaveBeenCalledTimes(1);
|
|
113
|
+
expect(setWorkingIndicator).toHaveBeenCalledWith();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("does nothing without a UI (pi -p, RPC)", () => {
|
|
117
|
+
const { fire, setWorkingIndicator } = loadExtension({}, false);
|
|
118
|
+
fire("session_start");
|
|
119
|
+
expect(setWorkingIndicator).not.toHaveBeenCalled();
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("tolerates a pi without setWorkingIndicator", () => {
|
|
123
|
+
const { fire } = loadExtension({ setWorkingIndicator: undefined });
|
|
124
|
+
expect(() => fire("session_start")).not.toThrow();
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("swallows a stale ctx instead of crashing pi", () => {
|
|
128
|
+
const { fire } = loadExtension({
|
|
129
|
+
setWorkingIndicator: () => {
|
|
130
|
+
throw new Error("extension ctx is stale");
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
expect(() => fire("session_start")).not.toThrow();
|
|
134
|
+
});
|
|
135
|
+
});
|