@eiei114/pi-sub-bar 1.5.1
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 +201 -0
- package/README.md +200 -0
- package/index.ts +1103 -0
- package/package.json +39 -0
- package/src/core-settings.ts +25 -0
- package/src/dividers.ts +48 -0
- package/src/errors.ts +71 -0
- package/src/formatting.ts +937 -0
- package/src/paths.ts +21 -0
- package/src/providers/extras.ts +21 -0
- package/src/providers/metadata.ts +199 -0
- package/src/providers/settings.ts +359 -0
- package/src/providers/windows.ts +23 -0
- package/src/settings/display.ts +786 -0
- package/src/settings/menu.ts +183 -0
- package/src/settings/themes.ts +378 -0
- package/src/settings/ui.ts +1388 -0
- package/src/settings-types.ts +651 -0
- package/src/settings-ui.ts +5 -0
- package/src/settings.ts +176 -0
- package/src/share.ts +75 -0
- package/src/status.ts +103 -0
- package/src/storage.ts +61 -0
- package/src/types.ts +25 -0
- package/src/ui/keybindings.ts +92 -0
- package/src/ui/settings-list.ts +304 -0
- package/src/usage/types.ts +5 -0
- package/src/utils.ts +42 -0
- package/test/all.test.ts +6 -0
- package/test/dividers.test.ts +34 -0
- package/test/formatting.test.ts +437 -0
- package/test/keybindings.test.ts +59 -0
- package/test/providers.test.ts +42 -0
- package/test/settings.test.ts +336 -0
- package/test/status.test.ts +27 -0
- package/tsconfig.json +5 -0
|
@@ -0,0 +1,651 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Settings types and defaults for sub-bar
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { CoreSettings, ProviderName } from "@eiei114/pi-sub-shared";
|
|
6
|
+
import { PROVIDERS } from "@eiei114/pi-sub-shared";
|
|
7
|
+
import type { ThemeColor } from "@mariozechner/pi-coding-agent";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Bar display style
|
|
11
|
+
*/
|
|
12
|
+
export type BarStyle = "bar" | "percentage" | "both";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Bar rendering type
|
|
16
|
+
*/
|
|
17
|
+
export type BarType = "horizontal-bar" | "horizontal-single" | "vertical" | "braille" | "shade";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Color scheme for usage bars
|
|
21
|
+
*/
|
|
22
|
+
export type ColorScheme = "monochrome" | "base-warning-error" | "success-base-warning-error";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Progress bar character style
|
|
26
|
+
*/
|
|
27
|
+
export type BarCharacter = "light" | "heavy" | "double" | "block" | (string & {});
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Divider character style
|
|
31
|
+
*/
|
|
32
|
+
export type DividerCharacter =
|
|
33
|
+
| "none"
|
|
34
|
+
| "blank"
|
|
35
|
+
| "|"
|
|
36
|
+
| "│"
|
|
37
|
+
| "┃"
|
|
38
|
+
| "┆"
|
|
39
|
+
| "┇"
|
|
40
|
+
| "║"
|
|
41
|
+
| "•"
|
|
42
|
+
| "●"
|
|
43
|
+
| "○"
|
|
44
|
+
| "◇"
|
|
45
|
+
| (string & {});
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Widget overflow mode
|
|
49
|
+
*/
|
|
50
|
+
export type OverflowMode = "truncate" | "wrap";
|
|
51
|
+
export type WidgetWrapping = OverflowMode;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Widget placement
|
|
55
|
+
*/
|
|
56
|
+
export type WidgetPlacement = "aboveEditor" | "belowEditor" | "status";
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Alignment for the widget
|
|
60
|
+
*/
|
|
61
|
+
export type DisplayAlignment = "left" | "center" | "right" | "split";
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Provider label prefix
|
|
65
|
+
*/
|
|
66
|
+
export type ProviderLabel = "plan" | "subscription" | "sub" | "none" | (string & {});
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Reset timer format
|
|
70
|
+
*/
|
|
71
|
+
export type ResetTimeFormat = "relative" | "datetime";
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Reset timer containment style
|
|
75
|
+
*/
|
|
76
|
+
export type ResetTimerContainment = "none" | "blank" | "()" | "[]" | "<>" | (string & {});
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Status indicator display mode
|
|
80
|
+
*/
|
|
81
|
+
export type StatusIndicatorMode = "icon" | "text" | "icon+text";
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Status icon pack selection
|
|
85
|
+
*/
|
|
86
|
+
export type StatusIconPack = "minimal" | "emoji" | "custom";
|
|
87
|
+
|
|
88
|
+
export interface UsageColorTargets {
|
|
89
|
+
title: boolean;
|
|
90
|
+
timer: boolean;
|
|
91
|
+
bar: boolean;
|
|
92
|
+
usageLabel: boolean;
|
|
93
|
+
status: boolean;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Divider color options (subset of theme colors).
|
|
98
|
+
*/
|
|
99
|
+
export const DIVIDER_COLOR_OPTIONS = [
|
|
100
|
+
"primary",
|
|
101
|
+
"text",
|
|
102
|
+
"muted",
|
|
103
|
+
"dim",
|
|
104
|
+
"success",
|
|
105
|
+
"warning",
|
|
106
|
+
"error",
|
|
107
|
+
"border",
|
|
108
|
+
"borderMuted",
|
|
109
|
+
"borderAccent",
|
|
110
|
+
] as const;
|
|
111
|
+
|
|
112
|
+
export type DividerColor = (typeof DIVIDER_COLOR_OPTIONS)[number];
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Background color options (theme background colors).
|
|
116
|
+
*/
|
|
117
|
+
export const BACKGROUND_COLOR_OPTIONS = [
|
|
118
|
+
"selectedBg",
|
|
119
|
+
"userMessageBg",
|
|
120
|
+
"customMessageBg",
|
|
121
|
+
"toolPendingBg",
|
|
122
|
+
"toolSuccessBg",
|
|
123
|
+
"toolErrorBg",
|
|
124
|
+
] as const;
|
|
125
|
+
|
|
126
|
+
export type BackgroundColor = (typeof BACKGROUND_COLOR_OPTIONS)[number];
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Base text/background color options.
|
|
130
|
+
*/
|
|
131
|
+
export const BASE_COLOR_OPTIONS = [...DIVIDER_COLOR_OPTIONS, ...BACKGROUND_COLOR_OPTIONS] as const;
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Base text color for widget labels
|
|
135
|
+
*/
|
|
136
|
+
export type BaseTextColor = (typeof BASE_COLOR_OPTIONS)[number];
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Background options for the widget line.
|
|
140
|
+
*/
|
|
141
|
+
export const WIDGET_BACKGROUND_OPTIONS = ["none", ...BASE_COLOR_OPTIONS] as const;
|
|
142
|
+
|
|
143
|
+
export type WidgetBackgroundColor = (typeof WIDGET_BACKGROUND_OPTIONS)[number];
|
|
144
|
+
|
|
145
|
+
export function normalizeDividerColor(value?: string): DividerColor {
|
|
146
|
+
if (!value) return "borderMuted";
|
|
147
|
+
if (value === "accent" || value === "primary") return "primary";
|
|
148
|
+
if ((DIVIDER_COLOR_OPTIONS as readonly string[]).includes(value)) {
|
|
149
|
+
return value as DividerColor;
|
|
150
|
+
}
|
|
151
|
+
return "borderMuted";
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function resolveDividerColor(value?: string): ThemeColor {
|
|
155
|
+
const normalized = normalizeDividerColor(value);
|
|
156
|
+
switch (normalized) {
|
|
157
|
+
case "primary":
|
|
158
|
+
return "accent";
|
|
159
|
+
case "border":
|
|
160
|
+
case "borderMuted":
|
|
161
|
+
case "borderAccent":
|
|
162
|
+
case "success":
|
|
163
|
+
case "warning":
|
|
164
|
+
case "error":
|
|
165
|
+
case "muted":
|
|
166
|
+
case "dim":
|
|
167
|
+
case "text":
|
|
168
|
+
return normalized as ThemeColor;
|
|
169
|
+
default:
|
|
170
|
+
return "borderMuted";
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function isBackgroundColor(value?: BaseTextColor): value is BackgroundColor {
|
|
175
|
+
return !!value && (BACKGROUND_COLOR_OPTIONS as readonly string[]).includes(value);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function normalizeBaseTextColor(value?: string): BaseTextColor {
|
|
179
|
+
if (!value) return "dim";
|
|
180
|
+
if (value === "accent" || value === "primary") return "primary";
|
|
181
|
+
if ((BASE_COLOR_OPTIONS as readonly string[]).includes(value)) {
|
|
182
|
+
return value as BaseTextColor;
|
|
183
|
+
}
|
|
184
|
+
return "dim";
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export function normalizeBackgroundColor(value?: string): WidgetBackgroundColor {
|
|
188
|
+
if (!value || value === "none" || value === "transparent") return "none";
|
|
189
|
+
if (value === "accent" || value === "primary") return "primary";
|
|
190
|
+
if ((BASE_COLOR_OPTIONS as readonly string[]).includes(value)) {
|
|
191
|
+
return value as BaseTextColor;
|
|
192
|
+
}
|
|
193
|
+
return "none";
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function resolveBaseTextColor(value?: string): BaseTextColor {
|
|
197
|
+
return normalizeBaseTextColor(value);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function resolveBackgroundColor(value?: string): WidgetBackgroundColor {
|
|
201
|
+
return normalizeBackgroundColor(value);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Bar width configuration
|
|
206
|
+
*/
|
|
207
|
+
export type BarWidth = number | "fill";
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Divider blank spacing configuration
|
|
211
|
+
*/
|
|
212
|
+
export type DividerBlanks = number | "fill";
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Provider settings (UI-only)
|
|
216
|
+
*/
|
|
217
|
+
export interface BaseProviderSettings {
|
|
218
|
+
/** Show status indicator */
|
|
219
|
+
showStatus: boolean;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export interface AnthropicProviderSettings extends BaseProviderSettings {
|
|
223
|
+
windows: {
|
|
224
|
+
show5h: boolean;
|
|
225
|
+
show7d: boolean;
|
|
226
|
+
showExtra: boolean;
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export interface CopilotProviderSettings extends BaseProviderSettings {
|
|
231
|
+
showMultiplier: boolean;
|
|
232
|
+
showRequestsLeft: boolean;
|
|
233
|
+
quotaDisplay: "percentage" | "requests";
|
|
234
|
+
windows: {
|
|
235
|
+
showMonth: boolean;
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export interface GeminiProviderSettings extends BaseProviderSettings {
|
|
240
|
+
windows: {
|
|
241
|
+
showPro: boolean;
|
|
242
|
+
showFlash: boolean;
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export interface AntigravityProviderSettings extends BaseProviderSettings {
|
|
247
|
+
showCurrentModel: boolean;
|
|
248
|
+
showScopedModels: boolean;
|
|
249
|
+
windows: {
|
|
250
|
+
showModels: boolean;
|
|
251
|
+
};
|
|
252
|
+
modelVisibility: Record<string, boolean>;
|
|
253
|
+
modelOrder: string[];
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export interface CodexProviderSettings extends BaseProviderSettings {
|
|
257
|
+
invertUsage: boolean;
|
|
258
|
+
windows: {
|
|
259
|
+
showPrimary: boolean;
|
|
260
|
+
showSecondary: boolean;
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export interface KiroProviderSettings extends BaseProviderSettings {
|
|
265
|
+
windows: {
|
|
266
|
+
showCredits: boolean;
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export interface ZaiProviderSettings extends BaseProviderSettings {
|
|
271
|
+
windows: {
|
|
272
|
+
showTokens: boolean;
|
|
273
|
+
showMonthly: boolean;
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export interface ProviderSettingsMap {
|
|
278
|
+
anthropic: AnthropicProviderSettings;
|
|
279
|
+
copilot: CopilotProviderSettings;
|
|
280
|
+
gemini: GeminiProviderSettings;
|
|
281
|
+
antigravity: AntigravityProviderSettings;
|
|
282
|
+
codex: CodexProviderSettings;
|
|
283
|
+
kiro: KiroProviderSettings;
|
|
284
|
+
zai: ZaiProviderSettings;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export type { BehaviorSettings, CoreSettings } from "@eiei114/pi-sub-shared";
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Keybinding settings.
|
|
291
|
+
* Values are key-combo strings accepted by pi's registerShortcut (e.g. "ctrl+alt+p").
|
|
292
|
+
* Use "none" to disable a shortcut.
|
|
293
|
+
* Changes take effect after pi restart.
|
|
294
|
+
*/
|
|
295
|
+
export interface KeybindingSettings {
|
|
296
|
+
/** Shortcut to cycle through providers */
|
|
297
|
+
cycleProvider: string;
|
|
298
|
+
/** Shortcut to toggle reset timer format */
|
|
299
|
+
toggleResetFormat: string;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Display settings
|
|
304
|
+
*/
|
|
305
|
+
export interface DisplaySettings {
|
|
306
|
+
/** Alignment */
|
|
307
|
+
alignment: DisplayAlignment;
|
|
308
|
+
/** Bar display style */
|
|
309
|
+
barStyle: BarStyle;
|
|
310
|
+
/** Bar type */
|
|
311
|
+
barType: BarType;
|
|
312
|
+
/** Width of the progress bar in characters */
|
|
313
|
+
barWidth: BarWidth;
|
|
314
|
+
/** Progress bar character */
|
|
315
|
+
barCharacter: BarCharacter;
|
|
316
|
+
/** Contain bar within ▕ and ▏ */
|
|
317
|
+
containBar: boolean;
|
|
318
|
+
/** Fill empty braille segments with dim full blocks */
|
|
319
|
+
brailleFillEmpty: boolean;
|
|
320
|
+
/** Use full braille blocks for filled segments */
|
|
321
|
+
brailleFullBlocks: boolean;
|
|
322
|
+
/** Color scheme for bars */
|
|
323
|
+
colorScheme: ColorScheme;
|
|
324
|
+
/** Elements colored by the usage scheme */
|
|
325
|
+
usageColorTargets: UsageColorTargets;
|
|
326
|
+
/** Reset time display position */
|
|
327
|
+
resetTimePosition: "off" | "front" | "back" | "integrated";
|
|
328
|
+
/** Reset time format */
|
|
329
|
+
resetTimeFormat: ResetTimeFormat;
|
|
330
|
+
/** Reset timer containment */
|
|
331
|
+
resetTimeContainment: ResetTimerContainment;
|
|
332
|
+
/** Status indicator mode */
|
|
333
|
+
statusIndicatorMode: StatusIndicatorMode;
|
|
334
|
+
/** Status icon pack */
|
|
335
|
+
statusIconPack: StatusIconPack;
|
|
336
|
+
/** Custom status icon pack (four characters) */
|
|
337
|
+
statusIconCustom: string;
|
|
338
|
+
/** Show divider between status and provider */
|
|
339
|
+
statusProviderDivider: boolean;
|
|
340
|
+
/** Dismiss status when operational */
|
|
341
|
+
statusDismissOk: boolean;
|
|
342
|
+
/** Show provider display name */
|
|
343
|
+
showProviderName: boolean;
|
|
344
|
+
/** Provider label prefix */
|
|
345
|
+
providerLabel: ProviderLabel;
|
|
346
|
+
/** Show colon after provider label */
|
|
347
|
+
providerLabelColon: boolean;
|
|
348
|
+
/** Bold provider name and colon */
|
|
349
|
+
providerLabelBold: boolean;
|
|
350
|
+
/** Base text color for widget labels */
|
|
351
|
+
baseTextColor: BaseTextColor;
|
|
352
|
+
/** Background color for the widget line */
|
|
353
|
+
backgroundColor: WidgetBackgroundColor;
|
|
354
|
+
/** Show window titles (5h, Week, etc.) */
|
|
355
|
+
showWindowTitle: boolean;
|
|
356
|
+
/** Bold window titles (5h, Week, etc.) */
|
|
357
|
+
boldWindowTitle: boolean;
|
|
358
|
+
/** Show usage labels (used/rem.) */
|
|
359
|
+
showUsageLabels: boolean;
|
|
360
|
+
/** Divider character */
|
|
361
|
+
dividerCharacter: DividerCharacter;
|
|
362
|
+
/** Divider color */
|
|
363
|
+
dividerColor: DividerColor;
|
|
364
|
+
/** Blanks before and after divider */
|
|
365
|
+
dividerBlanks: DividerBlanks;
|
|
366
|
+
/** Show divider between provider label and usage */
|
|
367
|
+
showProviderDivider: boolean;
|
|
368
|
+
/** Show leading divider in status-line placement */
|
|
369
|
+
statusLeadingDivider: boolean;
|
|
370
|
+
/** Show trailing divider in status-line placement */
|
|
371
|
+
statusTrailingDivider: boolean;
|
|
372
|
+
/** Connect divider glyphs to the bottom divider line */
|
|
373
|
+
dividerFooterJoin: boolean;
|
|
374
|
+
/** Show divider line above the bar */
|
|
375
|
+
showTopDivider: boolean;
|
|
376
|
+
/** Show divider line below the bar */
|
|
377
|
+
showBottomDivider: boolean;
|
|
378
|
+
/** Widget overflow mode */
|
|
379
|
+
overflow: OverflowMode;
|
|
380
|
+
/** Left padding inside widget */
|
|
381
|
+
paddingLeft: number;
|
|
382
|
+
/** Right padding inside widget */
|
|
383
|
+
paddingRight: number;
|
|
384
|
+
/** Widget placement */
|
|
385
|
+
widgetPlacement: WidgetPlacement;
|
|
386
|
+
/** Show context window usage as leftmost progress bar */
|
|
387
|
+
showContextBar: boolean;
|
|
388
|
+
/** Error threshold (percentage remaining below this = red) */
|
|
389
|
+
errorThreshold: number;
|
|
390
|
+
/** Warning threshold (percentage remaining below this = yellow) */
|
|
391
|
+
warningThreshold: number;
|
|
392
|
+
/** Success threshold (percentage remaining above this = green, gradient only) */
|
|
393
|
+
successThreshold: number;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* All settings
|
|
399
|
+
*/
|
|
400
|
+
export interface DisplayTheme {
|
|
401
|
+
id: string;
|
|
402
|
+
name: string;
|
|
403
|
+
display: DisplaySettings;
|
|
404
|
+
source?: "saved" | "imported";
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
export interface Settings extends Omit<CoreSettings, "providers"> {
|
|
408
|
+
/** Version for migration */
|
|
409
|
+
version: number;
|
|
410
|
+
/** Provider-specific UI settings */
|
|
411
|
+
providers: ProviderSettingsMap;
|
|
412
|
+
/** Display settings */
|
|
413
|
+
display: DisplaySettings;
|
|
414
|
+
/** Stored display themes */
|
|
415
|
+
displayThemes: DisplayTheme[];
|
|
416
|
+
/** Snapshot of the previous display theme */
|
|
417
|
+
displayUserTheme: DisplaySettings | null;
|
|
418
|
+
/** Pinned provider override for display */
|
|
419
|
+
pinnedProvider: ProviderName | null;
|
|
420
|
+
/** Keybinding settings (changes require pi restart) */
|
|
421
|
+
keybindings: KeybindingSettings;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Current settings version
|
|
426
|
+
*/
|
|
427
|
+
export const SETTINGS_VERSION = 2;
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Default settings
|
|
431
|
+
*/
|
|
432
|
+
export function getDefaultSettings(): Settings {
|
|
433
|
+
return {
|
|
434
|
+
version: SETTINGS_VERSION,
|
|
435
|
+
providers: {
|
|
436
|
+
anthropic: {
|
|
437
|
+
showStatus: true,
|
|
438
|
+
windows: {
|
|
439
|
+
show5h: true,
|
|
440
|
+
show7d: true,
|
|
441
|
+
showExtra: false,
|
|
442
|
+
},
|
|
443
|
+
},
|
|
444
|
+
copilot: {
|
|
445
|
+
showStatus: true,
|
|
446
|
+
showMultiplier: true,
|
|
447
|
+
showRequestsLeft: true,
|
|
448
|
+
quotaDisplay: "percentage",
|
|
449
|
+
windows: {
|
|
450
|
+
showMonth: true,
|
|
451
|
+
},
|
|
452
|
+
},
|
|
453
|
+
gemini: {
|
|
454
|
+
showStatus: true,
|
|
455
|
+
windows: {
|
|
456
|
+
showPro: true,
|
|
457
|
+
showFlash: true,
|
|
458
|
+
},
|
|
459
|
+
},
|
|
460
|
+
antigravity: {
|
|
461
|
+
showStatus: true,
|
|
462
|
+
showCurrentModel: true,
|
|
463
|
+
showScopedModels: true,
|
|
464
|
+
windows: {
|
|
465
|
+
showModels: true,
|
|
466
|
+
},
|
|
467
|
+
modelVisibility: {},
|
|
468
|
+
modelOrder: [],
|
|
469
|
+
},
|
|
470
|
+
codex: {
|
|
471
|
+
showStatus: true,
|
|
472
|
+
invertUsage: false,
|
|
473
|
+
windows: {
|
|
474
|
+
showPrimary: true,
|
|
475
|
+
showSecondary: true,
|
|
476
|
+
},
|
|
477
|
+
},
|
|
478
|
+
kiro: {
|
|
479
|
+
showStatus: false,
|
|
480
|
+
windows: {
|
|
481
|
+
showCredits: true,
|
|
482
|
+
},
|
|
483
|
+
},
|
|
484
|
+
zai: {
|
|
485
|
+
showStatus: false,
|
|
486
|
+
windows: {
|
|
487
|
+
showTokens: true,
|
|
488
|
+
showMonthly: true,
|
|
489
|
+
},
|
|
490
|
+
},
|
|
491
|
+
},
|
|
492
|
+
display: {
|
|
493
|
+
alignment: "split",
|
|
494
|
+
barStyle: "both",
|
|
495
|
+
barType: "horizontal-bar",
|
|
496
|
+
barWidth: "fill",
|
|
497
|
+
barCharacter: "heavy",
|
|
498
|
+
containBar: false,
|
|
499
|
+
brailleFillEmpty: false,
|
|
500
|
+
brailleFullBlocks: false,
|
|
501
|
+
colorScheme: "base-warning-error",
|
|
502
|
+
usageColorTargets: {
|
|
503
|
+
title: true,
|
|
504
|
+
timer: true,
|
|
505
|
+
bar: true,
|
|
506
|
+
usageLabel: true,
|
|
507
|
+
status: true,
|
|
508
|
+
},
|
|
509
|
+
resetTimePosition: "front",
|
|
510
|
+
resetTimeFormat: "relative",
|
|
511
|
+
resetTimeContainment: "blank",
|
|
512
|
+
statusIndicatorMode: "icon",
|
|
513
|
+
statusIconPack: "emoji",
|
|
514
|
+
statusIconCustom: "✓⚠×?",
|
|
515
|
+
statusProviderDivider: false,
|
|
516
|
+
statusDismissOk: true,
|
|
517
|
+
showProviderName: true,
|
|
518
|
+
providerLabel: "none",
|
|
519
|
+
providerLabelColon: false,
|
|
520
|
+
providerLabelBold: true,
|
|
521
|
+
baseTextColor: "muted",
|
|
522
|
+
backgroundColor: "none",
|
|
523
|
+
showWindowTitle: true,
|
|
524
|
+
boldWindowTitle: true,
|
|
525
|
+
showUsageLabels: true,
|
|
526
|
+
dividerCharacter: "│",
|
|
527
|
+
dividerColor: "dim",
|
|
528
|
+
dividerBlanks: 1,
|
|
529
|
+
showProviderDivider: true,
|
|
530
|
+
statusLeadingDivider: false,
|
|
531
|
+
statusTrailingDivider: false,
|
|
532
|
+
dividerFooterJoin: true,
|
|
533
|
+
showTopDivider: false,
|
|
534
|
+
showBottomDivider: true,
|
|
535
|
+
paddingLeft: 1,
|
|
536
|
+
paddingRight: 1,
|
|
537
|
+
widgetPlacement: "belowEditor",
|
|
538
|
+
showContextBar: false,
|
|
539
|
+
errorThreshold: 25,
|
|
540
|
+
warningThreshold: 50,
|
|
541
|
+
overflow: "truncate",
|
|
542
|
+
successThreshold: 75,
|
|
543
|
+
},
|
|
544
|
+
|
|
545
|
+
displayThemes: [],
|
|
546
|
+
displayUserTheme: null,
|
|
547
|
+
pinnedProvider: null,
|
|
548
|
+
|
|
549
|
+
keybindings: {
|
|
550
|
+
cycleProvider: "ctrl+alt+p",
|
|
551
|
+
toggleResetFormat: "ctrl+alt+r",
|
|
552
|
+
},
|
|
553
|
+
|
|
554
|
+
behavior: {
|
|
555
|
+
refreshInterval: 60,
|
|
556
|
+
minRefreshInterval: 10,
|
|
557
|
+
refreshOnTurnStart: false,
|
|
558
|
+
refreshOnToolResult: false,
|
|
559
|
+
},
|
|
560
|
+
statusRefresh: {
|
|
561
|
+
refreshInterval: 60,
|
|
562
|
+
minRefreshInterval: 10,
|
|
563
|
+
refreshOnTurnStart: false,
|
|
564
|
+
refreshOnToolResult: false,
|
|
565
|
+
},
|
|
566
|
+
providerOrder: [...PROVIDERS],
|
|
567
|
+
defaultProvider: null,
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
/**
|
|
572
|
+
* Deep merge two objects
|
|
573
|
+
*/
|
|
574
|
+
function deepMerge<T extends object>(target: T, source: Partial<T>): T {
|
|
575
|
+
const result = { ...target };
|
|
576
|
+
for (const key of Object.keys(source) as (keyof T)[]) {
|
|
577
|
+
const sourceValue = source[key];
|
|
578
|
+
const targetValue = target[key];
|
|
579
|
+
if (
|
|
580
|
+
sourceValue !== undefined &&
|
|
581
|
+
typeof sourceValue === "object" &&
|
|
582
|
+
sourceValue !== null &&
|
|
583
|
+
!Array.isArray(sourceValue) &&
|
|
584
|
+
typeof targetValue === "object" &&
|
|
585
|
+
targetValue !== null &&
|
|
586
|
+
!Array.isArray(targetValue)
|
|
587
|
+
) {
|
|
588
|
+
result[key] = deepMerge(targetValue, sourceValue as Partial<typeof targetValue>);
|
|
589
|
+
} else if (sourceValue !== undefined) {
|
|
590
|
+
result[key] = sourceValue as T[keyof T];
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
return result;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/**
|
|
597
|
+
* Merge settings with defaults (no legacy migrations).
|
|
598
|
+
*/
|
|
599
|
+
export function mergeSettings(loaded: Partial<Settings>): Settings {
|
|
600
|
+
const migrated = migrateSettings(loaded);
|
|
601
|
+
return deepMerge(getDefaultSettings(), migrated);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
const WIDGET_PLACEMENTS = ["aboveEditor", "belowEditor", "status"] as const;
|
|
605
|
+
|
|
606
|
+
function coerceWidgetPlacement(raw?: unknown): WidgetPlacement | undefined {
|
|
607
|
+
if (typeof raw !== "string") return undefined;
|
|
608
|
+
if ((WIDGET_PLACEMENTS as readonly string[]).includes(raw)) {
|
|
609
|
+
return raw as WidgetPlacement;
|
|
610
|
+
}
|
|
611
|
+
return undefined;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
function migrateDisplaySettings(display?: Partial<DisplaySettings> | null): void {
|
|
615
|
+
if (!display) return;
|
|
616
|
+
const displayAny = display as Partial<DisplaySettings> & { widgetWrapping?: OverflowMode; paddingX?: number };
|
|
617
|
+
const normalizedPlacement = coerceWidgetPlacement(displayAny.widgetPlacement);
|
|
618
|
+
if (displayAny.widgetPlacement !== undefined) {
|
|
619
|
+
displayAny.widgetPlacement = normalizedPlacement ?? "belowEditor";
|
|
620
|
+
}
|
|
621
|
+
if (displayAny.widgetPlacement === "status") {
|
|
622
|
+
displayAny.alignment = "left";
|
|
623
|
+
displayAny.overflow = "truncate";
|
|
624
|
+
}
|
|
625
|
+
if (displayAny.widgetWrapping !== undefined && displayAny.overflow === undefined) {
|
|
626
|
+
displayAny.overflow = displayAny.widgetWrapping;
|
|
627
|
+
}
|
|
628
|
+
if (displayAny.paddingX !== undefined) {
|
|
629
|
+
if (displayAny.paddingLeft === undefined) {
|
|
630
|
+
displayAny.paddingLeft = displayAny.paddingX;
|
|
631
|
+
}
|
|
632
|
+
if (displayAny.paddingRight === undefined) {
|
|
633
|
+
displayAny.paddingRight = displayAny.paddingX;
|
|
634
|
+
}
|
|
635
|
+
delete (displayAny as { paddingX?: unknown }).paddingX;
|
|
636
|
+
}
|
|
637
|
+
if ("widgetWrapping" in displayAny) {
|
|
638
|
+
delete (displayAny as { widgetWrapping?: unknown }).widgetWrapping;
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
function migrateSettings(loaded: Partial<Settings>): Partial<Settings> {
|
|
643
|
+
migrateDisplaySettings(loaded.display);
|
|
644
|
+
migrateDisplaySettings(loaded.displayUserTheme);
|
|
645
|
+
if (Array.isArray(loaded.displayThemes)) {
|
|
646
|
+
for (const theme of loaded.displayThemes) {
|
|
647
|
+
migrateDisplaySettings(theme.display as Partial<DisplaySettings> | undefined);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
return loaded;
|
|
651
|
+
}
|