@marckrenn/pi-sub-bar 1.0.1 → 1.0.2
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 +18 -0
- package/README.md +11 -12
- package/index.ts +103 -4
- package/package.json +12 -4
- package/src/formatting.ts +19 -6
- package/src/providers/metadata.ts +46 -15
- package/src/providers/settings.ts +54 -23
- package/src/providers/windows.ts +8 -3
- package/src/settings/display.ts +8 -5
- package/src/settings/menu.ts +11 -5
- package/src/settings/themes.ts +4 -4
- package/src/settings/ui.ts +30 -1
- package/src/settings-types.ts +41 -13
- package/src/types.ts +6 -0
- package/src/utils.ts +1 -1
- package/test/formatting.test.ts +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# @marckrenn/pi-sub-bar
|
|
2
2
|
|
|
3
|
+
## 1.0.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#3](https://github.com/marckrenn/pi-sub/pull/3) [`4ceb5ad`](https://github.com/marckrenn/pi-sub/commit/4ceb5ad133166237652d197ba9296ad1589a813c) Thanks [@marckrenn](https://github.com/marckrenn)! - Bundle sub-core with sub-bar, refresh Antigravity quotas + settings, and update UI copy/controls.
|
|
8
|
+
|
|
9
|
+
- Updated dependencies [[`4ceb5ad`](https://github.com/marckrenn/pi-sub/commit/4ceb5ad133166237652d197ba9296ad1589a813c)]:
|
|
10
|
+
- @marckrenn/pi-sub-core@1.0.2
|
|
11
|
+
- @marckrenn/pi-sub-shared@1.0.2
|
|
12
|
+
|
|
13
|
+
## 1.0.1
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- Align repo version with npm publish.
|
|
18
|
+
- Updated dependencies:
|
|
19
|
+
- @marckrenn/pi-sub-shared@1.0.1
|
|
20
|
+
|
|
3
21
|
## 1.0.0
|
|
4
22
|
|
|
5
23
|
### Major Changes
|
package/README.md
CHANGED
|
@@ -29,10 +29,10 @@ https://github.com/user-attachments/assets/d61d82f6-afd0-45fc-82f3-69910543aa7a
|
|
|
29
29
|
|
|
30
30
|
| Provider | Usage Data | Status Page |
|
|
31
31
|
|----------|-----------|-------------|
|
|
32
|
-
| Anthropic (Claude) | 5h/
|
|
32
|
+
| Anthropic (Claude) | 5h/Week windows, extra usage | ✅ |
|
|
33
33
|
| GitHub Copilot | Monthly quota, requests | ✅ |
|
|
34
34
|
| Google Gemini | Pro/Flash quotas | ✅ |
|
|
35
|
-
| Antigravity |
|
|
35
|
+
| Antigravity | Model quotas | ✅ |
|
|
36
36
|
| OpenAI Codex | Primary/secondary windows | ✅ |
|
|
37
37
|
| AWS Kiro | Credits | - |
|
|
38
38
|
| z.ai | Tokens/monthly limits | - |
|
|
@@ -41,27 +41,25 @@ https://github.com/user-attachments/assets/d61d82f6-afd0-45fc-82f3-69910543aa7a
|
|
|
41
41
|
|
|
42
42
|
| Provider | Usage Windows | Extra Info | Status Indicator | Tested | Notes |
|
|
43
43
|
|----------|--------------|------------|------------------|--------|-------|
|
|
44
|
-
| Anthropic (Claude) | 5h,
|
|
44
|
+
| Anthropic (Claude) | 5h, Week, Extra | Extra usage label | ✅ | ✅ | Extra usage can show on/off state |
|
|
45
45
|
| GitHub Copilot | Month | Model multiplier + requests left | ✅ | ✅ | Requests left uses model multiplier |
|
|
46
46
|
| Google Gemini | Pro, Flash | - | ✅ | - | Quotas aggregated per model family |
|
|
47
|
-
| Antigravity |
|
|
47
|
+
| Antigravity | Models | - | ✅ | ✅ | Sandbox Cloud Code Assist quotas |
|
|
48
48
|
| OpenAI Codex | Primary, Secondary | - | ✅ | ✅ | Credits not yet supported (PRs welcome!) |
|
|
49
49
|
| AWS Kiro | Credits | - | - | - | - |
|
|
50
50
|
| z.ai | Tokens, Monthly | - | - | - | API quota limits |
|
|
51
51
|
|
|
52
52
|
## Installation
|
|
53
53
|
|
|
54
|
-
Install via the pi package manager (recommended). `sub-bar`
|
|
54
|
+
Install via the pi package manager (recommended). `sub-bar` bundles `sub-core`, so you only need to install `sub-bar`:
|
|
55
55
|
|
|
56
56
|
```bash
|
|
57
|
-
pi install npm:@marckrenn/pi-sub-core
|
|
58
57
|
pi install npm:@marckrenn/pi-sub-bar
|
|
59
58
|
```
|
|
60
59
|
|
|
61
60
|
Use `-l` to install into project settings instead of global:
|
|
62
61
|
|
|
63
62
|
```bash
|
|
64
|
-
pi install -l npm:@marckrenn/pi-sub-core
|
|
65
63
|
pi install -l npm:@marckrenn/pi-sub-bar
|
|
66
64
|
```
|
|
67
65
|
|
|
@@ -69,17 +67,19 @@ Manual install (local development):
|
|
|
69
67
|
|
|
70
68
|
```bash
|
|
71
69
|
git clone https://github.com/marckrenn/pi-sub.git
|
|
70
|
+
cd pi-sub
|
|
71
|
+
npm install
|
|
72
72
|
|
|
73
|
-
ln -s /path/to/pi-sub/packages/sub-core ~/.pi/agent/extensions/sub-core
|
|
74
73
|
ln -s /path/to/pi-sub/packages/sub-bar ~/.pi/agent/extensions/sub-bar
|
|
75
74
|
```
|
|
76
75
|
|
|
77
|
-
|
|
76
|
+
If you want to develop `sub-core` separately, also symlink `packages/sub-core` into `~/.pi/agent/extensions`.
|
|
77
|
+
|
|
78
|
+
Alternative (no symlink): add `sub-bar` to `~/.pi/agent/settings.json`:
|
|
78
79
|
|
|
79
80
|
```json
|
|
80
81
|
{
|
|
81
82
|
"extensions": [
|
|
82
|
-
"/path/to/pi-sub/packages/sub-core/index.ts",
|
|
83
83
|
"/path/to/pi-sub/packages/sub-bar/index.ts"
|
|
84
84
|
]
|
|
85
85
|
}
|
|
@@ -142,7 +142,6 @@ Credentials are loaded by sub-core from:
|
|
|
142
142
|
Pi packages use a `pi` field in `package.json` plus the `pi-package` keyword for discoverability. This repo already declares `pi.extensions`, so you can install via:
|
|
143
143
|
|
|
144
144
|
```bash
|
|
145
|
-
pi install npm:@marckrenn/pi-sub-core
|
|
146
145
|
pi install npm:@marckrenn/pi-sub-bar
|
|
147
146
|
```
|
|
148
147
|
|
|
@@ -186,7 +185,7 @@ npm run check
|
|
|
186
185
|
|
|
187
186
|
## Credits
|
|
188
187
|
|
|
189
|
-
- Hannes Januschka ([
|
|
188
|
+
- ~Hannes~ Helmut Januschka ([usage-bar.ts](https://github.com/hjanuschka/shitty-extensions?tab=readme-ov-file#usage-barts), [@hjanuschka](https://x.com/hjanuschka))
|
|
190
189
|
- Peter Steinberger ([CodexBar](https://github.com/steipete/CodexBar), [@steipete](https://x.com/steipete))
|
|
191
190
|
|
|
192
191
|
## License
|
package/index.ts
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
import type { ExtensionAPI, ExtensionContext, Theme, ThemeColor } from "@mariozechner/pi-coding-agent";
|
|
8
8
|
import { Container, Input, SelectList, Spacer, Text, truncateToWidth, wrapTextWithAnsi, visibleWidth } from "@mariozechner/pi-tui";
|
|
9
9
|
import * as fs from "node:fs";
|
|
10
|
+
import { homedir } from "node:os";
|
|
11
|
+
import { join } from "node:path";
|
|
10
12
|
import type { ProviderName, ProviderUsageEntry, SubCoreAllState, SubCoreState, UsageSnapshot } from "./src/types.js";
|
|
11
13
|
import type { Settings, BaseTextColor } from "./src/settings-types.js";
|
|
12
14
|
import { isBackgroundColor, resolveBaseTextColor, resolveDividerColor } from "./src/settings-types.js";
|
|
@@ -58,6 +60,56 @@ function applyBaseTextColor(theme: Theme, color: BaseTextColor, text: string): s
|
|
|
58
60
|
return theme.fg(resolveDividerColor(color), text);
|
|
59
61
|
}
|
|
60
62
|
|
|
63
|
+
type PiSettings = {
|
|
64
|
+
enabledModels?: unknown;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const AGENT_SETTINGS_ENV = "PI_CODING_AGENT_DIR";
|
|
68
|
+
const DEFAULT_AGENT_DIR = join(homedir(), ".pi", "agent");
|
|
69
|
+
const PROJECT_SETTINGS_DIR = ".pi";
|
|
70
|
+
const SETTINGS_FILE_NAME = "settings.json";
|
|
71
|
+
|
|
72
|
+
function expandTilde(value: string): string {
|
|
73
|
+
if (value === "~") return homedir();
|
|
74
|
+
if (value.startsWith("~/")) return join(homedir(), value.slice(2));
|
|
75
|
+
return value;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function resolveAgentSettingsPath(): string {
|
|
79
|
+
const envDir = process.env[AGENT_SETTINGS_ENV];
|
|
80
|
+
const agentDir = envDir ? expandTilde(envDir) : DEFAULT_AGENT_DIR;
|
|
81
|
+
return join(agentDir, SETTINGS_FILE_NAME);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function readPiSettings(path: string): PiSettings | null {
|
|
85
|
+
try {
|
|
86
|
+
if (!fs.existsSync(path)) return null;
|
|
87
|
+
const content = fs.readFileSync(path, "utf-8");
|
|
88
|
+
return JSON.parse(content) as PiSettings;
|
|
89
|
+
} catch {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function loadScopedModelPatterns(cwd: string): string[] {
|
|
95
|
+
const globalSettings = readPiSettings(resolveAgentSettingsPath());
|
|
96
|
+
const projectSettingsPath = join(cwd, PROJECT_SETTINGS_DIR, SETTINGS_FILE_NAME);
|
|
97
|
+
const projectSettings = readPiSettings(projectSettingsPath);
|
|
98
|
+
|
|
99
|
+
let enabledModels = Array.isArray(globalSettings?.enabledModels)
|
|
100
|
+
? (globalSettings?.enabledModels as string[])
|
|
101
|
+
: undefined;
|
|
102
|
+
|
|
103
|
+
if (projectSettings && Object.prototype.hasOwnProperty.call(projectSettings, "enabledModels")) {
|
|
104
|
+
enabledModels = Array.isArray(projectSettings.enabledModels)
|
|
105
|
+
? (projectSettings.enabledModels as string[])
|
|
106
|
+
: [];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (!enabledModels || enabledModels.length === 0) return [];
|
|
110
|
+
return enabledModels.filter((value) => typeof value === "string");
|
|
111
|
+
}
|
|
112
|
+
|
|
61
113
|
/**
|
|
62
114
|
* Create the extension
|
|
63
115
|
*/
|
|
@@ -69,6 +121,7 @@ export default function createExtension(pi: ExtensionAPI) {
|
|
|
69
121
|
let coreAvailable = false;
|
|
70
122
|
let coreSettings: CoreSettings = getFallbackCoreSettings(settings);
|
|
71
123
|
let fetchFailureTimer: NodeJS.Timeout | undefined;
|
|
124
|
+
const antigravityHiddenModels = new Set(["tab_flash_lite_preview"]);
|
|
72
125
|
let settingsWatcher: fs.FSWatcher | undefined;
|
|
73
126
|
let settingsPoll: NodeJS.Timeout | undefined;
|
|
74
127
|
let settingsDebounce: NodeJS.Timeout | undefined;
|
|
@@ -233,11 +286,15 @@ export default function createExtension(pi: ExtensionAPI) {
|
|
|
233
286
|
const wantsSplit = alignment === "split";
|
|
234
287
|
const shouldAlign = !hasFill && !wantsSplit && (alignment === "center" || alignment === "right");
|
|
235
288
|
const baseTextColor = resolveBaseTextColor(settings.display.baseTextColor);
|
|
289
|
+
const scopedModelPatterns = loadScopedModelPatterns(ctx.cwd);
|
|
290
|
+
const modelInfo = ctx.model
|
|
291
|
+
? { provider: ctx.model.provider, id: ctx.model.id, scopedModelPatterns }
|
|
292
|
+
: { scopedModelPatterns };
|
|
236
293
|
const formatted = message
|
|
237
294
|
? applyBaseTextColor(theme, baseTextColor, message)
|
|
238
295
|
: (hasFill || wantsSplit)
|
|
239
|
-
? formatUsageStatusWithWidth(theme, usage!, contentWidth,
|
|
240
|
-
: formatUsageStatus(theme, usage!,
|
|
296
|
+
? formatUsageStatusWithWidth(theme, usage!, contentWidth, modelInfo, settings, { labelGapFill: wantsSplit })
|
|
297
|
+
: formatUsageStatus(theme, usage!, modelInfo, settings);
|
|
241
298
|
|
|
242
299
|
const alignLine = (line: string) => {
|
|
243
300
|
if (!shouldAlign) return line;
|
|
@@ -251,7 +308,7 @@ export default function createExtension(pi: ExtensionAPI) {
|
|
|
251
308
|
let lines: string[] = [];
|
|
252
309
|
if (!formatted) {
|
|
253
310
|
lines = [];
|
|
254
|
-
} else if (settings.display.
|
|
311
|
+
} else if (settings.display.overflow === "wrap") {
|
|
255
312
|
lines = wrapTextWithAnsi(formatted, contentWidth).map(alignLine);
|
|
256
313
|
} else {
|
|
257
314
|
const trimmed = alignLine(truncateToWidth(formatted, contentWidth, theme.fg("dim", "...")));
|
|
@@ -295,6 +352,46 @@ export default function createExtension(pi: ExtensionAPI) {
|
|
|
295
352
|
return currentUsage;
|
|
296
353
|
}
|
|
297
354
|
|
|
355
|
+
function syncAntigravityModels(usage?: UsageSnapshot): void {
|
|
356
|
+
if (!usage || usage.provider !== "antigravity") return;
|
|
357
|
+
const normalizeModel = (label: string) => label.toLowerCase().replace(/\s+/g, "_");
|
|
358
|
+
const labels = usage.windows
|
|
359
|
+
.map((window) => window.label?.trim())
|
|
360
|
+
.filter((label): label is string => Boolean(label))
|
|
361
|
+
.filter((label) => !antigravityHiddenModels.has(normalizeModel(label)));
|
|
362
|
+
const uniqueModels = Array.from(new Set(labels));
|
|
363
|
+
const antigravitySettings = settings.providers.antigravity;
|
|
364
|
+
const visibility = { ...(antigravitySettings.modelVisibility ?? {}) };
|
|
365
|
+
const modelSet = new Set(uniqueModels);
|
|
366
|
+
let changed = false;
|
|
367
|
+
|
|
368
|
+
for (const model of uniqueModels) {
|
|
369
|
+
if (!(model in visibility)) {
|
|
370
|
+
visibility[model] = false;
|
|
371
|
+
changed = true;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
for (const existing of Object.keys(visibility)) {
|
|
376
|
+
if (!modelSet.has(existing)) {
|
|
377
|
+
delete visibility[existing];
|
|
378
|
+
changed = true;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const currentOrder = antigravitySettings.modelOrder ?? [];
|
|
383
|
+
const orderChanged = currentOrder.length !== uniqueModels.length
|
|
384
|
+
|| currentOrder.some((model, index) => model !== uniqueModels[index]);
|
|
385
|
+
if (orderChanged) {
|
|
386
|
+
changed = true;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (!changed) return;
|
|
390
|
+
antigravitySettings.modelVisibility = visibility;
|
|
391
|
+
antigravitySettings.modelOrder = uniqueModels;
|
|
392
|
+
saveSettings(settings);
|
|
393
|
+
}
|
|
394
|
+
|
|
298
395
|
function updateEntries(entries: ProviderUsageEntry[] | undefined): void {
|
|
299
396
|
if (!entries) return;
|
|
300
397
|
const next: Partial<Record<ProviderName, UsageSnapshot>> = {};
|
|
@@ -303,6 +400,7 @@ export default function createExtension(pi: ExtensionAPI) {
|
|
|
303
400
|
next[entry.provider] = entry.usage;
|
|
304
401
|
}
|
|
305
402
|
usageEntries = next;
|
|
403
|
+
syncAntigravityModels(next.antigravity);
|
|
306
404
|
updateFetchFailureTicker();
|
|
307
405
|
}
|
|
308
406
|
|
|
@@ -323,7 +421,7 @@ export default function createExtension(pi: ExtensionAPI) {
|
|
|
323
421
|
|
|
324
422
|
function renderCurrent(ctx: ExtensionContext): void {
|
|
325
423
|
if (!coreAvailable) {
|
|
326
|
-
renderUsageWidget(ctx, undefined, "sub-core
|
|
424
|
+
renderUsageWidget(ctx, undefined, "pi-sub-core required. install with: pi install npm:@marckrenn/pi-sub-core");
|
|
327
425
|
return;
|
|
328
426
|
}
|
|
329
427
|
const usage = resolveDisplayedUsage();
|
|
@@ -332,6 +430,7 @@ export default function createExtension(pi: ExtensionAPI) {
|
|
|
332
430
|
|
|
333
431
|
function updateUsage(usage: UsageSnapshot | undefined): void {
|
|
334
432
|
currentUsage = usage;
|
|
433
|
+
syncAntigravityModels(usage);
|
|
335
434
|
updateFetchFailureTicker();
|
|
336
435
|
if (lastContext) {
|
|
337
436
|
renderCurrent(lastContext);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@marckrenn/pi-sub-bar",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Usage widget extension for pi-coding-agent - shows current provider usage above the editor",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package"
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
},
|
|
14
14
|
"pi": {
|
|
15
15
|
"extensions": [
|
|
16
|
-
"./index.ts"
|
|
16
|
+
"./index.ts",
|
|
17
|
+
"node_modules/@marckrenn/pi-sub-core/index.ts"
|
|
17
18
|
]
|
|
18
19
|
},
|
|
19
20
|
"scripts": {
|
|
@@ -28,9 +29,16 @@
|
|
|
28
29
|
"typescript": "^5.8.0"
|
|
29
30
|
},
|
|
30
31
|
"dependencies": {
|
|
31
|
-
"@marckrenn/pi-sub-
|
|
32
|
+
"@marckrenn/pi-sub-core": "^1.0.2",
|
|
33
|
+
"@marckrenn/pi-sub-shared": "^1.0.2"
|
|
32
34
|
},
|
|
35
|
+
"bundledDependencies": [
|
|
36
|
+
"@marckrenn/pi-sub-core"
|
|
37
|
+
],
|
|
33
38
|
"peerDependencies": {
|
|
34
39
|
"@mariozechner/pi-coding-agent": "*"
|
|
35
|
-
}
|
|
40
|
+
},
|
|
41
|
+
"bundleDependencies": [
|
|
42
|
+
"@marckrenn/pi-sub-core"
|
|
43
|
+
]
|
|
36
44
|
}
|
package/src/formatting.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import type { Theme } from "@mariozechner/pi-coding-agent";
|
|
6
6
|
import { truncateToWidth, visibleWidth } from "@mariozechner/pi-tui";
|
|
7
|
-
import type { RateWindow, UsageSnapshot, ProviderStatus } from "./types.js";
|
|
7
|
+
import type { RateWindow, UsageSnapshot, ProviderStatus, ModelInfo } from "./types.js";
|
|
8
8
|
import type {
|
|
9
9
|
BaseTextColor,
|
|
10
10
|
BarStyle,
|
|
@@ -29,6 +29,13 @@ export interface UsageWindowParts {
|
|
|
29
29
|
reset: string;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
type ModelInput = ModelInfo | string | undefined;
|
|
33
|
+
|
|
34
|
+
function resolveModelInfo(model?: ModelInput): ModelInfo | undefined {
|
|
35
|
+
if (!model) return undefined;
|
|
36
|
+
return typeof model === "string" ? { id: model } : model;
|
|
37
|
+
}
|
|
38
|
+
|
|
32
39
|
/**
|
|
33
40
|
* Get the characters to use for progress bars
|
|
34
41
|
*/
|
|
@@ -272,7 +279,9 @@ function formatProviderLabel(theme: Theme, usage: UsageSnapshot, settings?: Sett
|
|
|
272
279
|
const baseStatus = showStatus ? usage.status : undefined;
|
|
273
280
|
const lastSuccessAt = usage.lastSuccessAt;
|
|
274
281
|
const elapsed = lastSuccessAt ? formatElapsedSince(lastSuccessAt) : undefined;
|
|
275
|
-
const fetchDescription = elapsed
|
|
282
|
+
const fetchDescription = elapsed
|
|
283
|
+
? (elapsed === "just now" ? "Last upd.: just now" : `Last upd.: ${elapsed} ago`)
|
|
284
|
+
: "Fetch failed";
|
|
276
285
|
const fetchStatus: ProviderStatus | undefined = fetchError
|
|
277
286
|
? { indicator: "minor", description: fetchDescription }
|
|
278
287
|
: undefined;
|
|
@@ -572,7 +581,7 @@ export function formatUsageWindowParts(
|
|
|
572
581
|
export function formatUsageStatus(
|
|
573
582
|
theme: Theme,
|
|
574
583
|
usage: UsageSnapshot,
|
|
575
|
-
|
|
584
|
+
model?: ModelInput,
|
|
576
585
|
settings?: Settings
|
|
577
586
|
): string | undefined {
|
|
578
587
|
const baseTextColor = resolveBaseTextColor(settings?.display.baseTextColor);
|
|
@@ -593,10 +602,12 @@ export function formatUsageStatus(
|
|
|
593
602
|
const parts: string[] = [];
|
|
594
603
|
const isCodex = usage.provider === "codex";
|
|
595
604
|
const invertUsage = isCodex && (settings?.providers.codex.invertUsage ?? false);
|
|
605
|
+
const modelInfo = resolveModelInfo(model);
|
|
606
|
+
const modelId = modelInfo?.id;
|
|
596
607
|
|
|
597
608
|
for (const w of usage.windows) {
|
|
598
609
|
// Skip windows that are disabled in settings
|
|
599
|
-
if (!shouldShowWindow(usage, w, settings)) {
|
|
610
|
+
if (!shouldShowWindow(usage, w, settings, modelInfo)) {
|
|
600
611
|
continue;
|
|
601
612
|
}
|
|
602
613
|
parts.push(formatUsageWindow(theme, w, invertUsage, settings, usage));
|
|
@@ -630,7 +641,7 @@ export function formatUsageStatusWithWidth(
|
|
|
630
641
|
theme: Theme,
|
|
631
642
|
usage: UsageSnapshot,
|
|
632
643
|
width: number,
|
|
633
|
-
|
|
644
|
+
model?: ModelInput,
|
|
634
645
|
settings?: Settings,
|
|
635
646
|
options?: { labelGapFill?: boolean }
|
|
636
647
|
): string | undefined {
|
|
@@ -671,9 +682,11 @@ export function formatUsageStatusWithWidth(
|
|
|
671
682
|
const windows: RateWindow[] = [];
|
|
672
683
|
const isCodex = usage.provider === "codex";
|
|
673
684
|
const invertUsage = isCodex && (settings?.providers.codex.invertUsage ?? false);
|
|
685
|
+
const modelInfo = resolveModelInfo(model);
|
|
686
|
+
const modelId = modelInfo?.id;
|
|
674
687
|
|
|
675
688
|
for (const w of usage.windows) {
|
|
676
|
-
if (!shouldShowWindow(usage, w, settings)) {
|
|
689
|
+
if (!shouldShowWindow(usage, w, settings, modelInfo)) {
|
|
677
690
|
continue;
|
|
678
691
|
}
|
|
679
692
|
windows.push(w);
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
* Provider metadata shared across the extension.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import type { RateWindow, UsageSnapshot, ProviderName } from "../types.js";
|
|
5
|
+
import type { RateWindow, UsageSnapshot, ProviderName, ModelInfo } from "../types.js";
|
|
6
6
|
import type { Settings } from "../settings-types.js";
|
|
7
|
-
import { getModelMultiplier } from "../utils.js";
|
|
7
|
+
import { getModelMultiplier, normalizeTokens } from "../utils.js";
|
|
8
8
|
import { PROVIDER_METADATA as BASE_METADATA, type ProviderMetadata as BaseProviderMetadata } from "@marckrenn/pi-sub-shared";
|
|
9
9
|
|
|
10
10
|
export { PROVIDERS, PROVIDER_DISPLAY_NAMES } from "@marckrenn/pi-sub-shared";
|
|
@@ -15,27 +15,27 @@ export interface UsageExtra {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
export interface ProviderMetadata extends BaseProviderMetadata {
|
|
18
|
-
isWindowVisible?: (usage: UsageSnapshot, window: RateWindow, settings?: Settings) => boolean;
|
|
18
|
+
isWindowVisible?: (usage: UsageSnapshot, window: RateWindow, settings?: Settings, model?: ModelInfo) => boolean;
|
|
19
19
|
getExtras?: (usage: UsageSnapshot, settings?: Settings, modelId?: string) => UsageExtra[];
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
const anthropicWindowVisible: ProviderMetadata["isWindowVisible"] = (_usage, window, settings) => {
|
|
22
|
+
const anthropicWindowVisible: ProviderMetadata["isWindowVisible"] = (_usage, window, settings, _model) => {
|
|
23
23
|
if (!settings) return true;
|
|
24
24
|
const ps = settings.providers.anthropic;
|
|
25
25
|
if (window.label === "5h") return ps.windows.show5h;
|
|
26
|
-
if (window.label === "
|
|
26
|
+
if (window.label === "Week") return ps.windows.show7d;
|
|
27
27
|
if (window.label.startsWith("Extra [")) return ps.windows.showExtra;
|
|
28
28
|
return true;
|
|
29
29
|
};
|
|
30
30
|
|
|
31
|
-
const copilotWindowVisible: ProviderMetadata["isWindowVisible"] = (_usage, window, settings) => {
|
|
31
|
+
const copilotWindowVisible: ProviderMetadata["isWindowVisible"] = (_usage, window, settings, _model) => {
|
|
32
32
|
if (!settings) return true;
|
|
33
33
|
const ps = settings.providers.copilot;
|
|
34
34
|
if (window.label === "Month") return ps.windows.showMonth;
|
|
35
35
|
return true;
|
|
36
36
|
};
|
|
37
37
|
|
|
38
|
-
const geminiWindowVisible: ProviderMetadata["isWindowVisible"] = (_usage, window, settings) => {
|
|
38
|
+
const geminiWindowVisible: ProviderMetadata["isWindowVisible"] = (_usage, window, settings, _model) => {
|
|
39
39
|
if (!settings) return true;
|
|
40
40
|
const ps = settings.providers.gemini;
|
|
41
41
|
if (window.label === "Pro") return ps.windows.showPro;
|
|
@@ -43,16 +43,47 @@ const geminiWindowVisible: ProviderMetadata["isWindowVisible"] = (_usage, window
|
|
|
43
43
|
return true;
|
|
44
44
|
};
|
|
45
45
|
|
|
46
|
-
const antigravityWindowVisible: ProviderMetadata["isWindowVisible"] = (_usage, window, settings) => {
|
|
46
|
+
const antigravityWindowVisible: ProviderMetadata["isWindowVisible"] = (_usage, window, settings, model) => {
|
|
47
47
|
if (!settings) return true;
|
|
48
48
|
const ps = settings.providers.antigravity;
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if (
|
|
52
|
-
|
|
49
|
+
const label = window.label.trim();
|
|
50
|
+
const normalized = label.toLowerCase().replace(/\s+/g, "_");
|
|
51
|
+
if (normalized === "tab_flash_lite_preview") return false;
|
|
52
|
+
|
|
53
|
+
const labelTokens = normalizeTokens(label);
|
|
54
|
+
|
|
55
|
+
const modelProvider = model?.provider?.toLowerCase() ?? "";
|
|
56
|
+
const modelId = model?.id;
|
|
57
|
+
const providerMatches = modelProvider.includes("antigravity");
|
|
58
|
+
if (ps.showCurrentModel && providerMatches && modelId) {
|
|
59
|
+
const modelTokens = normalizeTokens(modelId);
|
|
60
|
+
const match = modelTokens.length > 0 && modelTokens.every((token) => labelTokens.includes(token));
|
|
61
|
+
if (match) return true;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (ps.showScopedModels) {
|
|
65
|
+
const scopedPatterns = model?.scopedModelPatterns ?? [];
|
|
66
|
+
const matchesScoped = scopedPatterns.some((pattern) => {
|
|
67
|
+
if (!pattern) return false;
|
|
68
|
+
const [rawPattern] = pattern.split(":");
|
|
69
|
+
const trimmed = rawPattern?.trim();
|
|
70
|
+
if (!trimmed) return false;
|
|
71
|
+
const hasProvider = trimmed.includes("/");
|
|
72
|
+
if (!hasProvider) return false;
|
|
73
|
+
const providerPart = trimmed.slice(0, trimmed.indexOf("/")).trim().toLowerCase();
|
|
74
|
+
if (!providerPart.includes("antigravity")) return false;
|
|
75
|
+
const base = trimmed.slice(trimmed.lastIndexOf("/") + 1);
|
|
76
|
+
const tokens = normalizeTokens(base);
|
|
77
|
+
return tokens.length > 0 && tokens.every((token) => labelTokens.includes(token));
|
|
78
|
+
});
|
|
79
|
+
if (matchesScoped) return true;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const visibility = ps.modelVisibility?.[label];
|
|
83
|
+
return visibility === true;
|
|
53
84
|
};
|
|
54
85
|
|
|
55
|
-
const codexWindowVisible: ProviderMetadata["isWindowVisible"] = (_usage, window, settings) => {
|
|
86
|
+
const codexWindowVisible: ProviderMetadata["isWindowVisible"] = (_usage, window, settings, _model) => {
|
|
56
87
|
if (!settings) return true;
|
|
57
88
|
const ps = settings.providers.codex;
|
|
58
89
|
if (window.label.match(/^\d+h$/)) return ps.windows.showPrimary;
|
|
@@ -60,14 +91,14 @@ const codexWindowVisible: ProviderMetadata["isWindowVisible"] = (_usage, window,
|
|
|
60
91
|
return true;
|
|
61
92
|
};
|
|
62
93
|
|
|
63
|
-
const kiroWindowVisible: ProviderMetadata["isWindowVisible"] = (_usage, window, settings) => {
|
|
94
|
+
const kiroWindowVisible: ProviderMetadata["isWindowVisible"] = (_usage, window, settings, _model) => {
|
|
64
95
|
if (!settings) return true;
|
|
65
96
|
const ps = settings.providers.kiro;
|
|
66
97
|
if (window.label === "Credits") return ps.windows.showCredits;
|
|
67
98
|
return true;
|
|
68
99
|
};
|
|
69
100
|
|
|
70
|
-
const zaiWindowVisible: ProviderMetadata["isWindowVisible"] = (_usage, window, settings) => {
|
|
101
|
+
const zaiWindowVisible: ProviderMetadata["isWindowVisible"] = (_usage, window, settings, _model) => {
|
|
71
102
|
if (!settings) return true;
|
|
72
103
|
const ps = settings.providers.zai;
|
|
73
104
|
if (window.label === "Tokens") return ps.windows.showTokens;
|
|
@@ -57,10 +57,10 @@ export function buildProviderSettingsItems(settings: Settings, provider: Provide
|
|
|
57
57
|
},
|
|
58
58
|
{
|
|
59
59
|
id: "show7d",
|
|
60
|
-
label: "Show
|
|
60
|
+
label: "Show Week Window",
|
|
61
61
|
currentValue: anthroSettings.windows.show7d ? "on" : "off",
|
|
62
62
|
values: ["on", "off"],
|
|
63
|
-
description: "Show the
|
|
63
|
+
description: "Show the weekly usage window.",
|
|
64
64
|
},
|
|
65
65
|
{
|
|
66
66
|
id: "showExtra",
|
|
@@ -130,27 +130,41 @@ export function buildProviderSettingsItems(settings: Settings, provider: Provide
|
|
|
130
130
|
const antigravitySettings = ps as AntigravityProviderSettings;
|
|
131
131
|
items.push(
|
|
132
132
|
{
|
|
133
|
-
id: "
|
|
134
|
-
label: "Show
|
|
135
|
-
currentValue: antigravitySettings.
|
|
133
|
+
id: "showCurrentModel",
|
|
134
|
+
label: "Always Show Current Model",
|
|
135
|
+
currentValue: antigravitySettings.showCurrentModel ? "on" : "off",
|
|
136
136
|
values: ["on", "off"],
|
|
137
|
-
description: "Show the
|
|
137
|
+
description: "Show the active Antigravity model even if hidden.",
|
|
138
138
|
},
|
|
139
139
|
{
|
|
140
|
-
id: "
|
|
141
|
-
label: "Show
|
|
142
|
-
currentValue: antigravitySettings.
|
|
143
|
-
values: ["on", "off"],
|
|
144
|
-
description: "Show the Gemini Pro quota window.",
|
|
145
|
-
},
|
|
146
|
-
{
|
|
147
|
-
id: "showFlash",
|
|
148
|
-
label: "Show Flash Window",
|
|
149
|
-
currentValue: antigravitySettings.windows.showFlash ? "on" : "off",
|
|
140
|
+
id: "showScopedModels",
|
|
141
|
+
label: "Show Scoped Models",
|
|
142
|
+
currentValue: antigravitySettings.showScopedModels ? "on" : "off",
|
|
150
143
|
values: ["on", "off"],
|
|
151
|
-
description: "Show the
|
|
144
|
+
description: "Show Antigravity models that are in the scoped model rotation.",
|
|
152
145
|
},
|
|
153
146
|
);
|
|
147
|
+
|
|
148
|
+
const modelVisibility = antigravitySettings.modelVisibility ?? {};
|
|
149
|
+
const modelOrder = antigravitySettings.modelOrder?.length
|
|
150
|
+
? antigravitySettings.modelOrder
|
|
151
|
+
: Object.keys(modelVisibility).sort((a, b) => a.localeCompare(b));
|
|
152
|
+
const seenModels = new Set<string>();
|
|
153
|
+
|
|
154
|
+
for (const model of modelOrder) {
|
|
155
|
+
if (!model || seenModels.has(model)) continue;
|
|
156
|
+
seenModels.add(model);
|
|
157
|
+
const normalized = model.toLowerCase().replace(/\s+/g, "_");
|
|
158
|
+
if (normalized === "tab_flash_lite_preview") continue;
|
|
159
|
+
const visible = modelVisibility[model] !== false;
|
|
160
|
+
items.push({
|
|
161
|
+
id: `model:${model}`,
|
|
162
|
+
label: model,
|
|
163
|
+
currentValue: visible ? "on" : "off",
|
|
164
|
+
values: ["on", "off"],
|
|
165
|
+
description: "Toggle this model window.",
|
|
166
|
+
});
|
|
167
|
+
}
|
|
154
168
|
}
|
|
155
169
|
|
|
156
170
|
if (provider === "codex") {
|
|
@@ -276,14 +290,31 @@ export function applyProviderSettingsChange(
|
|
|
276
290
|
if (provider === "antigravity") {
|
|
277
291
|
const antigravitySettings = ps as AntigravityProviderSettings;
|
|
278
292
|
switch (id) {
|
|
279
|
-
case "
|
|
280
|
-
antigravitySettings.windows.
|
|
293
|
+
case "showModels":
|
|
294
|
+
antigravitySettings.windows.showModels = value === "on";
|
|
281
295
|
break;
|
|
282
|
-
case "
|
|
283
|
-
antigravitySettings.
|
|
296
|
+
case "showCurrentModel":
|
|
297
|
+
antigravitySettings.showCurrentModel = value === "on";
|
|
284
298
|
break;
|
|
285
|
-
case "
|
|
286
|
-
antigravitySettings.
|
|
299
|
+
case "showScopedModels":
|
|
300
|
+
antigravitySettings.showScopedModels = value === "on";
|
|
301
|
+
break;
|
|
302
|
+
default:
|
|
303
|
+
if (id.startsWith("model:")) {
|
|
304
|
+
const model = id.slice("model:".length);
|
|
305
|
+
if (model) {
|
|
306
|
+
if (!antigravitySettings.modelVisibility) {
|
|
307
|
+
antigravitySettings.modelVisibility = {};
|
|
308
|
+
}
|
|
309
|
+
antigravitySettings.modelVisibility[model] = value === "on";
|
|
310
|
+
if (!antigravitySettings.modelOrder) {
|
|
311
|
+
antigravitySettings.modelOrder = [];
|
|
312
|
+
}
|
|
313
|
+
if (!antigravitySettings.modelOrder.includes(model)) {
|
|
314
|
+
antigravitySettings.modelOrder.push(model);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
287
318
|
break;
|
|
288
319
|
}
|
|
289
320
|
}
|
package/src/providers/windows.ts
CHANGED
|
@@ -2,17 +2,22 @@
|
|
|
2
2
|
* Provider-specific window visibility rules.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import type { RateWindow, UsageSnapshot } from "../types.js";
|
|
5
|
+
import type { RateWindow, UsageSnapshot, ModelInfo } from "../types.js";
|
|
6
6
|
import type { Settings } from "../settings-types.js";
|
|
7
7
|
import { PROVIDER_METADATA } from "./metadata.js";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Check if a window should be shown based on settings.
|
|
11
11
|
*/
|
|
12
|
-
export function shouldShowWindow(
|
|
12
|
+
export function shouldShowWindow(
|
|
13
|
+
usage: UsageSnapshot,
|
|
14
|
+
window: RateWindow,
|
|
15
|
+
settings?: Settings,
|
|
16
|
+
model?: ModelInfo
|
|
17
|
+
): boolean {
|
|
13
18
|
const handler = PROVIDER_METADATA[usage.provider]?.isWindowVisible;
|
|
14
19
|
if (handler) {
|
|
15
|
-
return handler(usage, window, settings);
|
|
20
|
+
return handler(usage, window, settings, model);
|
|
16
21
|
}
|
|
17
22
|
return true;
|
|
18
23
|
}
|
package/src/settings/display.ts
CHANGED
|
@@ -42,11 +42,11 @@ export function buildDisplayLayoutItems(settings: Settings): SettingItem[] {
|
|
|
42
42
|
description: "Align the usage line inside the widget.",
|
|
43
43
|
},
|
|
44
44
|
{
|
|
45
|
-
id: "
|
|
46
|
-
label: "
|
|
47
|
-
currentValue: settings.display.
|
|
45
|
+
id: "overflow",
|
|
46
|
+
label: "Overflow",
|
|
47
|
+
currentValue: settings.display.overflow,
|
|
48
48
|
values: ["truncate", "wrap"] as WidgetWrapping[],
|
|
49
|
-
description: "Wrap the usage line or truncate with ellipsis.",
|
|
49
|
+
description: "Wrap the usage line or truncate with ellipsis (requires bar width ≠ fill and alignment ≠ split).",
|
|
50
50
|
},
|
|
51
51
|
{
|
|
52
52
|
id: "paddingX",
|
|
@@ -666,8 +666,11 @@ export function applyDisplayChange(settings: Settings, id: string, value: string
|
|
|
666
666
|
case "showBottomDivider":
|
|
667
667
|
settings.display.showBottomDivider = value === "on";
|
|
668
668
|
break;
|
|
669
|
+
case "overflow":
|
|
670
|
+
settings.display.overflow = value as WidgetWrapping;
|
|
671
|
+
break;
|
|
669
672
|
case "widgetWrapping":
|
|
670
|
-
settings.display.
|
|
673
|
+
settings.display.overflow = value as WidgetWrapping;
|
|
671
674
|
break;
|
|
672
675
|
case "errorThreshold": {
|
|
673
676
|
const parsed = parseClampedNumber(value, 0, 100);
|
package/src/settings/menu.ts
CHANGED
|
@@ -22,7 +22,7 @@ export function buildMainMenuItems(settings: Settings, pinnedProvider?: Provider
|
|
|
22
22
|
{
|
|
23
23
|
value: "providers",
|
|
24
24
|
label: "Provider Settings",
|
|
25
|
-
description:
|
|
25
|
+
description: "provider specific settings",
|
|
26
26
|
tooltip: "Configure provider display toggles and window visibility.",
|
|
27
27
|
},
|
|
28
28
|
{
|
|
@@ -134,9 +134,9 @@ export function buildDisplayThemeMenuItems(): TooltipSelectItem[] {
|
|
|
134
134
|
},
|
|
135
135
|
{
|
|
136
136
|
value: "display-theme-load",
|
|
137
|
-
label: "Load
|
|
138
|
-
description: "restore
|
|
139
|
-
tooltip: "Load
|
|
137
|
+
label: "Manage & Load themes",
|
|
138
|
+
description: "load, share, delete and restore themes",
|
|
139
|
+
tooltip: "Load, share, delete, and restore saved themes.",
|
|
140
140
|
},
|
|
141
141
|
{
|
|
142
142
|
value: "display-theme-import",
|
|
@@ -148,7 +148,13 @@ export function buildDisplayThemeMenuItems(): TooltipSelectItem[] {
|
|
|
148
148
|
value: "display-theme-random",
|
|
149
149
|
label: "Random theme",
|
|
150
150
|
description: "generate a new theme",
|
|
151
|
-
tooltip: "Generate a
|
|
151
|
+
tooltip: "Generate a random display theme as inspiration or a starting point.",
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
value: "display-theme-restore",
|
|
155
|
+
label: "Restore previous state",
|
|
156
|
+
description: "restore your last theme",
|
|
157
|
+
tooltip: "Restore your previous display theme.",
|
|
152
158
|
},
|
|
153
159
|
];
|
|
154
160
|
}
|
package/src/settings/themes.ts
CHANGED
|
@@ -9,7 +9,7 @@ type BarWidth = DisplaySettings["barWidth"];
|
|
|
9
9
|
type DividerCharacter = DisplaySettings["dividerCharacter"];
|
|
10
10
|
type DividerBlanks = DisplaySettings["dividerBlanks"];
|
|
11
11
|
type DisplayAlignment = DisplaySettings["alignment"];
|
|
12
|
-
type
|
|
12
|
+
type OverflowMode = DisplaySettings["overflow"];
|
|
13
13
|
type BaseTextColor = DisplaySettings["baseTextColor"];
|
|
14
14
|
type DividerColor = DisplaySettings["dividerColor"];
|
|
15
15
|
type ResetTimeFormat = DisplaySettings["resetTimeFormat"];
|
|
@@ -34,7 +34,7 @@ const RANDOM_BAR_CHARACTERS: BarCharacter[] = [
|
|
|
34
34
|
"🚀_",
|
|
35
35
|
];
|
|
36
36
|
const RANDOM_ALIGNMENTS: DisplayAlignment[] = ["left", "center", "right", "split"];
|
|
37
|
-
const
|
|
37
|
+
const RANDOM_OVERFLOW: OverflowMode[] = ["truncate", "wrap"];
|
|
38
38
|
const RANDOM_RESET_POSITIONS: DisplaySettings["resetTimePosition"][] = ["off", "front", "back", "integrated"];
|
|
39
39
|
const RANDOM_RESET_FORMATS: ResetTimeFormat[] = ["relative", "datetime"];
|
|
40
40
|
const RANDOM_RESET_CONTAINMENTS: ResetTimerContainment[] = ["none", "blank", "()", "[]", "<>"];
|
|
@@ -189,7 +189,7 @@ export function resolveDisplayThemeTarget(
|
|
|
189
189
|
widgetPlacement: "belowEditor",
|
|
190
190
|
errorThreshold: 25,
|
|
191
191
|
warningThreshold: 50,
|
|
192
|
-
|
|
192
|
+
overflow: "truncate",
|
|
193
193
|
successThreshold: 75,
|
|
194
194
|
},
|
|
195
195
|
deletable: false,
|
|
@@ -208,7 +208,7 @@ export function buildRandomDisplay(base: DisplaySettings): DisplaySettings {
|
|
|
208
208
|
const display: DisplaySettings = { ...base };
|
|
209
209
|
|
|
210
210
|
display.alignment = pickRandom(RANDOM_ALIGNMENTS);
|
|
211
|
-
display.
|
|
211
|
+
display.overflow = pickRandom(RANDOM_OVERFLOW);
|
|
212
212
|
display.paddingX = pickRandom(RANDOM_PADDING);
|
|
213
213
|
display.barStyle = pickRandom(RANDOM_BAR_STYLES);
|
|
214
214
|
display.barType = pickRandom(RANDOM_BAR_TYPES);
|
package/src/settings/ui.ts
CHANGED
|
@@ -62,6 +62,7 @@ type SettingsCategory =
|
|
|
62
62
|
| "display-theme-import"
|
|
63
63
|
| "display-theme-import-action"
|
|
64
64
|
| "display-theme-random"
|
|
65
|
+
| "display-theme-restore"
|
|
65
66
|
| "display-layout"
|
|
66
67
|
| "display-bar"
|
|
67
68
|
| "display-provider"
|
|
@@ -100,6 +101,7 @@ export async function showSettingsUI(
|
|
|
100
101
|
let themeActionTarget: { id?: string; name: string; display: Settings["display"]; deletable: boolean } | null = null;
|
|
101
102
|
let displayPreviewBackup: Settings["display"] | null = null;
|
|
102
103
|
let randomThemeBackup: Settings["display"] | null = null;
|
|
104
|
+
let displayThemeSelection: string | null = null;
|
|
103
105
|
let pinnedProviderBackup: ProviderName | null | undefined;
|
|
104
106
|
let importCandidate: DecodedDisplayShare | null = null;
|
|
105
107
|
let importBackup: Settings["display"] | null = null;
|
|
@@ -326,6 +328,7 @@ export async function showSettingsUI(
|
|
|
326
328
|
"display-theme-load": "Load Theme",
|
|
327
329
|
"display-theme-action": "Manage Theme",
|
|
328
330
|
"display-theme-import": "Import Theme",
|
|
331
|
+
"display-theme-restore": "Restore Theme",
|
|
329
332
|
"display-layout": "Layout & Structure",
|
|
330
333
|
"display-bar": "Bars",
|
|
331
334
|
"display-provider": "Labels & Text",
|
|
@@ -547,8 +550,15 @@ export async function showSettingsUI(
|
|
|
547
550
|
scrollInfo: (t: string) => theme.fg("dim", t),
|
|
548
551
|
noMatch: (t: string) => theme.fg("warning", t),
|
|
549
552
|
});
|
|
553
|
+
if (displayThemeSelection) {
|
|
554
|
+
const index = items.findIndex((item) => item.value === displayThemeSelection);
|
|
555
|
+
if (index >= 0) {
|
|
556
|
+
selectList.setSelectedIndex(index);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
550
559
|
attachTooltip(items, selectList);
|
|
551
560
|
selectList.onSelect = (item) => {
|
|
561
|
+
displayThemeSelection = item.value;
|
|
552
562
|
currentCategory = item.value as SettingsCategory;
|
|
553
563
|
rebuild();
|
|
554
564
|
tui.requestRender();
|
|
@@ -651,6 +661,7 @@ export async function showSettingsUI(
|
|
|
651
661
|
randomThemeBackup = { ...settings.display };
|
|
652
662
|
settings.displayUserTheme = { ...randomThemeBackup };
|
|
653
663
|
}
|
|
664
|
+
displayThemeSelection = "display-theme-random";
|
|
654
665
|
const randomDisplay = buildRandomDisplay(settings.display);
|
|
655
666
|
settings.display = { ...randomDisplay };
|
|
656
667
|
saveSettings(settings);
|
|
@@ -658,6 +669,23 @@ export async function showSettingsUI(
|
|
|
658
669
|
currentCategory = "display-theme";
|
|
659
670
|
rebuild();
|
|
660
671
|
tui.requestRender();
|
|
672
|
+
} else if (currentCategory === "display-theme-restore") {
|
|
673
|
+
displayThemeSelection = "display-theme-restore";
|
|
674
|
+
const defaults = getDefaultSettings();
|
|
675
|
+
const fallbackUser = settings.displayUserTheme ?? settings.display;
|
|
676
|
+
const target = resolveDisplayThemeTarget("user", settings, defaults, fallbackUser);
|
|
677
|
+
if (target) {
|
|
678
|
+
const backup = displayPreviewBackup ?? settings.display;
|
|
679
|
+
settings.displayUserTheme = { ...backup };
|
|
680
|
+
settings.display = { ...target.display };
|
|
681
|
+
saveSettings(settings);
|
|
682
|
+
if (onSettingsChange) void onSettingsChange(settings);
|
|
683
|
+
if (onDisplayThemeApplied) void onDisplayThemeApplied(target.name, { source: "manual" });
|
|
684
|
+
displayPreviewBackup = null;
|
|
685
|
+
}
|
|
686
|
+
currentCategory = "display-theme";
|
|
687
|
+
rebuild();
|
|
688
|
+
tui.requestRender();
|
|
661
689
|
} else if (currentCategory === "display-theme-import") {
|
|
662
690
|
const input = new Input();
|
|
663
691
|
input.focused = true;
|
|
@@ -1018,7 +1046,8 @@ export async function showSettingsUI(
|
|
|
1018
1046
|
currentCategory === "display-theme" ||
|
|
1019
1047
|
currentCategory === "display-theme-load" ||
|
|
1020
1048
|
currentCategory === "display-theme-action" ||
|
|
1021
|
-
currentCategory === "display-theme-random"
|
|
1049
|
+
currentCategory === "display-theme-random" ||
|
|
1050
|
+
currentCategory === "display-theme-restore"
|
|
1022
1051
|
) {
|
|
1023
1052
|
helpText = "↑↓ navigate • Enter/Space select • Esc back";
|
|
1024
1053
|
} else {
|
package/src/settings-types.ts
CHANGED
|
@@ -45,9 +45,10 @@ export type DividerCharacter =
|
|
|
45
45
|
| (string & {});
|
|
46
46
|
|
|
47
47
|
/**
|
|
48
|
-
* Widget
|
|
48
|
+
* Widget overflow mode
|
|
49
49
|
*/
|
|
50
|
-
export type
|
|
50
|
+
export type OverflowMode = "truncate" | "wrap";
|
|
51
|
+
export type WidgetWrapping = OverflowMode;
|
|
51
52
|
|
|
52
53
|
/**
|
|
53
54
|
* Widget placement
|
|
@@ -223,11 +224,13 @@ export interface GeminiProviderSettings extends BaseProviderSettings {
|
|
|
223
224
|
}
|
|
224
225
|
|
|
225
226
|
export interface AntigravityProviderSettings extends BaseProviderSettings {
|
|
227
|
+
showCurrentModel: boolean;
|
|
228
|
+
showScopedModels: boolean;
|
|
226
229
|
windows: {
|
|
227
|
-
|
|
228
|
-
showPro: boolean;
|
|
229
|
-
showFlash: boolean;
|
|
230
|
+
showModels: boolean;
|
|
230
231
|
};
|
|
232
|
+
modelVisibility: Record<string, boolean>;
|
|
233
|
+
modelOrder: string[];
|
|
231
234
|
}
|
|
232
235
|
|
|
233
236
|
export interface CodexProviderSettings extends BaseProviderSettings {
|
|
@@ -335,8 +338,8 @@ export interface DisplaySettings {
|
|
|
335
338
|
showTopDivider: boolean;
|
|
336
339
|
/** Show divider line below the bar */
|
|
337
340
|
showBottomDivider: boolean;
|
|
338
|
-
/** Widget
|
|
339
|
-
|
|
341
|
+
/** Widget overflow mode */
|
|
342
|
+
overflow: OverflowMode;
|
|
340
343
|
/** Left/right padding inside widget */
|
|
341
344
|
paddingX: number;
|
|
342
345
|
/** Widget placement */
|
|
@@ -412,12 +415,14 @@ export function getDefaultSettings(): Settings {
|
|
|
412
415
|
},
|
|
413
416
|
},
|
|
414
417
|
antigravity: {
|
|
415
|
-
showStatus:
|
|
418
|
+
showStatus: true,
|
|
419
|
+
showCurrentModel: true,
|
|
420
|
+
showScopedModels: true,
|
|
416
421
|
windows: {
|
|
417
|
-
|
|
418
|
-
showPro: true,
|
|
419
|
-
showFlash: true,
|
|
422
|
+
showModels: true,
|
|
420
423
|
},
|
|
424
|
+
modelVisibility: {},
|
|
425
|
+
modelOrder: [],
|
|
421
426
|
},
|
|
422
427
|
codex: {
|
|
423
428
|
showStatus: true,
|
|
@@ -486,7 +491,7 @@ export function getDefaultSettings(): Settings {
|
|
|
486
491
|
widgetPlacement: "belowEditor",
|
|
487
492
|
errorThreshold: 25,
|
|
488
493
|
warningThreshold: 50,
|
|
489
|
-
|
|
494
|
+
overflow: "truncate",
|
|
490
495
|
successThreshold: 75,
|
|
491
496
|
},
|
|
492
497
|
|
|
@@ -538,5 +543,28 @@ function deepMerge<T extends object>(target: T, source: Partial<T>): T {
|
|
|
538
543
|
* Merge settings with defaults (no legacy migrations).
|
|
539
544
|
*/
|
|
540
545
|
export function mergeSettings(loaded: Partial<Settings>): Settings {
|
|
541
|
-
|
|
546
|
+
const migrated = migrateSettings(loaded);
|
|
547
|
+
return deepMerge(getDefaultSettings(), migrated);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
function migrateDisplaySettings(display?: Partial<DisplaySettings> | null): void {
|
|
551
|
+
if (!display) return;
|
|
552
|
+
const displayAny = display as Partial<DisplaySettings> & { widgetWrapping?: OverflowMode };
|
|
553
|
+
if (displayAny.widgetWrapping !== undefined && displayAny.overflow === undefined) {
|
|
554
|
+
displayAny.overflow = displayAny.widgetWrapping;
|
|
555
|
+
}
|
|
556
|
+
if ("widgetWrapping" in displayAny) {
|
|
557
|
+
delete (displayAny as { widgetWrapping?: unknown }).widgetWrapping;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
function migrateSettings(loaded: Partial<Settings>): Partial<Settings> {
|
|
562
|
+
migrateDisplaySettings(loaded.display);
|
|
563
|
+
migrateDisplaySettings(loaded.displayUserTheme);
|
|
564
|
+
if (Array.isArray(loaded.displayThemes)) {
|
|
565
|
+
for (const theme of loaded.displayThemes) {
|
|
566
|
+
migrateDisplaySettings(theme.display as Partial<DisplaySettings> | undefined);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
return loaded;
|
|
542
570
|
}
|
package/src/types.ts
CHANGED
package/src/utils.ts
CHANGED
package/test/formatting.test.ts
CHANGED
|
@@ -172,7 +172,7 @@ test("fetch errors rely on status text instead of appended warning", () => {
|
|
|
172
172
|
|
|
173
173
|
const output = formatUsageStatus(theme, usage, undefined, settings);
|
|
174
174
|
assert.ok(output);
|
|
175
|
-
assert.ok(output.includes("
|
|
175
|
+
assert.ok(output.includes("Last upd.: 5m ago"));
|
|
176
176
|
assert.ok(!output.includes("(Fetch failed)"));
|
|
177
177
|
assert.ok(output.includes("5h"));
|
|
178
178
|
});
|
|
@@ -238,7 +238,7 @@ test("extras render even when usage windows are hidden", () => {
|
|
|
238
238
|
displayName: "Anthropic (Claude)",
|
|
239
239
|
windows: [
|
|
240
240
|
{ label: "5h", usedPercent: 10 },
|
|
241
|
-
{ label: "
|
|
241
|
+
{ label: "Week", usedPercent: 20 },
|
|
242
242
|
],
|
|
243
243
|
extraUsageEnabled: false,
|
|
244
244
|
};
|
|
@@ -247,7 +247,7 @@ test("extras render even when usage windows are hidden", () => {
|
|
|
247
247
|
assert.ok(output);
|
|
248
248
|
assert.ok(output.includes("Extra [off]"));
|
|
249
249
|
assert.ok(!output.includes("5h"));
|
|
250
|
-
assert.ok(!output.includes("
|
|
250
|
+
assert.ok(!output.includes("Week"));
|
|
251
251
|
});
|
|
252
252
|
|
|
253
253
|
test("percentage labels clamp to bounds", () => {
|