@bismawy/pi-arnative 0.2.3 → 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/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
+ }
@@ -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
- if (!line || !line.includes('"role":"assistant"')) continue;
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",
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": {
@@ -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 PALET table below.
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 KUNCI_VARS = [
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 KUNCI_COLORS = [
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 KUNCI_EXPORT = ["pageBg", "cardBg", "infoBg"];
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 PALET = {
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(nama, palet) {
219
+ function render(name, palette) {
220
220
  let out = baseText;
221
- const tukar = (oldS, newS, apa) => {
221
+ const subst = (oldS, newS, what) => {
222
222
  const n = out.split(oldS).length - 1;
223
- if (n !== 1) throw new Error(`${nama}: ${apa} "${oldS}" muncul ${n}x (harus 1)`);
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
- tukar(`"name": ${JSON.stringify(base.name)}`, `"name": ${JSON.stringify(nama)}`, "name");
228
- for (const [bagian, kunci, wajib] of [
229
- ["vars", palet.vars, KUNCI_VARS],
230
- ["colors", palet.colors, KUNCI_COLORS],
231
- ["export", palet.export, KUNCI_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 kurang = wajib.filter((k) => !(k in kunci));
234
- const lebih = Object.keys(kunci).filter((k) => !wajib.includes(k));
235
- if (kurang.length || lebih.length) {
236
- throw new Error(`${nama}/${bagian}: kurang [${kurang}] lebih [${lebih}]`);
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 wajib) {
239
- const lama = base[bagian][k];
240
- if (lama === undefined) throw new Error(`${nama}: base.${bagian}.${k} tidak ada`);
241
- tukar(`"${k}": ${JSON.stringify(lama)}`, `"${k}": ${JSON.stringify(kunci[k])}`, `${bagian}.${k}`);
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 cek = process.argv.includes("--check");
248
- let gagal = 0;
249
- for (const nama of Object.keys(PALET)) {
250
- const isi = render(nama, PALET[nama]);
251
- const berkas = new URL(`${nama}.json`, import.meta.url);
252
- if (cek) {
253
- let lama = null;
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
- lama = readFileSync(berkas, "utf8");
255
+ old = readFileSync(file, "utf8");
256
256
  } catch {}
257
- if (lama !== isi) {
258
- console.error(`BEDA: themes/${nama}.json`);
259
- gagal++;
257
+ if (old !== content) {
258
+ console.error(`DIFFERS: themes/${name}.json`);
259
+ failed++;
260
260
  }
261
261
  } else {
262
- writeFileSync(berkas, isi);
263
- console.log(`tulis themes/${nama}.json`);
262
+ writeFileSync(file, content);
263
+ console.log(`wrote themes/${name}.json`);
264
264
  }
265
265
  }
266
- if (cek) {
267
- if (gagal) {
268
- console.error(`${gagal} tema tidak sinkron dengan generator (jalankan: node themes/gen-themes.mjs)`);
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(PALET).length} tema sinkron dengan generator`);
271
+ console.log(`OK: ${Object.keys(PALETTES).length} themes in sync with the generator`);
272
272
  }