@pi-unipi/notify 0.1.8 → 0.1.9
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/commands.ts +8 -6
- package/events.ts +38 -25
- package/index.ts +2 -1
- package/package.json +1 -1
- package/skills/configure-notify/SKILL.md +43 -6
- package/tools.ts +6 -4
- package/tui/ntfy-setup.ts +68 -15
- package/tui/settings-overlay.ts +21 -6
package/commands.ts
CHANGED
|
@@ -13,6 +13,7 @@ import { TelegramSetupOverlay } from "./tui/telegram-setup.js";
|
|
|
13
13
|
import { NtfySetupOverlay } from "./tui/ntfy-setup.js";
|
|
14
14
|
import { RecapModelSelectorOverlay } from "./tui/recap-model-selector.js";
|
|
15
15
|
import { loadConfig } from "./settings.js";
|
|
16
|
+
import { loadNtfyConfig } from "./ntfy-config.js";
|
|
16
17
|
import { sendNativeNotification } from "./platforms/native.js";
|
|
17
18
|
import { sendGotifyNotification } from "./platforms/gotify.js";
|
|
18
19
|
import { sendTelegramNotification } from "./platforms/telegram.js";
|
|
@@ -310,16 +311,17 @@ export function registerNotifyCommands(pi: ExtensionAPI): void {
|
|
|
310
311
|
}
|
|
311
312
|
}
|
|
312
313
|
|
|
313
|
-
// ntfy
|
|
314
|
-
|
|
314
|
+
// ntfy — resolved from project/global ntfy.json
|
|
315
|
+
const ntfyConfig = loadNtfyConfig(process.cwd());
|
|
316
|
+
if (ntfyConfig.enabled && ntfyConfig.serverUrl && ntfyConfig.topic) {
|
|
315
317
|
try {
|
|
316
318
|
await sendNtfyNotification(
|
|
317
|
-
|
|
318
|
-
|
|
319
|
+
ntfyConfig.serverUrl,
|
|
320
|
+
ntfyConfig.topic,
|
|
319
321
|
title,
|
|
320
322
|
message,
|
|
321
|
-
|
|
322
|
-
|
|
323
|
+
ntfyConfig.priority,
|
|
324
|
+
ntfyConfig.token
|
|
323
325
|
);
|
|
324
326
|
results.push("✓ ntfy: sent");
|
|
325
327
|
} catch (err) {
|
package/events.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
9
9
|
import { UNIPI_EVENTS, emitEvent } from "@pi-unipi/core";
|
|
10
10
|
import type { NotifyConfig, NotifyPlatform, NotifyDispatchResult } from "./types.js";
|
|
11
|
+
import { loadNtfyConfig } from "./ntfy-config.js";
|
|
11
12
|
import { sendNativeNotification } from "./platforms/native.js";
|
|
12
13
|
import { sendGotifyNotification } from "./platforms/gotify.js";
|
|
13
14
|
import { sendTelegramNotification } from "./platforms/telegram.js";
|
|
@@ -47,7 +48,8 @@ export const BUILTIN_EVENTS: Record<
|
|
|
47
48
|
*/
|
|
48
49
|
export function registerEventListeners(
|
|
49
50
|
pi: ExtensionAPI,
|
|
50
|
-
config: NotifyConfig
|
|
51
|
+
config: NotifyConfig,
|
|
52
|
+
cwd: string
|
|
51
53
|
): void {
|
|
52
54
|
// Register built-in events (except agent_end which has custom logic)
|
|
53
55
|
for (const [eventKey, def] of Object.entries(BUILTIN_EVENTS)) {
|
|
@@ -60,8 +62,10 @@ export function registerEventListeners(
|
|
|
60
62
|
const title = `Pi — ${def.label}`;
|
|
61
63
|
const message = buildEventMessage(eventKey, payload);
|
|
62
64
|
// Fire-and-forget: don't block the event emitter
|
|
63
|
-
dispatchNotification(pi, title, message, eventConfig.platforms, eventKey, config).catch(
|
|
64
|
-
(
|
|
65
|
+
dispatchNotification(pi, title, message, eventConfig.platforms, eventKey, config, cwd).catch(
|
|
66
|
+
() => {
|
|
67
|
+
// Silently ignore — background notification failure is non-blocking.
|
|
68
|
+
}
|
|
65
69
|
);
|
|
66
70
|
};
|
|
67
71
|
|
|
@@ -96,9 +100,11 @@ export function registerEventListeners(
|
|
|
96
100
|
})
|
|
97
101
|
.catch(() => buildAgentEndMessage(sessionName))
|
|
98
102
|
.then((message) =>
|
|
99
|
-
dispatchNotification(pi, title, message, agentEndConfig.platforms, "agent_end", config)
|
|
103
|
+
dispatchNotification(pi, title, message, agentEndConfig.platforms, "agent_end", config, cwd)
|
|
100
104
|
)
|
|
101
|
-
.catch((
|
|
105
|
+
.catch(() => {
|
|
106
|
+
// Silently ignore — background agent_end notification failure is non-blocking.
|
|
107
|
+
});
|
|
102
108
|
return;
|
|
103
109
|
}
|
|
104
110
|
}
|
|
@@ -106,8 +112,10 @@ export function registerEventListeners(
|
|
|
106
112
|
|
|
107
113
|
// No recap or recap unavailable: dispatch immediately in background
|
|
108
114
|
const message = buildAgentEndMessage(sessionName);
|
|
109
|
-
dispatchNotification(pi, title, message, agentEndConfig.platforms, "agent_end", config).catch(
|
|
110
|
-
(
|
|
115
|
+
dispatchNotification(pi, title, message, agentEndConfig.platforms, "agent_end", config, cwd).catch(
|
|
116
|
+
() => {
|
|
117
|
+
// Silently ignore — background agent_end notification failure is non-blocking.
|
|
118
|
+
}
|
|
111
119
|
);
|
|
112
120
|
};
|
|
113
121
|
|
|
@@ -126,12 +134,12 @@ export function registerEventListeners(
|
|
|
126
134
|
}
|
|
127
135
|
|
|
128
136
|
/** Get all platforms that are currently enabled in config */
|
|
129
|
-
function getEnabledPlatforms(config: NotifyConfig): NotifyPlatform[] {
|
|
137
|
+
function getEnabledPlatforms(config: NotifyConfig, ntfyEnabled: boolean): NotifyPlatform[] {
|
|
130
138
|
const enabled: NotifyPlatform[] = [];
|
|
131
139
|
if (config.native.enabled) enabled.push("native");
|
|
132
140
|
if (config.gotify.enabled) enabled.push("gotify");
|
|
133
141
|
if (config.telegram.enabled) enabled.push("telegram");
|
|
134
|
-
if (
|
|
142
|
+
if (ntfyEnabled) enabled.push("ntfy");
|
|
135
143
|
return enabled;
|
|
136
144
|
}
|
|
137
145
|
|
|
@@ -148,34 +156,35 @@ export async function dispatchNotification(
|
|
|
148
156
|
message: string,
|
|
149
157
|
eventPlatforms: NotifyPlatform[],
|
|
150
158
|
eventType: string,
|
|
151
|
-
config: NotifyConfig
|
|
159
|
+
config: NotifyConfig,
|
|
160
|
+
cwd: string
|
|
152
161
|
): Promise<NotifyDispatchResult> {
|
|
162
|
+
// Resolve ntfy config from project/global ntfy.json
|
|
163
|
+
const ntfyConfig = loadNtfyConfig(cwd);
|
|
164
|
+
|
|
153
165
|
// Resolve platforms: event-specific → all enabled → global defaults
|
|
154
166
|
const platforms =
|
|
155
167
|
eventPlatforms.length > 0
|
|
156
168
|
? eventPlatforms
|
|
157
|
-
: getEnabledPlatforms(config).length > 0
|
|
158
|
-
? getEnabledPlatforms(config)
|
|
169
|
+
: getEnabledPlatforms(config, ntfyConfig.enabled).length > 0
|
|
170
|
+
? getEnabledPlatforms(config, ntfyConfig.enabled)
|
|
159
171
|
: config.defaultPlatforms;
|
|
160
172
|
|
|
161
173
|
const enabledPlatforms = platforms.filter((p) => {
|
|
162
174
|
if (p === "native") return config.native.enabled;
|
|
163
175
|
if (p === "gotify") return config.gotify.enabled;
|
|
164
176
|
if (p === "telegram") return config.telegram.enabled;
|
|
165
|
-
if (p === "ntfy") return
|
|
177
|
+
if (p === "ntfy") return ntfyConfig.enabled;
|
|
166
178
|
return false;
|
|
167
179
|
});
|
|
168
180
|
|
|
169
181
|
const results = await Promise.all(
|
|
170
182
|
enabledPlatforms.map(async (platform) => {
|
|
171
183
|
try {
|
|
172
|
-
await sendToPlatform(platform, title, message, config);
|
|
184
|
+
await sendToPlatform(platform, title, message, config, cwd);
|
|
173
185
|
return { platform, success: true };
|
|
174
186
|
} catch (err) {
|
|
175
|
-
|
|
176
|
-
`[notify] Failed to send via ${platform}:`,
|
|
177
|
-
err instanceof Error ? err.message : err
|
|
178
|
-
);
|
|
187
|
+
// Silently ignore — platform send failure is tracked in results.
|
|
179
188
|
return {
|
|
180
189
|
platform,
|
|
181
190
|
success: false,
|
|
@@ -203,7 +212,8 @@ async function sendToPlatform(
|
|
|
203
212
|
platform: NotifyPlatform,
|
|
204
213
|
title: string,
|
|
205
214
|
message: string,
|
|
206
|
-
config: NotifyConfig
|
|
215
|
+
config: NotifyConfig,
|
|
216
|
+
cwd: string
|
|
207
217
|
): Promise<void> {
|
|
208
218
|
switch (platform) {
|
|
209
219
|
case "native":
|
|
@@ -234,19 +244,22 @@ async function sendToPlatform(
|
|
|
234
244
|
message
|
|
235
245
|
);
|
|
236
246
|
break;
|
|
237
|
-
case "ntfy":
|
|
238
|
-
|
|
247
|
+
case "ntfy": {
|
|
248
|
+
const ntfyConfig = loadNtfyConfig(cwd);
|
|
249
|
+
if (!ntfyConfig.enabled) return;
|
|
250
|
+
if (!ntfyConfig.serverUrl || !ntfyConfig.topic) {
|
|
239
251
|
throw new Error("ntfy: serverUrl and topic are required");
|
|
240
252
|
}
|
|
241
253
|
await sendNtfyNotification(
|
|
242
|
-
|
|
243
|
-
|
|
254
|
+
ntfyConfig.serverUrl,
|
|
255
|
+
ntfyConfig.topic,
|
|
244
256
|
title,
|
|
245
257
|
message,
|
|
246
|
-
|
|
247
|
-
|
|
258
|
+
ntfyConfig.priority,
|
|
259
|
+
ntfyConfig.token
|
|
248
260
|
);
|
|
249
261
|
break;
|
|
262
|
+
}
|
|
250
263
|
}
|
|
251
264
|
}
|
|
252
265
|
|
package/index.ts
CHANGED
|
@@ -42,8 +42,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
42
42
|
// Session lifecycle — register events and announce module
|
|
43
43
|
pi.on("session_start", async (_event, ctx) => {
|
|
44
44
|
setSessionContext(ctx);
|
|
45
|
+
const cwd = process.cwd();
|
|
45
46
|
const config = loadConfig();
|
|
46
|
-
registerEventListeners(pi, config);
|
|
47
|
+
registerEventListeners(pi, config, cwd);
|
|
47
48
|
|
|
48
49
|
emitEvent(pi, UNIPI_EVENTS.MODULE_READY, {
|
|
49
50
|
name: MODULES.NOTIFY,
|
package/package.json
CHANGED
|
@@ -16,9 +16,13 @@ Help users configure the `@pi-unipi/notify` notification system.
|
|
|
16
16
|
- User wants to change which events trigger notifications
|
|
17
17
|
- User asks about notification settings
|
|
18
18
|
|
|
19
|
-
## Config
|
|
19
|
+
## Config locations
|
|
20
20
|
|
|
21
|
-
`~/.unipi/config/notify/config.json`
|
|
21
|
+
**Main config (platforms + events):** `~/.unipi/config/notify/config.json`
|
|
22
|
+
|
|
23
|
+
**ntfy config (dedicated file):**
|
|
24
|
+
- Global: `~/.unipi/config/notify/ntfy.json`
|
|
25
|
+
- Project: `<project>/.unipi/config/notify/ntfy.json`
|
|
22
26
|
|
|
23
27
|
## Config structure
|
|
24
28
|
|
|
@@ -54,7 +58,8 @@ Help users configure the `@pi-unipi/notify` notification system.
|
|
|
54
58
|
"topic": null,
|
|
55
59
|
"token": null,
|
|
56
60
|
"priority": 3
|
|
57
|
-
}
|
|
61
|
+
},
|
|
62
|
+
"NOTE": "ntfy section is legacy — migrated to ntfy.json on first run"
|
|
58
63
|
}
|
|
59
64
|
```
|
|
60
65
|
|
|
@@ -92,9 +97,41 @@ Requires:
|
|
|
92
97
|
- `priority` — 1-5 (default: 3)
|
|
93
98
|
|
|
94
99
|
**Setup options:**
|
|
95
|
-
1. **Interactive overlay:** Run `/unipi:notify-set-ntfy` for guided setup with connection test
|
|
96
|
-
2. **Manual config:** Edit `
|
|
97
|
-
3. **Agent can write config:** Read the current
|
|
100
|
+
1. **Interactive overlay:** Run `/unipi:notify-set-ntfy` for guided setup with scope selection and connection test
|
|
101
|
+
2. **Manual config:** Edit `ntfy.json` directly (see Project-Level ntfy Config below)
|
|
102
|
+
3. **Agent can write config:** Read the current ntfy.json, merge changes, write back
|
|
103
|
+
|
|
104
|
+
### Project-Level ntfy Config
|
|
105
|
+
|
|
106
|
+
ntfy uses dedicated `ntfy.json` files at both global and project scope, with full override semantics.
|
|
107
|
+
|
|
108
|
+
**File locations:**
|
|
109
|
+
- Global: `~/.unipi/config/notify/ntfy.json` (all projects)
|
|
110
|
+
- Project: `<project>/.unipi/config/notify/ntfy.json` (this project only)
|
|
111
|
+
|
|
112
|
+
**Resolution order (at dispatch time):**
|
|
113
|
+
1. Project `ntfy.json` exists → use it (full override)
|
|
114
|
+
2. No project config → use global `ntfy.json`
|
|
115
|
+
3. Neither exists → ntfy is effectively disabled
|
|
116
|
+
|
|
117
|
+
**ntfy.json shape:**
|
|
118
|
+
```json
|
|
119
|
+
{
|
|
120
|
+
"enabled": true,
|
|
121
|
+
"serverUrl": "https://ntfy.sh",
|
|
122
|
+
"topic": "my-project-alerts",
|
|
123
|
+
"token": null,
|
|
124
|
+
"priority": 3
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**Scope selection in wizard:** When running `/unipi:notify-set-ntfy`, the wizard now asks where to save the config (Global or Project). Re-running the wizard pre-selects the current scope and pre-fills existing values.
|
|
129
|
+
|
|
130
|
+
**Settings overlay:** The ntfy line in `/unipi:notify-settings` shows topic, priority, and scope label (`[project]`, `[global]`, or "Not configured").
|
|
131
|
+
|
|
132
|
+
**Migration:** On first run, if `config.json` has ntfy settings and `ntfy.json` doesn't exist, settings are automatically migrated to `ntfy.json`. The legacy `config.json` ntfy section is left untouched for backward compatibility.
|
|
133
|
+
|
|
134
|
+
**Manual config:** Edit the appropriate `ntfy.json` file directly with the fields above.
|
|
98
135
|
|
|
99
136
|
## Commands
|
|
100
137
|
|
package/tools.ts
CHANGED
|
@@ -64,16 +64,18 @@ export function registerNotifyTools(pi: ExtensionAPI): void {
|
|
|
64
64
|
const notifPlatforms = platforms || config.defaultPlatforms;
|
|
65
65
|
|
|
66
66
|
// Fire-and-forget: dispatch in background so the tool doesn't block the agent
|
|
67
|
+
const cwd = process.cwd();
|
|
67
68
|
dispatchNotification(
|
|
68
69
|
pi,
|
|
69
70
|
notifTitle,
|
|
70
71
|
message,
|
|
71
72
|
notifPlatforms,
|
|
72
73
|
"agent_tool",
|
|
73
|
-
config
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
74
|
+
config,
|
|
75
|
+
cwd
|
|
76
|
+
).catch(() => {
|
|
77
|
+
// Silently ignore — background dispatch failure is non-blocking.
|
|
78
|
+
});
|
|
77
79
|
|
|
78
80
|
return {
|
|
79
81
|
content: [
|
package/tui/ntfy-setup.ts
CHANGED
|
@@ -10,10 +10,11 @@ import type { Component } from "@mariozechner/pi-tui";
|
|
|
10
10
|
import { truncateToWidth, visibleWidth } from "@mariozechner/pi-tui";
|
|
11
11
|
import type { Theme } from "@mariozechner/pi-coding-agent";
|
|
12
12
|
import { sendNtfyNotification } from "../platforms/ntfy.js";
|
|
13
|
-
import {
|
|
13
|
+
import { loadNtfyConfig, saveNtfyConfig, getNtfyConfigScope } from "../ntfy-config.js";
|
|
14
14
|
|
|
15
15
|
type SetupPhase =
|
|
16
16
|
| "instructions"
|
|
17
|
+
| "scope"
|
|
17
18
|
| "server-url"
|
|
18
19
|
| "topic"
|
|
19
20
|
| "token"
|
|
@@ -28,6 +29,8 @@ type SetupPhase =
|
|
|
28
29
|
*/
|
|
29
30
|
export class NtfySetupOverlay implements Component {
|
|
30
31
|
private phase: SetupPhase = "instructions";
|
|
32
|
+
private scope: "global" | "project" = "global";
|
|
33
|
+
private scopeIndex = 0; // 0 = global, 1 = project
|
|
31
34
|
private serverUrl = "";
|
|
32
35
|
private topic = "";
|
|
33
36
|
private token = "";
|
|
@@ -41,12 +44,18 @@ export class NtfySetupOverlay implements Component {
|
|
|
41
44
|
private theme: Theme | null = null;
|
|
42
45
|
|
|
43
46
|
constructor() {
|
|
44
|
-
//
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
if (
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
// Determine current scope and pre-fill from resolved config
|
|
48
|
+
const cwd = process.cwd();
|
|
49
|
+
const existingScope = getNtfyConfigScope(cwd);
|
|
50
|
+
if (existingScope !== "none") {
|
|
51
|
+
this.scope = existingScope;
|
|
52
|
+
this.scopeIndex = existingScope === "project" ? 1 : 0;
|
|
53
|
+
}
|
|
54
|
+
const config = loadNtfyConfig(cwd);
|
|
55
|
+
if (config.serverUrl) this.serverUrl = config.serverUrl;
|
|
56
|
+
if (config.topic) this.topic = config.topic;
|
|
57
|
+
if (config.token) this.token = config.token;
|
|
58
|
+
if (config.priority) this.priority = String(config.priority);
|
|
50
59
|
}
|
|
51
60
|
|
|
52
61
|
setTheme(theme: Theme): void {
|
|
@@ -59,6 +68,21 @@ export class NtfySetupOverlay implements Component {
|
|
|
59
68
|
switch (this.phase) {
|
|
60
69
|
case "instructions":
|
|
61
70
|
if (data === "\r" || data === " ") {
|
|
71
|
+
this.phase = "scope";
|
|
72
|
+
} else if (data === "\x1b") {
|
|
73
|
+
this.onClose?.();
|
|
74
|
+
}
|
|
75
|
+
break;
|
|
76
|
+
|
|
77
|
+
case "scope":
|
|
78
|
+
if (data === "\x1b[A" || data === "k") {
|
|
79
|
+
// Up
|
|
80
|
+
this.scopeIndex = Math.max(0, this.scopeIndex - 1);
|
|
81
|
+
} else if (data === "\x1b[B" || data === "j") {
|
|
82
|
+
// Down
|
|
83
|
+
this.scopeIndex = Math.min(1, this.scopeIndex + 1);
|
|
84
|
+
} else if (data === "\r" || data === " ") {
|
|
85
|
+
this.scope = this.scopeIndex === 1 ? "project" : "global";
|
|
62
86
|
this.phase = this.serverUrl ? "topic" : "server-url";
|
|
63
87
|
} else if (data === "\x1b") {
|
|
64
88
|
this.onClose?.();
|
|
@@ -206,14 +230,13 @@ export class NtfySetupOverlay implements Component {
|
|
|
206
230
|
}
|
|
207
231
|
|
|
208
232
|
private saveConfig(): void {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
},
|
|
233
|
+
const cwd = process.cwd();
|
|
234
|
+
saveNtfyConfig(this.scope, cwd, {
|
|
235
|
+
enabled: true,
|
|
236
|
+
serverUrl: this.serverUrl.replace(/\/$/, ""),
|
|
237
|
+
topic: this.topic,
|
|
238
|
+
token: this.token || undefined,
|
|
239
|
+
priority: parseInt(this.priority, 10) || 3,
|
|
217
240
|
});
|
|
218
241
|
}
|
|
219
242
|
|
|
@@ -336,6 +359,36 @@ export class NtfySetupOverlay implements Component {
|
|
|
336
359
|
);
|
|
337
360
|
break;
|
|
338
361
|
|
|
362
|
+
case "scope": {
|
|
363
|
+
lines.push(
|
|
364
|
+
this.frameLine(
|
|
365
|
+
this.fg("dim", "Where should this config be saved?"),
|
|
366
|
+
innerWidth
|
|
367
|
+
)
|
|
368
|
+
);
|
|
369
|
+
lines.push(this.frameLine("", innerWidth));
|
|
370
|
+
const options = ["Global (all projects)", "Project (this project only)"];
|
|
371
|
+
for (let i = 0; i < options.length; i++) {
|
|
372
|
+
const isSelected = i === this.scopeIndex;
|
|
373
|
+
const label = isSelected ? this.bold(options[i]) : this.fg("dim", options[i]);
|
|
374
|
+
lines.push(
|
|
375
|
+
this.frameLine(
|
|
376
|
+
` ${isSelected ? this.fg("accent", "▸") : " "} ${label}`,
|
|
377
|
+
innerWidth
|
|
378
|
+
)
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
lines.push(this.frameLine("", innerWidth));
|
|
382
|
+
lines.push(this.ruleLine(innerWidth));
|
|
383
|
+
lines.push(
|
|
384
|
+
this.frameLine(
|
|
385
|
+
this.fg("dim", "↑↓ select · Enter confirm · Esc cancel"),
|
|
386
|
+
innerWidth
|
|
387
|
+
)
|
|
388
|
+
);
|
|
389
|
+
break;
|
|
390
|
+
}
|
|
391
|
+
|
|
339
392
|
case "server-url":
|
|
340
393
|
lines.push(
|
|
341
394
|
this.frameLine(
|
package/tui/settings-overlay.ts
CHANGED
|
@@ -13,7 +13,8 @@ import {
|
|
|
13
13
|
saveConfig,
|
|
14
14
|
validateConfig,
|
|
15
15
|
} from "../settings.js";
|
|
16
|
-
import
|
|
16
|
+
import { loadNtfyConfig, saveNtfyConfig, getNtfyConfigScope } from "../ntfy-config.js";
|
|
17
|
+
import type { NotifyConfig, NtfyConfig } from "../types.js";
|
|
17
18
|
|
|
18
19
|
/** Section types */
|
|
19
20
|
type Section = "platforms" | "events" | "recap";
|
|
@@ -23,6 +24,8 @@ type Section = "platforms" | "events" | "recap";
|
|
|
23
24
|
*/
|
|
24
25
|
export class NotifySettingsOverlay implements Component {
|
|
25
26
|
private config: NotifyConfig;
|
|
27
|
+
private ntfyConfig: NtfyConfig;
|
|
28
|
+
private ntfyScope: "project" | "global" | "none";
|
|
26
29
|
private section: Section = "platforms";
|
|
27
30
|
private selectedIndex = 0;
|
|
28
31
|
private error: string | null = null;
|
|
@@ -35,6 +38,9 @@ export class NotifySettingsOverlay implements Component {
|
|
|
35
38
|
|
|
36
39
|
constructor() {
|
|
37
40
|
this.config = loadConfig();
|
|
41
|
+
const cwd = process.cwd();
|
|
42
|
+
this.ntfyConfig = loadNtfyConfig(cwd);
|
|
43
|
+
this.ntfyScope = getNtfyConfigScope(cwd);
|
|
38
44
|
}
|
|
39
45
|
|
|
40
46
|
setTheme(theme: Theme): void {
|
|
@@ -93,7 +99,10 @@ export class NotifySettingsOverlay implements Component {
|
|
|
93
99
|
"ntfy",
|
|
94
100
|
];
|
|
95
101
|
const key = platforms[this.selectedIndex];
|
|
96
|
-
if (key) {
|
|
102
|
+
if (key === "ntfy") {
|
|
103
|
+
// ntfy toggle updates the resolved ntfy config
|
|
104
|
+
this.ntfyConfig.enabled = !this.ntfyConfig.enabled;
|
|
105
|
+
} else if (key) {
|
|
97
106
|
this.config[key].enabled = !this.config[key].enabled;
|
|
98
107
|
}
|
|
99
108
|
} else if (this.section === "recap") {
|
|
@@ -115,6 +124,10 @@ export class NotifySettingsOverlay implements Component {
|
|
|
115
124
|
}
|
|
116
125
|
this.error = null;
|
|
117
126
|
saveConfig(this.config);
|
|
127
|
+
// Save ntfy config to its own file if scope is known
|
|
128
|
+
if (this.ntfyScope !== "none") {
|
|
129
|
+
saveNtfyConfig(this.ntfyScope, process.cwd(), this.ntfyConfig);
|
|
130
|
+
}
|
|
118
131
|
this.saved = true;
|
|
119
132
|
setTimeout(() => this.onClose?.(), 500);
|
|
120
133
|
}
|
|
@@ -237,9 +250,9 @@ export class NotifySettingsOverlay implements Component {
|
|
|
237
250
|
{
|
|
238
251
|
key: "ntfy",
|
|
239
252
|
label: "ntfy",
|
|
240
|
-
detail: this.
|
|
241
|
-
? `
|
|
242
|
-
: "
|
|
253
|
+
detail: this.ntfyScope !== "none"
|
|
254
|
+
? `Topic: ${this.ntfyConfig.topic ?? "—"} · P${this.ntfyConfig.priority} · [${this.ntfyScope}]`
|
|
255
|
+
: "Not configured",
|
|
243
256
|
},
|
|
244
257
|
];
|
|
245
258
|
|
|
@@ -248,7 +261,9 @@ export class NotifySettingsOverlay implements Component {
|
|
|
248
261
|
const isSelected = i === this.selectedIndex;
|
|
249
262
|
const toggleOn = this.fg("success", "●");
|
|
250
263
|
const toggleOff = this.fg("dim", "○");
|
|
251
|
-
|
|
264
|
+
// ntfy enabled state comes from resolved ntfy.json, not config.json
|
|
265
|
+
const isEnabled = p.key === "ntfy" ? this.ntfyConfig.enabled : this.config[p.key].enabled;
|
|
266
|
+
const toggle = isEnabled ? toggleOn : toggleOff;
|
|
252
267
|
const label = isSelected ? this.bold(p.label) : this.fg("dim", p.label);
|
|
253
268
|
|
|
254
269
|
lines.push(
|