@pi-unipi/notify 2.6.0 → 2.6.2

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.
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  import type { Component } from "@earendil-works/pi-tui";
9
- import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
9
+ import { matchesKey } from "@earendil-works/pi-tui";
10
10
  import type { Theme } from "@earendil-works/pi-coding-agent";
11
11
  import {
12
12
  loadConfig,
@@ -15,7 +15,7 @@ import {
15
15
  } from "../settings.js";
16
16
  import { loadNtfyConfig, saveNtfyConfig, getNtfyConfigScope } from "../ntfy-config.js";
17
17
  import type { NotifyConfig, NtfyConfig } from "../types.js";
18
- import { boxInnerWidth } from "@pi-unipi/core";
18
+ import { OverlayTheme, boxInnerWidth } from "@pi-unipi/core";
19
19
 
20
20
  /** Section types */
21
21
  type Section = "platforms" | "events" | "recap";
@@ -35,7 +35,7 @@ export class NotifySettingsOverlay implements Component {
35
35
  requestRender?: () => void;
36
36
  /** Called when user presses M in recap section to open model selector */
37
37
  onOpenModelSelector?: () => void;
38
- private theme: Theme | null = null;
38
+ private overlay = new OverlayTheme();
39
39
 
40
40
  constructor() {
41
41
  this.config = loadConfig();
@@ -45,43 +45,56 @@ export class NotifySettingsOverlay implements Component {
45
45
  }
46
46
 
47
47
  setTheme(theme: Theme): void {
48
- this.theme = theme;
48
+ this.overlay.setTheme(theme);
49
49
  }
50
50
 
51
51
  invalidate(): void {}
52
52
 
53
53
  handleInput(data: string): void {
54
- switch (data) {
55
- case "\x1b[A": // Up
56
- case "k":
57
- this.selectedIndex = Math.max(0, this.selectedIndex - 1);
58
- break;
59
- case "\x1b[B": // Down
60
- case "j":
61
- this.selectedIndex = Math.min(this.maxItems - 1, this.selectedIndex + 1);
62
- break;
63
- case " ": // Space - toggle
64
- this.toggleCurrent();
65
- break;
66
- case "\t": // Tab - switch section
67
- {
68
- const sections: Section[] = ["platforms", "events", "recap"];
69
- const idx = sections.indexOf(this.section);
70
- this.section = sections[(idx + 1) % sections.length];
71
- this.selectedIndex = 0;
72
- }
73
- break;
74
- case "m": // M - open model selector (only in recap section)
75
- if (this.section === "recap") {
76
- this.onOpenModelSelector?.();
77
- }
78
- break;
79
- case "\r": // Enter - save
80
- this.save();
81
- break;
82
- case "\x1b": // Escape - close
83
- this.onClose?.();
84
- break;
54
+ // Ctrl+C always closes — escape hatch for terminals with key encodings
55
+ // this overlay does not understand (issue #27).
56
+ if (matchesKey(data, "ctrl+c")) {
57
+ this.onClose?.();
58
+ return;
59
+ }
60
+ // Navigation keys are matched via matchesKey, never raw byte comparison:
61
+ // under the kitty keyboard protocol / enhanced encodings (Ghostty, Herdr)
62
+ // Escape arrives as "\x1b[27u" (or "\x1b[27;1;27~" with modifyOtherKeys)
63
+ // and arrows as "\x1b[57419u"/"\x1b[57420u" exact legacy comparisons
64
+ // like data === "\x1b[A" silently fail there.
65
+ if (matchesKey(data, "up") || data === "k") {
66
+ this.selectedIndex = Math.max(0, this.selectedIndex - 1);
67
+ return;
68
+ }
69
+ if (matchesKey(data, "down") || data === "j") {
70
+ this.selectedIndex = Math.min(this.maxItems - 1, this.selectedIndex + 1);
71
+ return;
72
+ }
73
+ if (matchesKey(data, "space")) {
74
+ this.toggleCurrent();
75
+ return;
76
+ }
77
+ if (matchesKey(data, "tab")) {
78
+ const sections: Section[] = ["platforms", "events", "recap"];
79
+ const idx = sections.indexOf(this.section);
80
+ this.section = sections[(idx + 1) % sections.length];
81
+ this.selectedIndex = 0;
82
+ return;
83
+ }
84
+ if (data === "m" || data === "M") {
85
+ // Open model selector (only in recap section)
86
+ if (this.section === "recap") {
87
+ this.onOpenModelSelector?.();
88
+ }
89
+ return;
90
+ }
91
+ if (matchesKey(data, "enter")) {
92
+ this.save();
93
+ return;
94
+ }
95
+ if (matchesKey(data, "escape")) {
96
+ this.onClose?.();
97
+ return;
85
98
  }
86
99
  }
87
100
 
@@ -138,66 +151,30 @@ export class NotifySettingsOverlay implements Component {
138
151
  setTimeout(() => this.onClose?.(), 500);
139
152
  }
140
153
 
141
- // ─── Theme helpers ───────────────────────────────────────────────────
142
-
143
- private fg(color: string, text: string): string {
144
- if (this.theme) return this.theme.fg(color as any, text);
145
- const c: Record<string, string> = {
146
- accent: "\x1b[36m", success: "\x1b[32m", warning: "\x1b[33m",
147
- error: "\x1b[31m", dim: "\x1b[2m", borderMuted: "\x1b[90m",
148
- };
149
- return `${c[color] ?? ""}${text}\x1b[0m`;
150
- }
151
-
152
- private bold(text: string): string {
153
- return this.theme ? this.theme.bold(text) : `\x1b[1m${text}\x1b[0m`;
154
- }
155
-
156
- private frameLine(content: string, innerWidth: number): string {
157
- const truncated = truncateToWidth(content, innerWidth, "");
158
- const padding = Math.max(0, innerWidth - visibleWidth(truncated));
159
- return `${this.fg("borderMuted", "│")}${truncated}${" ".repeat(padding)}${this.fg("borderMuted", "│")}`;
160
- }
161
-
162
- private ruleLine(innerWidth: number): string {
163
- return this.fg("borderMuted", `├${"─".repeat(innerWidth)}┤`);
164
- }
165
-
166
- private borderLine(innerWidth: number, edge: "top" | "bottom"): string {
167
- const left = edge === "top" ? "┌" : "└";
168
- const right = edge === "top" ? "┐" : "┘";
169
- return this.fg("borderMuted", `${left}${"─".repeat(innerWidth)}${right}`);
170
- }
171
-
172
- private getDialogHeight(): number {
173
- const terminalRows = process.stdout.rows ?? 30;
174
- return Math.max(14, Math.min(24, Math.floor(terminalRows * 0.65)));
175
- }
176
-
177
154
  render(width: number): string[] {
178
155
  const innerWidth = boxInnerWidth(width);
179
156
  const lines: string[] = [];
180
157
 
181
- lines.push(this.borderLine(innerWidth, "top"));
182
- lines.push(this.frameLine(this.fg("accent", this.bold("🔔 Notify Settings")), innerWidth));
183
- lines.push(this.frameLine(this.fg("dim", "Configure notification platforms and events"), innerWidth));
184
- lines.push(this.ruleLine(innerWidth));
158
+ lines.push(this.overlay.borderLine(innerWidth, "top"));
159
+ lines.push(this.overlay.frameLine(this.overlay.fg("accent", this.overlay.bold("🔔 Notify Settings")), innerWidth));
160
+ lines.push(this.overlay.frameLine(this.overlay.fg("dim", "Configure notification platforms and events"), innerWidth));
161
+ lines.push(this.overlay.ruleLine(innerWidth));
185
162
 
186
163
  // Section tabs
187
164
  const platformTab =
188
165
  this.section === "platforms"
189
- ? this.fg("accent", this.bold("[Platforms]"))
190
- : this.fg("dim", "Platforms");
166
+ ? this.overlay.fg("accent", this.overlay.bold("[Platforms]"))
167
+ : this.overlay.fg("dim", "Platforms");
191
168
  const eventsTab =
192
169
  this.section === "events"
193
- ? this.fg("accent", this.bold("[Events]"))
194
- : this.fg("dim", "Events");
170
+ ? this.overlay.fg("accent", this.overlay.bold("[Events]"))
171
+ : this.overlay.fg("dim", "Events");
195
172
  const recapTab =
196
173
  this.section === "recap"
197
- ? this.fg("accent", this.bold("[Recap]"))
198
- : this.fg("dim", "Recap");
199
- lines.push(this.frameLine(` ${platformTab} ${eventsTab} ${recapTab}`, innerWidth));
200
- lines.push(this.ruleLine(innerWidth));
174
+ ? this.overlay.fg("accent", this.overlay.bold("[Recap]"))
175
+ : this.overlay.fg("dim", "Recap");
176
+ lines.push(this.overlay.frameLine(` ${platformTab} ${eventsTab} ${recapTab}`, innerWidth));
177
+ lines.push(this.overlay.ruleLine(innerWidth));
201
178
 
202
179
  if (this.section === "platforms") {
203
180
  this.renderPlatforms(lines, innerWidth);
@@ -209,21 +186,21 @@ export class NotifySettingsOverlay implements Component {
209
186
 
210
187
  // Status messages
211
188
  if (this.error) {
212
- lines.push(this.ruleLine(innerWidth));
213
- lines.push(this.frameLine(` ${this.fg("error", `⚠ ${this.error}`)}`, innerWidth));
189
+ lines.push(this.overlay.ruleLine(innerWidth));
190
+ lines.push(this.overlay.frameLine(` ${this.overlay.fg("error", `⚠ ${this.error}`)}`, innerWidth));
214
191
  }
215
192
  if (this.saved) {
216
- lines.push(this.ruleLine(innerWidth));
217
- lines.push(this.frameLine(` ${this.fg("success", "✓ Settings saved")}`, innerWidth));
193
+ lines.push(this.overlay.ruleLine(innerWidth));
194
+ lines.push(this.overlay.frameLine(` ${this.overlay.fg("success", "✓ Settings saved")}`, innerWidth));
218
195
  }
219
196
 
220
197
  // Footer
221
- lines.push(this.ruleLine(innerWidth));
198
+ lines.push(this.overlay.ruleLine(innerWidth));
222
199
  const footerHint = this.section === "recap"
223
200
  ? "↑↓ navigate · Space toggle · M change model · Tab switch · Enter save · Esc cancel"
224
201
  : "↑↓ navigate · Space toggle · Tab switch · Enter save · Esc cancel";
225
- lines.push(this.frameLine(this.fg("dim", footerHint), innerWidth));
226
- lines.push(this.borderLine(innerWidth, "bottom"));
202
+ lines.push(this.overlay.frameLine(this.overlay.fg("dim", footerHint), innerWidth));
203
+ lines.push(this.overlay.borderLine(innerWidth, "bottom"));
227
204
 
228
205
  return lines;
229
206
  }
@@ -265,16 +242,16 @@ export class NotifySettingsOverlay implements Component {
265
242
  for (let i = 0; i < platforms.length; i++) {
266
243
  const p = platforms[i];
267
244
  const isSelected = i === this.selectedIndex;
268
- const toggleOn = this.fg("success", "●");
269
- const toggleOff = this.fg("dim", "○");
245
+ const toggleOn = this.overlay.fg("success", "●");
246
+ const toggleOff = this.overlay.fg("dim", "○");
270
247
  // ntfy enabled state comes from resolved ntfy.json, not config.json
271
248
  const isEnabled = p.key === "ntfy" ? this.ntfyConfig.enabled : this.config[p.key].enabled;
272
249
  const toggle = isEnabled ? toggleOn : toggleOff;
273
- const label = isSelected ? this.bold(p.label) : this.fg("dim", p.label);
250
+ const label = isSelected ? this.overlay.bold(p.label) : this.overlay.fg("dim", p.label);
274
251
 
275
252
  lines.push(
276
- this.frameLine(
277
- `${isSelected ? this.fg("accent", "▸") : " "} ${toggle} ${label} ${this.fg("dim", p.detail)}`,
253
+ this.overlay.frameLine(
254
+ `${isSelected ? this.overlay.fg("accent", "▸") : " "} ${toggle} ${label} ${this.overlay.fg("dim", p.detail)}`,
278
255
  innerWidth
279
256
  )
280
257
  );
@@ -285,17 +262,17 @@ export class NotifySettingsOverlay implements Component {
285
262
  const i = platforms.length;
286
263
  const isSelected = i === this.selectedIndex;
287
264
  const isEnabled = this.config.native.suppressWhenFocused === true;
288
- const toggleOn = this.fg("success", "●");
289
- const toggleOff = this.fg("dim", "○");
265
+ const toggleOn = this.overlay.fg("success", "●");
266
+ const toggleOff = this.overlay.fg("dim", "○");
290
267
  const toggle = isEnabled ? toggleOn : toggleOff;
291
268
  const label = isSelected
292
- ? this.bold("Suppress when focused")
293
- : this.fg("dim", "Suppress when focused");
294
- const detail = this.fg("dim", isEnabled ? "Windows only — terminal in foreground → skip" : "Windows only");
269
+ ? this.overlay.bold("Suppress when focused")
270
+ : this.overlay.fg("dim", "Suppress when focused");
271
+ const detail = this.overlay.fg("dim", isEnabled ? "Windows only — terminal in foreground → skip" : "Windows only");
295
272
 
296
273
  lines.push(
297
- this.frameLine(
298
- `${isSelected ? this.fg("accent", "▸") : " "} ${toggle} ${label} ${detail}`,
274
+ this.overlay.frameLine(
275
+ `${isSelected ? this.overlay.fg("accent", "▸") : " "} ${toggle} ${label} ${detail}`,
299
276
  innerWidth
300
277
  )
301
278
  );
@@ -308,14 +285,14 @@ export class NotifySettingsOverlay implements Component {
308
285
  for (let i = 0; i < events.length; i++) {
309
286
  const [key, cfg] = events[i];
310
287
  const isSelected = i === this.selectedIndex;
311
- const toggleOn = this.fg("success", "●");
312
- const toggleOff = this.fg("dim", "○");
288
+ const toggleOn = this.overlay.fg("success", "●");
289
+ const toggleOff = this.overlay.fg("dim", "○");
313
290
  const toggle = cfg.enabled ? toggleOn : toggleOff;
314
- const label = isSelected ? this.bold(key) : this.fg("dim", key);
291
+ const label = isSelected ? this.overlay.bold(key) : this.overlay.fg("dim", key);
315
292
 
316
293
  lines.push(
317
- this.frameLine(
318
- `${isSelected ? this.fg("accent", "▸") : " "} ${toggle} ${label}`,
294
+ this.overlay.frameLine(
295
+ `${isSelected ? this.overlay.fg("accent", "▸") : " "} ${toggle} ${label}`,
319
296
  innerWidth
320
297
  )
321
298
  );
@@ -325,27 +302,27 @@ export class NotifySettingsOverlay implements Component {
325
302
  private renderRecap(lines: string[], innerWidth: number): void {
326
303
  // Toggle
327
304
  const isSelected = this.selectedIndex === 0;
328
- const toggleOn = this.fg("success", "●");
329
- const toggleOff = this.fg("dim", "○");
305
+ const toggleOn = this.overlay.fg("success", "●");
306
+ const toggleOff = this.overlay.fg("dim", "○");
330
307
  const toggle = this.config.recap.enabled ? toggleOn : toggleOff;
331
308
  const label = isSelected
332
- ? this.bold("Enable Recap")
333
- : this.fg("dim", "Enable Recap");
309
+ ? this.overlay.bold("Enable Recap")
310
+ : this.overlay.fg("dim", "Enable Recap");
334
311
 
335
312
  lines.push(
336
- this.frameLine(
337
- `${isSelected ? this.fg("accent", "▸") : " "} ${toggle} ${label}`,
313
+ this.overlay.frameLine(
314
+ `${isSelected ? this.overlay.fg("accent", "▸") : " "} ${toggle} ${label}`,
338
315
  innerWidth
339
316
  )
340
317
  );
341
318
 
342
319
  // Current model display
343
320
  const modelRef = this.config.recap.model;
344
- const modelLabel = this.fg("dim", ` Model: ${modelRef}`);
345
- lines.push(this.frameLine(modelLabel, innerWidth));
321
+ const modelLabel = this.overlay.fg("dim", ` Model: ${modelRef}`);
322
+ lines.push(this.overlay.frameLine(modelLabel, innerWidth));
346
323
  lines.push(
347
- this.frameLine(
348
- this.fg("dim", " Press M to change model"),
324
+ this.overlay.frameLine(
325
+ this.overlay.fg("dim", " Press M to change model"),
349
326
  innerWidth
350
327
  )
351
328
  );
@@ -6,11 +6,11 @@
6
6
  */
7
7
 
8
8
  import type { Component } from "@earendil-works/pi-tui";
9
- import { matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
9
+ import { matchesKey } from "@earendil-works/pi-tui";
10
10
  import type { Theme } from "@earendil-works/pi-coding-agent";
11
11
  import { pollForChatId } from "../platforms/telegram.js";
12
12
  import { updateConfig } from "../settings.js";
13
- import { boxInnerWidth } from "@pi-unipi/core";
13
+ import { OverlayTheme, boxInnerWidth } from "@pi-unipi/core";
14
14
 
15
15
  type SetupPhase = "instructions" | "token" | "polling" | "success" | "error" | "timeout";
16
16
 
@@ -35,10 +35,10 @@ export class TelegramSetupOverlay implements Component {
35
35
  private pasteBuffer = "";
36
36
  onClose?: () => void;
37
37
  requestRender?: () => void;
38
- private theme: Theme | null = null;
38
+ private overlay = new OverlayTheme();
39
39
 
40
40
  setTheme(theme: Theme): void {
41
- this.theme = theme;
41
+ this.overlay.setTheme(theme);
42
42
  }
43
43
 
44
44
  invalidate(): void {}
@@ -193,110 +193,79 @@ export class TelegramSetupOverlay implements Component {
193
193
  }
194
194
  }
195
195
 
196
- // ─── Theme helpers ───────────────────────────────────────────────────
197
-
198
- private fg(color: string, text: string): string {
199
- if (this.theme) return this.theme.fg(color as any, text);
200
- const c: Record<string, string> = {
201
- accent: "\x1b[36m", success: "\x1b[32m", warning: "\x1b[33m",
202
- error: "\x1b[31m", dim: "\x1b[2m", borderMuted: "\x1b[90m",
203
- };
204
- return `${c[color] ?? ""}${text}\x1b[0m`;
205
- }
206
-
207
- private bold(text: string): string {
208
- return this.theme ? this.theme.bold(text) : `\x1b[1m${text}\x1b[0m`;
209
- }
210
-
211
- private frameLine(content: string, innerWidth: number): string {
212
- const truncated = truncateToWidth(content, innerWidth, "");
213
- const padding = Math.max(0, innerWidth - visibleWidth(truncated));
214
- return `${this.fg("borderMuted", "│")}${truncated}${" ".repeat(padding)}${this.fg("borderMuted", "│")}`;
215
- }
216
-
217
- private ruleLine(innerWidth: number): string {
218
- return this.fg("borderMuted", `├${"─".repeat(innerWidth)}┤`);
219
- }
220
-
221
- private borderLine(innerWidth: number, edge: "top" | "bottom"): string {
222
- const left = edge === "top" ? "┌" : "└";
223
- const right = edge === "top" ? "┐" : "┘";
224
- return this.fg("borderMuted", `${left}${"─".repeat(innerWidth)}${right}`);
225
- }
226
-
227
196
  render(width: number): string[] {
228
197
  const innerWidth = boxInnerWidth(width);
229
198
  const lines: string[] = [];
230
199
 
231
- lines.push(this.borderLine(innerWidth, "top"));
232
- lines.push(this.frameLine(this.fg("accent", this.bold("🤖 Telegram Bot Setup")), innerWidth));
233
- lines.push(this.ruleLine(innerWidth));
200
+ lines.push(this.overlay.borderLine(innerWidth, "top"));
201
+ lines.push(this.overlay.frameLine(this.overlay.fg("accent", this.overlay.bold("🤖 Telegram Bot Setup")), innerWidth));
202
+ lines.push(this.overlay.ruleLine(innerWidth));
234
203
 
235
204
  switch (this.phase) {
236
205
  case "instructions":
237
- lines.push(this.frameLine(this.fg("dim", "Set up Telegram notifications in 3 steps:"), innerWidth));
238
- lines.push(this.frameLine("", innerWidth));
239
- lines.push(this.frameLine(` ${this.bold("1.")} Open Telegram and message ${this.fg("accent", "@BotFather")}`, innerWidth));
240
- lines.push(this.frameLine(` Send /newbot and follow the prompts to create a bot`, innerWidth));
241
- lines.push(this.frameLine("", innerWidth));
242
- lines.push(this.frameLine(` ${this.bold("2.")} Copy the bot token from BotFather`, innerWidth));
243
- lines.push(this.frameLine("", innerWidth));
244
- lines.push(this.frameLine(` ${this.bold("3.")} Send any message to your new bot`, innerWidth));
245
- lines.push(this.frameLine(` (We'll detect your chat ID automatically)`, innerWidth));
246
- lines.push(this.ruleLine(innerWidth));
247
- lines.push(this.frameLine(this.fg("dim", "Press Enter to continue, Esc to cancel"), innerWidth));
206
+ lines.push(this.overlay.frameLine(this.overlay.fg("dim", "Set up Telegram notifications in 3 steps:"), innerWidth));
207
+ lines.push(this.overlay.frameLine("", innerWidth));
208
+ lines.push(this.overlay.frameLine(` ${this.overlay.bold("1.")} Open Telegram and message ${this.overlay.fg("accent", "@BotFather")}`, innerWidth));
209
+ lines.push(this.overlay.frameLine(` Send /newbot and follow the prompts to create a bot`, innerWidth));
210
+ lines.push(this.overlay.frameLine("", innerWidth));
211
+ lines.push(this.overlay.frameLine(` ${this.overlay.bold("2.")} Copy the bot token from BotFather`, innerWidth));
212
+ lines.push(this.overlay.frameLine("", innerWidth));
213
+ lines.push(this.overlay.frameLine(` ${this.overlay.bold("3.")} Send any message to your new bot`, innerWidth));
214
+ lines.push(this.overlay.frameLine(` (We'll detect your chat ID automatically)`, innerWidth));
215
+ lines.push(this.overlay.ruleLine(innerWidth));
216
+ lines.push(this.overlay.frameLine(this.overlay.fg("dim", "Press Enter to continue, Esc to cancel"), innerWidth));
248
217
  break;
249
218
 
250
219
  case "token":
251
- lines.push(this.frameLine(this.fg("dim", "Paste your bot token from BotFather:"), innerWidth));
252
- lines.push(this.frameLine("", innerWidth));
253
- const display = this.fg("accent", this.bold(this.botToken || " "));
254
- lines.push(this.frameLine(` ${display}${this.fg("dim", "█")}`, innerWidth));
255
- lines.push(this.frameLine("", innerWidth));
256
- lines.push(this.frameLine(this.fg("dim", "Enter to start polling · Esc to cancel"), innerWidth));
220
+ lines.push(this.overlay.frameLine(this.overlay.fg("dim", "Paste your bot token from BotFather:"), innerWidth));
221
+ lines.push(this.overlay.frameLine("", innerWidth));
222
+ const display = this.overlay.fg("accent", this.overlay.bold(this.botToken || " "));
223
+ lines.push(this.overlay.frameLine(` ${display}${this.overlay.fg("dim", "█")}`, innerWidth));
224
+ lines.push(this.overlay.frameLine("", innerWidth));
225
+ lines.push(this.overlay.frameLine(this.overlay.fg("dim", "Enter to start polling · Esc to cancel"), innerWidth));
257
226
  break;
258
227
 
259
228
  case "polling": {
260
229
  const frame = SPINNER_FRAMES[this.spinnerFrame] || "⠋";
261
230
  const elapsed = Math.floor((Date.now() - this.startTime) / 1000);
262
231
  const remaining = Math.max(0, 300 - elapsed);
263
- lines.push(this.frameLine(` ${this.fg("accent", frame)} ${this.bold("Waiting for first message...")}`, innerWidth));
264
- lines.push(this.frameLine("", innerWidth));
265
- lines.push(this.frameLine(` ${this.fg("dim", "Send any message to your bot in Telegram")}`, innerWidth));
266
- lines.push(this.frameLine(` ${this.fg("dim", `Timeout: ${Math.floor(remaining / 60)}:${String(remaining % 60).padStart(2, "0")}`)}`, innerWidth));
267
- lines.push(this.ruleLine(innerWidth));
268
- lines.push(this.frameLine(this.fg("dim", "Esc to cancel"), innerWidth));
232
+ lines.push(this.overlay.frameLine(` ${this.overlay.fg("accent", frame)} ${this.overlay.bold("Waiting for first message...")}`, innerWidth));
233
+ lines.push(this.overlay.frameLine("", innerWidth));
234
+ lines.push(this.overlay.frameLine(` ${this.overlay.fg("dim", "Send any message to your bot in Telegram")}`, innerWidth));
235
+ lines.push(this.overlay.frameLine(` ${this.overlay.fg("dim", `Timeout: ${Math.floor(remaining / 60)}:${String(remaining % 60).padStart(2, "0")}`)}`, innerWidth));
236
+ lines.push(this.overlay.ruleLine(innerWidth));
237
+ lines.push(this.overlay.frameLine(this.overlay.fg("dim", "Esc to cancel"), innerWidth));
269
238
  break;
270
239
  }
271
240
 
272
241
  case "success":
273
- lines.push(this.frameLine(` ${this.fg("success", "✓ Telegram bot configured!")}`, innerWidth));
274
- lines.push(this.frameLine("", innerWidth));
275
- lines.push(this.frameLine(` ${this.fg("dim", `Chat ID: ${this.chatId}`)}`, innerWidth));
276
- lines.push(this.frameLine(` ${this.fg("dim", "Notifications will be sent to this chat")}`, innerWidth));
277
- lines.push(this.ruleLine(innerWidth));
278
- lines.push(this.frameLine(this.fg("dim", "Press Enter to close"), innerWidth));
242
+ lines.push(this.overlay.frameLine(` ${this.overlay.fg("success", "✓ Telegram bot configured!")}`, innerWidth));
243
+ lines.push(this.overlay.frameLine("", innerWidth));
244
+ lines.push(this.overlay.frameLine(` ${this.overlay.fg("dim", `Chat ID: ${this.chatId}`)}`, innerWidth));
245
+ lines.push(this.overlay.frameLine(` ${this.overlay.fg("dim", "Notifications will be sent to this chat")}`, innerWidth));
246
+ lines.push(this.overlay.ruleLine(innerWidth));
247
+ lines.push(this.overlay.frameLine(this.overlay.fg("dim", "Press Enter to close"), innerWidth));
279
248
  break;
280
249
 
281
250
  case "error":
282
- lines.push(this.frameLine(` ${this.fg("error", "✗ Setup failed")}`, innerWidth));
283
- lines.push(this.frameLine("", innerWidth));
284
- lines.push(this.frameLine(` ${this.fg("dim", this.error || "Unknown error")}`, innerWidth));
285
- lines.push(this.ruleLine(innerWidth));
286
- lines.push(this.frameLine(this.fg("dim", "Press Enter to close"), innerWidth));
251
+ lines.push(this.overlay.frameLine(` ${this.overlay.fg("error", "✗ Setup failed")}`, innerWidth));
252
+ lines.push(this.overlay.frameLine("", innerWidth));
253
+ lines.push(this.overlay.frameLine(` ${this.overlay.fg("dim", this.error || "Unknown error")}`, innerWidth));
254
+ lines.push(this.overlay.ruleLine(innerWidth));
255
+ lines.push(this.overlay.frameLine(this.overlay.fg("dim", "Press Enter to close"), innerWidth));
287
256
  break;
288
257
 
289
258
  case "timeout":
290
- lines.push(this.frameLine(` ${this.fg("warning", "⏰ Timed out after 5 minutes")}`, innerWidth));
291
- lines.push(this.frameLine("", innerWidth));
292
- lines.push(this.frameLine(` ${this.fg("dim", "Make sure you sent a message to your bot in Telegram")}`, innerWidth));
293
- lines.push(this.frameLine(` ${this.fg("dim", "You can try again with /unipi:notify-set-tg")}`, innerWidth));
294
- lines.push(this.ruleLine(innerWidth));
295
- lines.push(this.frameLine(this.fg("dim", "Press Enter to close"), innerWidth));
259
+ lines.push(this.overlay.frameLine(` ${this.overlay.fg("warning", "⏰ Timed out after 5 minutes")}`, innerWidth));
260
+ lines.push(this.overlay.frameLine("", innerWidth));
261
+ lines.push(this.overlay.frameLine(` ${this.overlay.fg("dim", "Make sure you sent a message to your bot in Telegram")}`, innerWidth));
262
+ lines.push(this.overlay.frameLine(` ${this.overlay.fg("dim", "You can try again with /unipi:notify-set-tg")}`, innerWidth));
263
+ lines.push(this.overlay.ruleLine(innerWidth));
264
+ lines.push(this.overlay.frameLine(this.overlay.fg("dim", "Press Enter to close"), innerWidth));
296
265
  break;
297
266
  }
298
267
 
299
- lines.push(this.borderLine(innerWidth, "bottom"));
268
+ lines.push(this.overlay.borderLine(innerWidth, "bottom"));
300
269
  return lines;
301
270
  }
302
271
  }
package/types.ts CHANGED
@@ -83,8 +83,6 @@ export interface NotifyConfig {
83
83
  gotify: GotifyConfig;
84
84
  /** Telegram settings */
85
85
  telegram: TelegramConfig;
86
- /** ntfy settings */
87
- ntfy: NtfyConfig;
88
86
  /** Recap summarization settings */
89
87
  recap: RecapConfig;
90
88
  }