@pi-unipi/notify 2.6.1 → 2.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/events.ts CHANGED
@@ -132,16 +132,6 @@ export function registerEventListeners(
132
132
 
133
133
  registerAgentNotification(pi, "agent_end", config, cwd);
134
134
  registerAgentNotification(pi, "agent_settled", config, cwd);
135
-
136
- // Listen for dynamic module events
137
- const moduleHandler = async (payload: unknown) => {
138
- const modPayload = payload as { name?: string; tools?: string[] };
139
- if (modPayload?.name && modPayload.name !== "@pi-unipi/notify") {
140
- // Module announced — check if it has events we should subscribe to
141
- // For now, modules register their own events through MODULE_READY
142
- }
143
- };
144
- unsubs.push(pi.events.on(UNIPI_EVENTS.MODULE_READY, moduleHandler));
145
135
  }
146
136
 
147
137
  /** Get all platforms that are currently enabled in config */
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pi-unipi/notify",
3
- "version": "2.6.1",
4
- "description": "Cross-platform notification extension for Pi native OS, Gotify, and Telegram notifications for agent lifecycle events",
3
+ "version": "2.9.0",
4
+ "description": "Cross-platform notification extension for Pi \u2014 native OS, Gotify, and Telegram notifications for agent lifecycle events",
5
5
  "type": "module",
6
6
  "main": "index.ts",
7
7
  "license": "MIT",
@@ -34,19 +34,21 @@
34
34
  "access": "public"
35
35
  },
36
36
  "dependencies": {
37
- "@pi-unipi/core": "2.6.1",
37
+ "@pi-unipi/core": "2.9.0",
38
38
  "node-notifier": "^10.0.1"
39
39
  },
40
40
  "peerDependencies": {
41
- "@earendil-works/pi-coding-agent": "^0.80.0",
42
- "@earendil-works/pi-tui": "^0.80.0",
41
+ "@earendil-works/pi-coding-agent": "^0.84.0",
42
+ "@earendil-works/pi-tui": "^0.84.0",
43
43
  "typebox": "^1.1.38"
44
44
  },
45
45
  "pi": {
46
46
  "extensions": [
47
47
  "./index.ts"
48
48
  ],
49
- "skills": [],
49
+ "skills": [
50
+ "./skills"
51
+ ],
50
52
  "prompts": [],
51
53
  "themes": []
52
54
  }
package/settings.ts CHANGED
@@ -41,11 +41,6 @@ export const DEFAULT_CONFIG: NotifyConfig = {
41
41
  telegram: {
42
42
  enabled: false,
43
43
  },
44
- ntfy: {
45
- enabled: false,
46
- serverUrl: "https://ntfy.sh",
47
- priority: 3,
48
- },
49
44
  recap: {
50
45
  enabled: false,
51
46
  model: "openrouter/openai/gpt-oss-20b",
@@ -110,19 +105,6 @@ export function validateConfig(config: NotifyConfig): string[] {
110
105
  errors.push("Gotify: priority must be between 1 and 10");
111
106
  }
112
107
 
113
- if (config.ntfy.enabled) {
114
- if (!config.ntfy.serverUrl) {
115
- errors.push("ntfy: serverUrl is required");
116
- }
117
- if (!config.ntfy.topic) {
118
- errors.push("ntfy: topic is required");
119
- }
120
- }
121
-
122
- if (config.ntfy.priority < 1 || config.ntfy.priority > 5) {
123
- errors.push("ntfy: priority must be between 1 and 5");
124
- }
125
-
126
108
  return errors;
127
109
  }
128
110
 
@@ -134,7 +116,6 @@ function mergeWithDefaults(loaded: Partial<NotifyConfig>): NotifyConfig {
134
116
  native: { ...DEFAULT_CONFIG.native, ...loaded.native },
135
117
  gotify: { ...DEFAULT_CONFIG.gotify, ...loaded.gotify },
136
118
  telegram: { ...DEFAULT_CONFIG.telegram, ...loaded.telegram },
137
- ntfy: { ...DEFAULT_CONFIG.ntfy, ...loaded.ntfy },
138
119
  recap: { ...DEFAULT_CONFIG.recap, ...loaded.recap },
139
120
  };
140
121
  }
@@ -7,11 +7,11 @@
7
7
  */
8
8
 
9
9
  import type { Component } from "@earendil-works/pi-tui";
10
- import { matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
10
+ import { matchesKey } from "@earendil-works/pi-tui";
11
11
  import type { Theme } from "@earendil-works/pi-coding-agent";
12
12
  import { sendGotifyNotification } from "../platforms/gotify.js";
13
13
  import { updateConfig, loadConfig } from "../settings.js";
14
- import { boxInnerWidth } from "@pi-unipi/core";
14
+ import { boxInnerWidth, OverlayTheme } from "@pi-unipi/core";
15
15
 
16
16
  type SetupPhase =
17
17
  | "instructions"
@@ -38,7 +38,7 @@ export class GotifySetupOverlay implements Component {
38
38
  private pasteTarget: "server-url" | "app-token" = "server-url";
39
39
  onClose?: () => void;
40
40
  requestRender?: () => void;
41
- private theme: Theme | null = null;
41
+ private overlay = new OverlayTheme();
42
42
 
43
43
  constructor() {
44
44
  // Pre-fill from existing config if available
@@ -49,7 +49,7 @@ export class GotifySetupOverlay implements Component {
49
49
  }
50
50
 
51
51
  setTheme(theme: Theme): void {
52
- this.theme = theme;
52
+ this.overlay.setTheme(theme);
53
53
  }
54
54
 
55
55
  invalidate(): void {}
@@ -195,41 +195,6 @@ export class GotifySetupOverlay implements Component {
195
195
  });
196
196
  }
197
197
 
198
- // ─── Theme helpers ───────────────────────────────────────────────────
199
-
200
- private fg(color: string, text: string): string {
201
- if (this.theme) return this.theme.fg(color as any, text);
202
- const c: Record<string, string> = {
203
- accent: "\x1b[36m",
204
- success: "\x1b[32m",
205
- warning: "\x1b[33m",
206
- error: "\x1b[31m",
207
- dim: "\x1b[2m",
208
- borderMuted: "\x1b[90m",
209
- };
210
- return `${c[color] ?? ""}${text}\x1b[0m`;
211
- }
212
-
213
- private bold(text: string): string {
214
- return this.theme ? this.theme.bold(text) : `\x1b[1m${text}\x1b[0m`;
215
- }
216
-
217
- private frameLine(content: string, innerWidth: number): string {
218
- const truncated = truncateToWidth(content, innerWidth, "");
219
- const padding = Math.max(0, innerWidth - visibleWidth(truncated));
220
- return `${this.fg("borderMuted", "│")}${truncated}${" ".repeat(padding)}${this.fg("borderMuted", "│")}`;
221
- }
222
-
223
- private ruleLine(innerWidth: number): string {
224
- return this.fg("borderMuted", `├${"─".repeat(innerWidth)}┤`);
225
- }
226
-
227
- private borderLine(innerWidth: number, edge: "top" | "bottom"): string {
228
- const left = edge === "top" ? "┌" : "└";
229
- const right = edge === "top" ? "┐" : "┘";
230
- return this.fg("borderMuted", `${left}${"─".repeat(innerWidth)}${right}`);
231
- }
232
-
233
198
  private maskToken(token: string): string {
234
199
  if (token.length <= 8) return token;
235
200
  return token.slice(0, 4) + "•".repeat(token.length - 8) + token.slice(-4);
@@ -239,82 +204,82 @@ export class GotifySetupOverlay implements Component {
239
204
  const innerWidth = boxInnerWidth(width);
240
205
  const lines: string[] = [];
241
206
 
242
- lines.push(this.borderLine(innerWidth, "top"));
207
+ lines.push(this.overlay.borderLine(innerWidth, "top"));
243
208
  lines.push(
244
- this.frameLine(
245
- this.fg("accent", this.bold("📡 Gotify Setup")),
209
+ this.overlay.frameLine(
210
+ this.overlay.fg("accent", this.overlay.bold("📡 Gotify Setup")),
246
211
  innerWidth
247
212
  )
248
213
  );
249
- lines.push(this.ruleLine(innerWidth));
214
+ lines.push(this.overlay.ruleLine(innerWidth));
250
215
 
251
216
  switch (this.phase) {
252
217
  case "instructions":
253
218
  lines.push(
254
- this.frameLine(
255
- this.fg("dim", "Set up Gotify push notifications:"),
219
+ this.overlay.frameLine(
220
+ this.overlay.fg("dim", "Set up Gotify push notifications:"),
256
221
  innerWidth
257
222
  )
258
223
  );
259
- lines.push(this.frameLine("", innerWidth));
224
+ lines.push(this.overlay.frameLine("", innerWidth));
260
225
  lines.push(
261
- this.frameLine(
262
- ` ${this.bold("1.")} Run a Gotify server (or use an existing one)`,
226
+ this.overlay.frameLine(
227
+ ` ${this.overlay.bold("1.")} Run a Gotify server (or use an existing one)`,
263
228
  innerWidth
264
229
  )
265
230
  );
266
231
  lines.push(
267
- this.frameLine(
268
- ` ${this.fg("dim", "See: https://gotify.net/docs/install")}`,
232
+ this.overlay.frameLine(
233
+ ` ${this.overlay.fg("dim", "See: https://gotify.net/docs/install")}`,
269
234
  innerWidth
270
235
  )
271
236
  );
272
- lines.push(this.frameLine("", innerWidth));
237
+ lines.push(this.overlay.frameLine("", innerWidth));
273
238
  lines.push(
274
- this.frameLine(
275
- ` ${this.bold("2.")} Open your Gotify web UI`,
239
+ this.overlay.frameLine(
240
+ ` ${this.overlay.bold("2.")} Open your Gotify web UI`,
276
241
  innerWidth
277
242
  )
278
243
  );
279
244
  lines.push(
280
- this.frameLine(
281
- ` Go to ${this.fg("accent", "Apps")} → Create Application`,
245
+ this.overlay.frameLine(
246
+ ` Go to ${this.overlay.fg("accent", "Apps")} → Create Application`,
282
247
  innerWidth
283
248
  )
284
249
  );
285
250
  lines.push(
286
- this.frameLine(
287
- ` Copy the ${this.fg("accent", "app token")}`,
251
+ this.overlay.frameLine(
252
+ ` Copy the ${this.overlay.fg("accent", "app token")}`,
288
253
  innerWidth
289
254
  )
290
255
  );
291
- lines.push(this.frameLine("", innerWidth));
256
+ lines.push(this.overlay.frameLine("", innerWidth));
292
257
  lines.push(
293
- this.frameLine(
294
- ` ${this.bold("3.")} Enter your server URL and app token below`,
258
+ this.overlay.frameLine(
259
+ ` ${this.overlay.bold("3.")} Enter your server URL and app token below`,
295
260
  innerWidth
296
261
  )
297
262
  );
298
263
  if (this.serverUrl) {
299
264
  lines.push(
300
- this.frameLine(
301
- ` ${this.fg("success", "✓")} Server URL pre-filled from existing config`,
265
+ this.overlay.frameLine(
266
+ ` ${this.overlay.fg("success", "✓")} Server URL pre-filled from existing config`,
302
267
  innerWidth
303
268
  )
304
269
  );
305
270
  }
306
271
  if (this.appToken) {
307
272
  lines.push(
308
- this.frameLine(
309
- ` ${this.fg("success", "✓")} App token pre-filled from existing config`,
273
+ this.overlay.frameLine(
274
+ ` ${this.overlay.fg("success", "✓")} App token pre-filled from existing config`,
310
275
  innerWidth
311
276
  )
312
277
  );
313
278
  }
314
- lines.push(this.ruleLine(innerWidth));
279
+ lines.push(this.overlay.ruleLine(innerWidth));
315
280
  lines.push(
316
- this.frameLine(
317
- this.fg("dim", "Press Enter to continue, Esc to cancel"),
281
+ this.overlay.frameLine(
282
+ this.overlay.fg("dim", "Press Enter to continue, Esc to cancel"),
318
283
  innerWidth
319
284
  )
320
285
  );
@@ -322,29 +287,29 @@ export class GotifySetupOverlay implements Component {
322
287
 
323
288
  case "server-url":
324
289
  lines.push(
325
- this.frameLine(
326
- this.fg("dim", "Enter your Gotify server URL:"),
290
+ this.overlay.frameLine(
291
+ this.overlay.fg("dim", "Enter your Gotify server URL:"),
327
292
  innerWidth
328
293
  )
329
294
  );
330
- lines.push(this.frameLine("", innerWidth));
295
+ lines.push(this.overlay.frameLine("", innerWidth));
331
296
  lines.push(
332
- this.frameLine(
333
- ` ${this.fg("accent", this.bold(this.serverUrl || " "))}${this.fg("dim", "█")}`,
297
+ this.overlay.frameLine(
298
+ ` ${this.overlay.fg("accent", this.overlay.bold(this.serverUrl || " "))}${this.overlay.fg("dim", "█")}`,
334
299
  innerWidth
335
300
  )
336
301
  );
337
- lines.push(this.frameLine("", innerWidth));
302
+ lines.push(this.overlay.frameLine("", innerWidth));
338
303
  lines.push(
339
- this.frameLine(
340
- this.fg("dim", "Example: https://gotify.example.com"),
304
+ this.overlay.frameLine(
305
+ this.overlay.fg("dim", "Example: https://gotify.example.com"),
341
306
  innerWidth
342
307
  )
343
308
  );
344
- lines.push(this.ruleLine(innerWidth));
309
+ lines.push(this.overlay.ruleLine(innerWidth));
345
310
  lines.push(
346
- this.frameLine(
347
- this.fg("dim", "Enter to continue · Esc to cancel"),
311
+ this.overlay.frameLine(
312
+ this.overlay.fg("dim", "Enter to continue · Esc to cancel"),
348
313
  innerWidth
349
314
  )
350
315
  );
@@ -352,32 +317,32 @@ export class GotifySetupOverlay implements Component {
352
317
 
353
318
  case "app-token":
354
319
  lines.push(
355
- this.frameLine(
356
- this.fg("dim", "Enter your app token:"),
320
+ this.overlay.frameLine(
321
+ this.overlay.fg("dim", "Enter your app token:"),
357
322
  innerWidth
358
323
  )
359
324
  );
360
- lines.push(this.frameLine("", innerWidth));
325
+ lines.push(this.overlay.frameLine("", innerWidth));
361
326
  const displayToken = this.appToken
362
- ? this.fg("accent", this.bold(this.maskToken(this.appToken)))
327
+ ? this.overlay.fg("accent", this.overlay.bold(this.maskToken(this.appToken)))
363
328
  : " ";
364
329
  lines.push(
365
- this.frameLine(
366
- ` ${displayToken}${this.fg("dim", "█")}`,
330
+ this.overlay.frameLine(
331
+ ` ${displayToken}${this.overlay.fg("dim", "█")}`,
367
332
  innerWidth
368
333
  )
369
334
  );
370
- lines.push(this.frameLine("", innerWidth));
335
+ lines.push(this.overlay.frameLine("", innerWidth));
371
336
  lines.push(
372
- this.frameLine(
373
- this.fg("dim", "Found in Gotify → Apps → your app"),
337
+ this.overlay.frameLine(
338
+ this.overlay.fg("dim", "Found in Gotify → Apps → your app"),
374
339
  innerWidth
375
340
  )
376
341
  );
377
- lines.push(this.ruleLine(innerWidth));
342
+ lines.push(this.overlay.ruleLine(innerWidth));
378
343
  lines.push(
379
- this.frameLine(
380
- this.fg("dim", "Enter to continue · Esc to cancel"),
344
+ this.overlay.frameLine(
345
+ this.overlay.fg("dim", "Enter to continue · Esc to cancel"),
381
346
  innerWidth
382
347
  )
383
348
  );
@@ -385,145 +350,145 @@ export class GotifySetupOverlay implements Component {
385
350
 
386
351
  case "priority":
387
352
  lines.push(
388
- this.frameLine(
389
- this.fg("dim", "Set notification priority (1-10):"),
353
+ this.overlay.frameLine(
354
+ this.overlay.fg("dim", "Set notification priority (1-10):"),
390
355
  innerWidth
391
356
  )
392
357
  );
393
- lines.push(this.frameLine("", innerWidth));
358
+ lines.push(this.overlay.frameLine("", innerWidth));
394
359
  lines.push(
395
- this.frameLine(
396
- ` ${this.fg("accent", this.bold(this.priority || " "))}${this.fg("dim", "█")}`,
360
+ this.overlay.frameLine(
361
+ ` ${this.overlay.fg("accent", this.overlay.bold(this.priority || " "))}${this.overlay.fg("dim", "█")}`,
397
362
  innerWidth
398
363
  )
399
364
  );
400
- lines.push(this.frameLine("", innerWidth));
365
+ lines.push(this.overlay.frameLine("", innerWidth));
401
366
  lines.push(
402
- this.frameLine(
403
- ` ${this.fg("dim", "1")} = low · ${this.fg("dim", "5")} = normal · ${this.fg("dim", "10")} = high`,
367
+ this.overlay.frameLine(
368
+ ` ${this.overlay.fg("dim", "1")} = low · ${this.overlay.fg("dim", "5")} = normal · ${this.overlay.fg("dim", "10")} = high`,
404
369
  innerWidth
405
370
  )
406
371
  );
407
- lines.push(this.ruleLine(innerWidth));
372
+ lines.push(this.overlay.ruleLine(innerWidth));
408
373
  lines.push(
409
- this.frameLine(
410
- this.fg("dim", "Enter to test connection · Esc to cancel"),
374
+ this.overlay.frameLine(
375
+ this.overlay.fg("dim", "Enter to test connection · Esc to cancel"),
411
376
  innerWidth
412
377
  )
413
378
  );
414
379
  break;
415
380
 
416
381
  case "testing":
417
- lines.push(this.frameLine("", innerWidth));
382
+ lines.push(this.overlay.frameLine("", innerWidth));
418
383
  lines.push(
419
- this.frameLine(
420
- ` ${this.fg("accent", "⠋")} ${this.bold("Testing connection...")}`,
384
+ this.overlay.frameLine(
385
+ ` ${this.overlay.fg("accent", "⠋")} ${this.overlay.bold("Testing connection...")}`,
421
386
  innerWidth
422
387
  )
423
388
  );
424
- lines.push(this.frameLine("", innerWidth));
389
+ lines.push(this.overlay.frameLine("", innerWidth));
425
390
  lines.push(
426
- this.frameLine(
427
- ` ${this.fg("dim", `Sending test to ${this.serverUrl}`)}`,
391
+ this.overlay.frameLine(
392
+ ` ${this.overlay.fg("dim", `Sending test to ${this.serverUrl}`)}`,
428
393
  innerWidth
429
394
  )
430
395
  );
431
- lines.push(this.ruleLine(innerWidth));
396
+ lines.push(this.overlay.ruleLine(innerWidth));
432
397
  lines.push(
433
- this.frameLine(
434
- this.fg("dim", "Esc to cancel"),
398
+ this.overlay.frameLine(
399
+ this.overlay.fg("dim", "Esc to cancel"),
435
400
  innerWidth
436
401
  )
437
402
  );
438
403
  break;
439
404
 
440
405
  case "success":
441
- lines.push(this.frameLine("", innerWidth));
406
+ lines.push(this.overlay.frameLine("", innerWidth));
442
407
  lines.push(
443
- this.frameLine(
444
- ` ${this.fg("success", "✓ Gotify configured successfully!")}`,
408
+ this.overlay.frameLine(
409
+ ` ${this.overlay.fg("success", "✓ Gotify configured successfully!")}`,
445
410
  innerWidth
446
411
  )
447
412
  );
448
- lines.push(this.frameLine("", innerWidth));
413
+ lines.push(this.overlay.frameLine("", innerWidth));
449
414
  lines.push(
450
- this.frameLine(
451
- ` ${this.fg("dim", `Server: ${this.serverUrl}`)}`,
415
+ this.overlay.frameLine(
416
+ ` ${this.overlay.fg("dim", `Server: ${this.serverUrl}`)}`,
452
417
  innerWidth
453
418
  )
454
419
  );
455
420
  lines.push(
456
- this.frameLine(
457
- ` ${this.fg("dim", `Priority: ${this.priority}`)}`,
421
+ this.overlay.frameLine(
422
+ ` ${this.overlay.fg("dim", `Priority: ${this.priority}`)}`,
458
423
  innerWidth
459
424
  )
460
425
  );
461
- lines.push(this.ruleLine(innerWidth));
426
+ lines.push(this.overlay.ruleLine(innerWidth));
462
427
  lines.push(
463
- this.frameLine(
464
- this.fg("dim", "Closing..."),
428
+ this.overlay.frameLine(
429
+ this.overlay.fg("dim", "Closing..."),
465
430
  innerWidth
466
431
  )
467
432
  );
468
433
  break;
469
434
 
470
435
  case "test-failed":
471
- lines.push(this.frameLine("", innerWidth));
436
+ lines.push(this.overlay.frameLine("", innerWidth));
472
437
  lines.push(
473
- this.frameLine(
474
- ` ${this.fg("error", "✗ Connection test failed")}`,
438
+ this.overlay.frameLine(
439
+ ` ${this.overlay.fg("error", "✗ Connection test failed")}`,
475
440
  innerWidth
476
441
  )
477
442
  );
478
- lines.push(this.frameLine("", innerWidth));
443
+ lines.push(this.overlay.frameLine("", innerWidth));
479
444
  lines.push(
480
- this.frameLine(
481
- ` ${this.fg("dim", this.testError || "Unknown error")}`,
445
+ this.overlay.frameLine(
446
+ ` ${this.overlay.fg("dim", this.testError || "Unknown error")}`,
482
447
  innerWidth
483
448
  )
484
449
  );
485
- lines.push(this.frameLine("", innerWidth));
450
+ lines.push(this.overlay.frameLine("", innerWidth));
486
451
  lines.push(
487
- this.frameLine(
488
- ` ${this.fg("dim", "Check your server URL and app token")}`,
452
+ this.overlay.frameLine(
453
+ ` ${this.overlay.fg("dim", "Check your server URL and app token")}`,
489
454
  innerWidth
490
455
  )
491
456
  );
492
- lines.push(this.ruleLine(innerWidth));
457
+ lines.push(this.overlay.ruleLine(innerWidth));
493
458
  lines.push(
494
- this.frameLine(
495
- this.fg("dim", "Press Enter to close"),
459
+ this.overlay.frameLine(
460
+ this.overlay.fg("dim", "Press Enter to close"),
496
461
  innerWidth
497
462
  )
498
463
  );
499
464
  break;
500
465
 
501
466
  case "error":
502
- lines.push(this.frameLine("", innerWidth));
467
+ lines.push(this.overlay.frameLine("", innerWidth));
503
468
  lines.push(
504
- this.frameLine(
505
- ` ${this.fg("error", "✗ Setup failed")}`,
469
+ this.overlay.frameLine(
470
+ ` ${this.overlay.fg("error", "✗ Setup failed")}`,
506
471
  innerWidth
507
472
  )
508
473
  );
509
- lines.push(this.frameLine("", innerWidth));
474
+ lines.push(this.overlay.frameLine("", innerWidth));
510
475
  lines.push(
511
- this.frameLine(
512
- ` ${this.fg("dim", this.error || "Unknown error")}`,
476
+ this.overlay.frameLine(
477
+ ` ${this.overlay.fg("dim", this.error || "Unknown error")}`,
513
478
  innerWidth
514
479
  )
515
480
  );
516
- lines.push(this.ruleLine(innerWidth));
481
+ lines.push(this.overlay.ruleLine(innerWidth));
517
482
  lines.push(
518
- this.frameLine(
519
- this.fg("dim", "Press Enter to close"),
483
+ this.overlay.frameLine(
484
+ this.overlay.fg("dim", "Press Enter to close"),
520
485
  innerWidth
521
486
  )
522
487
  );
523
488
  break;
524
489
  }
525
490
 
526
- lines.push(this.borderLine(innerWidth, "bottom"));
491
+ lines.push(this.overlay.borderLine(innerWidth, "bottom"));
527
492
  return lines;
528
493
  }
529
494
  }