osdy-pi 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +160 -30
- package/bin/osdy-pi.mjs +29 -6
- package/bin/osdy.mjs +43 -0
- package/extensions/osdy-pi/account-profiles.test.ts +111 -97
- package/extensions/osdy-pi/account-profiles.ts +91 -162
- package/extensions/osdy-pi/animation.test.ts +84 -0
- package/extensions/osdy-pi/animation.ts +17 -8
- package/extensions/osdy-pi/codex-usage-ui.test.ts +1145 -0
- package/extensions/osdy-pi/codex-usage-ui.ts +728 -0
- package/extensions/osdy-pi/codex-usage.test.ts +276 -0
- package/extensions/osdy-pi/codex-usage.ts +276 -0
- package/extensions/osdy-pi/constants.ts +134 -34
- package/extensions/osdy-pi/context-usage-ui.test.ts +168 -0
- package/extensions/osdy-pi/context-usage-ui.ts +60 -0
- package/extensions/osdy-pi/editor-settings.test.ts +121 -23
- package/extensions/osdy-pi/editor-settings.ts +39 -3
- package/extensions/osdy-pi/metrics.test.ts +95 -0
- package/extensions/osdy-pi/metrics.ts +39 -1
- package/extensions/osdy-pi/profile-label.ts +2 -2
- package/extensions/osdy-pi/runtime-helpers.test.ts +220 -1
- package/extensions/osdy-pi/runtime-helpers.ts +42 -19
- package/extensions/osdy-pi/runtime.test.ts +355 -7
- package/extensions/osdy-pi/runtime.ts +344 -66
- package/extensions/osdy-pi/types.ts +26 -1
- package/extensions/osdy-pi/ui.test.ts +128 -0
- package/extensions/osdy-pi/ui.ts +57 -40
- package/package.json +5 -2
- package/scripts/osdy-pi-account-profiles.mjs +521 -76
- package/scripts/osdy-pi-gentle-coexistence.mjs +200 -0
- package/scripts/osdy-pi-profile-setup.mjs +175 -0
- package/themes/osdy-pi-catppuccin-frappe.json +6 -2
- package/themes/osdy-pi-catppuccin-latte.json +6 -2
- package/themes/osdy-pi-catppuccin-macchiato.json +6 -2
- package/themes/osdy-pi-catppuccin-mocha.json +6 -2
- package/themes/osdy-pi-dark.json +6 -2
- package/themes/osdy-pi-dracula.json +6 -2
- package/themes/osdy-pi-kanagawa-dragon.json +6 -2
- package/themes/osdy-pi-kanagawa-lotus.json +6 -2
- package/themes/osdy-pi-kanagawa-wave.json +6 -2
- package/themes/osdy-pi-lucent-orange.json +6 -2
- package/themes/osdy-pi-matrix.json +6 -2
- package/themes/osdy-pi-new.json +6 -2
- package/themes/osdy-pi-sexy.json +6 -2
- package/themes/osdy-pi-tokyo-night.json +6 -2
|
@@ -1,5 +1,12 @@
|
|
|
1
|
+
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
1
2
|
import type { TUI } from "@earendil-works/pi-tui";
|
|
2
3
|
|
|
4
|
+
type EditorFactory = Exclude<
|
|
5
|
+
ReturnType<ExtensionContext["ui"]["getEditorComponent"]>,
|
|
6
|
+
undefined
|
|
7
|
+
>;
|
|
8
|
+
import type { CodexUsageSnapshot } from "./codex-usage.js";
|
|
9
|
+
|
|
3
10
|
export type AnimationMode = "off" | "intro" | "continuous";
|
|
4
11
|
|
|
5
12
|
export const EDITOR_MODES = {
|
|
@@ -14,8 +21,11 @@ export const DEFAULT_EDITOR_MODE = EDITOR_MODES.AUTO;
|
|
|
14
21
|
|
|
15
22
|
export interface GlobalEditorSettings {
|
|
16
23
|
version: number;
|
|
24
|
+
enabled: boolean;
|
|
17
25
|
editorMode: EditorMode;
|
|
18
26
|
workingTreeEnabled: boolean;
|
|
27
|
+
headerVariant: HeaderVariant;
|
|
28
|
+
mascot: MascotChoice;
|
|
19
29
|
}
|
|
20
30
|
|
|
21
31
|
export function resolveEffectiveEditorMode(
|
|
@@ -30,15 +40,30 @@ export function shouldShowFooterMetadata(editorEffective: boolean): boolean {
|
|
|
30
40
|
return !editorEffective;
|
|
31
41
|
}
|
|
32
42
|
|
|
33
|
-
export
|
|
43
|
+
export const HEADER_VARIANT_CHOICES = ["osdy-theme", "neon"] as const;
|
|
44
|
+
|
|
45
|
+
export type HeaderVariant = (typeof HEADER_VARIANT_CHOICES)[number];
|
|
46
|
+
|
|
47
|
+
export const MASCOT_CHOICES = ["current", "bts"] as const;
|
|
48
|
+
|
|
49
|
+
export type MascotChoice = (typeof MASCOT_CHOICES)[number];
|
|
34
50
|
|
|
35
51
|
export type WorkingTreePlacement = "aboveEditor" | "belowEditor";
|
|
36
52
|
|
|
53
|
+
export type CodexUsageState =
|
|
54
|
+
| { kind: "idle" }
|
|
55
|
+
| { kind: "loading"; snapshot: CodexUsageSnapshot | undefined }
|
|
56
|
+
| { kind: "ready"; snapshot: CodexUsageSnapshot }
|
|
57
|
+
| { kind: "error"; message: string; snapshot: CodexUsageSnapshot | undefined };
|
|
58
|
+
|
|
37
59
|
export type OsdyState = {
|
|
60
|
+
codexUsage: CodexUsageState;
|
|
38
61
|
enabled: boolean;
|
|
39
62
|
editorEffective: boolean;
|
|
40
63
|
editorMode: EditorMode;
|
|
64
|
+
fallbackEditorFactory: EditorFactory | undefined;
|
|
41
65
|
headerVariant: HeaderVariant;
|
|
66
|
+
mascot: MascotChoice;
|
|
42
67
|
smallMode: boolean;
|
|
43
68
|
tui: TUI | undefined;
|
|
44
69
|
workingTreeEnabled: boolean;
|
|
@@ -4,6 +4,8 @@ import test from "node:test";
|
|
|
4
4
|
// @ts-expect-error Node's native TypeScript runner resolves test-only TypeScript source imports.
|
|
5
5
|
import * as profileLabel from "./profile-label.ts";
|
|
6
6
|
// @ts-expect-error Node's native TypeScript runner resolves test-only TypeScript source imports.
|
|
7
|
+
import { HEADER_VARIANTS, MASCOTS, mascotForChoice, scaleHeader } from "./constants.ts";
|
|
8
|
+
// @ts-expect-error Node's native TypeScript runner resolves test-only TypeScript source imports.
|
|
7
9
|
import { shouldShowFooterMetadata } from "./types.ts";
|
|
8
10
|
|
|
9
11
|
void test("footer metadata is shown only when the native editor is effective", () => {
|
|
@@ -11,16 +13,135 @@ void test("footer metadata is shown only when the native editor is effective", (
|
|
|
11
13
|
assert.equal(shouldShowFooterMetadata(true), false);
|
|
12
14
|
});
|
|
13
15
|
|
|
16
|
+
void test("neon header keeps the parsed 129 by 13 transparent tone structure", () => {
|
|
17
|
+
assert.deepEqual(Object.keys(HEADER_VARIANTS), ["osdy-theme", "neon"]);
|
|
18
|
+
const neon = scaleHeader("neon", 1_000, 100);
|
|
19
|
+
assert.equal(neon.header.length, 13);
|
|
20
|
+
const toneMap = neon.toneMap;
|
|
21
|
+
assert.ok(toneMap);
|
|
22
|
+
assert.equal(toneMap.length, 13);
|
|
23
|
+
assert.equal(Array.from(neon.header[0] ?? "").length, 129);
|
|
24
|
+
assert.equal(
|
|
25
|
+
neon.header.reduce(
|
|
26
|
+
(count, row) =>
|
|
27
|
+
count + Array.from(row).filter((character) => character !== " ").length,
|
|
28
|
+
0,
|
|
29
|
+
),
|
|
30
|
+
1121,
|
|
31
|
+
);
|
|
32
|
+
for (let row = 0; row < neon.header.length; row += 1) {
|
|
33
|
+
const glyphs = Array.from(neon.header[row] ?? "");
|
|
34
|
+
const tones: string[] = Array.from(toneMap[row] ?? "");
|
|
35
|
+
assert.equal(glyphs.length, tones.length);
|
|
36
|
+
for (let column = 0; column < glyphs.length; column += 1)
|
|
37
|
+
if (glyphs[column] === " ") assert.equal(tones[column], " ");
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
void test("neon header keeps white and black fixed while pink follows accent", () => {
|
|
42
|
+
assert.deepEqual(HEADER_VARIANTS.neon.headerTonePalette, {
|
|
43
|
+
b: "#FFFFFF",
|
|
44
|
+
h: { kind: "theme", token: "accent" },
|
|
45
|
+
l: { kind: "theme", token: "accent", dimmed: true },
|
|
46
|
+
m: { kind: "theme", token: "accent", dimmed: true },
|
|
47
|
+
d: "#12011B",
|
|
48
|
+
p: { kind: "theme", token: "accent", dimmed: true },
|
|
49
|
+
c: "#FFFFFF",
|
|
50
|
+
v: "#FFFFFF",
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
void test("selected headers render through foreground-only tone-map animation", () => {
|
|
55
|
+
const ui = readFileSync(new URL("./ui.ts", import.meta.url), "utf8");
|
|
56
|
+
const animation = readFileSync(new URL("./animation.ts", import.meta.url), "utf8");
|
|
57
|
+
assert.match(ui, /const variant = HEADER_VARIANTS\[state\.headerVariant\];/);
|
|
58
|
+
assert.match(ui, /variant\.headerMap,/);
|
|
59
|
+
assert.match(ui, /variant\.headerTonePalette/);
|
|
60
|
+
assert.match(animation, /return `\\u001B\[38;2;/);
|
|
61
|
+
assert.doesNotMatch(animation, /\[48;/);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
void test("mascot selection wires Bts art through the responsive renderer", () => {
|
|
65
|
+
const ui = readFileSync(new URL("./ui.ts", import.meta.url), "utf8");
|
|
66
|
+
|
|
67
|
+
assert.deepEqual(Object.keys(MASCOTS), ["current", "bts"]);
|
|
68
|
+
assert.ok(mascotForChoice("bts"));
|
|
69
|
+
assert.match(ui, /const mascot = mascotForChoice\(state\.mascot\);/);
|
|
70
|
+
assert.match(ui, /scaleMascot\(\s*mascot\.art,/);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
void test("Bts retains the 60 by 26 raccoon glyph structure and transparent tone cells", () => {
|
|
74
|
+
const art = MASCOTS.bts.art;
|
|
75
|
+
assert.equal(art.mascot.length, 26);
|
|
76
|
+
assert.equal(Array.from(art.mascot[0] ?? "").length, 60);
|
|
77
|
+
assert.equal(
|
|
78
|
+
art.mascot.reduce(
|
|
79
|
+
(count, row) =>
|
|
80
|
+
count + Array.from(row).filter((character) => character !== " ").length,
|
|
81
|
+
0,
|
|
82
|
+
),
|
|
83
|
+
682,
|
|
84
|
+
);
|
|
85
|
+
for (let row = 0; row < art.mascot.length; row += 1)
|
|
86
|
+
for (
|
|
87
|
+
let column = 0;
|
|
88
|
+
column < Array.from(art.mascot[row] ?? "").length;
|
|
89
|
+
column += 1
|
|
90
|
+
)
|
|
91
|
+
if (Array.from(art.mascot[row] ?? "")[column] === " ")
|
|
92
|
+
assert.equal(Array.from(art.toneMap[row] ?? "")[column], " ");
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
void test("Bts receives the original right-edge glow", () => {
|
|
96
|
+
const art = MASCOTS.bts.art;
|
|
97
|
+
for (let row = 0; row < art.mascot.length; row += 1) {
|
|
98
|
+
const glyphs = Array.from(art.mascot[row] ?? "");
|
|
99
|
+
const tones = Array.from(art.toneMap[row] ?? "");
|
|
100
|
+
const rightmost = glyphs
|
|
101
|
+
.map((glyph, column) => ({ glyph, column }))
|
|
102
|
+
.filter(({ glyph }) => glyph !== " ")
|
|
103
|
+
.slice(-3)
|
|
104
|
+
.reverse();
|
|
105
|
+
assert.deepEqual(
|
|
106
|
+
rightmost.map(({ column }) => tones[column]),
|
|
107
|
+
["p", "c", "v"].slice(0, rightmost.length),
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
void test("mascot tone rendering never emits terminal background sequences", () => {
|
|
113
|
+
const animation = readFileSync(
|
|
114
|
+
new URL("./animation.ts", import.meta.url),
|
|
115
|
+
"utf8",
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
assert.match(animation, /return `\\u001B\[38;2;/);
|
|
119
|
+
assert.doesNotMatch(animation, /\[48;/);
|
|
120
|
+
});
|
|
121
|
+
|
|
14
122
|
void test("maximum thinking uses the existing extra-high theme color", () => {
|
|
15
123
|
const source = readFileSync(new URL("./ui.ts", import.meta.url), "utf8");
|
|
16
124
|
assert.match(source, /max:\s*"thinkingXhigh"/);
|
|
17
125
|
});
|
|
18
126
|
|
|
127
|
+
void test("renders the extended editor thinking level in bold", () => {
|
|
128
|
+
const source = readFileSync(new URL("./ui.ts", import.meta.url), "utf8");
|
|
129
|
+
assert.match(source, /theme\.bold\(thinkingLevel\)/);
|
|
130
|
+
});
|
|
131
|
+
|
|
19
132
|
void test("places a managed profile beside the model in simple mode and preserves its fallback", () => {
|
|
20
133
|
assert.equal(
|
|
21
134
|
profileLabel.resolveActiveProfileLabel({ OSDY_PI_PROFILE_NAME: "work" }),
|
|
22
135
|
"work",
|
|
23
136
|
);
|
|
137
|
+
assert.equal(
|
|
138
|
+
profileLabel.resolveActiveProfileLabel({ OSDY_PI_PROFILE_NAME: "WORK" }),
|
|
139
|
+
"WORK",
|
|
140
|
+
);
|
|
141
|
+
assert.equal(
|
|
142
|
+
profileLabel.resolveActiveProfileLabel({ OSDY_PI_PROFILE_NAME: "DEFAULT" }),
|
|
143
|
+
undefined,
|
|
144
|
+
);
|
|
24
145
|
assert.equal(profileLabel.resolveActiveProfileLabel({}), undefined);
|
|
25
146
|
assert.equal(
|
|
26
147
|
profileLabel.formatModelMetadata("gpt-5", "high", "work"),
|
|
@@ -38,6 +159,13 @@ void test("places a managed profile beside the model in simple mode and preserve
|
|
|
38
159
|
);
|
|
39
160
|
});
|
|
40
161
|
|
|
162
|
+
void test("wires themed Codex quota bars below both native footer and extended editor", () => {
|
|
163
|
+
const source = readFileSync(new URL("./ui.ts", import.meta.url), "utf8");
|
|
164
|
+
assert.match(source, /renderCompactCodexQuotaBars/);
|
|
165
|
+
assert.equal((source.match(/renderCompactCodexQuotaBars\(/g) ?? []).length, 2);
|
|
166
|
+
assert.doesNotMatch(source, /formatCodexUsageMetadata/);
|
|
167
|
+
});
|
|
168
|
+
|
|
41
169
|
void test("replaces the extended editor Osdy-Pi title with a managed profile or preserves its fallback", () => {
|
|
42
170
|
assert.equal(profileLabel.resolveEditorTitleLabel("work"), "work");
|
|
43
171
|
assert.equal(
|
package/extensions/osdy-pi/ui.ts
CHANGED
|
@@ -18,6 +18,11 @@ import {
|
|
|
18
18
|
asciiAnimationMode,
|
|
19
19
|
} from "./animation.js";
|
|
20
20
|
import { fitBorder } from "./border.js";
|
|
21
|
+
import { formatContextUsage } from "./context-usage-ui.js";
|
|
22
|
+
import {
|
|
23
|
+
renderCompactCodexQuotaBars,
|
|
24
|
+
resolveCodexUsageLayout,
|
|
25
|
+
} from "./codex-usage-ui.js";
|
|
21
26
|
import {
|
|
22
27
|
ANIMATION_INTERVAL_MS,
|
|
23
28
|
COMPACT_HEADER_HIDDEN_COLUMNS,
|
|
@@ -25,17 +30,19 @@ import {
|
|
|
25
30
|
HEADER_VARIANTS,
|
|
26
31
|
INTRO_ANIMATION_FRAMES,
|
|
27
32
|
MASCOT_GAP,
|
|
33
|
+
mascotForChoice,
|
|
28
34
|
mascotWidthForRows,
|
|
29
35
|
scaleHeader,
|
|
30
36
|
scaleMascot,
|
|
31
37
|
STACKED_CONTENT_MAX_ROWS,
|
|
32
38
|
STACKED_HEADER_MAX_ROWS,
|
|
33
39
|
STACKED_HEADER_MIN_ROWS,
|
|
40
|
+
type MascotConfig,
|
|
34
41
|
type ScaledHeaderArt,
|
|
35
42
|
WORKING_SPINNER_FRAMES,
|
|
36
43
|
} from "./constants.js";
|
|
37
44
|
import { formatPath } from "./format.js";
|
|
38
|
-
import {
|
|
45
|
+
import { contextUsageData, modelLabel } from "./metrics.js";
|
|
39
46
|
import {
|
|
40
47
|
formatModelMetadata,
|
|
41
48
|
resolveActiveProfileLabel,
|
|
@@ -88,16 +95,14 @@ type HeaderConfig = (typeof HEADER_VARIANTS)[HeaderVariant];
|
|
|
88
95
|
function calculateHeaderLayout(
|
|
89
96
|
variantName: HeaderVariant,
|
|
90
97
|
variant: HeaderConfig,
|
|
98
|
+
mascot: MascotConfig,
|
|
91
99
|
width: number,
|
|
92
100
|
terminalColumns: number,
|
|
93
101
|
terminalRows: number,
|
|
94
102
|
): HeaderLayout {
|
|
95
103
|
const renderWidth = Math.max(1, width);
|
|
96
104
|
const fullHeaderWidth = headerWidth(variantName);
|
|
97
|
-
const mascotArt =
|
|
98
|
-
mascot: variant.mascot ?? [],
|
|
99
|
-
toneMap: variant.mascotMap ?? [],
|
|
100
|
-
};
|
|
105
|
+
const mascotArt = mascot.art;
|
|
101
106
|
const hasMascot = mascotArt.mascot.length > 0 && mascotArt.toneMap.length > 0;
|
|
102
107
|
const heightLimitedMascotWidth = hasMascot
|
|
103
108
|
? mascotWidthForRows(mascotArt, terminalRows)
|
|
@@ -132,7 +137,7 @@ function calculateHeaderLayout(
|
|
|
132
137
|
}
|
|
133
138
|
|
|
134
139
|
function renderMascotLines(
|
|
135
|
-
|
|
140
|
+
mascot: MascotConfig,
|
|
136
141
|
mascotArt: ReturnType<typeof scaleMascot>,
|
|
137
142
|
frame: number,
|
|
138
143
|
theme: SimpleTheme,
|
|
@@ -140,14 +145,14 @@ function renderMascotLines(
|
|
|
140
145
|
): string[] {
|
|
141
146
|
return mascotArt.mascot.map((line, index) => {
|
|
142
147
|
const toneLine = mascotArt.toneMap[index];
|
|
143
|
-
if (toneLine
|
|
148
|
+
if (toneLine) {
|
|
144
149
|
return animateAsciiLineWithToneMap(
|
|
145
150
|
line,
|
|
146
151
|
toneLine,
|
|
147
152
|
index,
|
|
148
153
|
frame,
|
|
149
154
|
theme,
|
|
150
|
-
|
|
155
|
+
mascot.tonePalette,
|
|
151
156
|
animationStyle,
|
|
152
157
|
);
|
|
153
158
|
}
|
|
@@ -156,9 +161,9 @@ function renderMascotLines(
|
|
|
156
161
|
index,
|
|
157
162
|
frame,
|
|
158
163
|
theme,
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
164
|
+
"accent",
|
|
165
|
+
"mdHeading",
|
|
166
|
+
"mdLink",
|
|
162
167
|
animationStyle,
|
|
163
168
|
);
|
|
164
169
|
});
|
|
@@ -211,22 +216,20 @@ function renderResponsiveHeader(
|
|
|
211
216
|
animationStyle: "animated" | "static",
|
|
212
217
|
): string[] {
|
|
213
218
|
const variant = HEADER_VARIANTS[state.headerVariant];
|
|
219
|
+
const mascot = mascotForChoice(state.mascot);
|
|
214
220
|
const layout = calculateHeaderLayout(
|
|
215
221
|
state.headerVariant,
|
|
216
222
|
variant,
|
|
223
|
+
mascot,
|
|
217
224
|
width,
|
|
218
225
|
terminalColumns,
|
|
219
226
|
terminalRows,
|
|
220
227
|
);
|
|
221
228
|
const mascotArt = layout.hasMascot
|
|
222
|
-
? scaleMascot(
|
|
223
|
-
{ mascot: variant.mascot ?? [], toneMap: variant.mascotMap ?? [] },
|
|
224
|
-
layout.mascotWidthBudget,
|
|
225
|
-
layout.mascotRowBudget,
|
|
226
|
-
)
|
|
229
|
+
? scaleMascot(mascot.art, layout.mascotWidthBudget, layout.mascotRowBudget)
|
|
227
230
|
: { mascot: [], toneMap: [] };
|
|
228
231
|
const mascotLines = renderMascotLines(
|
|
229
|
-
|
|
232
|
+
mascot,
|
|
230
233
|
mascotArt,
|
|
231
234
|
frame,
|
|
232
235
|
theme,
|
|
@@ -318,31 +321,33 @@ class OsdyFooter implements Component {
|
|
|
318
321
|
: [locationLine];
|
|
319
322
|
}
|
|
320
323
|
const thinkingLevel = this.pi.getThinkingLevel();
|
|
321
|
-
const
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
`\u001B[1m${thinkingLevel}\u001B[22m`,
|
|
327
|
-
),
|
|
328
|
-
resolveActiveProfileLabel(),
|
|
324
|
+
const baseMetadata = formatModelMetadata(
|
|
325
|
+
modelLabel(this.ctx),
|
|
326
|
+
this.theme.fg(
|
|
327
|
+
THINKING_THEME_TOKENS[thinkingLevel],
|
|
328
|
+
`\u001B[1m${thinkingLevel}\u001B[22m`,
|
|
329
329
|
),
|
|
330
|
-
|
|
331
|
-
ellipsis,
|
|
330
|
+
resolveActiveProfileLabel(),
|
|
332
331
|
);
|
|
332
|
+
const cachedCodexUsage =
|
|
333
|
+
this.state.codexUsage.kind === "idle"
|
|
334
|
+
? undefined
|
|
335
|
+
: this.state.codexUsage.snapshot;
|
|
336
|
+
const codexSnapshot =
|
|
337
|
+
this.ctx.model?.provider === "openai-codex" ? cachedCodexUsage : undefined;
|
|
338
|
+
const thinkingLine = truncateToWidth(baseMetadata, width, ellipsis);
|
|
339
|
+
const quotaLines = codexSnapshot
|
|
340
|
+
? renderCompactCodexQuotaBars(this.theme, codexSnapshot, width)
|
|
341
|
+
: [];
|
|
333
342
|
const usageLine = truncateToWidth(
|
|
334
|
-
|
|
343
|
+
formatContextUsage(this.theme, contextUsageData(this.ctx), width),
|
|
335
344
|
width,
|
|
336
345
|
ellipsis,
|
|
337
346
|
);
|
|
347
|
+
const lines = [thinkingLine, ...quotaLines, usageLine, locationLine];
|
|
338
348
|
return statusLine
|
|
339
|
-
? [
|
|
340
|
-
|
|
341
|
-
usageLine,
|
|
342
|
-
locationLine,
|
|
343
|
-
truncateToWidth(statusLine, width, ellipsis),
|
|
344
|
-
]
|
|
345
|
-
: [thinkingLine, usageLine, locationLine];
|
|
349
|
+
? [...lines, truncateToWidth(statusLine, width, ellipsis)]
|
|
350
|
+
: lines;
|
|
346
351
|
}
|
|
347
352
|
|
|
348
353
|
invalidate(): void {}
|
|
@@ -433,6 +438,7 @@ export function createWorkingWidgetFactory(workingState: WorkingWidgetState) {
|
|
|
433
438
|
export function createEditorComponent(
|
|
434
439
|
pi: ExtensionAPI,
|
|
435
440
|
ctx: ExtensionContext,
|
|
441
|
+
state: OsdyState,
|
|
436
442
|
): (
|
|
437
443
|
tui: TUI,
|
|
438
444
|
theme: EditorTheme,
|
|
@@ -476,15 +482,26 @@ export function createEditorComponent(
|
|
|
476
482
|
"mdLink",
|
|
477
483
|
` ${resolveEditorTitleLabel(resolveActiveProfileLabel())} `,
|
|
478
484
|
);
|
|
479
|
-
const
|
|
480
|
-
"
|
|
481
|
-
|
|
485
|
+
const cachedCodexUsage =
|
|
486
|
+
state.codexUsage.kind === "idle" ? undefined : state.codexUsage.snapshot;
|
|
487
|
+
const codexSnapshot =
|
|
488
|
+
ctx.model?.provider === "openai-codex" ? cachedCodexUsage : undefined;
|
|
489
|
+
const thinkingLevel = pi.getThinkingLevel();
|
|
490
|
+
const modelAndThinking = `${modelLabel(ctx)} · think ${ctx.ui.theme.bold(thinkingLevel)}`;
|
|
491
|
+
const layout = resolveCodexUsageLayout(topLeft, ` ${modelAndThinking} `);
|
|
492
|
+
const topRight = ctx.ui.theme.fg("muted", layout.topRight);
|
|
493
|
+
const bottomLeft = formatContextUsage(
|
|
494
|
+
ctx.ui.theme,
|
|
495
|
+
contextUsageData(ctx),
|
|
496
|
+
innerWidth,
|
|
482
497
|
);
|
|
483
|
-
const bottomLeft = ctx.ui.theme.fg("muted", usageLabel(ctx));
|
|
484
498
|
lines[0] = `${borderColor("╭")}${fitBorder(topLeft, topRight, editorWidth - 2, borderColor)}${borderColor("╮")}`;
|
|
485
499
|
lines[bottomIndex] =
|
|
486
500
|
`${borderColor("╰")}${fitBorder(bottomLeft, "", editorWidth - 2, borderColor)}${borderColor("╯")}`;
|
|
487
|
-
|
|
501
|
+
const quotaLines = codexSnapshot
|
|
502
|
+
? renderCompactCodexQuotaBars(ctx.ui.theme, codexSnapshot, editorWidth)
|
|
503
|
+
: [];
|
|
504
|
+
return [...lines, ...quotaLines];
|
|
488
505
|
}
|
|
489
506
|
}
|
|
490
507
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "osdy-pi",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Osdy Pi package: themes, header, and custom editor for Pi.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
"extension"
|
|
12
12
|
],
|
|
13
13
|
"bin": {
|
|
14
|
-
"osdy-pi": "bin/osdy-pi.mjs"
|
|
14
|
+
"osdy-pi": "bin/osdy-pi.mjs",
|
|
15
|
+
"osdy": "bin/osdy.mjs"
|
|
15
16
|
},
|
|
16
17
|
"scripts": {
|
|
17
18
|
"typecheck": "tsc --noEmit",
|
|
@@ -24,6 +25,8 @@
|
|
|
24
25
|
"LICENSE",
|
|
25
26
|
"bin",
|
|
26
27
|
"scripts/osdy-pi-account-profiles.mjs",
|
|
28
|
+
"scripts/osdy-pi-gentle-coexistence.mjs",
|
|
29
|
+
"scripts/osdy-pi-profile-setup.mjs",
|
|
27
30
|
"themes",
|
|
28
31
|
"extensions",
|
|
29
32
|
"mapche1.png"
|