@pi-unipi/notify 0.1.5 → 0.1.6
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 +24 -1
- package/commands.ts +130 -0
- package/events.ts +150 -3
- package/index.ts +6 -2
- package/package.json +1 -1
- package/platforms/ntfy.ts +48 -0
- package/settings.ts +26 -5
- package/skills/configure-notify/SKILL.md +25 -0
- package/tools.ts +3 -3
- package/tui/ntfy-setup.ts +599 -0
- package/tui/recap-model-selector.ts +301 -0
- package/tui/settings-overlay.ts +68 -8
- package/types.ts +27 -1
|
@@ -0,0 +1,599 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @pi-unipi/notify — ntfy Setup TUI Component
|
|
3
|
+
*
|
|
4
|
+
* Interactive overlay for setting up ntfy push notifications.
|
|
5
|
+
* Guides user through server URL, topic, optional token, and priority.
|
|
6
|
+
* Tests connection before saving.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { Component } from "@mariozechner/pi-tui";
|
|
10
|
+
import { truncateToWidth, visibleWidth } from "@mariozechner/pi-tui";
|
|
11
|
+
import type { Theme } from "@mariozechner/pi-coding-agent";
|
|
12
|
+
import { sendNtfyNotification } from "../platforms/ntfy.js";
|
|
13
|
+
import { updateConfig, loadConfig } from "../settings.js";
|
|
14
|
+
|
|
15
|
+
type SetupPhase =
|
|
16
|
+
| "instructions"
|
|
17
|
+
| "server-url"
|
|
18
|
+
| "topic"
|
|
19
|
+
| "token"
|
|
20
|
+
| "priority"
|
|
21
|
+
| "testing"
|
|
22
|
+
| "success"
|
|
23
|
+
| "error"
|
|
24
|
+
| "test-failed";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* ntfy setup overlay component.
|
|
28
|
+
*/
|
|
29
|
+
export class NtfySetupOverlay implements Component {
|
|
30
|
+
private phase: SetupPhase = "instructions";
|
|
31
|
+
private serverUrl = "";
|
|
32
|
+
private topic = "";
|
|
33
|
+
private token = "";
|
|
34
|
+
private priority = "3";
|
|
35
|
+
private error: string | null = null;
|
|
36
|
+
private testError: string | null = null;
|
|
37
|
+
private isInPaste = false;
|
|
38
|
+
private pasteBuffer = "";
|
|
39
|
+
onClose?: () => void;
|
|
40
|
+
requestRender?: () => void;
|
|
41
|
+
private theme: Theme | null = null;
|
|
42
|
+
|
|
43
|
+
constructor() {
|
|
44
|
+
// Pre-fill from existing config if available
|
|
45
|
+
const config = loadConfig();
|
|
46
|
+
if (config.ntfy.serverUrl) this.serverUrl = config.ntfy.serverUrl;
|
|
47
|
+
if (config.ntfy.topic) this.topic = config.ntfy.topic;
|
|
48
|
+
if (config.ntfy.token) this.token = config.ntfy.token;
|
|
49
|
+
if (config.ntfy.priority) this.priority = String(config.ntfy.priority);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
setTheme(theme: Theme): void {
|
|
53
|
+
this.theme = theme;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
invalidate(): void {}
|
|
57
|
+
|
|
58
|
+
handleInput(data: string): void {
|
|
59
|
+
switch (this.phase) {
|
|
60
|
+
case "instructions":
|
|
61
|
+
if (data === "\r" || data === " ") {
|
|
62
|
+
this.phase = this.serverUrl ? "topic" : "server-url";
|
|
63
|
+
} else if (data === "\x1b") {
|
|
64
|
+
this.onClose?.();
|
|
65
|
+
}
|
|
66
|
+
break;
|
|
67
|
+
|
|
68
|
+
case "server-url":
|
|
69
|
+
this.handleTextInput(data, "server-url", () => {
|
|
70
|
+
if (!this.serverUrl) {
|
|
71
|
+
this.serverUrl = "https://ntfy.sh";
|
|
72
|
+
}
|
|
73
|
+
this.phase = "topic";
|
|
74
|
+
});
|
|
75
|
+
break;
|
|
76
|
+
|
|
77
|
+
case "topic":
|
|
78
|
+
this.handleTextInput(data, "topic", () => {
|
|
79
|
+
this.phase = "token";
|
|
80
|
+
});
|
|
81
|
+
break;
|
|
82
|
+
|
|
83
|
+
case "token":
|
|
84
|
+
this.handleTextInput(data, "token", () => {
|
|
85
|
+
this.phase = "priority";
|
|
86
|
+
});
|
|
87
|
+
break;
|
|
88
|
+
|
|
89
|
+
case "priority":
|
|
90
|
+
if (data === "\r" && this.isValidPriority()) {
|
|
91
|
+
this.testConnection();
|
|
92
|
+
} else if (data === "\x1b") {
|
|
93
|
+
this.onClose?.();
|
|
94
|
+
} else if (data === "\x7f" || data === "\b") {
|
|
95
|
+
this.priority = this.priority.slice(0, -1);
|
|
96
|
+
} else {
|
|
97
|
+
const ch = data.replace(/[^\d]/g, "");
|
|
98
|
+
if (ch && this.priority.length < 1) {
|
|
99
|
+
this.priority += ch;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
break;
|
|
103
|
+
|
|
104
|
+
case "testing":
|
|
105
|
+
if (data === "\x1b") {
|
|
106
|
+
this.onClose?.();
|
|
107
|
+
}
|
|
108
|
+
break;
|
|
109
|
+
|
|
110
|
+
case "success":
|
|
111
|
+
case "error":
|
|
112
|
+
case "test-failed":
|
|
113
|
+
if (data === "\r" || data === " " || data === "\x1b") {
|
|
114
|
+
this.onClose?.();
|
|
115
|
+
}
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
private handleTextInput(
|
|
121
|
+
data: string,
|
|
122
|
+
target: "server-url" | "topic" | "token",
|
|
123
|
+
onEnter: () => void
|
|
124
|
+
): void {
|
|
125
|
+
// Handle bracketed paste mode
|
|
126
|
+
if (this.isInPaste) {
|
|
127
|
+
this.pasteBuffer += data;
|
|
128
|
+
const endIndex = this.pasteBuffer.indexOf("\x1b[201~");
|
|
129
|
+
if (endIndex !== -1) {
|
|
130
|
+
const pasteContent = this.pasteBuffer.substring(0, endIndex).trim();
|
|
131
|
+
if (target === "server-url") {
|
|
132
|
+
this.serverUrl = pasteContent;
|
|
133
|
+
} else if (target === "topic") {
|
|
134
|
+
this.topic = pasteContent;
|
|
135
|
+
} else {
|
|
136
|
+
this.token = pasteContent;
|
|
137
|
+
}
|
|
138
|
+
this.isInPaste = false;
|
|
139
|
+
this.pasteBuffer = "";
|
|
140
|
+
}
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
// Detect start of bracketed paste
|
|
144
|
+
if (data.includes("\x1b[200~")) {
|
|
145
|
+
this.isInPaste = true;
|
|
146
|
+
this.pasteBuffer = data.replace("\x1b[200~", "");
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (data === "\r") {
|
|
150
|
+
onEnter();
|
|
151
|
+
} else if (data === "\x1b") {
|
|
152
|
+
// Escape during token phase — skip token (optional field)
|
|
153
|
+
if (target === "token") {
|
|
154
|
+
this.phase = "priority";
|
|
155
|
+
} else {
|
|
156
|
+
this.onClose?.();
|
|
157
|
+
}
|
|
158
|
+
} else if (data === "\x7f" || data === "\b") {
|
|
159
|
+
if (target === "server-url") {
|
|
160
|
+
this.serverUrl = this.serverUrl.slice(0, -1);
|
|
161
|
+
} else if (target === "topic") {
|
|
162
|
+
this.topic = this.topic.slice(0, -1);
|
|
163
|
+
} else {
|
|
164
|
+
this.token = this.token.slice(0, -1);
|
|
165
|
+
}
|
|
166
|
+
} else {
|
|
167
|
+
// Ignore escape sequences
|
|
168
|
+
if (data.startsWith("\x1b[")) return;
|
|
169
|
+
if (target === "server-url") {
|
|
170
|
+
this.serverUrl += data;
|
|
171
|
+
} else if (target === "topic") {
|
|
172
|
+
this.topic += data;
|
|
173
|
+
} else {
|
|
174
|
+
this.token += data;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
private isValidPriority(): boolean {
|
|
180
|
+
const num = parseInt(this.priority, 10);
|
|
181
|
+
return !isNaN(num) && num >= 1 && num <= 5;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
private async testConnection(): Promise<void> {
|
|
185
|
+
this.phase = "testing";
|
|
186
|
+
this.requestRender?.();
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
await sendNtfyNotification(
|
|
190
|
+
this.serverUrl.replace(/\/$/, ""),
|
|
191
|
+
this.topic,
|
|
192
|
+
"Pi — Setup Test",
|
|
193
|
+
`ntfy configured successfully at ${new Date().toLocaleTimeString()}`,
|
|
194
|
+
parseInt(this.priority, 10) || 3,
|
|
195
|
+
this.token || undefined
|
|
196
|
+
);
|
|
197
|
+
this.saveConfig();
|
|
198
|
+
this.phase = "success";
|
|
199
|
+
this.requestRender?.();
|
|
200
|
+
setTimeout(() => this.onClose?.(), 1500);
|
|
201
|
+
} catch (err) {
|
|
202
|
+
this.testError = err instanceof Error ? err.message : String(err);
|
|
203
|
+
this.phase = "test-failed";
|
|
204
|
+
this.requestRender?.();
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
private saveConfig(): void {
|
|
209
|
+
updateConfig({
|
|
210
|
+
ntfy: {
|
|
211
|
+
enabled: true,
|
|
212
|
+
serverUrl: this.serverUrl.replace(/\/$/, ""),
|
|
213
|
+
topic: this.topic,
|
|
214
|
+
token: this.token || undefined,
|
|
215
|
+
priority: parseInt(this.priority, 10) || 3,
|
|
216
|
+
},
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// ─── Theme helpers ───────────────────────────────────────────────────
|
|
221
|
+
|
|
222
|
+
private fg(color: string, text: string): string {
|
|
223
|
+
if (this.theme) return this.theme.fg(color as any, text);
|
|
224
|
+
const c: Record<string, string> = {
|
|
225
|
+
accent: "\x1b[36m",
|
|
226
|
+
success: "\x1b[32m",
|
|
227
|
+
warning: "\x1b[33m",
|
|
228
|
+
error: "\x1b[31m",
|
|
229
|
+
dim: "\x1b[2m",
|
|
230
|
+
borderMuted: "\x1b[90m",
|
|
231
|
+
};
|
|
232
|
+
return `${c[color] ?? ""}${text}\x1b[0m`;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
private bold(text: string): string {
|
|
236
|
+
return this.theme ? this.theme.bold(text) : `\x1b[1m${text}\x1b[0m`;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
private frameLine(content: string, innerWidth: number): string {
|
|
240
|
+
const truncated = truncateToWidth(content, innerWidth, "");
|
|
241
|
+
const padding = Math.max(0, innerWidth - visibleWidth(truncated));
|
|
242
|
+
return `${this.fg("borderMuted", "│")}${truncated}${" ".repeat(padding)}${this.fg("borderMuted", "│")}`;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
private ruleLine(innerWidth: number): string {
|
|
246
|
+
return this.fg("borderMuted", `├${"─".repeat(innerWidth)}┤`);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
private borderLine(innerWidth: number, edge: "top" | "bottom"): string {
|
|
250
|
+
const left = edge === "top" ? "┌" : "└";
|
|
251
|
+
const right = edge === "top" ? "┐" : "┘";
|
|
252
|
+
return this.fg("borderMuted", `${left}${"─".repeat(innerWidth)}${right}`);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
private maskToken(token: string): string {
|
|
256
|
+
if (token.length <= 8) return token;
|
|
257
|
+
return token.slice(0, 4) + "•".repeat(token.length - 8) + token.slice(-4);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
render(width: number): string[] {
|
|
261
|
+
const innerWidth = Math.max(22, width - 2);
|
|
262
|
+
const lines: string[] = [];
|
|
263
|
+
|
|
264
|
+
lines.push(this.borderLine(innerWidth, "top"));
|
|
265
|
+
lines.push(
|
|
266
|
+
this.frameLine(
|
|
267
|
+
this.fg("accent", this.bold("📢 ntfy Setup")),
|
|
268
|
+
innerWidth
|
|
269
|
+
)
|
|
270
|
+
);
|
|
271
|
+
lines.push(this.ruleLine(innerWidth));
|
|
272
|
+
|
|
273
|
+
switch (this.phase) {
|
|
274
|
+
case "instructions":
|
|
275
|
+
lines.push(
|
|
276
|
+
this.frameLine(
|
|
277
|
+
this.fg("dim", "Set up ntfy push notifications:"),
|
|
278
|
+
innerWidth
|
|
279
|
+
)
|
|
280
|
+
);
|
|
281
|
+
lines.push(this.frameLine("", innerWidth));
|
|
282
|
+
lines.push(
|
|
283
|
+
this.frameLine(
|
|
284
|
+
` ${this.bold("1.")} ntfy is a simple HTTP-based notification service`,
|
|
285
|
+
innerWidth
|
|
286
|
+
)
|
|
287
|
+
);
|
|
288
|
+
lines.push(
|
|
289
|
+
this.frameLine(
|
|
290
|
+
` ${this.fg("dim", "Public: https://ntfy.sh | Self-hosted: any ntfy server")}`,
|
|
291
|
+
innerWidth
|
|
292
|
+
)
|
|
293
|
+
);
|
|
294
|
+
lines.push(this.frameLine("", innerWidth));
|
|
295
|
+
lines.push(
|
|
296
|
+
this.frameLine(
|
|
297
|
+
` ${this.bold("2.")} Choose a topic (acts as a channel name)`,
|
|
298
|
+
innerWidth
|
|
299
|
+
)
|
|
300
|
+
);
|
|
301
|
+
lines.push(
|
|
302
|
+
this.frameLine(
|
|
303
|
+
` ${this.fg("dim", "Subscribe to the topic in the ntfy app or web UI")}`,
|
|
304
|
+
innerWidth
|
|
305
|
+
)
|
|
306
|
+
);
|
|
307
|
+
lines.push(this.frameLine("", innerWidth));
|
|
308
|
+
lines.push(
|
|
309
|
+
this.frameLine(
|
|
310
|
+
` ${this.bold("3.")} Optionally set an access token for private servers`,
|
|
311
|
+
innerWidth
|
|
312
|
+
)
|
|
313
|
+
);
|
|
314
|
+
if (this.serverUrl) {
|
|
315
|
+
lines.push(
|
|
316
|
+
this.frameLine(
|
|
317
|
+
` ${this.fg("success", "✓")} Server URL pre-filled from existing config`,
|
|
318
|
+
innerWidth
|
|
319
|
+
)
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
if (this.topic) {
|
|
323
|
+
lines.push(
|
|
324
|
+
this.frameLine(
|
|
325
|
+
` ${this.fg("success", "✓")} Topic pre-filled from existing config`,
|
|
326
|
+
innerWidth
|
|
327
|
+
)
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
lines.push(this.ruleLine(innerWidth));
|
|
331
|
+
lines.push(
|
|
332
|
+
this.frameLine(
|
|
333
|
+
this.fg("dim", "Press Enter to continue, Esc to cancel"),
|
|
334
|
+
innerWidth
|
|
335
|
+
)
|
|
336
|
+
);
|
|
337
|
+
break;
|
|
338
|
+
|
|
339
|
+
case "server-url":
|
|
340
|
+
lines.push(
|
|
341
|
+
this.frameLine(
|
|
342
|
+
this.fg("dim", "Enter ntfy server URL:"),
|
|
343
|
+
innerWidth
|
|
344
|
+
)
|
|
345
|
+
);
|
|
346
|
+
lines.push(this.frameLine("", innerWidth));
|
|
347
|
+
lines.push(
|
|
348
|
+
this.frameLine(
|
|
349
|
+
` ${this.fg("accent", this.bold(this.serverUrl || " "))}${this.fg("dim", "█")}`,
|
|
350
|
+
innerWidth
|
|
351
|
+
)
|
|
352
|
+
);
|
|
353
|
+
lines.push(this.frameLine("", innerWidth));
|
|
354
|
+
lines.push(
|
|
355
|
+
this.frameLine(
|
|
356
|
+
this.fg("dim", "Default: https://ntfy.sh (public)"),
|
|
357
|
+
innerWidth
|
|
358
|
+
)
|
|
359
|
+
);
|
|
360
|
+
lines.push(
|
|
361
|
+
this.frameLine(
|
|
362
|
+
this.fg("dim", "Leave empty and press Enter for default"),
|
|
363
|
+
innerWidth
|
|
364
|
+
)
|
|
365
|
+
);
|
|
366
|
+
lines.push(this.ruleLine(innerWidth));
|
|
367
|
+
lines.push(
|
|
368
|
+
this.frameLine(
|
|
369
|
+
this.fg("dim", "Enter to continue · Esc to cancel"),
|
|
370
|
+
innerWidth
|
|
371
|
+
)
|
|
372
|
+
);
|
|
373
|
+
break;
|
|
374
|
+
|
|
375
|
+
case "topic":
|
|
376
|
+
lines.push(
|
|
377
|
+
this.frameLine(
|
|
378
|
+
this.fg("dim", "Enter topic name:"),
|
|
379
|
+
innerWidth
|
|
380
|
+
)
|
|
381
|
+
);
|
|
382
|
+
lines.push(this.frameLine("", innerWidth));
|
|
383
|
+
lines.push(
|
|
384
|
+
this.frameLine(
|
|
385
|
+
` ${this.fg("accent", this.bold(this.topic || " "))}${this.fg("dim", "█")}`,
|
|
386
|
+
innerWidth
|
|
387
|
+
)
|
|
388
|
+
);
|
|
389
|
+
lines.push(this.frameLine("", innerWidth));
|
|
390
|
+
lines.push(
|
|
391
|
+
this.frameLine(
|
|
392
|
+
this.fg("dim", "e.g. my-pi-notifications, project-alerts"),
|
|
393
|
+
innerWidth
|
|
394
|
+
)
|
|
395
|
+
);
|
|
396
|
+
lines.push(
|
|
397
|
+
this.frameLine(
|
|
398
|
+
this.fg("dim", "Pick something unique if using public ntfy.sh"),
|
|
399
|
+
innerWidth
|
|
400
|
+
)
|
|
401
|
+
);
|
|
402
|
+
lines.push(this.ruleLine(innerWidth));
|
|
403
|
+
lines.push(
|
|
404
|
+
this.frameLine(
|
|
405
|
+
this.fg("dim", "Enter to continue · Esc to cancel"),
|
|
406
|
+
innerWidth
|
|
407
|
+
)
|
|
408
|
+
);
|
|
409
|
+
break;
|
|
410
|
+
|
|
411
|
+
case "token":
|
|
412
|
+
lines.push(
|
|
413
|
+
this.frameLine(
|
|
414
|
+
this.fg("dim", "Enter access token (optional):"),
|
|
415
|
+
innerWidth
|
|
416
|
+
)
|
|
417
|
+
);
|
|
418
|
+
lines.push(this.frameLine("", innerWidth));
|
|
419
|
+
const displayToken = this.token
|
|
420
|
+
? this.fg("accent", this.bold(this.maskToken(this.token)))
|
|
421
|
+
: " ";
|
|
422
|
+
lines.push(
|
|
423
|
+
this.frameLine(
|
|
424
|
+
` ${displayToken}${this.fg("dim", "█")}`,
|
|
425
|
+
innerWidth
|
|
426
|
+
)
|
|
427
|
+
);
|
|
428
|
+
lines.push(this.frameLine("", innerWidth));
|
|
429
|
+
lines.push(
|
|
430
|
+
this.frameLine(
|
|
431
|
+
this.fg("dim", "Only needed for private/authenticated ntfy servers"),
|
|
432
|
+
innerWidth
|
|
433
|
+
)
|
|
434
|
+
);
|
|
435
|
+
lines.push(
|
|
436
|
+
this.frameLine(
|
|
437
|
+
this.fg("dim", "Not needed for public ntfy.sh"),
|
|
438
|
+
innerWidth
|
|
439
|
+
)
|
|
440
|
+
);
|
|
441
|
+
lines.push(this.ruleLine(innerWidth));
|
|
442
|
+
lines.push(
|
|
443
|
+
this.frameLine(
|
|
444
|
+
this.fg("dim", "Enter to continue · Esc to skip"),
|
|
445
|
+
innerWidth
|
|
446
|
+
)
|
|
447
|
+
);
|
|
448
|
+
break;
|
|
449
|
+
|
|
450
|
+
case "priority":
|
|
451
|
+
lines.push(
|
|
452
|
+
this.frameLine(
|
|
453
|
+
this.fg("dim", "Set notification priority (1-5):"),
|
|
454
|
+
innerWidth
|
|
455
|
+
)
|
|
456
|
+
);
|
|
457
|
+
lines.push(this.frameLine("", innerWidth));
|
|
458
|
+
lines.push(
|
|
459
|
+
this.frameLine(
|
|
460
|
+
` ${this.fg("accent", this.bold(this.priority || " "))}${this.fg("dim", "█")}`,
|
|
461
|
+
innerWidth
|
|
462
|
+
)
|
|
463
|
+
);
|
|
464
|
+
lines.push(this.frameLine("", innerWidth));
|
|
465
|
+
lines.push(
|
|
466
|
+
this.frameLine(
|
|
467
|
+
` ${this.fg("dim", "1")} = min · ${this.fg("dim", "3")} = default · ${this.fg("dim", "5")} = max`,
|
|
468
|
+
innerWidth
|
|
469
|
+
)
|
|
470
|
+
);
|
|
471
|
+
lines.push(this.ruleLine(innerWidth));
|
|
472
|
+
lines.push(
|
|
473
|
+
this.frameLine(
|
|
474
|
+
this.fg("dim", "Enter to test connection · Esc to cancel"),
|
|
475
|
+
innerWidth
|
|
476
|
+
)
|
|
477
|
+
);
|
|
478
|
+
break;
|
|
479
|
+
|
|
480
|
+
case "testing":
|
|
481
|
+
lines.push(this.frameLine("", innerWidth));
|
|
482
|
+
lines.push(
|
|
483
|
+
this.frameLine(
|
|
484
|
+
` ${this.fg("accent", "⠋")} ${this.bold("Testing connection...")}`,
|
|
485
|
+
innerWidth
|
|
486
|
+
)
|
|
487
|
+
);
|
|
488
|
+
lines.push(this.frameLine("", innerWidth));
|
|
489
|
+
lines.push(
|
|
490
|
+
this.frameLine(
|
|
491
|
+
` ${this.fg("dim", `Sending test to ${this.serverUrl}/${this.topic}`)}`,
|
|
492
|
+
innerWidth
|
|
493
|
+
)
|
|
494
|
+
);
|
|
495
|
+
lines.push(this.ruleLine(innerWidth));
|
|
496
|
+
lines.push(
|
|
497
|
+
this.frameLine(
|
|
498
|
+
this.fg("dim", "Esc to cancel"),
|
|
499
|
+
innerWidth
|
|
500
|
+
)
|
|
501
|
+
);
|
|
502
|
+
break;
|
|
503
|
+
|
|
504
|
+
case "success":
|
|
505
|
+
lines.push(this.frameLine("", innerWidth));
|
|
506
|
+
lines.push(
|
|
507
|
+
this.frameLine(
|
|
508
|
+
` ${this.fg("success", "✓ ntfy configured successfully!")}`,
|
|
509
|
+
innerWidth
|
|
510
|
+
)
|
|
511
|
+
);
|
|
512
|
+
lines.push(this.frameLine("", innerWidth));
|
|
513
|
+
lines.push(
|
|
514
|
+
this.frameLine(
|
|
515
|
+
` ${this.fg("dim", `Server: ${this.serverUrl}`)}`,
|
|
516
|
+
innerWidth
|
|
517
|
+
)
|
|
518
|
+
);
|
|
519
|
+
lines.push(
|
|
520
|
+
this.frameLine(
|
|
521
|
+
` ${this.fg("dim", `Topic: ${this.topic}`)}`,
|
|
522
|
+
innerWidth
|
|
523
|
+
)
|
|
524
|
+
);
|
|
525
|
+
lines.push(
|
|
526
|
+
this.frameLine(
|
|
527
|
+
` ${this.fg("dim", `Priority: ${this.priority}`)}`,
|
|
528
|
+
innerWidth
|
|
529
|
+
)
|
|
530
|
+
);
|
|
531
|
+
lines.push(this.ruleLine(innerWidth));
|
|
532
|
+
lines.push(
|
|
533
|
+
this.frameLine(
|
|
534
|
+
this.fg("dim", "Closing..."),
|
|
535
|
+
innerWidth
|
|
536
|
+
)
|
|
537
|
+
);
|
|
538
|
+
break;
|
|
539
|
+
|
|
540
|
+
case "test-failed":
|
|
541
|
+
lines.push(this.frameLine("", innerWidth));
|
|
542
|
+
lines.push(
|
|
543
|
+
this.frameLine(
|
|
544
|
+
` ${this.fg("error", "✗ Connection test failed")}`,
|
|
545
|
+
innerWidth
|
|
546
|
+
)
|
|
547
|
+
);
|
|
548
|
+
lines.push(this.frameLine("", innerWidth));
|
|
549
|
+
lines.push(
|
|
550
|
+
this.frameLine(
|
|
551
|
+
` ${this.fg("dim", this.testError || "Unknown error")}`,
|
|
552
|
+
innerWidth
|
|
553
|
+
)
|
|
554
|
+
);
|
|
555
|
+
lines.push(this.frameLine("", innerWidth));
|
|
556
|
+
lines.push(
|
|
557
|
+
this.frameLine(
|
|
558
|
+
` ${this.fg("dim", "Check your server URL and topic")}`,
|
|
559
|
+
innerWidth
|
|
560
|
+
)
|
|
561
|
+
);
|
|
562
|
+
lines.push(this.ruleLine(innerWidth));
|
|
563
|
+
lines.push(
|
|
564
|
+
this.frameLine(
|
|
565
|
+
this.fg("dim", "Press Enter to close"),
|
|
566
|
+
innerWidth
|
|
567
|
+
)
|
|
568
|
+
);
|
|
569
|
+
break;
|
|
570
|
+
|
|
571
|
+
case "error":
|
|
572
|
+
lines.push(this.frameLine("", innerWidth));
|
|
573
|
+
lines.push(
|
|
574
|
+
this.frameLine(
|
|
575
|
+
` ${this.fg("error", "✗ Setup failed")}`,
|
|
576
|
+
innerWidth
|
|
577
|
+
)
|
|
578
|
+
);
|
|
579
|
+
lines.push(this.frameLine("", innerWidth));
|
|
580
|
+
lines.push(
|
|
581
|
+
this.frameLine(
|
|
582
|
+
` ${this.fg("dim", this.error || "Unknown error")}`,
|
|
583
|
+
innerWidth
|
|
584
|
+
)
|
|
585
|
+
);
|
|
586
|
+
lines.push(this.ruleLine(innerWidth));
|
|
587
|
+
lines.push(
|
|
588
|
+
this.frameLine(
|
|
589
|
+
this.fg("dim", "Press Enter to close"),
|
|
590
|
+
innerWidth
|
|
591
|
+
)
|
|
592
|
+
);
|
|
593
|
+
break;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
lines.push(this.borderLine(innerWidth, "bottom"));
|
|
597
|
+
return lines;
|
|
598
|
+
}
|
|
599
|
+
}
|