@selesai/code 0.7.0 → 0.8.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.
Files changed (73) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +4 -2
  3. package/dist/extensions/agent-browser.test.ts +195 -0
  4. package/dist/extensions/agent-browser.ts +158 -0
  5. package/dist/extensions/inline-skills.test.ts +14 -0
  6. package/dist/extensions/inline-skills.ts +14 -7
  7. package/dist/extensions/package.json +2 -1
  8. package/dist/extensions/pi-powerline-footer/.github/workflows/test.yml +27 -0
  9. package/dist/extensions/pi-powerline-footer/CHANGELOG.md +156 -0
  10. package/dist/extensions/pi-powerline-footer/CONTRIBUTING.md +16 -0
  11. package/dist/extensions/pi-powerline-footer/README.md +192 -44
  12. package/dist/extensions/pi-powerline-footer/bash-mode/completion.ts +19 -4
  13. package/dist/extensions/pi-powerline-footer/bash-mode/editor.ts +80 -25
  14. package/dist/extensions/pi-powerline-footer/bash-mode/history.ts +81 -29
  15. package/dist/extensions/pi-powerline-footer/bash-mode/types.ts +1 -1
  16. package/dist/extensions/pi-powerline-footer/cd-command.ts +190 -0
  17. package/dist/extensions/pi-powerline-footer/colors.ts +2 -1
  18. package/dist/extensions/pi-powerline-footer/context-usage.ts +121 -6
  19. package/dist/extensions/pi-powerline-footer/currency-rates.ts +142 -0
  20. package/dist/extensions/pi-powerline-footer/editor-composition.ts +32 -0
  21. package/dist/extensions/pi-powerline-footer/git-status.ts +108 -6
  22. package/dist/extensions/pi-powerline-footer/guide.ts +1 -1
  23. package/dist/extensions/pi-powerline-footer/icons.ts +11 -2
  24. package/dist/extensions/pi-powerline-footer/index.ts +1056 -324
  25. package/dist/extensions/pi-powerline-footer/lifecycle.ts +10 -0
  26. package/dist/extensions/pi-powerline-footer/package.json +10 -6
  27. package/dist/extensions/pi-powerline-footer/paths.ts +22 -0
  28. package/dist/extensions/pi-powerline-footer/powerline-config.ts +297 -15
  29. package/dist/extensions/pi-powerline-footer/presets.ts +6 -25
  30. package/dist/extensions/pi-powerline-footer/queue/store.ts +361 -0
  31. package/dist/extensions/pi-powerline-footer/queue/types.ts +54 -0
  32. package/dist/extensions/pi-powerline-footer/render-scheduler.ts +12 -7
  33. package/dist/extensions/pi-powerline-footer/segments.ts +114 -107
  34. package/dist/extensions/pi-powerline-footer/session-usage.ts +1 -1
  35. package/dist/extensions/pi-powerline-footer/shortcuts.ts +17 -3
  36. package/dist/extensions/pi-powerline-footer/tests/bash-mode.test.ts +189 -17
  37. package/dist/extensions/pi-powerline-footer/tests/cd-command.test.ts +144 -0
  38. package/dist/extensions/pi-powerline-footer/tests/context-usage.test.ts +120 -2
  39. package/dist/extensions/pi-powerline-footer/tests/custom-items.test.ts +245 -22
  40. package/dist/extensions/pi-powerline-footer/tests/editor-composition.test.ts +48 -0
  41. package/dist/extensions/pi-powerline-footer/tests/editor-responsiveness.test.ts +34 -0
  42. package/dist/extensions/pi-powerline-footer/tests/fixed-editor-border.test.ts +39 -0
  43. package/dist/extensions/pi-powerline-footer/tests/git-status.test.ts +92 -0
  44. package/dist/extensions/pi-powerline-footer/tests/guide.test.ts +11 -6
  45. package/dist/extensions/pi-powerline-footer/tests/jump-shortcuts.test.ts +33 -197
  46. package/dist/extensions/pi-powerline-footer/tests/mode-aware-trigger.test.ts +79 -0
  47. package/dist/extensions/pi-powerline-footer/tests/paths.test.ts +63 -0
  48. package/dist/extensions/pi-powerline-footer/tests/queue-store.test.ts +195 -0
  49. package/dist/extensions/pi-powerline-footer/tests/quit-cursor.test.ts +14 -0
  50. package/dist/extensions/pi-powerline-footer/tests/remaining-regressions.test.ts +167 -0
  51. package/dist/extensions/pi-powerline-footer/tests/stash-shortcut.test.ts +23 -18
  52. package/dist/extensions/pi-powerline-footer/tests/thinking-segment.test.ts +16 -1
  53. package/dist/extensions/pi-powerline-footer/tests/token-stats.test.ts +249 -0
  54. package/dist/extensions/pi-powerline-footer/tests/usage-display.test.ts +237 -0
  55. package/dist/extensions/pi-powerline-footer/tests/welcome.test.ts +199 -0
  56. package/dist/extensions/pi-powerline-footer/tests/working-vibes.test.ts +191 -100
  57. package/dist/extensions/pi-powerline-footer/theme.ts +32 -18
  58. package/dist/extensions/pi-powerline-footer/token-stats.ts +304 -0
  59. package/dist/extensions/pi-powerline-footer/tsconfig.json +13 -0
  60. package/dist/extensions/pi-powerline-footer/types.ts +74 -29
  61. package/dist/extensions/pi-powerline-footer/welcome-dismiss.ts +34 -0
  62. package/dist/extensions/pi-powerline-footer/welcome.ts +65 -10
  63. package/dist/extensions/pi-powerline-footer/working-vibes.ts +44 -11
  64. package/dist/extensions/pi-subagents/package-lock.json +2 -2
  65. package/dist/extensions/pi-subagents/package.json +1 -1
  66. package/dist/extensions/pi-subagents/src/slash/inline-subagents.ts +57 -24
  67. package/dist/extensions/pi-subagents/test/unit/inline-subagents.test.ts +82 -7
  68. package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +1 -1
  69. package/dist/extensions/ponytail/index.js +1 -31
  70. package/dist/extensions/ponytail/test/extension.test.js +5 -22
  71. package/dist/extensions/rtk.test.ts +27 -12
  72. package/dist/extensions/rtk.ts +41 -35
  73. 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, registerFauxProvider } = await import(FAUX_PROVIDER_PATH);
72
+ const { fauxAssistantMessage, fauxProvider } = await importFauxProviderTools();
50
73
  const { generateVibesBatch, initVibeManager, setVibeModel } = await import("../working-vibes.ts");
51
74
 
52
- const registration = registerFauxProvider({
75
+ const registration = fauxProvider({
53
76
  provider: "test-provider",
54
77
  models: [{ id: "test-model" }],
55
78
  });
56
79
 
57
- try {
58
- const model = registration.getModel("test-model");
59
- assert.ok(model);
60
-
61
- registration.setResponses([
62
- (context) => {
63
- assert.match(context.systemPrompt ?? "", /loading messages/i);
64
- return fauxAssistantMessage("Engaging warp drive...\nRunning diagnostics...");
65
- },
66
- ]);
67
-
68
- initVibeManager({
69
- modelRegistry: {
70
- find(provider: string, modelId: string) {
71
- return provider === "test-provider" && modelId === "test-model" ? model : undefined;
72
- },
73
- async getApiKeyAndHeaders() {
74
- return { ok: true, apiKey: "test-key", headers: {} };
75
- },
76
- },
77
- });
78
-
79
- assert.equal(setVibeModel("test-provider/test-model"), true);
80
-
81
- const result = await generateVibesBatch("star trek", 2);
82
-
83
- assert.equal(result.success, true);
84
- assert.equal(result.count, 2);
85
- assert.equal(existsSync(result.filePath), true);
86
- assert.deepEqual(readFileSync(result.filePath, "utf8").trim().split("\n"), [
87
- "Engaging warp drive...",
88
- "Running diagnostics...",
89
- ]);
90
- } finally {
91
- registration.unregister();
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, registerFauxProvider } = await import(FAUX_PROVIDER_PATH);
198
+ const { fauxAssistantMessage, fauxProvider } = await importFauxProviderTools();
112
199
  const { initVibeManager, onVibeAgentStart, onVibeBeforeAgentStart, setVibeModel, setVibeTheme } = await import("../working-vibes.ts");
113
200
 
114
- const registration = registerFauxProvider({
201
+ const registration = fauxProvider({
115
202
  provider: "test-provider",
116
203
  models: [{ id: "test-model" }],
117
204
  });
118
205
 
119
- try {
120
- const model = registration.getModel("test-model");
121
- assert.ok(model);
206
+ const model = registration.getModel("test-model");
207
+ assert.ok(model);
122
208
 
123
- registration.setResponses([
124
- (context) => {
125
- assert.match(context.systemPrompt ?? "", /loading messages/i);
126
- return fauxAssistantMessage("Engaging warp drive...");
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
- initVibeManager({
131
- modelRegistry: {
132
- find(provider: string, modelId: string) {
133
- return provider === "test-provider" && modelId === "test-model" ? model : undefined;
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
- assert.equal(setVibeTheme("star trek"), true);
142
- assert.equal(setVibeModel("test-provider/test-model"), true);
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
- const updates: Array<string | undefined> = [];
145
- onVibeAgentStart();
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
- const start = Date.now();
151
- while (!updates.includes("Engaging warp drive...") && Date.now() - start < 1000) {
152
- await new Promise((resolve) => setTimeout(resolve, 10));
153
- }
236
+ const updates: Array<string | undefined> = [];
237
+ onVibeAgentStart();
238
+ onVibeBeforeAgentStart("fix a bug", (message) => {
239
+ updates.push(message);
240
+ });
154
241
 
155
- assert.equal(updates[0], "Channeling star trek...");
156
- assert.ok(updates.includes("Engaging warp drive..."));
157
- } finally {
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, registerFauxProvider } = await import(FAUX_PROVIDER_PATH);
267
+ const { fauxAssistantMessage, fauxProvider } = await importFauxProviderTools();
179
268
  const { generateVibesBatch, initVibeManager, setVibeModel } = await import("../working-vibes.ts");
180
269
 
181
- const registration = registerFauxProvider({
270
+ const registration = fauxProvider({
182
271
  provider: "test-provider",
183
272
  models: [{ id: "test-model" }],
184
273
  });
185
274
 
186
- try {
187
- const model = registration.getModel("test-model");
188
- assert.ok(model);
275
+ const model = registration.getModel("test-model");
276
+ assert.ok(model);
189
277
 
190
- registration.setResponses([
191
- fauxAssistantMessage([], {
192
- stopReason: "error",
193
- errorMessage: "Instructions are required",
194
- }),
195
- ]);
278
+ registration.setResponses([
279
+ fauxAssistantMessage([], {
280
+ stopReason: "error",
281
+ errorMessage: "Instructions are required",
282
+ }),
283
+ ]);
196
284
 
197
- initVibeManager({
198
- modelRegistry: {
199
- find(provider: string, modelId: string) {
200
- return provider === "test-provider" && modelId === "test-model" ? model : undefined;
201
- },
202
- async getApiKeyAndHeaders() {
203
- return { ok: true, apiKey: "test-key", headers: {} };
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
- assert.equal(setVibeModel("test-provider/test-model"), true);
302
+ assert.equal(setVibeModel("test-provider/test-model"), true);
209
303
 
210
- const result = await generateVibesBatch("noir", 2);
304
+ const result = await generateVibesBatch("noir", 2);
211
305
 
212
- assert.equal(result.success, false);
213
- assert.equal(result.error, "Instructions are required");
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 the path to the theme.json file
89
+ * Get theme.json lookup paths.
86
90
  */
87
- function getThemePath(): string {
91
+ function getThemePaths(): string[] {
88
92
  const extDir = dirname(fileURLToPath(import.meta.url));
89
- return join(extDir, "theme.json");
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
- if (themeConfigCache && now - themeConfigCacheTime < CACHE_TTL) {
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 = getThemePath();
102
- try {
103
- if (existsSync(themePath)) {
104
- const content = readFileSync(themePath, "utf-8");
105
- const parsed = JSON.parse(content);
106
- themeConfigCache = isRecord(parsed) ? parsed : {};
107
- themeConfigCacheTime = now;
108
- return themeConfigCache;
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
- if (userThemeCache && now - userThemeCacheTime < CACHE_TTL) {
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