@xynogen/pix-data 0.2.5 → 0.3.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 +84 -1
- package/package.json +7 -1
- package/src/collapse.ts +40 -0
- package/src/pix-config.ts +305 -0
package/README.md
CHANGED
|
@@ -74,7 +74,7 @@ heuristic = 0.30·coding_score + 0.60·agentic_score + 0.10·reasoning_score
|
|
|
74
74
|
score = round(clamp₀₁₀₀(120.6·heuristic − 10.6)) // fitted to the index
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
-
|
|
77
|
+
1. **benchlm.ai fallback** — if the model exists in benchlm but modelgrep has
|
|
78
78
|
no AA index and no raw benches, look up the benchlm `overallScore` (0–100)
|
|
79
79
|
and use it verbatim. Match strategy (in `lookupBenchlmScore`): exact
|
|
80
80
|
normalized slug, then prefix overlap either way, then take the
|
|
@@ -119,6 +119,89 @@ place if your priorities differ.
|
|
|
119
119
|
| `lookupModelsDev` | Sync lookup by id from in-memory cache (joined on slug) |
|
|
120
120
|
| `lookupBenchmark` | Sync lookup a model by id — returns score + rank + pricing |
|
|
121
121
|
| `benchScoreColor` | Map a 0–100 score to a `success`/`warning`/`error`/`muted` token |
|
|
122
|
+
| `pixConfig` | `@xynogen/pix-data/pix-config` — load/access the unified `pix.json` config |
|
|
123
|
+
| `reloadPixConfig` | Force a fresh read of `pix.json` from disk |
|
|
124
|
+
| `shouldCollapse` | `@xynogen/pix-data/collapse` — whether a tool's output card should auto-collapse |
|
|
125
|
+
| `collapseDelayMs` | Configured delay (ms) before a card collapses (default 10 000) |
|
|
126
|
+
| `tickCollapse` | Call in `renderResult` to schedule the timed auto-collapse for a card |
|
|
127
|
+
|
|
128
|
+
## Unified config — `~/.pi/agent/pix.json`
|
|
129
|
+
|
|
130
|
+
pix-data hosts the **single shared config file** consumed by every `pix-*` package. The file is auto-created with defaults on the first session that loads pix-data — you never need to create it manually.
|
|
131
|
+
|
|
132
|
+
**Location:** `~/.pi/agent/pix.json`
|
|
133
|
+
|
|
134
|
+
### Full schema
|
|
135
|
+
|
|
136
|
+
```jsonc
|
|
137
|
+
{
|
|
138
|
+
// Auto-collapse for tool output cards (pix-bash, pix-read, pix-grep, …)
|
|
139
|
+
"collapse": {
|
|
140
|
+
"enabled": true, // master switch
|
|
141
|
+
"delayMs": 10000, // ms before collapse fires (default 10s)
|
|
142
|
+
"tools": {
|
|
143
|
+
// per-tool overrides — set false to disable for a specific tool
|
|
144
|
+
"bash": true,
|
|
145
|
+
"read": true,
|
|
146
|
+
"grep": true,
|
|
147
|
+
"edit": true,
|
|
148
|
+
"write": true,
|
|
149
|
+
"find": true,
|
|
150
|
+
"ls": true,
|
|
151
|
+
"todo": true
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
|
|
155
|
+
// Rendering options (pix-pretty)
|
|
156
|
+
"pretty": {
|
|
157
|
+
"theme": "monokai", // syntax-highlight theme (overrides PRETTY_THEME)
|
|
158
|
+
"icons": "nerd", // icon mode: nerd | unicode | ascii (overrides PRETTY_ICONS)
|
|
159
|
+
"maxPreviewLines": 50, // overrides PRETTY_MAX_PREVIEW_LINES
|
|
160
|
+
"diffColors": true // colored diff output
|
|
161
|
+
},
|
|
162
|
+
|
|
163
|
+
// Optimizer initial state (pix-optimizer)
|
|
164
|
+
"optimizer": {
|
|
165
|
+
"caveman": "off", // off | lite | full | ultra | micro
|
|
166
|
+
"rtk": false,
|
|
167
|
+
"toon": false,
|
|
168
|
+
"ponytail": "off" // off | lite | full | ultra
|
|
169
|
+
},
|
|
170
|
+
|
|
171
|
+
// Gate rules (pix-gate)
|
|
172
|
+
"gate": {
|
|
173
|
+
"disableDefaults": false,
|
|
174
|
+
"extraRules": [], // same shape as pix-gate.json extraRules
|
|
175
|
+
"autoApprove": [] // regex strings that skip the dialog
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
All sections are optional — missing keys fall back to the defaults shown above. Environment variables (e.g. `PRETTY_THEME`) still take precedence over `pix.json` values.
|
|
181
|
+
|
|
182
|
+
### API — `@xynogen/pix-data/pix-config`
|
|
183
|
+
|
|
184
|
+
```ts
|
|
185
|
+
import { pixConfig, reloadPixConfig } from "@xynogen/pix-data/pix-config";
|
|
186
|
+
|
|
187
|
+
const cfg = pixConfig(); // returns cached PixConfig (loaded once per session)
|
|
188
|
+
await reloadPixConfig(); // force re-read from disk (e.g. after /config reload)
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### API — `@xynogen/pix-data/collapse`
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
import { shouldCollapse, collapseDelayMs, tickCollapse } from "@xynogen/pix-data/collapse";
|
|
195
|
+
|
|
196
|
+
// In a tool's renderResult:
|
|
197
|
+
if (shouldCollapse("bash")) {
|
|
198
|
+
tickCollapse(card, collapseDelayMs()); // schedules timed collapse
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
- `shouldCollapse(tool)` — returns `true` when `collapse.enabled` is true and the named tool is not opted out.
|
|
203
|
+
- `collapseDelayMs()` — returns `collapse.delayMs` from config (default `10000`).
|
|
204
|
+
- `tickCollapse(card, delayMs)` — sets a timeout that calls `card.collapse()` after the delay. Safe to call multiple times — only the first registered timeout fires.
|
|
122
205
|
|
|
123
206
|
## Install
|
|
124
207
|
|
package/package.json
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xynogen/pix-data",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Pi extension — shared model data layer (models.dev + BenchLM), cached at ~/.cache/pi",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/index.ts",
|
|
9
|
+
"./src/*": "./src/*",
|
|
10
|
+
"./pix-config": "./src/pix-config.ts",
|
|
11
|
+
"./collapse": "./src/collapse.ts"
|
|
12
|
+
},
|
|
7
13
|
"scripts": {
|
|
8
14
|
"test": "bun test"
|
|
9
15
|
},
|
package/src/collapse.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* collapse.ts — auto-collapse helper for tool renderResult cards.
|
|
3
|
+
*
|
|
4
|
+
* Provides a timer-based collapse mechanism: show full output for N seconds,
|
|
5
|
+
* then collapse to a one-line dim summary. Each tool call gets its own card
|
|
6
|
+
* with its own timer, so the latest call stays expanded while older ones fold.
|
|
7
|
+
*
|
|
8
|
+
* Configuration is read from ~/.pi/agent/pix.json via pix-config.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { collapseDelayMs, shouldCollapse } from "./pix-config.js";
|
|
12
|
+
|
|
13
|
+
export interface CollapseState {
|
|
14
|
+
collapsed?: boolean;
|
|
15
|
+
timer?: ReturnType<typeof setTimeout>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Run the collapse timer for a tool card. Call this inside `renderResult`.
|
|
20
|
+
*
|
|
21
|
+
* @param toolName — the tool name (e.g. "bash", "read") for per-tool config
|
|
22
|
+
* @param state — the render context's `state` bag (mutable, per-card)
|
|
23
|
+
* @param invalidate — `context.invalidate()` to trigger re-render
|
|
24
|
+
* @returns `true` if the card is currently collapsed
|
|
25
|
+
*/
|
|
26
|
+
export function tickCollapse(
|
|
27
|
+
toolName: string,
|
|
28
|
+
state: CollapseState,
|
|
29
|
+
invalidate: () => void,
|
|
30
|
+
): boolean {
|
|
31
|
+
if (!shouldCollapse(toolName)) return false;
|
|
32
|
+
if (state.collapsed) return true;
|
|
33
|
+
if (!state.timer) {
|
|
34
|
+
state.timer = setTimeout(() => {
|
|
35
|
+
state.collapsed = true;
|
|
36
|
+
invalidate();
|
|
37
|
+
}, collapseDelayMs());
|
|
38
|
+
}
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pix-config.ts — unified config loader for ~/.pi/agent/pix.json
|
|
3
|
+
*
|
|
4
|
+
* Single source of truth for all pix-* configuration. The file is read once
|
|
5
|
+
* on first access and cached in-process. A `reloadPixConfig()` function is
|
|
6
|
+
* exposed for slash-commands that edit the file live.
|
|
7
|
+
*
|
|
8
|
+
* Design:
|
|
9
|
+
* - Every key is explicit: absence → default, `false` → disabled.
|
|
10
|
+
* - Env vars still win when set (backward compat), but the JSON file is the
|
|
11
|
+
* primary config surface.
|
|
12
|
+
* - Schema is flat-ish with namespaced top-level sections.
|
|
13
|
+
*
|
|
14
|
+
* File: ~/.pi/agent/pix.json
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
18
|
+
import { dirname, join } from "node:path";
|
|
19
|
+
|
|
20
|
+
// ── Types ────────────────────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
export interface CollapseConfig {
|
|
23
|
+
/** Master toggle. `false` = never collapse any tool. Default: `true`. */
|
|
24
|
+
enabled: boolean;
|
|
25
|
+
/** Seconds before a tool card collapses. Default: `10`. */
|
|
26
|
+
delaySec: number;
|
|
27
|
+
/** Per-tool overrides. Missing key = follows `enabled`. */
|
|
28
|
+
tools: Partial<Record<string, boolean>>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface DiffColors {
|
|
32
|
+
splitMinWidth: number;
|
|
33
|
+
splitMinCodeWidth: number;
|
|
34
|
+
bgAdd: string;
|
|
35
|
+
bgDel: string;
|
|
36
|
+
bgAddHighlight: string;
|
|
37
|
+
bgDelHighlight: string;
|
|
38
|
+
bgGutterAdd: string;
|
|
39
|
+
bgGutterDel: string;
|
|
40
|
+
fgAdd: string;
|
|
41
|
+
fgDel: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface PrettyConfig {
|
|
45
|
+
theme: string;
|
|
46
|
+
icons: string;
|
|
47
|
+
maxPreviewLines: number;
|
|
48
|
+
maxRenderLines: number;
|
|
49
|
+
maxHighlightChars: number;
|
|
50
|
+
cacheLimit: number;
|
|
51
|
+
diff: DiffColors;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface OptimizerConfig {
|
|
55
|
+
caveman: string;
|
|
56
|
+
rtk: string;
|
|
57
|
+
toon: string;
|
|
58
|
+
ponytail: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface GateRuleConfig {
|
|
62
|
+
pattern: string;
|
|
63
|
+
flags?: string;
|
|
64
|
+
severity?: string;
|
|
65
|
+
reason?: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface GateConfig {
|
|
69
|
+
disableDefaults: boolean;
|
|
70
|
+
autoApprove: string[];
|
|
71
|
+
extraRules: GateRuleConfig[];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface PixConfig {
|
|
75
|
+
collapse: CollapseConfig;
|
|
76
|
+
pretty: PrettyConfig;
|
|
77
|
+
optimizer: OptimizerConfig;
|
|
78
|
+
gate: GateConfig;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ── Defaults ─────────────────────────────────────────────────────────────────
|
|
82
|
+
|
|
83
|
+
const DEFAULT_DIFF: DiffColors = {
|
|
84
|
+
splitMinWidth: 150,
|
|
85
|
+
splitMinCodeWidth: 60,
|
|
86
|
+
bgAdd: "#163826",
|
|
87
|
+
bgDel: "#2d1919",
|
|
88
|
+
bgAddHighlight: "#234b32",
|
|
89
|
+
bgDelHighlight: "#502323",
|
|
90
|
+
bgGutterAdd: "#12201a",
|
|
91
|
+
bgGutterDel: "#261616",
|
|
92
|
+
fgAdd: "#64b478",
|
|
93
|
+
fgDel: "#c86464",
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const DEFAULT_COLLAPSE: CollapseConfig = {
|
|
97
|
+
enabled: true,
|
|
98
|
+
delaySec: 10,
|
|
99
|
+
tools: {},
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const DEFAULT_PRETTY: PrettyConfig = {
|
|
103
|
+
theme: "github-dark",
|
|
104
|
+
icons: "nerd",
|
|
105
|
+
maxPreviewLines: 80,
|
|
106
|
+
maxRenderLines: 150,
|
|
107
|
+
maxHighlightChars: 80_000,
|
|
108
|
+
cacheLimit: 128,
|
|
109
|
+
diff: { ...DEFAULT_DIFF },
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const DEFAULT_OPTIMIZER: OptimizerConfig = {
|
|
113
|
+
caveman: "off",
|
|
114
|
+
rtk: "off",
|
|
115
|
+
toon: "off",
|
|
116
|
+
ponytail: "off",
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const DEFAULT_GATE: GateConfig = {
|
|
120
|
+
disableDefaults: false,
|
|
121
|
+
autoApprove: [],
|
|
122
|
+
extraRules: [],
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
/** Full default config — used by tests and documentation. */
|
|
126
|
+
export const DEFAULT_CONFIG: PixConfig = {
|
|
127
|
+
collapse: { ...DEFAULT_COLLAPSE },
|
|
128
|
+
pretty: { ...DEFAULT_PRETTY },
|
|
129
|
+
optimizer: { ...DEFAULT_OPTIMIZER },
|
|
130
|
+
gate: { ...DEFAULT_GATE },
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
// ── Loader ───────────────────────────────────────────────────────────────────
|
|
134
|
+
|
|
135
|
+
let cached: PixConfig | null = null;
|
|
136
|
+
|
|
137
|
+
function configPath(): string | undefined {
|
|
138
|
+
const home = process.env.HOME ?? "";
|
|
139
|
+
if (!home) return undefined;
|
|
140
|
+
return join(home, ".pi/agent", "pix.json");
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Seed file written on first load so users have a reference to edit. */
|
|
144
|
+
function seedConfigFile(p: string): void {
|
|
145
|
+
try {
|
|
146
|
+
mkdirSync(dirname(p), { recursive: true });
|
|
147
|
+
writeFileSync(p, `${JSON.stringify(DEFAULT_CONFIG, null, 2)}\n`, {
|
|
148
|
+
flag: "wx", // exclusive create — no-op if file appeared between check and write
|
|
149
|
+
});
|
|
150
|
+
} catch {
|
|
151
|
+
/* race / permission — harmless */
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function readRawConfig(): Record<string, unknown> {
|
|
156
|
+
try {
|
|
157
|
+
const p = configPath();
|
|
158
|
+
if (!p) return {};
|
|
159
|
+
if (!existsSync(p)) {
|
|
160
|
+
seedConfigFile(p);
|
|
161
|
+
return {};
|
|
162
|
+
}
|
|
163
|
+
return JSON.parse(readFileSync(p, "utf-8")) as Record<string, unknown>;
|
|
164
|
+
} catch {
|
|
165
|
+
return {};
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// ── Merge helpers ────────────────────────────────────────────────────────────
|
|
170
|
+
|
|
171
|
+
function isObj(v: unknown): v is Record<string, unknown> {
|
|
172
|
+
return v !== null && typeof v === "object" && !Array.isArray(v);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function num(v: unknown, fallback: number): number {
|
|
176
|
+
return typeof v === "number" && Number.isFinite(v) && v > 0 ? v : fallback;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function str(v: unknown, fallback: string): string {
|
|
180
|
+
return typeof v === "string" && v.length > 0 ? v : fallback;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function bool(v: unknown, fallback: boolean): boolean {
|
|
184
|
+
return typeof v === "boolean" ? v : fallback;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function strArr(v: unknown): string[] {
|
|
188
|
+
if (!Array.isArray(v)) return [];
|
|
189
|
+
return v.filter((x): x is string => typeof x === "string");
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function mergeCollapse(raw: unknown): CollapseConfig {
|
|
193
|
+
if (!isObj(raw)) return { ...DEFAULT_COLLAPSE };
|
|
194
|
+
const tools: Partial<Record<string, boolean>> = {};
|
|
195
|
+
if (isObj(raw.tools)) {
|
|
196
|
+
for (const [k, v] of Object.entries(raw.tools)) {
|
|
197
|
+
if (typeof v === "boolean") tools[k] = v;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return {
|
|
201
|
+
enabled: bool(raw.enabled, DEFAULT_COLLAPSE.enabled),
|
|
202
|
+
delaySec: num(raw.delaySec, DEFAULT_COLLAPSE.delaySec),
|
|
203
|
+
tools,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function mergeDiff(raw: unknown): DiffColors {
|
|
208
|
+
if (!isObj(raw)) return { ...DEFAULT_DIFF };
|
|
209
|
+
return {
|
|
210
|
+
splitMinWidth: num(raw.splitMinWidth, DEFAULT_DIFF.splitMinWidth),
|
|
211
|
+
splitMinCodeWidth: num(
|
|
212
|
+
raw.splitMinCodeWidth,
|
|
213
|
+
DEFAULT_DIFF.splitMinCodeWidth,
|
|
214
|
+
),
|
|
215
|
+
bgAdd: str(raw.bgAdd, DEFAULT_DIFF.bgAdd),
|
|
216
|
+
bgDel: str(raw.bgDel, DEFAULT_DIFF.bgDel),
|
|
217
|
+
bgAddHighlight: str(raw.bgAddHighlight, DEFAULT_DIFF.bgAddHighlight),
|
|
218
|
+
bgDelHighlight: str(raw.bgDelHighlight, DEFAULT_DIFF.bgDelHighlight),
|
|
219
|
+
bgGutterAdd: str(raw.bgGutterAdd, DEFAULT_DIFF.bgGutterAdd),
|
|
220
|
+
bgGutterDel: str(raw.bgGutterDel, DEFAULT_DIFF.bgGutterDel),
|
|
221
|
+
fgAdd: str(raw.fgAdd, DEFAULT_DIFF.fgAdd),
|
|
222
|
+
fgDel: str(raw.fgDel, DEFAULT_DIFF.fgDel),
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function mergePretty(raw: unknown): PrettyConfig {
|
|
227
|
+
if (!isObj(raw)) return { ...DEFAULT_PRETTY };
|
|
228
|
+
return {
|
|
229
|
+
theme: str(raw.theme, DEFAULT_PRETTY.theme),
|
|
230
|
+
icons: str(raw.icons, DEFAULT_PRETTY.icons),
|
|
231
|
+
maxPreviewLines: num(raw.maxPreviewLines, DEFAULT_PRETTY.maxPreviewLines),
|
|
232
|
+
maxRenderLines: num(raw.maxRenderLines, DEFAULT_PRETTY.maxRenderLines),
|
|
233
|
+
maxHighlightChars: num(
|
|
234
|
+
raw.maxHighlightChars,
|
|
235
|
+
DEFAULT_PRETTY.maxHighlightChars,
|
|
236
|
+
),
|
|
237
|
+
cacheLimit: num(raw.cacheLimit, DEFAULT_PRETTY.cacheLimit),
|
|
238
|
+
diff: mergeDiff(raw.diff),
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function mergeOptimizer(raw: unknown): OptimizerConfig {
|
|
243
|
+
if (!isObj(raw)) return { ...DEFAULT_OPTIMIZER };
|
|
244
|
+
return {
|
|
245
|
+
caveman: str(raw.caveman, DEFAULT_OPTIMIZER.caveman),
|
|
246
|
+
rtk: str(raw.rtk, DEFAULT_OPTIMIZER.rtk),
|
|
247
|
+
toon: str(raw.toon, DEFAULT_OPTIMIZER.toon),
|
|
248
|
+
ponytail: str(raw.ponytail, DEFAULT_OPTIMIZER.ponytail),
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function mergeGateRules(raw: unknown): GateRuleConfig[] {
|
|
253
|
+
if (!Array.isArray(raw)) return [];
|
|
254
|
+
return raw.filter(
|
|
255
|
+
(r): r is GateRuleConfig =>
|
|
256
|
+
isObj(r) && typeof (r as Record<string, unknown>).pattern === "string",
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function mergeGate(raw: unknown): GateConfig {
|
|
261
|
+
if (!isObj(raw)) return { ...DEFAULT_GATE };
|
|
262
|
+
return {
|
|
263
|
+
disableDefaults: bool(raw.disableDefaults, DEFAULT_GATE.disableDefaults),
|
|
264
|
+
autoApprove: strArr(raw.autoApprove),
|
|
265
|
+
extraRules: mergeGateRules(raw.extraRules),
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function buildConfig(raw: Record<string, unknown>): PixConfig {
|
|
270
|
+
return {
|
|
271
|
+
collapse: mergeCollapse(raw.collapse),
|
|
272
|
+
pretty: mergePretty(raw.pretty),
|
|
273
|
+
optimizer: mergeOptimizer(raw.optimizer),
|
|
274
|
+
gate: mergeGate(raw.gate),
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// ── Public API ───────────────────────────────────────────────────────────────
|
|
279
|
+
|
|
280
|
+
/** Get the resolved pix config. Loads from disk on first call, cached after. */
|
|
281
|
+
export function pixConfig(): PixConfig {
|
|
282
|
+
if (!cached) cached = buildConfig(readRawConfig());
|
|
283
|
+
return cached;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/** Re-read pix.json from disk. Call after editing the file live. */
|
|
287
|
+
export function reloadPixConfig(): PixConfig {
|
|
288
|
+
cached = buildConfig(readRawConfig());
|
|
289
|
+
return cached;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/** Check if a tool should auto-collapse its output card. */
|
|
293
|
+
export function shouldCollapse(toolName: string): boolean {
|
|
294
|
+
const c = pixConfig().collapse;
|
|
295
|
+
// Per-tool override wins.
|
|
296
|
+
const perTool = c.tools[toolName];
|
|
297
|
+
if (typeof perTool === "boolean") return perTool;
|
|
298
|
+
// Fall back to master toggle.
|
|
299
|
+
return c.enabled;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/** Get the collapse delay in milliseconds. */
|
|
303
|
+
export function collapseDelayMs(): number {
|
|
304
|
+
return pixConfig().collapse.delaySec * 1000;
|
|
305
|
+
}
|