pinglet-cli 0.6.1 → 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.
@@ -34,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.runStatusline = runStatusline;
37
+ exports.writeStatusline = writeStatusline;
37
38
  const fs = __importStar(require("fs"));
38
39
  const config_1 = require("../config");
39
40
  const cache_1 = require("../cache");
@@ -42,6 +43,7 @@ const picker_1 = require("../picker");
42
43
  const queue_1 = require("../queue");
43
44
  const claude_1 = require("../adapters/claude");
44
45
  const runtime_1 = require("../runtime");
46
+ const hud_1 = require("../hud");
45
47
  /**
46
48
  * online 캐시 유효 기간 — refresh(≈5분) 2주기. 서버의 온라인 판정 창
47
49
  * (ONLINE_WINDOW_MS 10분)과 정렬해, 서버가 이미 오프라인으로 세는 시점에
@@ -61,7 +63,8 @@ function onlineOthers(now) {
61
63
  }
62
64
  function readPayload() {
63
65
  try {
64
- return JSON.parse(fs.readFileSync(0, "utf8"));
66
+ const parsed = JSON.parse(fs.readFileSync(0, "utf8"));
67
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
65
68
  }
66
69
  catch {
67
70
  return {};
@@ -102,6 +105,10 @@ function rotateSpinner(state, now) {
102
105
  visibleMs: prev.visibleMs,
103
106
  });
104
107
  prev.visibleMs = 0; // A failed replacement must not account this interval twice.
108
+ // The event is already on disk. Persist the reset now: if arming below throws
109
+ // (e.g. settings.json is temporarily unparseable) the tick ends before the
110
+ // final saveState and the next tick would settle the same interval again.
111
+ (0, cache_1.saveState)(state);
105
112
  }
106
113
  const message = (0, picker_1.pickMessage)((0, cache_1.loadFeedMessages)(), state);
107
114
  if (!message) {
@@ -134,15 +141,18 @@ function rotateSpinner(state, now) {
134
141
  };
135
142
  }
136
143
  /**
137
- * Claude Code statusLine hook 진입점.
144
+ * Claude Code statusLine hook 진입점 (로컬 잠금 안, 동기).
138
145
  * - spinner 회전(pool=1): ROTATE_MS마다 다음 메시지를 armed (설정 핫리로드로 즉시 반영)
139
146
  * - 노출 측정: stdin payload의 cost 델타로 armed 메시지의 실제 표시 시간을 누적
140
- * - statusline 표시: 지금 함께 켜져 있는 터미널 수
141
147
  * - flush/refresh 백그라운드 트리거 (Claude 사용 중 항상 호출되므로 이 경로에서)
148
+ * 출력할 줄과 payload를 돌려주면 호출부가 잠금을 놓고 `writeStatusline`으로 HUD까지 그린다.
142
149
  */
143
150
  function runStatusline() {
144
- if (!fs.existsSync(config_1.CONFIG_PATH) || !(0, config_1.loadConfig)().adapters.claude)
145
- return;
151
+ if (!fs.existsSync(config_1.CONFIG_PATH))
152
+ return undefined;
153
+ const config = (0, config_1.loadConfig)();
154
+ if (!config.adapters.claude)
155
+ return undefined;
146
156
  const payload = readPayload();
147
157
  const state = (0, cache_1.loadState)();
148
158
  const now = Date.now();
@@ -161,6 +171,23 @@ function runStatusline() {
161
171
  state.lastTickAt = now;
162
172
  (0, cache_1.saveState)(state);
163
173
  }
174
+ const lines = [];
164
175
  const others = onlineOthers(now);
165
- process.stdout.write(others !== null ? (0, render_1.formatOnlineNow)(others) : "");
176
+ if (others !== null)
177
+ lines.push((0, render_1.formatOnlineNow)(others));
178
+ return { payload, lines, hud: (0, hud_1.loadHudConfig)(config) };
179
+ }
180
+ /**
181
+ * 잠금 밖에서 HUD(모델·컨텍스트·사용량·활동)를 그려 "함께 코딩 중" 줄 아래에 출력한다.
182
+ * HUD는 실패해도 위 줄에 영향을 주면 안 된다.
183
+ */
184
+ async function writeStatusline(tick, now = Date.now(), write = (text) => { process.stdout.write(text); }) {
185
+ const lines = [...tick.lines];
186
+ try {
187
+ lines.push(...(await (0, hud_1.renderHudLines)(tick.hud, tick.payload, now)));
188
+ }
189
+ catch {
190
+ // 손상된 transcript·git 오류 등 — 이번 tick은 HUD 없이 그린다.
191
+ }
192
+ write(lines.join("\n"));
166
193
  }
package/dist/config.js CHANGED
@@ -93,6 +93,7 @@ function loadConfig() {
93
93
  const object = (v) => !!v && typeof v === "object" && !Array.isArray(v);
94
94
  if (typeof existing.apiBaseUrl !== "string" || typeof existing.createdAt !== "string" ||
95
95
  (existing.userToken !== undefined && typeof existing.userToken !== "string") ||
96
+ (existing.hud !== undefined && !object(existing.hud)) ||
96
97
  (existing.installations !== undefined && !object(existing.installations)) ||
97
98
  (existing.adapters !== undefined && !object(existing.adapters))) {
98
99
  throw new Error(`Invalid ${exports.CONFIG_PATH}; original file preserved.`);
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.quotaBar = exports.contextBar = exports.critical = exports.warning = exports.label = exports.gitBranch = exports.git = exports.project = exports.model = exports.dim = exports.magenta = exports.cyan = exports.red = exports.yellow = exports.green = exports.RESET = void 0;
4
+ exports.contextColor = contextColor;
5
+ exports.quotaColor = quotaColor;
6
+ exports.RESET = "\x1b[0m";
7
+ const DIM = "\x1b[2m";
8
+ const ANSI_BY_NAME = {
9
+ dim: DIM,
10
+ red: "\x1b[31m",
11
+ green: "\x1b[32m",
12
+ yellow: "\x1b[33m",
13
+ magenta: "\x1b[35m",
14
+ cyan: "\x1b[36m",
15
+ brightBlue: "\x1b[94m",
16
+ brightMagenta: "\x1b[95m",
17
+ };
18
+ function resolveAnsi(value, fallback) {
19
+ if (value === undefined || value === null)
20
+ return fallback;
21
+ if (typeof value === "number")
22
+ return `\x1b[38;5;${value}m`;
23
+ if (typeof value === "string" && value.startsWith("#") && value.length === 7) {
24
+ const r = parseInt(value.slice(1, 3), 16);
25
+ const g = parseInt(value.slice(3, 5), 16);
26
+ const b = parseInt(value.slice(5, 7), 16);
27
+ return `\x1b[38;2;${r};${g};${b}m`;
28
+ }
29
+ return ANSI_BY_NAME[value] ?? fallback;
30
+ }
31
+ const colorize = (text, ansi) => `${ansi}${text}${exports.RESET}`;
32
+ const green = (t) => colorize(t, ANSI_BY_NAME.green);
33
+ exports.green = green;
34
+ const yellow = (t) => colorize(t, ANSI_BY_NAME.yellow);
35
+ exports.yellow = yellow;
36
+ const red = (t) => colorize(t, ANSI_BY_NAME.red);
37
+ exports.red = red;
38
+ const cyan = (t) => colorize(t, ANSI_BY_NAME.cyan);
39
+ exports.cyan = cyan;
40
+ const magenta = (t) => colorize(t, ANSI_BY_NAME.magenta);
41
+ exports.magenta = magenta;
42
+ const dim = (t) => colorize(t, DIM);
43
+ exports.dim = dim;
44
+ const model = (t, c) => colorize(t, resolveAnsi(c.model, ANSI_BY_NAME.cyan));
45
+ exports.model = model;
46
+ const project = (t, c) => colorize(t, resolveAnsi(c.project, ANSI_BY_NAME.yellow));
47
+ exports.project = project;
48
+ const git = (t, c) => colorize(t, resolveAnsi(c.git, ANSI_BY_NAME.magenta));
49
+ exports.git = git;
50
+ const gitBranch = (t, c) => colorize(t, resolveAnsi(c.gitBranch, ANSI_BY_NAME.cyan));
51
+ exports.gitBranch = gitBranch;
52
+ const label = (t, c) => colorize(t, resolveAnsi(c.label, DIM));
53
+ exports.label = label;
54
+ const warning = (t, c) => colorize(t, resolveAnsi(c.warning, ANSI_BY_NAME.yellow));
55
+ exports.warning = warning;
56
+ const critical = (t, c) => colorize(t, resolveAnsi(c.critical, ANSI_BY_NAME.red));
57
+ exports.critical = critical;
58
+ function contextColor(percent, c, thresholds) {
59
+ if (percent >= thresholds.critical)
60
+ return resolveAnsi(c.critical, ANSI_BY_NAME.red);
61
+ if (percent >= thresholds.warning)
62
+ return resolveAnsi(c.warning, ANSI_BY_NAME.yellow);
63
+ return resolveAnsi(c.context, ANSI_BY_NAME.green);
64
+ }
65
+ function quotaColor(percent, c) {
66
+ if (percent >= 90)
67
+ return resolveAnsi(c.critical, ANSI_BY_NAME.red);
68
+ if (percent >= 75)
69
+ return resolveAnsi(c.usageWarning, ANSI_BY_NAME.brightMagenta);
70
+ return resolveAnsi(c.usage, ANSI_BY_NAME.brightBlue);
71
+ }
72
+ function bar(percent, width, ansi, c) {
73
+ const safeWidth = Math.max(0, Math.round(width));
74
+ const safePercent = Number.isFinite(percent) ? Math.min(100, Math.max(0, percent)) : 0;
75
+ const filled = Math.round((safePercent / 100) * safeWidth);
76
+ return `${ansi}${c.barFilled.repeat(filled)}${DIM}${c.barEmpty.repeat(safeWidth - filled)}${exports.RESET}`;
77
+ }
78
+ const contextBar = (percent, width, c, thresholds) => bar(percent, width, contextColor(percent, c, thresholds), c);
79
+ exports.contextBar = contextBar;
80
+ const quotaBar = (percent, width, c) => bar(percent, width, quotaColor(percent, c), c);
81
+ exports.quotaBar = quotaBar;
@@ -0,0 +1,300 @@
1
+ "use strict";
2
+ /**
3
+ * HUD 설정 — claude-hud(jarrodwatts/claude-hud)와 같은 키 체계를 쓴다.
4
+ * `~/.pinglet/config.json`의 `hud` 섹션에 저장되며, 값이 없거나 잘못되면
5
+ * 기본값으로 되돌린다 (statusline 렌더 경로에서 예외를 던지지 않기 위해).
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.PRESETS = exports.TOGGLE_KEYS = exports.DEFAULT_HUD_CONFIG = exports.DEFAULT_MERGE_GROUPS = exports.KNOWN_FIRST_LINE_SEGMENTS = exports.KNOWN_ELEMENTS = exports.DEFAULT_ELEMENT_ORDER = void 0;
9
+ exports.setToggle = setToggle;
10
+ exports.applyPreset = applyPreset;
11
+ exports.normalizeHudConfig = normalizeHudConfig;
12
+ exports.DEFAULT_ELEMENT_ORDER = [
13
+ "project",
14
+ "context",
15
+ "usage",
16
+ "promptCache",
17
+ "memory",
18
+ "tools",
19
+ "skills",
20
+ "agents",
21
+ "todos",
22
+ ];
23
+ exports.KNOWN_ELEMENTS = new Set(exports.DEFAULT_ELEMENT_ORDER);
24
+ exports.KNOWN_FIRST_LINE_SEGMENTS = new Set([
25
+ "model",
26
+ "project",
27
+ "advisor",
28
+ "sessionName",
29
+ "duration",
30
+ "cost",
31
+ "speed",
32
+ ]);
33
+ /** 컨텍스트·사용량·캐시는 한 줄로 묶는다 (폭이 모자라면 렌더러가 다시 나눈다). */
34
+ exports.DEFAULT_MERGE_GROUPS = [["context", "usage", "promptCache"]];
35
+ /** 설정 파일에 없을 때 쓰는 기본값 — claude-hud의 "full" 프리셋과 같다. */
36
+ exports.DEFAULT_HUD_CONFIG = {
37
+ enabled: true,
38
+ lineLayout: "expanded",
39
+ showSeparators: false,
40
+ pathLevels: 1,
41
+ maxWidth: null,
42
+ forceMaxWidth: false,
43
+ elementOrder: [...exports.DEFAULT_ELEMENT_ORDER],
44
+ projectLineOrder: [],
45
+ gitStatus: {
46
+ enabled: true,
47
+ showDirty: true,
48
+ showAheadBehind: true,
49
+ showFileStats: true,
50
+ branchOverflow: "truncate",
51
+ pushWarningThreshold: 0,
52
+ pushCriticalThreshold: 0,
53
+ },
54
+ display: {
55
+ showModel: true,
56
+ modelFormat: "full",
57
+ showProject: true,
58
+ showContextBar: true,
59
+ contextValue: "percent",
60
+ showTokenBreakdown: true,
61
+ showUsage: true,
62
+ usageValue: "percent",
63
+ usageBarEnabled: true,
64
+ showResetLabel: true,
65
+ usageCompact: false,
66
+ showModelScopedUsage: true,
67
+ timeFormat: "relative",
68
+ showTools: true,
69
+ toolNameMaxLength: 0,
70
+ toolsMaxVisible: 4,
71
+ showSkills: true,
72
+ showAgents: true,
73
+ showTodos: true,
74
+ showSessionName: true,
75
+ showDuration: true,
76
+ showCost: true,
77
+ showSpeed: true,
78
+ showEffortLevel: true,
79
+ effortFormat: "full",
80
+ showSessionTokens: true,
81
+ showCompactions: true,
82
+ showMemoryUsage: true,
83
+ showPromptCache: true,
84
+ promptCacheTtlSeconds: 300,
85
+ showCacheHitRate: true,
86
+ showAdvisor: true,
87
+ advisorOverride: "",
88
+ mergeGroups: exports.DEFAULT_MERGE_GROUPS.map((group) => [...group]),
89
+ contextWarningThreshold: 70,
90
+ contextCriticalThreshold: 85,
91
+ usageThreshold: 0,
92
+ sevenDayThreshold: 80,
93
+ },
94
+ colors: {
95
+ context: "green",
96
+ usage: "brightBlue",
97
+ warning: "yellow",
98
+ usageWarning: "brightMagenta",
99
+ critical: "red",
100
+ model: "cyan",
101
+ project: "yellow",
102
+ git: "magenta",
103
+ gitBranch: "cyan",
104
+ label: "dim",
105
+ barFilled: "█",
106
+ barEmpty: "░",
107
+ },
108
+ };
109
+ /** `pinglet hud --show/--hide`에서 받는 토글 이름 → 설정 경로. */
110
+ exports.TOGGLE_KEYS = {
111
+ model: "display.showModel",
112
+ project: "display.showProject",
113
+ context: "display.showContextBar",
114
+ "context-bar": "display.showContextBar",
115
+ usage: "display.showUsage",
116
+ "model-scoped-usage": "display.showModelScopedUsage",
117
+ tools: "display.showTools",
118
+ skills: "display.showSkills",
119
+ agents: "display.showAgents",
120
+ todos: "display.showTodos",
121
+ memory: "display.showMemoryUsage",
122
+ "prompt-cache": "display.showPromptCache",
123
+ "cache-hit": "display.showCacheHitRate",
124
+ advisor: "display.showAdvisor",
125
+ "session-name": "display.showSessionName",
126
+ duration: "display.showDuration",
127
+ cost: "display.showCost",
128
+ speed: "display.showSpeed",
129
+ effort: "display.showEffortLevel",
130
+ "session-tokens": "display.showSessionTokens",
131
+ compactions: "display.showCompactions",
132
+ "token-breakdown": "display.showTokenBreakdown",
133
+ "usage-bar": "display.usageBarEnabled",
134
+ "reset-label": "display.showResetLabel",
135
+ "usage-compact": "display.usageCompact",
136
+ git: "gitStatus.enabled",
137
+ "git-dirty": "gitStatus.showDirty",
138
+ "git-ahead-behind": "gitStatus.showAheadBehind",
139
+ "git-files": "gitStatus.showFileStats",
140
+ };
141
+ /** 프리셋이 손대는 토글 — 나머지 키는 프리셋을 바꿔도 그대로 둔다. */
142
+ const PRESET_TOGGLES = [
143
+ "model", "project", "context", "usage", "model-scoped-usage", "tools", "skills", "agents", "todos",
144
+ "memory", "prompt-cache", "cache-hit", "advisor", "session-name", "duration", "cost", "speed", "effort",
145
+ "session-tokens", "compactions", "git", "git-ahead-behind", "git-files",
146
+ ];
147
+ /**
148
+ * 프리셋 — claude-hud의 setup/configure 흐름과 같은 세 단계.
149
+ * full: 모두 켜기
150
+ * essential: 활동 줄(tools/skills/agents/todos) + 모델·프로젝트·컨텍스트·git 브랜치, 정보 최소
151
+ * minimal: 모델명 + context bar만
152
+ */
153
+ exports.PRESETS = {
154
+ full: new Set(PRESET_TOGGLES),
155
+ essential: new Set([
156
+ "model", "project", "context", "usage", "model-scoped-usage", "prompt-cache", "cache-hit",
157
+ "tools", "skills", "agents", "todos", "git",
158
+ ]),
159
+ minimal: new Set(["model", "context"]),
160
+ };
161
+ function setToggle(config, name, value) {
162
+ const target = exports.TOGGLE_KEYS[name];
163
+ if (!target)
164
+ return;
165
+ const [section, key] = target.split(".");
166
+ config[section][key] = value;
167
+ }
168
+ function applyPreset(config, preset) {
169
+ const next = structuredClone(config);
170
+ const on = exports.PRESETS[preset];
171
+ for (const name of PRESET_TOGGLES)
172
+ setToggle(next, name, on.has(name));
173
+ // 프리셋은 배치까지 정하므로 병합 그룹도 기본값(컨텍스트·사용량·캐시 한 줄)으로 되돌린다.
174
+ next.display.mergeGroups = exports.DEFAULT_MERGE_GROUPS.map((group) => [...group]);
175
+ return next;
176
+ }
177
+ const isObject = (v) => !!v && typeof v === "object" && !Array.isArray(v);
178
+ const bool = (v, fallback) => (typeof v === "boolean" ? v : fallback);
179
+ const oneOf = (v, allowed, fallback) => allowed.includes(v) ? v : fallback;
180
+ const int = (v, fallback, min = 0, max = 100_000) => typeof v === "number" && Number.isFinite(v) && v >= min && v <= max ? Math.trunc(v) : fallback;
181
+ function uniqueKnown(value, known) {
182
+ if (!Array.isArray(value))
183
+ return [];
184
+ const out = [];
185
+ for (const item of value) {
186
+ if (typeof item === "string" && known.has(item) && !out.includes(item))
187
+ out.push(item);
188
+ }
189
+ return out;
190
+ }
191
+ const COLOR_NAMES = [
192
+ "dim", "red", "green", "yellow", "magenta", "cyan", "brightBlue", "brightMagenta",
193
+ ];
194
+ function colorValue(value, fallback) {
195
+ if (typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 255)
196
+ return value;
197
+ if (typeof value === "string" && /^#[0-9a-fA-F]{6}$/.test(value))
198
+ return value;
199
+ if (typeof value === "string" && COLOR_NAMES.includes(value))
200
+ return value;
201
+ return fallback;
202
+ }
203
+ /** 바 문자: 제어·서식 문자를 제외한 단일 grapheme만 허용한다 (터미널 방어). */
204
+ function barChar(value, fallback) {
205
+ if (typeof value !== "string" || value.length === 0 || value.length > 4)
206
+ return fallback;
207
+ if (/[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/u.test(value))
208
+ return fallback;
209
+ return Array.from(value).length === 1 ? value : fallback;
210
+ }
211
+ /** 어떤 입력이 와도 유효한 HudConfig를 돌려준다. 알 수 없는 키는 버린다. */
212
+ function normalizeHudConfig(raw) {
213
+ const d = exports.DEFAULT_HUD_CONFIG;
214
+ if (!isObject(raw))
215
+ return structuredClone(d);
216
+ const display = isObject(raw.display) ? raw.display : {};
217
+ const git = isObject(raw.gitStatus) ? raw.gitStatus : {};
218
+ const colors = isObject(raw.colors) ? raw.colors : {};
219
+ const elementOrder = uniqueKnown(raw.elementOrder, exports.KNOWN_ELEMENTS);
220
+ const mergeGroups = Array.isArray(display.mergeGroups)
221
+ ? display.mergeGroups
222
+ .map((group) => uniqueKnown(group, exports.KNOWN_ELEMENTS))
223
+ .filter((group) => group.length > 1)
224
+ : d.display.mergeGroups.map((group) => [...group]);
225
+ const warning = int(display.contextWarningThreshold, d.display.contextWarningThreshold, 0, 100);
226
+ const critical = int(display.contextCriticalThreshold, d.display.contextCriticalThreshold, 0, 100);
227
+ return {
228
+ enabled: bool(raw.enabled, d.enabled),
229
+ lineLayout: oneOf(raw.lineLayout, ["compact", "expanded"], d.lineLayout),
230
+ showSeparators: bool(raw.showSeparators, d.showSeparators),
231
+ pathLevels: oneOf(raw.pathLevels, [1, 2, 3, "full"], d.pathLevels),
232
+ maxWidth: typeof raw.maxWidth === "number" && raw.maxWidth > 0 ? Math.min(1000, Math.trunc(raw.maxWidth)) : null,
233
+ forceMaxWidth: bool(raw.forceMaxWidth, d.forceMaxWidth),
234
+ elementOrder: elementOrder.length > 0 ? elementOrder : [...d.elementOrder],
235
+ projectLineOrder: uniqueKnown(raw.projectLineOrder, exports.KNOWN_FIRST_LINE_SEGMENTS),
236
+ gitStatus: {
237
+ enabled: bool(git.enabled, d.gitStatus.enabled),
238
+ showDirty: bool(git.showDirty, d.gitStatus.showDirty),
239
+ showAheadBehind: bool(git.showAheadBehind, d.gitStatus.showAheadBehind),
240
+ showFileStats: bool(git.showFileStats, d.gitStatus.showFileStats),
241
+ branchOverflow: oneOf(git.branchOverflow, ["truncate", "wrap"], d.gitStatus.branchOverflow),
242
+ pushWarningThreshold: int(git.pushWarningThreshold, d.gitStatus.pushWarningThreshold, 0, 10_000),
243
+ pushCriticalThreshold: int(git.pushCriticalThreshold, d.gitStatus.pushCriticalThreshold, 0, 10_000),
244
+ },
245
+ display: {
246
+ showModel: bool(display.showModel, d.display.showModel),
247
+ modelFormat: oneOf(display.modelFormat, ["full", "compact", "short"], d.display.modelFormat),
248
+ showProject: bool(display.showProject, d.display.showProject),
249
+ showContextBar: bool(display.showContextBar, d.display.showContextBar),
250
+ contextValue: oneOf(display.contextValue, ["percent", "tokens", "remaining", "both"], d.display.contextValue),
251
+ showTokenBreakdown: bool(display.showTokenBreakdown, d.display.showTokenBreakdown),
252
+ showUsage: bool(display.showUsage, d.display.showUsage),
253
+ usageValue: oneOf(display.usageValue, ["percent", "remaining"], d.display.usageValue),
254
+ usageBarEnabled: bool(display.usageBarEnabled, d.display.usageBarEnabled),
255
+ showResetLabel: bool(display.showResetLabel, d.display.showResetLabel),
256
+ usageCompact: bool(display.usageCompact, d.display.usageCompact),
257
+ showModelScopedUsage: bool(display.showModelScopedUsage, d.display.showModelScopedUsage),
258
+ timeFormat: oneOf(display.timeFormat, ["relative", "absolute", "both"], d.display.timeFormat),
259
+ showTools: bool(display.showTools, d.display.showTools),
260
+ toolNameMaxLength: int(display.toolNameMaxLength, d.display.toolNameMaxLength, 0, 64),
261
+ toolsMaxVisible: int(display.toolsMaxVisible, d.display.toolsMaxVisible, 0, 20),
262
+ showSkills: bool(display.showSkills, d.display.showSkills),
263
+ showAgents: bool(display.showAgents, d.display.showAgents),
264
+ showTodos: bool(display.showTodos, d.display.showTodos),
265
+ showSessionName: bool(display.showSessionName, d.display.showSessionName),
266
+ showDuration: bool(display.showDuration, d.display.showDuration),
267
+ showCost: bool(display.showCost, d.display.showCost),
268
+ showSpeed: bool(display.showSpeed, d.display.showSpeed),
269
+ showEffortLevel: bool(display.showEffortLevel, d.display.showEffortLevel),
270
+ effortFormat: oneOf(display.effortFormat, ["full", "symbol", "text"], d.display.effortFormat),
271
+ showSessionTokens: bool(display.showSessionTokens, d.display.showSessionTokens),
272
+ showCompactions: bool(display.showCompactions, d.display.showCompactions),
273
+ showMemoryUsage: bool(display.showMemoryUsage, d.display.showMemoryUsage),
274
+ showPromptCache: bool(display.showPromptCache, d.display.showPromptCache),
275
+ promptCacheTtlSeconds: int(display.promptCacheTtlSeconds, d.display.promptCacheTtlSeconds, 1, 86_400),
276
+ showCacheHitRate: bool(display.showCacheHitRate, d.display.showCacheHitRate),
277
+ showAdvisor: bool(display.showAdvisor, d.display.showAdvisor),
278
+ advisorOverride: typeof display.advisorOverride === "string" ? display.advisorOverride.slice(0, 64) : "",
279
+ mergeGroups,
280
+ contextWarningThreshold: Math.min(warning, critical),
281
+ contextCriticalThreshold: critical,
282
+ usageThreshold: int(display.usageThreshold, d.display.usageThreshold, 0, 100),
283
+ sevenDayThreshold: int(display.sevenDayThreshold, d.display.sevenDayThreshold, 0, 100),
284
+ },
285
+ colors: {
286
+ context: colorValue(colors.context, d.colors.context),
287
+ usage: colorValue(colors.usage, d.colors.usage),
288
+ warning: colorValue(colors.warning, d.colors.warning),
289
+ usageWarning: colorValue(colors.usageWarning, d.colors.usageWarning),
290
+ critical: colorValue(colors.critical, d.colors.critical),
291
+ model: colorValue(colors.model, d.colors.model),
292
+ project: colorValue(colors.project, d.colors.project),
293
+ git: colorValue(colors.git, d.colors.git),
294
+ gitBranch: colorValue(colors.gitBranch, d.colors.gitBranch),
295
+ label: colorValue(colors.label, d.colors.label),
296
+ barFilled: barChar(colors.barFilled, d.colors.barFilled),
297
+ barEmpty: barChar(colors.barEmpty, d.colors.barEmpty),
298
+ },
299
+ };
300
+ }