@bismawy/pi-arnative 0.2.2 → 0.2.4
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 +81 -0
- package/README.md +18 -2
- package/extensions/footer.ts +48 -67
- package/extensions/section-headers.ts +196 -129
- package/extensions/timestamps.ts +75 -31
- package/extensions/tools.ts +157 -147
- package/extensions/ui-render-tweaks.ts +101 -119
- package/extensions/usage.ts +17 -25
- package/lib/ansi.ts +22 -9
- package/lib/box.ts +8 -13
- package/lib/check.ts +12 -0
- package/lib/format.ts +33 -0
- package/lib/usage-store.ts +3 -1
- package/package.json +2 -1
- package/themes/gen-themes.mjs +38 -38
package/lib/format.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Display-name formatting shared by the footer and the header tabs.
|
|
2
|
+
// Lives outside `extensions/*.ts` so pi never loads it as an extension.
|
|
3
|
+
import { keyText } from "@earendil-works/pi-coding-agent";
|
|
4
|
+
|
|
5
|
+
export function capitalize(s: string): string {
|
|
6
|
+
return s ? s.charAt(0).toUpperCase() + s.slice(1) : "";
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// Model label pieces: titlecased id (or the given name) + capitalized provider.
|
|
10
|
+
export function modelDisplayParts(model?: { id?: string; name?: string; provider?: string }): { name: string; provider: string } {
|
|
11
|
+
if (!model) return { name: "No Model", provider: "" };
|
|
12
|
+
let name = model.name || model.id || "Model";
|
|
13
|
+
const provider = capitalize(model.provider || "");
|
|
14
|
+
if (name === model.id) {
|
|
15
|
+
name = name
|
|
16
|
+
.split(/[-_]/)
|
|
17
|
+
.map((w) => capitalize(w))
|
|
18
|
+
.join(" ");
|
|
19
|
+
}
|
|
20
|
+
if (provider && name.toLowerCase().endsWith(`(${provider.toLowerCase()})`)) {
|
|
21
|
+
name = name.slice(0, name.lastIndexOf("(")).trim();
|
|
22
|
+
}
|
|
23
|
+
return { name, provider };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Expand-hint key follows the active keybinding; outside a pi session keyText() is empty.
|
|
27
|
+
export function expandKeyName(): string {
|
|
28
|
+
try {
|
|
29
|
+
return keyText("app.tools.expand") || "ctrl+o";
|
|
30
|
+
} catch {
|
|
31
|
+
return "ctrl+o";
|
|
32
|
+
}
|
|
33
|
+
}
|
package/lib/usage-store.ts
CHANGED
|
@@ -139,7 +139,9 @@ function parseSessionFile(filePath: string): UsageEntry[] {
|
|
|
139
139
|
const content = readFileSync(filePath, "utf8");
|
|
140
140
|
const lines = content.split("\n");
|
|
141
141
|
for (const line of lines) {
|
|
142
|
-
|
|
142
|
+
// Loose prefilter (fast path only): exact key would break on JSON formatting changes
|
|
143
|
+
// and silently drop usage. The role is re-checked on the parsed object below.
|
|
144
|
+
if (!line || !line.includes('"role"')) continue;
|
|
143
145
|
try {
|
|
144
146
|
const obj = JSON.parse(line);
|
|
145
147
|
const m = obj?.message;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bismawy/pi-arnative",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"description": "Arnative look for Pi: 11 warm-neutral themes, rounded tool boxes, branded header with tabs, 2-line footer with git/model/status, transcript clock, and a /usage dashboard.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
"extensions",
|
|
22
22
|
"lib",
|
|
23
23
|
"themes",
|
|
24
|
+
"CHANGELOG.md",
|
|
24
25
|
"README.md"
|
|
25
26
|
],
|
|
26
27
|
"pi": {
|
package/themes/gen-themes.mjs
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Generates the arnative theme variants.
|
|
4
4
|
*
|
|
5
5
|
* Structure source of truth = `themes/arnative.json` (also the default theme,
|
|
6
|
-
* never rewritten here). Palette source of truth = the
|
|
6
|
+
* never rewritten here). Palette source of truth = the PALETTES table below.
|
|
7
7
|
* Variants are text substitutions on the base, so its original formatting
|
|
8
8
|
* (tabs + grouping blank lines) is preserved.
|
|
9
9
|
*
|
|
@@ -20,20 +20,20 @@ const baseText = readFileSync(baseUrl, "utf8");
|
|
|
20
20
|
const base = JSON.parse(baseText);
|
|
21
21
|
|
|
22
22
|
// Every theme must set ALL of these, so no color silently inherits the base.
|
|
23
|
-
const
|
|
23
|
+
const REQUIRED_VARS = [
|
|
24
24
|
"accent", "cyan", "blue", "green", "red", "yellow", "text", "gray", "dimGray", "darkGray",
|
|
25
25
|
"softCyan", "searchBg", "selectedBg", "userMsgBg", "toolPendingBg", "toolSuccessBg", "toolErrorBg", "customMsgBg",
|
|
26
26
|
];
|
|
27
|
-
const
|
|
27
|
+
const REQUIRED_COLORS = [
|
|
28
28
|
"customMessageLabel", "mdHeading", "mdLink",
|
|
29
29
|
"syntaxComment", "syntaxKeyword", "syntaxFunction", "syntaxVariable", "syntaxString",
|
|
30
30
|
"syntaxNumber", "syntaxType", "syntaxOperator", "syntaxPunctuation",
|
|
31
31
|
"thinkingMinimal", "thinkingLow", "thinkingMedium", "thinkingHigh", "thinkingXhigh",
|
|
32
32
|
];
|
|
33
|
-
const
|
|
33
|
+
const REQUIRED_EXPORT = ["pageBg", "cardBg", "infoBg"];
|
|
34
34
|
|
|
35
35
|
/** Per-theme palette. `vars.cyan`/`vars.blue` = borderAccent/border, `vars.softCyan` = tint, `vars.green/red/yellow` = success/error/warning + diff. */
|
|
36
|
-
const
|
|
36
|
+
const PALETTES = {
|
|
37
37
|
// --- base hue variants -------------------------------------------------
|
|
38
38
|
"arnative-sun": {
|
|
39
39
|
vars: {
|
|
@@ -216,57 +216,57 @@ const PALET = {
|
|
|
216
216
|
};
|
|
217
217
|
|
|
218
218
|
/** Text substitution on the base; each target must appear exactly once. */
|
|
219
|
-
function render(
|
|
219
|
+
function render(name, palette) {
|
|
220
220
|
let out = baseText;
|
|
221
|
-
const
|
|
221
|
+
const subst = (oldS, newS, what) => {
|
|
222
222
|
const n = out.split(oldS).length - 1;
|
|
223
|
-
if (n !== 1) throw new Error(`${
|
|
223
|
+
if (n !== 1) throw new Error(`${name}: ${what} "${oldS}" appears ${n}x (must be 1)`);
|
|
224
224
|
out = out.replace(oldS, newS);
|
|
225
225
|
};
|
|
226
226
|
|
|
227
|
-
|
|
228
|
-
for (const [
|
|
229
|
-
["vars",
|
|
230
|
-
["colors",
|
|
231
|
-
["export",
|
|
227
|
+
subst(`"name": ${JSON.stringify(base.name)}`, `"name": ${JSON.stringify(name)}`, "name");
|
|
228
|
+
for (const [section, keys, required] of [
|
|
229
|
+
["vars", palette.vars, REQUIRED_VARS],
|
|
230
|
+
["colors", palette.colors, REQUIRED_COLORS],
|
|
231
|
+
["export", palette.export, REQUIRED_EXPORT],
|
|
232
232
|
]) {
|
|
233
|
-
const
|
|
234
|
-
const
|
|
235
|
-
if (
|
|
236
|
-
throw new Error(`${
|
|
233
|
+
const missing = required.filter((k) => !(k in keys));
|
|
234
|
+
const extra = Object.keys(keys).filter((k) => !required.includes(k));
|
|
235
|
+
if (missing.length || extra.length) {
|
|
236
|
+
throw new Error(`${name}/${section}: missing [${missing}] extra [${extra}]`);
|
|
237
237
|
}
|
|
238
|
-
for (const k of
|
|
239
|
-
const
|
|
240
|
-
if (
|
|
241
|
-
|
|
238
|
+
for (const k of required) {
|
|
239
|
+
const old = base[section][k];
|
|
240
|
+
if (old === undefined) throw new Error(`${name}: base.${section}.${k} missing`);
|
|
241
|
+
subst(`"${k}": ${JSON.stringify(old)}`, `"${k}": ${JSON.stringify(keys[k])}`, `${section}.${k}`);
|
|
242
242
|
}
|
|
243
243
|
}
|
|
244
244
|
return out;
|
|
245
245
|
}
|
|
246
246
|
|
|
247
|
-
const
|
|
248
|
-
let
|
|
249
|
-
for (const
|
|
250
|
-
const
|
|
251
|
-
const
|
|
252
|
-
if (
|
|
253
|
-
let
|
|
247
|
+
const check = process.argv.includes("--check");
|
|
248
|
+
let failed = 0;
|
|
249
|
+
for (const name of Object.keys(PALETTES)) {
|
|
250
|
+
const content = render(name, PALETTES[name]);
|
|
251
|
+
const file = new URL(`${name}.json`, import.meta.url);
|
|
252
|
+
if (check) {
|
|
253
|
+
let old = null;
|
|
254
254
|
try {
|
|
255
|
-
|
|
255
|
+
old = readFileSync(file, "utf8");
|
|
256
256
|
} catch {}
|
|
257
|
-
if (
|
|
258
|
-
console.error(`
|
|
259
|
-
|
|
257
|
+
if (old !== content) {
|
|
258
|
+
console.error(`DIFFERS: themes/${name}.json`);
|
|
259
|
+
failed++;
|
|
260
260
|
}
|
|
261
261
|
} else {
|
|
262
|
-
writeFileSync(
|
|
263
|
-
console.log(`
|
|
262
|
+
writeFileSync(file, content);
|
|
263
|
+
console.log(`wrote themes/${name}.json`);
|
|
264
264
|
}
|
|
265
265
|
}
|
|
266
|
-
if (
|
|
267
|
-
if (
|
|
268
|
-
console.error(`${
|
|
266
|
+
if (check) {
|
|
267
|
+
if (failed) {
|
|
268
|
+
console.error(`${failed} themes out of sync with the generator (run: node themes/gen-themes.mjs)`);
|
|
269
269
|
process.exit(1);
|
|
270
270
|
}
|
|
271
|
-
console.log(`OK: ${Object.keys(
|
|
271
|
+
console.log(`OK: ${Object.keys(PALETTES).length} themes in sync with the generator`);
|
|
272
272
|
}
|