@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.
- package/README.md +1 -0
- package/commands.ts +61 -3
- package/events.ts +0 -10
- package/package.json +5 -3
- package/settings.ts +0 -19
- package/tui/gotify-setup.ts +108 -143
- package/tui/ntfy-setup.ts +137 -172
- package/tui/recap-model-selector.ts +107 -101
- package/tui/settings-overlay.ts +94 -117
- package/tui/telegram-setup.ts +48 -79
- package/types.ts +0 -2
package/tui/settings-overlay.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type { Component } from "@earendil-works/pi-tui";
|
|
9
|
-
import {
|
|
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
|
|
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
|
|
48
|
+
this.overlay.setTheme(theme);
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
invalidate(): void {}
|
|
52
52
|
|
|
53
53
|
handleInput(data: string): void {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
);
|
package/tui/telegram-setup.ts
CHANGED
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type { Component } from "@earendil-works/pi-tui";
|
|
9
|
-
import { matchesKey
|
|
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
|
|
38
|
+
private overlay = new OverlayTheme();
|
|
39
39
|
|
|
40
40
|
setTheme(theme: Theme): void {
|
|
41
|
-
this.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
|
}
|