@pi-unipi/notify 0.1.9 → 0.1.10
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 +51 -74
- package/ntfy-config.ts +178 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,21 +1,52 @@
|
|
|
1
1
|
# @pi-unipi/notify
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Push notifications when things happen. Workflow finishes, Ralph loop completes, MCP server errors — notify sends alerts to native OS, Gotify, Telegram, or ntfy.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Configure once, get alerts everywhere. Per-event platform routing lets you send critical errors to Telegram and routine completions to Gotify.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
## Commands
|
|
8
|
+
|
|
9
|
+
| Command | Description |
|
|
10
|
+
|---------|-------------|
|
|
11
|
+
| `/unipi:notify-settings` | Open settings overlay to configure platforms and events |
|
|
12
|
+
| `/unipi:notify-set-gotify` | Configure Gotify server connection |
|
|
13
|
+
| `/unipi:notify-set-tg` | Interactive Telegram bot setup |
|
|
14
|
+
| `/unipi:notify-set-ntfy` | Configure ntfy topic and server |
|
|
15
|
+
| `/unipi:notify-recap-model` | Set model for notification recaps |
|
|
16
|
+
| `/unipi:notify-test` | Send test notification to all enabled platforms |
|
|
11
17
|
|
|
12
|
-
##
|
|
18
|
+
## Special Triggers
|
|
13
19
|
|
|
14
|
-
|
|
20
|
+
Notify subscribes to Pi lifecycle events and routes notifications based on your config:
|
|
21
|
+
|
|
22
|
+
| Event | Default | Description |
|
|
23
|
+
|-------|---------|-------------|
|
|
24
|
+
| `workflow_end` | On | Workflow command completes |
|
|
25
|
+
| `ralph_loop_end` | On | Ralph loop completes |
|
|
26
|
+
| `mcp_server_error` | On | MCP server error |
|
|
27
|
+
| `agent_end` | Off | Agent finishes responding |
|
|
28
|
+
| `memory_consolidated` | Off | Memory auto-saved |
|
|
29
|
+
| `session_shutdown` | Off | Session ends |
|
|
30
|
+
|
|
31
|
+
Notify registers with the info-screen dashboard, showing enabled platforms and last notification time. The footer subscribes to `NOTIFICATION_SENT` events to display notification stats.
|
|
32
|
+
|
|
33
|
+
## Agent Tool
|
|
34
|
+
|
|
35
|
+
| Tool | Description |
|
|
36
|
+
|------|-------------|
|
|
37
|
+
| `notify_user` | Send cross-platform notification |
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
notify_user({
|
|
41
|
+
title: "Build Failed",
|
|
42
|
+
message: "TypeScript compilation failed with 12 errors.",
|
|
43
|
+
priority: "high"
|
|
44
|
+
})
|
|
45
|
+
```
|
|
15
46
|
|
|
16
47
|
## Platforms
|
|
17
48
|
|
|
18
|
-
### Native OS
|
|
49
|
+
### Native OS
|
|
19
50
|
|
|
20
51
|
Desktop notifications via [node-notifier](https://github.com/mikaelbr/node-notifier):
|
|
21
52
|
- **Windows:** SnoreToast (no admin required)
|
|
@@ -26,7 +57,7 @@ Zero configuration — works out of the box.
|
|
|
26
57
|
|
|
27
58
|
### Gotify
|
|
28
59
|
|
|
29
|
-
Self-hosted push notification server
|
|
60
|
+
Self-hosted push notification server:
|
|
30
61
|
|
|
31
62
|
```json
|
|
32
63
|
{
|
|
@@ -41,26 +72,14 @@ Self-hosted push notification server. Configure in settings:
|
|
|
41
72
|
|
|
42
73
|
### Telegram
|
|
43
74
|
|
|
44
|
-
Bot API notifications. Run setup
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
This guides you through:
|
|
51
|
-
1. Creating a bot via @BotFather
|
|
52
|
-
2. Pasting the bot token
|
|
53
|
-
3. Auto-detecting your chat ID
|
|
75
|
+
Bot API notifications. Run `/unipi:notify-set-tg` for interactive setup:
|
|
76
|
+
1. Create a bot via @BotFather
|
|
77
|
+
2. Paste the bot token
|
|
78
|
+
3. Auto-detect your chat ID
|
|
54
79
|
|
|
55
80
|
### ntfy
|
|
56
81
|
|
|
57
|
-
HTTP-based pub-sub notifications via [ntfy.sh](https://ntfy.sh) or self-hosted
|
|
58
|
-
|
|
59
|
-
```
|
|
60
|
-
/unipi:notify-set-ntfy
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
Or configure manually:
|
|
82
|
+
HTTP-based pub-sub notifications via [ntfy.sh](https://ntfy.sh) or self-hosted:
|
|
64
83
|
|
|
65
84
|
```json
|
|
66
85
|
{
|
|
@@ -73,54 +92,12 @@ Or configure manually:
|
|
|
73
92
|
}
|
|
74
93
|
```
|
|
75
94
|
|
|
76
|
-
##
|
|
77
|
-
|
|
78
|
-
| Command | Description |
|
|
79
|
-
|---------|-------------|
|
|
80
|
-
| `/unipi:notify-settings` | Open settings overlay to configure platforms and events |
|
|
81
|
-
| `/unipi:notify-set-gotify` | Configure Gotify server connection |
|
|
82
|
-
| `/unipi:notify-set-tg` | Interactive Telegram bot setup |
|
|
83
|
-
| `/unipi:notify-set-ntfy` | Configure ntfy topic and server |
|
|
84
|
-
| `/unipi:notify-test` | Send test notification to all enabled platforms |
|
|
85
|
-
|
|
86
|
-
## Agent Tool
|
|
87
|
-
|
|
88
|
-
The `notify_user` tool is available to the agent for ad-hoc notifications:
|
|
89
|
-
|
|
90
|
-
```
|
|
91
|
-
notify_user({
|
|
92
|
-
title: "Build Failed",
|
|
93
|
-
message: "TypeScript compilation failed with 12 errors.",
|
|
94
|
-
priority: "high"
|
|
95
|
-
})
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
See the bundled `notify` skill for full parameter documentation.
|
|
99
|
-
|
|
100
|
-
## Event Configuration
|
|
101
|
-
|
|
102
|
-
Notifications are triggered by these events (configurable in settings):
|
|
103
|
-
|
|
104
|
-
| Event | Default | Description |
|
|
105
|
-
|-------|---------|-------------|
|
|
106
|
-
| `workflow_end` | On | Workflow command completes |
|
|
107
|
-
| `ralph_loop_end` | On | Ralph loop completes |
|
|
108
|
-
| `mcp_server_error` | On | MCP server error |
|
|
109
|
-
| `agent_end` | Off | Agent finishes responding |
|
|
110
|
-
| `memory_consolidated` | Off | Memory auto-saved |
|
|
111
|
-
| `session_shutdown` | Off | Session ends |
|
|
95
|
+
## Configurables
|
|
112
96
|
|
|
113
|
-
|
|
97
|
+
Settings stored at `~/.unipi/config/notify/config.json`. Edit via `/unipi:notify-settings` or manual JSON editing.
|
|
114
98
|
|
|
115
|
-
|
|
116
|
-
- Settings overlay: `/unipi:notify-settings`
|
|
117
|
-
- Manual JSON editing
|
|
118
|
-
- The agent can read config via the settings module
|
|
99
|
+
Per-event platform routing lets you control where each event type goes. The settings overlay shows all events with platform toggles.
|
|
119
100
|
|
|
120
|
-
##
|
|
101
|
+
## License
|
|
121
102
|
|
|
122
|
-
|
|
123
|
-
- Enabled platform count
|
|
124
|
-
- Active event subscriptions
|
|
125
|
-
- Last notification timestamp
|
|
126
|
-
- Total notifications sent this session
|
|
103
|
+
MIT
|
package/ntfy-config.ts
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @pi-unipi/notify — Project-level ntfy configuration
|
|
3
|
+
*
|
|
4
|
+
* Loads, saves, and resolves ntfy config from dedicated ntfy.json files
|
|
5
|
+
* at global (~/.unipi/config/notify/ntfy.json) and project
|
|
6
|
+
* (<cwd>/.unipi/config/notify/ntfy.json) scope.
|
|
7
|
+
*
|
|
8
|
+
* Resolution: project → global → defaults.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
|
|
12
|
+
import { dirname, join } from "path";
|
|
13
|
+
import { homedir } from "os";
|
|
14
|
+
import type { NtfyConfig } from "./types.js";
|
|
15
|
+
|
|
16
|
+
/** Default ntfy configuration */
|
|
17
|
+
const DEFAULT_NTFY_CONFIG: NtfyConfig = {
|
|
18
|
+
enabled: false,
|
|
19
|
+
serverUrl: "https://ntfy.sh",
|
|
20
|
+
priority: 3,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/** Global ntfy.json path: ~/.unipi/config/notify/ntfy.json */
|
|
24
|
+
function getGlobalNtfyPath(): string {
|
|
25
|
+
return join(homedir(), ".unipi", "config", "notify", "ntfy.json");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Project ntfy.json path: <cwd>/.unipi/config/notify/ntfy.json */
|
|
29
|
+
function getProjectNtfyPath(cwd: string): string {
|
|
30
|
+
return join(cwd, ".unipi", "config", "notify", "ntfy.json");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Read and parse a ntfy.json file.
|
|
35
|
+
* Returns null if file doesn't exist (ENOENT).
|
|
36
|
+
* Returns null and logs warning on parse error.
|
|
37
|
+
*/
|
|
38
|
+
function readNtfyJson(filePath: string): NtfyConfig | null {
|
|
39
|
+
try {
|
|
40
|
+
const raw = readFileSync(filePath, "utf-8");
|
|
41
|
+
const parsed = JSON.parse(raw) as Partial<NtfyConfig>;
|
|
42
|
+
return { ...DEFAULT_NTFY_CONFIG, ...parsed };
|
|
43
|
+
} catch (err: unknown) {
|
|
44
|
+
if ((err as NodeJS.ErrnoException).code === "ENOENT") {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
console.warn(`[notify] Failed to parse ${filePath}: ${(err as Error).message}. Falling back.`);
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Resolve ntfy config with project → global → defaults priority.
|
|
54
|
+
*
|
|
55
|
+
* 1. If project ntfy.json exists → use it
|
|
56
|
+
* 2. If only global ntfy.json exists → use it
|
|
57
|
+
* 3. If neither exists → return defaults (ntfy disabled)
|
|
58
|
+
*
|
|
59
|
+
* Runs legacy migration once if global ntfy.json is missing.
|
|
60
|
+
*/
|
|
61
|
+
export function loadNtfyConfig(cwd: string): NtfyConfig {
|
|
62
|
+
// Attempt migration from legacy config.json if global ntfy.json doesn't exist
|
|
63
|
+
migrateFromLegacyConfig();
|
|
64
|
+
|
|
65
|
+
// Try project-level first
|
|
66
|
+
const projectConfig = readNtfyJson(getProjectNtfyPath(cwd));
|
|
67
|
+
if (projectConfig !== null) {
|
|
68
|
+
return projectConfig;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Try global level
|
|
72
|
+
const globalConfig = readNtfyJson(getGlobalNtfyPath());
|
|
73
|
+
if (globalConfig !== null) {
|
|
74
|
+
return globalConfig;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Neither exists — return defaults
|
|
78
|
+
return { ...DEFAULT_NTFY_CONFIG };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Save ntfy config to the chosen scope.
|
|
83
|
+
* Creates parent directory if needed.
|
|
84
|
+
*/
|
|
85
|
+
export function saveNtfyConfig(
|
|
86
|
+
scope: "project" | "global",
|
|
87
|
+
cwd: string,
|
|
88
|
+
config: NtfyConfig
|
|
89
|
+
): void {
|
|
90
|
+
const filePath =
|
|
91
|
+
scope === "project" ? getProjectNtfyPath(cwd) : getGlobalNtfyPath();
|
|
92
|
+
const dir = dirname(filePath);
|
|
93
|
+
mkdirSync(dir, { recursive: true });
|
|
94
|
+
writeFileSync(filePath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Detect which scope is currently active for ntfy config.
|
|
99
|
+
*
|
|
100
|
+
* - "project" if project ntfy.json exists
|
|
101
|
+
* - "global" if global ntfy.json exists (and no project override)
|
|
102
|
+
* - "none" if neither exists
|
|
103
|
+
*/
|
|
104
|
+
export function getNtfyConfigScope(
|
|
105
|
+
cwd: string
|
|
106
|
+
): "project" | "global" | "none" {
|
|
107
|
+
if (existsSync(getProjectNtfyPath(cwd))) {
|
|
108
|
+
return "project";
|
|
109
|
+
}
|
|
110
|
+
if (existsSync(getGlobalNtfyPath())) {
|
|
111
|
+
return "global";
|
|
112
|
+
}
|
|
113
|
+
return "none";
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* One-time migration from legacy config.json ntfy section to ntfy.json.
|
|
118
|
+
*
|
|
119
|
+
* Trigger conditions:
|
|
120
|
+
* - Global ntfy.json does NOT exist
|
|
121
|
+
* - config.json has non-default ntfy settings (enabled or topic/serverUrl set)
|
|
122
|
+
*
|
|
123
|
+
* After migration, config.json ntfy section is left untouched.
|
|
124
|
+
* Future reads use ntfy.json exclusively.
|
|
125
|
+
*/
|
|
126
|
+
export function migrateFromLegacyConfig(): void {
|
|
127
|
+
const globalNtfyPath = getGlobalNtfyPath();
|
|
128
|
+
|
|
129
|
+
// Only migrate if global ntfy.json doesn't exist yet
|
|
130
|
+
if (existsSync(globalNtfyPath)) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Try to read legacy config.json
|
|
135
|
+
const legacyConfigPath = join(
|
|
136
|
+
homedir(),
|
|
137
|
+
".unipi",
|
|
138
|
+
"config",
|
|
139
|
+
"notify",
|
|
140
|
+
"config.json"
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
if (!existsSync(legacyConfigPath)) return;
|
|
145
|
+
const raw = readFileSync(legacyConfigPath, "utf-8");
|
|
146
|
+
const parsed = JSON.parse(raw) as Record<string, unknown>;
|
|
147
|
+
const ntfySection = parsed.ntfy as Partial<NtfyConfig> | undefined;
|
|
148
|
+
|
|
149
|
+
if (!ntfySection) return;
|
|
150
|
+
|
|
151
|
+
// Only migrate if there's something meaningful to migrate
|
|
152
|
+
const hasCustomConfig =
|
|
153
|
+
ntfySection.enabled === true ||
|
|
154
|
+
(ntfySection.serverUrl && ntfySection.serverUrl !== "https://ntfy.sh") ||
|
|
155
|
+
ntfySection.topic;
|
|
156
|
+
|
|
157
|
+
if (!hasCustomConfig) return;
|
|
158
|
+
|
|
159
|
+
// Write ntfy.json from legacy config
|
|
160
|
+
const migratedConfig: NtfyConfig = {
|
|
161
|
+
enabled: ntfySection.enabled ?? false,
|
|
162
|
+
serverUrl: ntfySection.serverUrl ?? "https://ntfy.sh",
|
|
163
|
+
topic: ntfySection.topic,
|
|
164
|
+
token: ntfySection.token,
|
|
165
|
+
priority: ntfySection.priority ?? 3,
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const dir = dirname(globalNtfyPath);
|
|
169
|
+
mkdirSync(dir, { recursive: true });
|
|
170
|
+
writeFileSync(
|
|
171
|
+
globalNtfyPath,
|
|
172
|
+
JSON.stringify(migratedConfig, null, 2) + "\n",
|
|
173
|
+
"utf-8"
|
|
174
|
+
);
|
|
175
|
+
} catch {
|
|
176
|
+
// Migration failure is non-fatal — silently continue
|
|
177
|
+
}
|
|
178
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/notify",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"description": "Cross-platform notification extension for Pi — native OS, Gotify, and Telegram notifications for agent lifecycle events",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"commands.ts",
|
|
25
25
|
"settings.ts",
|
|
26
26
|
"events.ts",
|
|
27
|
+
"ntfy-config.ts",
|
|
27
28
|
"summarize.ts",
|
|
28
29
|
"types.ts",
|
|
29
30
|
"platforms/*",
|