pi-system-theme 0.1.1 → 0.2.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/.github/workflows/npm-publish.yml +36 -0
- package/README.md +7 -7
- package/index.ts +173 -201
- package/package.json +1 -1
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
name: Publish package to npm
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types:
|
|
6
|
+
- published
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
publish:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
id-token: write
|
|
14
|
+
steps:
|
|
15
|
+
- name: Checkout release tag
|
|
16
|
+
uses: actions/checkout@v4
|
|
17
|
+
with:
|
|
18
|
+
ref: ${{ github.event.release.tag_name }}
|
|
19
|
+
|
|
20
|
+
- name: Setup Node.js
|
|
21
|
+
uses: actions/setup-node@v4
|
|
22
|
+
with:
|
|
23
|
+
node-version: "22"
|
|
24
|
+
registry-url: "https://registry.npmjs.org"
|
|
25
|
+
|
|
26
|
+
- name: Verify tag matches package version
|
|
27
|
+
run: |
|
|
28
|
+
TAG="${{ github.event.release.tag_name }}"
|
|
29
|
+
VERSION="$(node -e "const fs=require('fs'); const pkg=JSON.parse(fs.readFileSync('package.json','utf8')); process.stdout.write(pkg.version)")"
|
|
30
|
+
if [ "v$VERSION" != "$TAG" ]; then
|
|
31
|
+
echo "Tag $TAG does not match package.json version $VERSION"
|
|
32
|
+
exit 1
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
- name: Publish to npm (trusted publisher / OIDC)
|
|
36
|
+
run: npm publish --provenance
|
package/README.md
CHANGED
|
@@ -41,31 +41,31 @@ Example:
|
|
|
41
41
|
|
|
42
42
|
## Interactive command
|
|
43
43
|
|
|
44
|
-
Use `/system-theme` to
|
|
44
|
+
Use `/system-theme` to open a small settings menu and edit:
|
|
45
45
|
|
|
46
46
|
1. dark theme name
|
|
47
47
|
2. light theme name
|
|
48
48
|
3. poll interval (ms)
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
Choose **Save and apply** to persist overrides and apply immediately.
|
|
51
51
|
|
|
52
52
|
## Notes
|
|
53
53
|
|
|
54
54
|
- This extension currently only acts on macOS (`process.platform === "darwin"`).
|
|
55
|
-
- If a configured theme name
|
|
55
|
+
- If a configured theme name does not exist, Pi keeps the current theme and logs a warning.
|
|
56
56
|
|
|
57
57
|
## Install
|
|
58
58
|
|
|
59
|
-
From
|
|
59
|
+
From npm (standalone package):
|
|
60
60
|
|
|
61
61
|
```bash
|
|
62
|
-
pi install
|
|
62
|
+
pi install npm:pi-system-theme
|
|
63
63
|
```
|
|
64
64
|
|
|
65
|
-
|
|
65
|
+
From git:
|
|
66
66
|
|
|
67
67
|
```bash
|
|
68
|
-
pi install
|
|
68
|
+
pi install git:github.com/ferologics/pi-system-theme
|
|
69
69
|
```
|
|
70
70
|
|
|
71
71
|
Or from local source while developing:
|
package/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { execFile } from "node:child_process";
|
|
2
|
-
import {
|
|
2
|
+
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { promisify } from "node:util";
|
|
@@ -7,20 +7,14 @@ import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext } from "@m
|
|
|
7
7
|
|
|
8
8
|
const execFileAsync = promisify(execFile);
|
|
9
9
|
|
|
10
|
-
type Appearance = "dark" | "light" | "unknown";
|
|
11
|
-
|
|
12
|
-
type RawConfig = {
|
|
13
|
-
darkTheme?: unknown;
|
|
14
|
-
lightTheme?: unknown;
|
|
15
|
-
pollMs?: unknown;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
10
|
type Config = {
|
|
19
11
|
darkTheme: string;
|
|
20
12
|
lightTheme: string;
|
|
21
13
|
pollMs: number;
|
|
22
14
|
};
|
|
23
15
|
|
|
16
|
+
type Appearance = "dark" | "light";
|
|
17
|
+
|
|
24
18
|
const DEFAULT_CONFIG: Config = {
|
|
25
19
|
darkTheme: "dark",
|
|
26
20
|
lightTheme: "light",
|
|
@@ -28,74 +22,32 @@ const DEFAULT_CONFIG: Config = {
|
|
|
28
22
|
};
|
|
29
23
|
|
|
30
24
|
const GLOBAL_CONFIG_PATH = path.join(os.homedir(), ".pi", "agent", "system-theme.json");
|
|
31
|
-
const MIN_POLL_MS = 500;
|
|
32
25
|
const DETECTION_TIMEOUT_MS = 1200;
|
|
33
|
-
|
|
34
|
-
function toNonEmptyString(value: unknown): string | undefined {
|
|
35
|
-
if (typeof value !== "string") return undefined;
|
|
36
|
-
|
|
37
|
-
const trimmed = value.trim();
|
|
38
|
-
return trimmed.length > 0 ? trimmed : undefined;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function toPositiveInteger(value: unknown): number | undefined {
|
|
42
|
-
if (typeof value !== "number" || !Number.isFinite(value)) return undefined;
|
|
43
|
-
|
|
44
|
-
const rounded = Math.round(value);
|
|
45
|
-
return rounded > 0 ? rounded : undefined;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function mergeConfig(base: Config, rawConfig: RawConfig | undefined): Config {
|
|
49
|
-
if (!rawConfig) return base;
|
|
50
|
-
|
|
51
|
-
const darkTheme = toNonEmptyString(rawConfig.darkTheme) ?? base.darkTheme;
|
|
52
|
-
const lightTheme = toNonEmptyString(rawConfig.lightTheme) ?? base.lightTheme;
|
|
53
|
-
|
|
54
|
-
const pollMsValue = toPositiveInteger(rawConfig.pollMs);
|
|
55
|
-
const pollMs = pollMsValue ? Math.max(pollMsValue, MIN_POLL_MS) : base.pollMs;
|
|
56
|
-
|
|
57
|
-
return {
|
|
58
|
-
darkTheme,
|
|
59
|
-
lightTheme,
|
|
60
|
-
pollMs,
|
|
61
|
-
};
|
|
62
|
-
}
|
|
26
|
+
const MIN_POLL_MS = 500;
|
|
63
27
|
|
|
64
28
|
function isObject(value: unknown): value is Record<string, unknown> {
|
|
65
29
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
66
30
|
}
|
|
67
31
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
} catch {
|
|
72
|
-
return undefined;
|
|
32
|
+
function toThemeName(value: unknown, fallback: string): string {
|
|
33
|
+
if (typeof value !== "string") {
|
|
34
|
+
return fallback;
|
|
73
35
|
}
|
|
74
36
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if (!isObject(parsed)) {
|
|
80
|
-
console.warn(`[pi-system-theme] Ignoring ${pathToConfig}: expected a JSON object.`);
|
|
81
|
-
return undefined;
|
|
82
|
-
}
|
|
37
|
+
const trimmed = value.trim();
|
|
38
|
+
return trimmed.length > 0 ? trimmed : fallback;
|
|
39
|
+
}
|
|
83
40
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
console.warn(`[pi-system-theme] Failed to load ${pathToConfig}: ${message}`);
|
|
88
|
-
return undefined;
|
|
41
|
+
function toPollMs(value: unknown, fallback: number): number {
|
|
42
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
43
|
+
return fallback;
|
|
89
44
|
}
|
|
90
|
-
}
|
|
91
45
|
|
|
92
|
-
|
|
93
|
-
const globalConfig = await readConfig(GLOBAL_CONFIG_PATH);
|
|
94
|
-
return mergeConfig(DEFAULT_CONFIG, globalConfig);
|
|
46
|
+
return Math.max(MIN_POLL_MS, Math.round(value));
|
|
95
47
|
}
|
|
96
48
|
|
|
97
|
-
function getOverrides(config: Config):
|
|
98
|
-
const overrides:
|
|
49
|
+
function getOverrides(config: Config): Partial<Config> {
|
|
50
|
+
const overrides: Partial<Config> = {};
|
|
99
51
|
|
|
100
52
|
if (config.darkTheme !== DEFAULT_CONFIG.darkTheme) {
|
|
101
53
|
overrides.darkTheme = config.darkTheme;
|
|
@@ -112,29 +64,65 @@ function getOverrides(config: Config): Record<string, string | number> {
|
|
|
112
64
|
return overrides;
|
|
113
65
|
}
|
|
114
66
|
|
|
115
|
-
async function
|
|
67
|
+
async function loadConfig(): Promise<Config> {
|
|
68
|
+
const config = { ...DEFAULT_CONFIG };
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
const rawContent = await readFile(GLOBAL_CONFIG_PATH, "utf8");
|
|
72
|
+
const parsed = JSON.parse(rawContent) as unknown;
|
|
73
|
+
|
|
74
|
+
if (!isObject(parsed)) {
|
|
75
|
+
console.warn(`[pi-system-theme] Ignoring ${GLOBAL_CONFIG_PATH}: expected JSON object.`);
|
|
76
|
+
return config;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
config.darkTheme = toThemeName(parsed.darkTheme, config.darkTheme);
|
|
80
|
+
config.lightTheme = toThemeName(parsed.lightTheme, config.lightTheme);
|
|
81
|
+
config.pollMs = toPollMs(parsed.pollMs, config.pollMs);
|
|
82
|
+
|
|
83
|
+
return config;
|
|
84
|
+
} catch (error) {
|
|
85
|
+
if ((error as { code?: string })?.code === "ENOENT") {
|
|
86
|
+
return config;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
90
|
+
console.warn(`[pi-system-theme] Failed to read ${GLOBAL_CONFIG_PATH}: ${message}`);
|
|
91
|
+
return config;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function saveConfig(config: Config): Promise<{ wroteFile: boolean; overrideCount: number }> {
|
|
116
96
|
const overrides = getOverrides(config);
|
|
117
97
|
const overrideCount = Object.keys(overrides).length;
|
|
118
98
|
|
|
119
99
|
if (overrideCount === 0) {
|
|
120
100
|
await rm(GLOBAL_CONFIG_PATH, { force: true });
|
|
121
|
-
return {
|
|
101
|
+
return {
|
|
102
|
+
wroteFile: false,
|
|
103
|
+
overrideCount,
|
|
104
|
+
};
|
|
122
105
|
}
|
|
123
106
|
|
|
124
107
|
await mkdir(path.dirname(GLOBAL_CONFIG_PATH), { recursive: true });
|
|
125
108
|
await writeFile(GLOBAL_CONFIG_PATH, `${JSON.stringify(overrides, null, 4)}\n`, "utf8");
|
|
126
109
|
|
|
127
|
-
return {
|
|
110
|
+
return {
|
|
111
|
+
wroteFile: true,
|
|
112
|
+
overrideCount,
|
|
113
|
+
};
|
|
128
114
|
}
|
|
129
115
|
|
|
130
116
|
function extractStderr(error: unknown): string {
|
|
131
|
-
if (typeof error !== "object"
|
|
117
|
+
if (!error || typeof error !== "object") {
|
|
118
|
+
return "";
|
|
119
|
+
}
|
|
132
120
|
|
|
133
|
-
const
|
|
134
|
-
return typeof
|
|
121
|
+
const stderr = (error as { stderr?: unknown }).stderr;
|
|
122
|
+
return typeof stderr === "string" ? stderr : "";
|
|
135
123
|
}
|
|
136
124
|
|
|
137
|
-
async function detectAppearance(): Promise<Appearance> {
|
|
125
|
+
async function detectAppearance(): Promise<Appearance | null> {
|
|
138
126
|
try {
|
|
139
127
|
const { stdout } = await execFileAsync("/usr/bin/defaults", ["read", "-g", "AppleInterfaceStyle"], {
|
|
140
128
|
timeout: DETECTION_TIMEOUT_MS,
|
|
@@ -142,153 +130,96 @@ async function detectAppearance(): Promise<Appearance> {
|
|
|
142
130
|
});
|
|
143
131
|
|
|
144
132
|
const normalized = stdout.trim().toLowerCase();
|
|
145
|
-
if (normalized === "dark")
|
|
146
|
-
|
|
133
|
+
if (normalized === "dark") {
|
|
134
|
+
return "dark";
|
|
135
|
+
}
|
|
147
136
|
|
|
148
|
-
|
|
137
|
+
if (normalized === "light") {
|
|
138
|
+
return "light";
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return null;
|
|
149
142
|
} catch (error) {
|
|
150
143
|
const stderr = extractStderr(error).toLowerCase();
|
|
151
144
|
if (stderr.includes("does not exist")) {
|
|
152
145
|
return "light";
|
|
153
146
|
}
|
|
154
147
|
|
|
155
|
-
return
|
|
148
|
+
return null;
|
|
156
149
|
}
|
|
157
150
|
}
|
|
158
151
|
|
|
159
|
-
function
|
|
160
|
-
return appearance === "dark" ? config.darkTheme : config.lightTheme;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function getBuiltinFallbackTheme(appearance: Exclude<Appearance, "unknown">): string {
|
|
164
|
-
return appearance === "dark" ? "dark" : "light";
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
function resolveThemeName(
|
|
168
|
-
ctx: ExtensionContext,
|
|
169
|
-
requestedTheme: string,
|
|
170
|
-
fallbackTheme: string,
|
|
171
|
-
warnedMissingThemes: Set<string>,
|
|
172
|
-
): string {
|
|
173
|
-
if (ctx.ui.getTheme(requestedTheme)) {
|
|
174
|
-
return requestedTheme;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
const warningKey = `${requestedTheme}=>${fallbackTheme}`;
|
|
178
|
-
if (!warnedMissingThemes.has(warningKey)) {
|
|
179
|
-
warnedMissingThemes.add(warningKey);
|
|
180
|
-
console.warn(
|
|
181
|
-
`[pi-system-theme] Theme "${requestedTheme}" is not available. Falling back to "${fallbackTheme}".`,
|
|
182
|
-
);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
if (ctx.ui.getTheme(fallbackTheme)) {
|
|
186
|
-
return fallbackTheme;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
return requestedTheme;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
function warnSetThemeFailureOnce(
|
|
193
|
-
warningKey: string,
|
|
194
|
-
warnings: Set<string>,
|
|
195
|
-
themeName: string,
|
|
196
|
-
errorMessage: string,
|
|
197
|
-
): void {
|
|
198
|
-
if (warnings.has(warningKey)) return;
|
|
199
|
-
|
|
200
|
-
warnings.add(warningKey);
|
|
201
|
-
console.warn(`[pi-system-theme] Failed to set theme "${themeName}": ${errorMessage}`);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
async function promptStringSetting(
|
|
152
|
+
async function promptTheme(
|
|
205
153
|
ctx: ExtensionCommandContext,
|
|
206
154
|
label: string,
|
|
207
155
|
currentValue: string,
|
|
208
156
|
): Promise<string | undefined> {
|
|
209
|
-
const
|
|
210
|
-
if (
|
|
157
|
+
const next = await ctx.ui.input(label, currentValue);
|
|
158
|
+
if (next === undefined) {
|
|
159
|
+
return undefined;
|
|
160
|
+
}
|
|
211
161
|
|
|
212
|
-
const trimmed =
|
|
162
|
+
const trimmed = next.trim();
|
|
213
163
|
return trimmed.length > 0 ? trimmed : currentValue;
|
|
214
164
|
}
|
|
215
165
|
|
|
216
|
-
async function
|
|
217
|
-
ctx: ExtensionCommandContext,
|
|
218
|
-
label: string,
|
|
219
|
-
currentValue: number,
|
|
220
|
-
minimum: number,
|
|
221
|
-
): Promise<number | undefined> {
|
|
166
|
+
async function promptPollMs(ctx: ExtensionCommandContext, currentValue: number): Promise<number | undefined> {
|
|
222
167
|
while (true) {
|
|
223
|
-
const
|
|
224
|
-
if (
|
|
168
|
+
const next = await ctx.ui.input("Poll interval (ms)", String(currentValue));
|
|
169
|
+
if (next === undefined) {
|
|
170
|
+
return undefined;
|
|
171
|
+
}
|
|
225
172
|
|
|
226
|
-
const trimmed =
|
|
227
|
-
if (trimmed.length === 0)
|
|
173
|
+
const trimmed = next.trim();
|
|
174
|
+
if (trimmed.length === 0) {
|
|
175
|
+
return currentValue;
|
|
176
|
+
}
|
|
228
177
|
|
|
229
178
|
const parsed = Number.parseInt(trimmed, 10);
|
|
230
|
-
if (Number.isFinite(parsed) && parsed >=
|
|
179
|
+
if (Number.isFinite(parsed) && parsed >= MIN_POLL_MS) {
|
|
231
180
|
return parsed;
|
|
232
181
|
}
|
|
233
182
|
|
|
234
|
-
ctx.ui.notify(`Enter a whole number >= ${
|
|
183
|
+
ctx.ui.notify(`Enter a whole number >= ${MIN_POLL_MS}.`, "warning");
|
|
235
184
|
}
|
|
236
185
|
}
|
|
237
186
|
|
|
238
187
|
export default function systemThemeExtension(pi: ExtensionAPI): void {
|
|
188
|
+
let activeConfig: Config = { ...DEFAULT_CONFIG };
|
|
239
189
|
let intervalId: ReturnType<typeof setInterval> | null = null;
|
|
240
190
|
let syncInProgress = false;
|
|
241
|
-
let
|
|
242
|
-
let activeConfig: Config = DEFAULT_CONFIG;
|
|
243
|
-
|
|
244
|
-
const missingThemeWarnings = new Set<string>();
|
|
245
|
-
const setThemeWarnings = new Set<string>();
|
|
191
|
+
let lastSetThemeError: string | null = null;
|
|
246
192
|
|
|
247
193
|
async function syncTheme(ctx: ExtensionContext): Promise<void> {
|
|
248
|
-
if (syncInProgress)
|
|
194
|
+
if (syncInProgress) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
249
198
|
syncInProgress = true;
|
|
250
199
|
|
|
251
200
|
try {
|
|
252
201
|
const appearance = await detectAppearance();
|
|
253
|
-
if (appearance
|
|
254
|
-
|
|
255
|
-
const requestedTheme = getRequestedTheme(activeConfig, appearance);
|
|
256
|
-
const fallbackTheme = getBuiltinFallbackTheme(appearance);
|
|
257
|
-
const targetTheme = resolveThemeName(ctx, requestedTheme, fallbackTheme, missingThemeWarnings);
|
|
258
|
-
|
|
259
|
-
const activeThemeName = ctx.ui.theme.name ?? lastAppliedThemeName;
|
|
260
|
-
if (activeThemeName === targetTheme) {
|
|
261
|
-
lastAppliedThemeName = activeThemeName;
|
|
202
|
+
if (!appearance) {
|
|
262
203
|
return;
|
|
263
204
|
}
|
|
264
205
|
|
|
265
|
-
const
|
|
266
|
-
if (
|
|
267
|
-
lastAppliedThemeName = targetTheme;
|
|
206
|
+
const targetTheme = appearance === "dark" ? activeConfig.darkTheme : activeConfig.lightTheme;
|
|
207
|
+
if (ctx.ui.theme.name === targetTheme) {
|
|
268
208
|
return;
|
|
269
209
|
}
|
|
270
210
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
targetTheme,
|
|
275
|
-
setResult.error ?? "unknown error",
|
|
276
|
-
);
|
|
277
|
-
|
|
278
|
-
if (targetTheme === fallbackTheme) return;
|
|
279
|
-
|
|
280
|
-
const fallbackResult = ctx.ui.setTheme(fallbackTheme);
|
|
281
|
-
if (fallbackResult.success) {
|
|
282
|
-
lastAppliedThemeName = fallbackTheme;
|
|
211
|
+
const result = ctx.ui.setTheme(targetTheme);
|
|
212
|
+
if (result.success) {
|
|
213
|
+
lastSetThemeError = null;
|
|
283
214
|
return;
|
|
284
215
|
}
|
|
285
216
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
217
|
+
const message = result.error ?? "unknown error";
|
|
218
|
+
const errorKey = `${targetTheme}:${message}`;
|
|
219
|
+
if (errorKey !== lastSetThemeError) {
|
|
220
|
+
lastSetThemeError = errorKey;
|
|
221
|
+
console.warn(`[pi-system-theme] Failed to set theme "${targetTheme}": ${message}`);
|
|
222
|
+
}
|
|
292
223
|
} finally {
|
|
293
224
|
syncInProgress = false;
|
|
294
225
|
}
|
|
@@ -305,58 +236,99 @@ export default function systemThemeExtension(pi: ExtensionAPI): void {
|
|
|
305
236
|
}
|
|
306
237
|
|
|
307
238
|
pi.registerCommand("system-theme", {
|
|
308
|
-
description: "Configure
|
|
239
|
+
description: "Configure pi-system-theme",
|
|
309
240
|
handler: async (_args, ctx) => {
|
|
310
241
|
if (process.platform !== "darwin") {
|
|
311
242
|
ctx.ui.notify("pi-system-theme currently supports macOS only.", "info");
|
|
312
243
|
return;
|
|
313
244
|
}
|
|
314
245
|
|
|
315
|
-
const
|
|
316
|
-
|
|
246
|
+
const draft: Config = { ...activeConfig };
|
|
247
|
+
|
|
248
|
+
while (true) {
|
|
249
|
+
const darkOption = `Dark theme: ${draft.darkTheme}`;
|
|
250
|
+
const lightOption = `Light theme: ${draft.lightTheme}`;
|
|
251
|
+
const pollOption = `Poll interval (ms): ${draft.pollMs}`;
|
|
252
|
+
const saveOption = "Save and apply";
|
|
253
|
+
const cancelOption = "Cancel";
|
|
254
|
+
|
|
255
|
+
const choice = await ctx.ui.select("pi-system-theme", [
|
|
256
|
+
darkOption,
|
|
257
|
+
lightOption,
|
|
258
|
+
pollOption,
|
|
259
|
+
saveOption,
|
|
260
|
+
cancelOption,
|
|
261
|
+
]);
|
|
262
|
+
|
|
263
|
+
if (choice === undefined || choice === cancelOption) {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
317
266
|
|
|
318
|
-
|
|
319
|
-
|
|
267
|
+
if (choice === darkOption) {
|
|
268
|
+
const next = await promptTheme(ctx, "Dark theme", draft.darkTheme);
|
|
269
|
+
if (next !== undefined) {
|
|
270
|
+
draft.darkTheme = next;
|
|
271
|
+
}
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
320
274
|
|
|
321
|
-
|
|
322
|
-
|
|
275
|
+
if (choice === lightOption) {
|
|
276
|
+
const next = await promptTheme(ctx, "Light theme", draft.lightTheme);
|
|
277
|
+
if (next !== undefined) {
|
|
278
|
+
draft.lightTheme = next;
|
|
279
|
+
}
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
323
282
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
283
|
+
if (choice === pollOption) {
|
|
284
|
+
const next = await promptPollMs(ctx, draft.pollMs);
|
|
285
|
+
if (next !== undefined) {
|
|
286
|
+
draft.pollMs = next;
|
|
287
|
+
}
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
329
290
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
291
|
+
if (choice === saveOption) {
|
|
292
|
+
activeConfig = draft;
|
|
293
|
+
|
|
294
|
+
try {
|
|
295
|
+
const result = await saveConfig(activeConfig);
|
|
296
|
+
if (result.wroteFile) {
|
|
297
|
+
ctx.ui.notify(
|
|
298
|
+
`Saved ${result.overrideCount} override(s) to ${GLOBAL_CONFIG_PATH}.`,
|
|
299
|
+
"info",
|
|
300
|
+
);
|
|
301
|
+
} else {
|
|
302
|
+
ctx.ui.notify("No overrides left. Using defaults.", "info");
|
|
303
|
+
}
|
|
304
|
+
} catch (error) {
|
|
305
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
306
|
+
ctx.ui.notify(`Failed to save config: ${message}`, "error");
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
await syncTheme(ctx);
|
|
311
|
+
restartPolling(ctx);
|
|
312
|
+
return;
|
|
336
313
|
}
|
|
337
|
-
} catch (error) {
|
|
338
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
339
|
-
ctx.ui.notify(`Failed to save config: ${message}`, "error");
|
|
340
|
-
return;
|
|
341
314
|
}
|
|
342
|
-
|
|
343
|
-
await syncTheme(ctx);
|
|
344
|
-
restartPolling(ctx);
|
|
345
315
|
},
|
|
346
316
|
});
|
|
347
317
|
|
|
348
318
|
pi.on("session_start", async (_event, ctx) => {
|
|
349
|
-
if (process.platform !== "darwin")
|
|
319
|
+
if (process.platform !== "darwin") {
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
350
322
|
|
|
351
323
|
activeConfig = await loadConfig();
|
|
352
|
-
lastAppliedThemeName = ctx.ui.theme.name;
|
|
353
|
-
|
|
354
324
|
await syncTheme(ctx);
|
|
355
325
|
restartPolling(ctx);
|
|
356
326
|
});
|
|
357
327
|
|
|
358
328
|
pi.on("session_shutdown", () => {
|
|
359
|
-
if (!intervalId)
|
|
329
|
+
if (!intervalId) {
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
360
332
|
|
|
361
333
|
clearInterval(intervalId);
|
|
362
334
|
intervalId = null;
|