@selesai/code 0.7.0 → 0.8.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/CHANGELOG.md +11 -0
- package/README.md +4 -2
- package/dist/extensions/agent-browser.test.ts +195 -0
- package/dist/extensions/agent-browser.ts +158 -0
- package/dist/extensions/inline-skills.test.ts +14 -0
- package/dist/extensions/inline-skills.ts +14 -7
- package/dist/extensions/package.json +2 -1
- package/dist/extensions/pi-powerline-footer/.github/workflows/test.yml +27 -0
- package/dist/extensions/pi-powerline-footer/CHANGELOG.md +153 -0
- package/dist/extensions/pi-powerline-footer/CONTRIBUTING.md +16 -0
- package/dist/extensions/pi-powerline-footer/README.md +192 -44
- package/dist/extensions/pi-powerline-footer/bash-mode/completion.ts +19 -4
- package/dist/extensions/pi-powerline-footer/bash-mode/editor.ts +80 -25
- package/dist/extensions/pi-powerline-footer/bash-mode/history.ts +81 -29
- package/dist/extensions/pi-powerline-footer/bash-mode/types.ts +1 -1
- package/dist/extensions/pi-powerline-footer/cd-command.ts +190 -0
- package/dist/extensions/pi-powerline-footer/colors.ts +2 -1
- package/dist/extensions/pi-powerline-footer/context-usage.ts +121 -6
- package/dist/extensions/pi-powerline-footer/currency-rates.ts +142 -0
- package/dist/extensions/pi-powerline-footer/editor-composition.ts +32 -0
- package/dist/extensions/pi-powerline-footer/git-status.ts +108 -6
- package/dist/extensions/pi-powerline-footer/icons.ts +11 -2
- package/dist/extensions/pi-powerline-footer/index.ts +1056 -324
- package/dist/extensions/pi-powerline-footer/lifecycle.ts +10 -0
- package/dist/extensions/pi-powerline-footer/package.json +10 -6
- package/dist/extensions/pi-powerline-footer/paths.ts +22 -0
- package/dist/extensions/pi-powerline-footer/powerline-config.ts +297 -15
- package/dist/extensions/pi-powerline-footer/presets.ts +6 -25
- package/dist/extensions/pi-powerline-footer/queue/store.ts +361 -0
- package/dist/extensions/pi-powerline-footer/queue/types.ts +54 -0
- package/dist/extensions/pi-powerline-footer/render-scheduler.ts +12 -7
- package/dist/extensions/pi-powerline-footer/segments.ts +114 -107
- package/dist/extensions/pi-powerline-footer/session-usage.ts +1 -1
- package/dist/extensions/pi-powerline-footer/shortcuts.ts +17 -3
- package/dist/extensions/pi-powerline-footer/tests/bash-mode.test.ts +189 -17
- package/dist/extensions/pi-powerline-footer/tests/cd-command.test.ts +144 -0
- package/dist/extensions/pi-powerline-footer/tests/context-usage.test.ts +120 -2
- package/dist/extensions/pi-powerline-footer/tests/custom-items.test.ts +245 -22
- package/dist/extensions/pi-powerline-footer/tests/editor-composition.test.ts +48 -0
- package/dist/extensions/pi-powerline-footer/tests/editor-responsiveness.test.ts +34 -0
- package/dist/extensions/pi-powerline-footer/tests/fixed-editor-border.test.ts +39 -0
- package/dist/extensions/pi-powerline-footer/tests/git-status.test.ts +92 -0
- package/dist/extensions/pi-powerline-footer/tests/guide.test.ts +4 -1
- package/dist/extensions/pi-powerline-footer/tests/jump-shortcuts.test.ts +33 -197
- package/dist/extensions/pi-powerline-footer/tests/mode-aware-trigger.test.ts +79 -0
- package/dist/extensions/pi-powerline-footer/tests/paths.test.ts +63 -0
- package/dist/extensions/pi-powerline-footer/tests/queue-store.test.ts +195 -0
- package/dist/extensions/pi-powerline-footer/tests/quit-cursor.test.ts +14 -0
- package/dist/extensions/pi-powerline-footer/tests/remaining-regressions.test.ts +167 -0
- package/dist/extensions/pi-powerline-footer/tests/stash-shortcut.test.ts +23 -18
- package/dist/extensions/pi-powerline-footer/tests/thinking-segment.test.ts +16 -1
- package/dist/extensions/pi-powerline-footer/tests/token-stats.test.ts +249 -0
- package/dist/extensions/pi-powerline-footer/tests/usage-display.test.ts +237 -0
- package/dist/extensions/pi-powerline-footer/tests/welcome.test.ts +199 -0
- package/dist/extensions/pi-powerline-footer/tests/working-vibes.test.ts +191 -100
- package/dist/extensions/pi-powerline-footer/theme.ts +32 -18
- package/dist/extensions/pi-powerline-footer/token-stats.ts +304 -0
- package/dist/extensions/pi-powerline-footer/tsconfig.json +13 -0
- package/dist/extensions/pi-powerline-footer/types.ts +74 -29
- package/dist/extensions/pi-powerline-footer/welcome-dismiss.ts +34 -0
- package/dist/extensions/pi-powerline-footer/welcome.ts +65 -10
- package/dist/extensions/pi-powerline-footer/working-vibes.ts +44 -11
- package/dist/extensions/pi-subagents/package-lock.json +2 -2
- package/dist/extensions/pi-subagents/package.json +1 -1
- package/dist/extensions/pi-subagents/src/slash/inline-subagents.ts +57 -24
- package/dist/extensions/pi-subagents/test/unit/inline-subagents.test.ts +82 -7
- package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +1 -1
- package/dist/extensions/ponytail/index.js +1 -31
- package/dist/extensions/ponytail/test/extension.test.js +5 -22
- package/dist/extensions/rtk.test.ts +27 -12
- package/dist/extensions/rtk.ts +41 -35
- package/package.json +1 -1
|
@@ -4,8 +4,22 @@ import { existsSync, mkdtempSync, mkdirSync, readFileSync, rmSync, symlinkSync }
|
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import { join } from "node:path";
|
|
6
6
|
|
|
7
|
+
import { parseVibeGenerateArgs } from "../working-vibes.ts";
|
|
8
|
+
|
|
7
9
|
const FAUX_PROVIDER_PATH = new URL("../node_modules/@earendil-works/pi-ai/dist/providers/faux.js", import.meta.url).href;
|
|
8
10
|
|
|
11
|
+
async function importFauxProviderTools() {
|
|
12
|
+
try {
|
|
13
|
+
return await import("@earendil-works/pi-ai/compat");
|
|
14
|
+
} catch (error) {
|
|
15
|
+
const code = error && typeof error === "object" ? Reflect.get(error, "code") : undefined;
|
|
16
|
+
if (code !== "ERR_PACKAGE_PATH_NOT_EXPORTED" && code !== "ERR_MODULE_NOT_FOUND") {
|
|
17
|
+
throw error;
|
|
18
|
+
}
|
|
19
|
+
return import(FAUX_PROVIDER_PATH);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
9
23
|
function ensurePiModuleLinks(): { cleanup: () => void } {
|
|
10
24
|
const nodeModulesDir = join(process.cwd(), "node_modules", "@earendil-works");
|
|
11
25
|
mkdirSync(nodeModulesDir, { recursive: true });
|
|
@@ -39,6 +53,15 @@ function ensurePiModuleLinks(): { cleanup: () => void } {
|
|
|
39
53
|
};
|
|
40
54
|
}
|
|
41
55
|
|
|
56
|
+
test("parseVibeGenerateArgs supports multi-word themes", () => {
|
|
57
|
+
assert.deepEqual(parseVibeGenerateArgs(["pirate", "200"]), { theme: "pirate", count: 200 });
|
|
58
|
+
assert.deepEqual(parseVibeGenerateArgs(["star", "trek", "200"]), { theme: "star trek", count: 200 });
|
|
59
|
+
assert.deepEqual(parseVibeGenerateArgs(["star", "trek"]), { theme: "star trek", count: 100 });
|
|
60
|
+
assert.deepEqual(parseVibeGenerateArgs(["star", "trek", "abc"]), { theme: "star trek abc", count: 100 });
|
|
61
|
+
assert.deepEqual(parseVibeGenerateArgs(["lord", "of", "rings", "999"]), { theme: "lord of rings", count: 500 });
|
|
62
|
+
assert.equal(parseVibeGenerateArgs([]), null);
|
|
63
|
+
});
|
|
64
|
+
|
|
42
65
|
test("generateVibesBatch includes a system prompt so faux providers can return text", async () => {
|
|
43
66
|
const links = ensurePiModuleLinks();
|
|
44
67
|
const home = mkdtempSync(join(tmpdir(), "powerline-vibes-home-"));
|
|
@@ -46,50 +69,114 @@ test("generateVibesBatch includes a system prompt so faux providers can return t
|
|
|
46
69
|
process.env.HOME = home;
|
|
47
70
|
|
|
48
71
|
try {
|
|
49
|
-
const { fauxAssistantMessage,
|
|
72
|
+
const { fauxAssistantMessage, fauxProvider } = await importFauxProviderTools();
|
|
50
73
|
const { generateVibesBatch, initVibeManager, setVibeModel } = await import("../working-vibes.ts");
|
|
51
74
|
|
|
52
|
-
const registration =
|
|
75
|
+
const registration = fauxProvider({
|
|
53
76
|
provider: "test-provider",
|
|
54
77
|
models: [{ id: "test-model" }],
|
|
55
78
|
});
|
|
56
79
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
(context
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
80
|
+
const model = registration.getModel("test-model");
|
|
81
|
+
assert.ok(model);
|
|
82
|
+
|
|
83
|
+
registration.setResponses([
|
|
84
|
+
(context) => {
|
|
85
|
+
assert.match(context.systemPrompt ?? "", /loading messages/i);
|
|
86
|
+
return fauxAssistantMessage("Engaging warp drive...\nRunning diagnostics...");
|
|
87
|
+
},
|
|
88
|
+
]);
|
|
89
|
+
|
|
90
|
+
initVibeManager({
|
|
91
|
+
modelRegistry: {
|
|
92
|
+
find(provider: string, modelId: string) {
|
|
93
|
+
return provider === "test-provider" && modelId === "test-model" ? model : undefined;
|
|
94
|
+
},
|
|
95
|
+
async getApiKeyAndHeaders() {
|
|
96
|
+
return { ok: true, apiKey: "test-key", headers: {} };
|
|
97
|
+
},
|
|
98
|
+
getProvider(provider: string) {
|
|
99
|
+
return provider === "test-provider" ? registration.provider : undefined;
|
|
100
|
+
},
|
|
101
|
+
async getProviderAuth() {
|
|
102
|
+
return undefined;
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
assert.equal(setVibeModel("test-provider/test-model"), true);
|
|
108
|
+
|
|
109
|
+
const result = await generateVibesBatch("star trek", 2);
|
|
110
|
+
|
|
111
|
+
assert.equal(result.success, true);
|
|
112
|
+
assert.equal(result.count, 2);
|
|
113
|
+
assert.equal(existsSync(result.filePath), true);
|
|
114
|
+
assert.deepEqual(readFileSync(result.filePath, "utf8").trim().split("\n"), [
|
|
115
|
+
"Engaging warp drive...",
|
|
116
|
+
"Running diagnostics...",
|
|
117
|
+
]);
|
|
118
|
+
} finally {
|
|
119
|
+
if (previousHome === undefined) {
|
|
120
|
+
delete process.env.HOME;
|
|
121
|
+
} else {
|
|
122
|
+
process.env.HOME = previousHome;
|
|
92
123
|
}
|
|
124
|
+
rmSync(home, { recursive: true, force: true });
|
|
125
|
+
links.cleanup();
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test("generateVibesBatch forwards resolved provider env and credential base URL", async () => {
|
|
130
|
+
const links = ensurePiModuleLinks();
|
|
131
|
+
const home = mkdtempSync(join(tmpdir(), "powerline-vibes-home-"));
|
|
132
|
+
const previousHome = process.env.HOME;
|
|
133
|
+
process.env.HOME = home;
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
const { fauxAssistantMessage, fauxProvider } = await importFauxProviderTools();
|
|
137
|
+
const { generateVibesBatch, initVibeManager, setVibeModel } = await import("../working-vibes.ts");
|
|
138
|
+
|
|
139
|
+
const registration = fauxProvider({
|
|
140
|
+
provider: "test-provider",
|
|
141
|
+
models: [{ id: "test-model" }],
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
const model = registration.getModel("test-model");
|
|
145
|
+
assert.ok(model);
|
|
146
|
+
|
|
147
|
+
registration.setResponses([
|
|
148
|
+
(_context, options, _state, requestModel) => {
|
|
149
|
+
assert.deepEqual(options?.env, { AWS_PROFILE: "vibes" });
|
|
150
|
+
assert.equal(requestModel.baseUrl, "https://credential.example/v1");
|
|
151
|
+
return fauxAssistantMessage("Signing the request...");
|
|
152
|
+
},
|
|
153
|
+
]);
|
|
154
|
+
|
|
155
|
+
initVibeManager({
|
|
156
|
+
modelRegistry: {
|
|
157
|
+
find(provider: string, modelId: string) {
|
|
158
|
+
return provider === "test-provider" && modelId === "test-model" ? model : undefined;
|
|
159
|
+
},
|
|
160
|
+
async getApiKeyAndHeaders() {
|
|
161
|
+
return { ok: true, apiKey: "test-key", headers: {}, env: { AWS_PROFILE: "vibes" } };
|
|
162
|
+
},
|
|
163
|
+
getProvider(provider: string) {
|
|
164
|
+
return provider === "test-provider" ? registration.provider : undefined;
|
|
165
|
+
},
|
|
166
|
+
async getProviderAuth() {
|
|
167
|
+
return { auth: { apiKey: "test-key", baseUrl: "https://credential.example/v1" } };
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
assert.equal(setVibeModel("test-provider/test-model"), true);
|
|
173
|
+
|
|
174
|
+
const result = await generateVibesBatch("bedrock", 1);
|
|
175
|
+
|
|
176
|
+
assert.equal(result.success, true);
|
|
177
|
+
assert.deepEqual(readFileSync(result.filePath, "utf8").trim().split("\n"), [
|
|
178
|
+
"Signing the request...",
|
|
179
|
+
]);
|
|
93
180
|
} finally {
|
|
94
181
|
if (previousHome === undefined) {
|
|
95
182
|
delete process.env.HOME;
|
|
@@ -108,55 +195,57 @@ test("on-demand vibe generation includes a system prompt for providers that requ
|
|
|
108
195
|
process.env.HOME = home;
|
|
109
196
|
|
|
110
197
|
try {
|
|
111
|
-
const { fauxAssistantMessage,
|
|
198
|
+
const { fauxAssistantMessage, fauxProvider } = await importFauxProviderTools();
|
|
112
199
|
const { initVibeManager, onVibeAgentStart, onVibeBeforeAgentStart, setVibeModel, setVibeTheme } = await import("../working-vibes.ts");
|
|
113
200
|
|
|
114
|
-
const registration =
|
|
201
|
+
const registration = fauxProvider({
|
|
115
202
|
provider: "test-provider",
|
|
116
203
|
models: [{ id: "test-model" }],
|
|
117
204
|
});
|
|
118
205
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
assert.ok(model);
|
|
206
|
+
const model = registration.getModel("test-model");
|
|
207
|
+
assert.ok(model);
|
|
122
208
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
209
|
+
registration.setResponses([
|
|
210
|
+
(context) => {
|
|
211
|
+
assert.match(context.systemPrompt ?? "", /loading messages/i);
|
|
212
|
+
return fauxAssistantMessage("Engaging warp drive...");
|
|
213
|
+
},
|
|
214
|
+
]);
|
|
129
215
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
},
|
|
135
|
-
async getApiKeyAndHeaders() {
|
|
136
|
-
return { ok: true, apiKey: "test-key", headers: {} };
|
|
137
|
-
},
|
|
216
|
+
initVibeManager({
|
|
217
|
+
modelRegistry: {
|
|
218
|
+
find(provider: string, modelId: string) {
|
|
219
|
+
return provider === "test-provider" && modelId === "test-model" ? model : undefined;
|
|
138
220
|
},
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
221
|
+
async getApiKeyAndHeaders() {
|
|
222
|
+
return { ok: true, apiKey: "test-key", headers: {} };
|
|
223
|
+
},
|
|
224
|
+
getProvider(provider: string) {
|
|
225
|
+
return provider === "test-provider" ? registration.provider : undefined;
|
|
226
|
+
},
|
|
227
|
+
async getProviderAuth() {
|
|
228
|
+
return undefined;
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
});
|
|
143
232
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
onVibeBeforeAgentStart("fix a bug", (message) => {
|
|
147
|
-
updates.push(message);
|
|
148
|
-
});
|
|
233
|
+
assert.equal(setVibeTheme("star trek"), true);
|
|
234
|
+
assert.equal(setVibeModel("test-provider/test-model"), true);
|
|
149
235
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
236
|
+
const updates: Array<string | undefined> = [];
|
|
237
|
+
onVibeAgentStart();
|
|
238
|
+
onVibeBeforeAgentStart("fix a bug", (message) => {
|
|
239
|
+
updates.push(message);
|
|
240
|
+
});
|
|
154
241
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
registration.unregister();
|
|
242
|
+
const start = Date.now();
|
|
243
|
+
while (!updates.includes("Engaging warp drive...") && Date.now() - start < 1000) {
|
|
244
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
159
245
|
}
|
|
246
|
+
|
|
247
|
+
assert.equal(updates[0], "Channeling star trek...");
|
|
248
|
+
assert.ok(updates.includes("Engaging warp drive..."));
|
|
160
249
|
} finally {
|
|
161
250
|
if (previousHome === undefined) {
|
|
162
251
|
delete process.env.HOME;
|
|
@@ -175,45 +264,47 @@ test("generateVibesBatch preserves provider errors instead of reporting an empty
|
|
|
175
264
|
process.env.HOME = home;
|
|
176
265
|
|
|
177
266
|
try {
|
|
178
|
-
const { fauxAssistantMessage,
|
|
267
|
+
const { fauxAssistantMessage, fauxProvider } = await importFauxProviderTools();
|
|
179
268
|
const { generateVibesBatch, initVibeManager, setVibeModel } = await import("../working-vibes.ts");
|
|
180
269
|
|
|
181
|
-
const registration =
|
|
270
|
+
const registration = fauxProvider({
|
|
182
271
|
provider: "test-provider",
|
|
183
272
|
models: [{ id: "test-model" }],
|
|
184
273
|
});
|
|
185
274
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
assert.ok(model);
|
|
275
|
+
const model = registration.getModel("test-model");
|
|
276
|
+
assert.ok(model);
|
|
189
277
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
278
|
+
registration.setResponses([
|
|
279
|
+
fauxAssistantMessage([], {
|
|
280
|
+
stopReason: "error",
|
|
281
|
+
errorMessage: "Instructions are required",
|
|
282
|
+
}),
|
|
283
|
+
]);
|
|
196
284
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
285
|
+
initVibeManager({
|
|
286
|
+
modelRegistry: {
|
|
287
|
+
find(provider: string, modelId: string) {
|
|
288
|
+
return provider === "test-provider" && modelId === "test-model" ? model : undefined;
|
|
289
|
+
},
|
|
290
|
+
async getApiKeyAndHeaders() {
|
|
291
|
+
return { ok: true, apiKey: "test-key", headers: {} };
|
|
292
|
+
},
|
|
293
|
+
getProvider(provider: string) {
|
|
294
|
+
return provider === "test-provider" ? registration.provider : undefined;
|
|
205
295
|
},
|
|
206
|
-
|
|
296
|
+
async getProviderAuth() {
|
|
297
|
+
return undefined;
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
});
|
|
207
301
|
|
|
208
|
-
|
|
302
|
+
assert.equal(setVibeModel("test-provider/test-model"), true);
|
|
209
303
|
|
|
210
|
-
|
|
304
|
+
const result = await generateVibesBatch("noir", 2);
|
|
211
305
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
} finally {
|
|
215
|
-
registration.unregister();
|
|
216
|
-
}
|
|
306
|
+
assert.equal(result.success, false);
|
|
307
|
+
assert.equal(result.error, "Instructions are required");
|
|
217
308
|
} finally {
|
|
218
309
|
if (previousHome === undefined) {
|
|
219
310
|
delete process.env.HOME;
|
|
@@ -11,6 +11,7 @@ import type { Theme, ThemeColor } from "@selesai/code";
|
|
|
11
11
|
import { existsSync, readFileSync } from "node:fs";
|
|
12
12
|
import { join, dirname } from "node:path";
|
|
13
13
|
import { fileURLToPath } from "node:url";
|
|
14
|
+
import { getAgentPath } from "./paths.ts";
|
|
14
15
|
import type { ColorScheme, ColorValue, SemanticColor, ThemeLike } from "./types.ts";
|
|
15
16
|
|
|
16
17
|
export interface PowerlineThemeConfig {
|
|
@@ -34,6 +35,7 @@ const DEFAULT_COLORS: Required<ColorScheme> = {
|
|
|
34
35
|
contextError: "error",
|
|
35
36
|
cost: "text",
|
|
36
37
|
tokens: "muted",
|
|
38
|
+
queue: "accent",
|
|
37
39
|
separator: "dim",
|
|
38
40
|
border: "borderMuted",
|
|
39
41
|
};
|
|
@@ -47,8 +49,10 @@ const RAINBOW_COLORS = [
|
|
|
47
49
|
// Cache for user theme overrides
|
|
48
50
|
let userThemeCache: ColorScheme | null = null;
|
|
49
51
|
let userThemeCacheTime = 0;
|
|
52
|
+
let userThemeCacheKey = "";
|
|
50
53
|
let themeConfigCache: PowerlineThemeConfig | null = null;
|
|
51
54
|
let themeConfigCacheTime = 0;
|
|
55
|
+
let themeConfigCacheKey = "";
|
|
52
56
|
const CACHE_TTL = 5000; // 5 seconds
|
|
53
57
|
const warnedInvalidThemeColors = new Set<string>();
|
|
54
58
|
|
|
@@ -82,50 +86,60 @@ function sanitizeUserThemeOverrides(value: unknown): ColorScheme {
|
|
|
82
86
|
}
|
|
83
87
|
|
|
84
88
|
/**
|
|
85
|
-
* Get
|
|
89
|
+
* Get theme.json lookup paths.
|
|
86
90
|
*/
|
|
87
|
-
function
|
|
91
|
+
function getThemePaths(): string[] {
|
|
88
92
|
const extDir = dirname(fileURLToPath(import.meta.url));
|
|
89
|
-
return
|
|
93
|
+
return Array.from(new Set([
|
|
94
|
+
getAgentPath("extensions", "powerline-footer", "theme.json"),
|
|
95
|
+
join(extDir, "theme.json"),
|
|
96
|
+
]));
|
|
90
97
|
}
|
|
91
98
|
|
|
92
99
|
/**
|
|
93
|
-
* Load user theme config from theme.json
|
|
100
|
+
* Load user theme config from theme.json.
|
|
94
101
|
*/
|
|
95
102
|
export function loadThemeConfig(): PowerlineThemeConfig {
|
|
96
103
|
const now = Date.now();
|
|
97
|
-
|
|
104
|
+
const themePaths = getThemePaths();
|
|
105
|
+
const cacheKey = themePaths.join("\0");
|
|
106
|
+
if (themeConfigCache && themeConfigCacheKey === cacheKey && now - themeConfigCacheTime < CACHE_TTL) {
|
|
98
107
|
return themeConfigCache;
|
|
99
108
|
}
|
|
100
109
|
|
|
101
|
-
const themePath
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
110
|
+
for (const themePath of themePaths) {
|
|
111
|
+
try {
|
|
112
|
+
if (existsSync(themePath)) {
|
|
113
|
+
const content = readFileSync(themePath, "utf-8");
|
|
114
|
+
const parsed = JSON.parse(content);
|
|
115
|
+
themeConfigCache = isRecord(parsed) ? parsed : {};
|
|
116
|
+
themeConfigCacheTime = now;
|
|
117
|
+
themeConfigCacheKey = cacheKey;
|
|
118
|
+
return themeConfigCache;
|
|
119
|
+
}
|
|
120
|
+
} catch (error) {
|
|
121
|
+
// Theme overrides are optional. If the file is unreadable or malformed,
|
|
122
|
+
// keep rendering with built-in defaults instead of breaking the footer.
|
|
123
|
+
console.debug(`[powerline-theme] Failed to load ${themePath}:`, error);
|
|
109
124
|
}
|
|
110
|
-
} catch (error) {
|
|
111
|
-
// Theme overrides are optional. If the file is unreadable or malformed,
|
|
112
|
-
// keep rendering with built-in defaults instead of breaking the footer.
|
|
113
|
-
console.debug(`[powerline-theme] Failed to load ${themePath}:`, error);
|
|
114
125
|
}
|
|
115
126
|
|
|
116
127
|
themeConfigCache = {};
|
|
117
128
|
themeConfigCacheTime = now;
|
|
129
|
+
themeConfigCacheKey = cacheKey;
|
|
118
130
|
return themeConfigCache;
|
|
119
131
|
}
|
|
120
132
|
|
|
121
133
|
function loadUserTheme(): ColorScheme {
|
|
122
134
|
const now = Date.now();
|
|
123
|
-
|
|
135
|
+
const cacheKey = getThemePaths().join("\0");
|
|
136
|
+
if (userThemeCache && userThemeCacheKey === cacheKey && now - userThemeCacheTime < CACHE_TTL) {
|
|
124
137
|
return userThemeCache;
|
|
125
138
|
}
|
|
126
139
|
|
|
127
140
|
userThemeCache = sanitizeUserThemeOverrides(loadThemeConfig().colors);
|
|
128
141
|
userThemeCacheTime = now;
|
|
142
|
+
userThemeCacheKey = cacheKey;
|
|
129
143
|
return userThemeCache;
|
|
130
144
|
}
|
|
131
145
|
|