opencode-pilot 0.1.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/.devcontainer/devcontainer.json +16 -0
- package/.github/workflows/ci.yml +67 -0
- package/.releaserc.cjs +28 -0
- package/AGENTS.md +71 -0
- package/CONTRIBUTING.md +102 -0
- package/LICENSE +21 -0
- package/README.md +72 -0
- package/bin/opencode-pilot +809 -0
- package/dist/opencode-ntfy.tar.gz +0 -0
- package/examples/config.yaml +73 -0
- package/examples/templates/default.md +7 -0
- package/examples/templates/devcontainer.md +7 -0
- package/examples/templates/review-feedback.md +7 -0
- package/examples/templates/review.md +15 -0
- package/install.sh +246 -0
- package/package.json +40 -0
- package/plugin/config.js +76 -0
- package/plugin/index.js +260 -0
- package/plugin/logger.js +125 -0
- package/plugin/notifier.js +110 -0
- package/service/actions.js +334 -0
- package/service/io.opencode.ntfy.plist +29 -0
- package/service/logger.js +82 -0
- package/service/poll-service.js +246 -0
- package/service/poller.js +339 -0
- package/service/readiness.js +234 -0
- package/service/repo-config.js +222 -0
- package/service/server.js +1523 -0
- package/service/utils.js +21 -0
- package/test/run_tests.bash +34 -0
- package/test/test_actions.bash +263 -0
- package/test/test_cli.bash +161 -0
- package/test/test_config.bash +438 -0
- package/test/test_helper.bash +140 -0
- package/test/test_logger.bash +401 -0
- package/test/test_notifier.bash +310 -0
- package/test/test_plist.bash +125 -0
- package/test/test_plugin.bash +952 -0
- package/test/test_poll_service.bash +179 -0
- package/test/test_poller.bash +120 -0
- package/test/test_readiness.bash +313 -0
- package/test/test_repo_config.bash +406 -0
- package/test/test_service.bash +1342 -0
- package/test/unit/actions.test.js +235 -0
- package/test/unit/config.test.js +86 -0
- package/test/unit/paths.test.js +77 -0
- package/test/unit/poll-service.test.js +142 -0
- package/test/unit/poller.test.js +347 -0
- package/test/unit/repo-config.test.js +441 -0
- package/test/unit/utils.test.js +53 -0
|
Binary file
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Example config.yaml for opencode-pilot
|
|
2
|
+
#
|
|
3
|
+
# Copy to ~/.config/opencode-pilot/config.yaml and customize
|
|
4
|
+
# Also create templates/ directory with prompt template files
|
|
5
|
+
|
|
6
|
+
# =============================================================================
|
|
7
|
+
# NOTIFICATIONS - ntfy settings for mobile notifications
|
|
8
|
+
# =============================================================================
|
|
9
|
+
notifications:
|
|
10
|
+
topic: your-secret-topic # Required: ntfy topic name
|
|
11
|
+
server: https://ntfy.sh # Optional: ntfy server URL
|
|
12
|
+
idle_delay_ms: 300000 # Optional: idle notification delay (5 min)
|
|
13
|
+
idle_notify: true # Optional: enable idle notifications
|
|
14
|
+
error_notify: true # Optional: enable error notifications
|
|
15
|
+
error_debounce_ms: 60000 # Optional: error debounce window (1 min)
|
|
16
|
+
debug: false # Optional: enable debug logging
|
|
17
|
+
|
|
18
|
+
# =============================================================================
|
|
19
|
+
# TOOLS - Field mappings for MCP servers (normalize different APIs)
|
|
20
|
+
# =============================================================================
|
|
21
|
+
tools:
|
|
22
|
+
github:
|
|
23
|
+
mappings: {} # GitHub already uses standard field names
|
|
24
|
+
|
|
25
|
+
linear:
|
|
26
|
+
mappings:
|
|
27
|
+
body: title # Use title as body
|
|
28
|
+
number: "url:/([A-Z0-9]+-[0-9]+)/" # Extract PROJ-123 from URL
|
|
29
|
+
|
|
30
|
+
# =============================================================================
|
|
31
|
+
# SOURCES - What to poll (generic MCP tool references)
|
|
32
|
+
# =============================================================================
|
|
33
|
+
sources:
|
|
34
|
+
# GitHub issues assigned to me - work in devcontainer
|
|
35
|
+
- name: my-issues
|
|
36
|
+
tool:
|
|
37
|
+
mcp: github
|
|
38
|
+
name: search_issues
|
|
39
|
+
args:
|
|
40
|
+
q: "is:issue assignee:@me state:open"
|
|
41
|
+
item:
|
|
42
|
+
id: "{html_url}"
|
|
43
|
+
prompt: devcontainer
|
|
44
|
+
agent: plan
|
|
45
|
+
|
|
46
|
+
# GitHub PRs needing review
|
|
47
|
+
- name: review-requests
|
|
48
|
+
tool:
|
|
49
|
+
mcp: github
|
|
50
|
+
name: search_issues
|
|
51
|
+
args:
|
|
52
|
+
q: "is:pr review-requested:@me state:open"
|
|
53
|
+
item:
|
|
54
|
+
id: "{html_url}"
|
|
55
|
+
prompt: review
|
|
56
|
+
agent: plan
|
|
57
|
+
|
|
58
|
+
# Linear issues - work in devcontainer
|
|
59
|
+
# NOTE: Replace teamId and assigneeId with your actual Linear UUIDs
|
|
60
|
+
# Find these via: linear_list_teams and check user IDs in team members
|
|
61
|
+
- name: linear-work
|
|
62
|
+
tool:
|
|
63
|
+
mcp: linear
|
|
64
|
+
name: list_issues
|
|
65
|
+
args:
|
|
66
|
+
teamId: "your-team-uuid" # Replace with actual team UUID
|
|
67
|
+
assigneeId: "your-user-uuid" # Replace with actual user UUID
|
|
68
|
+
status: "Todo"
|
|
69
|
+
item:
|
|
70
|
+
id: "linear:{id}"
|
|
71
|
+
working_dir: ~/code/myproject
|
|
72
|
+
prompt: devcontainer
|
|
73
|
+
agent: plan
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/review {html_url}
|
|
2
|
+
|
|
3
|
+
Review this pull request:
|
|
4
|
+
|
|
5
|
+
{title}
|
|
6
|
+
|
|
7
|
+
{body}
|
|
8
|
+
|
|
9
|
+
Check for:
|
|
10
|
+
- Correctness and edge cases
|
|
11
|
+
- Test coverage
|
|
12
|
+
- Code quality and maintainability
|
|
13
|
+
- Security considerations
|
|
14
|
+
|
|
15
|
+
Provide actionable feedback. Approve if ready, or request changes with specific suggestions.
|
package/install.sh
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# Install opencode-ntfy plugin and callback service
|
|
4
|
+
#
|
|
5
|
+
# Usage:
|
|
6
|
+
# curl -fsSL https://raw.githubusercontent.com/athal7/opencode-ntfy/main/install.sh | bash
|
|
7
|
+
#
|
|
8
|
+
# Or from a local clone:
|
|
9
|
+
# ./install.sh
|
|
10
|
+
#
|
|
11
|
+
|
|
12
|
+
set -euo pipefail
|
|
13
|
+
|
|
14
|
+
REPO="athal7/opencode-ntfy"
|
|
15
|
+
PLUGIN_NAME="opencode-ntfy"
|
|
16
|
+
PLUGIN_DIR="$HOME/.config/opencode/plugins/$PLUGIN_NAME"
|
|
17
|
+
SERVICE_DIR="$HOME/.local/share/opencode-ntfy"
|
|
18
|
+
CONFIG_FILE="$HOME/.config/opencode/opencode.json"
|
|
19
|
+
PLIST_DIR="$HOME/Library/LaunchAgents"
|
|
20
|
+
PLIST_NAME="io.opencode.ntfy.plist"
|
|
21
|
+
PLUGIN_FILES="index.js notifier.js callback.js hostname.js nonces.js config.js service-client.js"
|
|
22
|
+
SERVICE_FILES="server.js"
|
|
23
|
+
|
|
24
|
+
echo "Installing $PLUGIN_NAME..."
|
|
25
|
+
echo ""
|
|
26
|
+
|
|
27
|
+
# Create directories
|
|
28
|
+
mkdir -p "$PLUGIN_DIR"
|
|
29
|
+
mkdir -p "$SERVICE_DIR"
|
|
30
|
+
|
|
31
|
+
# Check if we're running from a local clone or need to download
|
|
32
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}" 2>/dev/null)" && pwd 2>/dev/null)" || SCRIPT_DIR=""
|
|
33
|
+
|
|
34
|
+
if [[ -n "$SCRIPT_DIR" ]] && [[ -f "$SCRIPT_DIR/plugin/index.js" ]]; then
|
|
35
|
+
# Local install from clone
|
|
36
|
+
echo "Installing from local directory..."
|
|
37
|
+
|
|
38
|
+
echo ""
|
|
39
|
+
echo "Plugin files:"
|
|
40
|
+
for file in $PLUGIN_FILES; do
|
|
41
|
+
if [[ -f "$SCRIPT_DIR/plugin/$file" ]]; then
|
|
42
|
+
cp "$SCRIPT_DIR/plugin/$file" "$PLUGIN_DIR/$file"
|
|
43
|
+
echo " Installed: plugin/$file -> $PLUGIN_DIR/$file"
|
|
44
|
+
fi
|
|
45
|
+
done
|
|
46
|
+
|
|
47
|
+
echo ""
|
|
48
|
+
echo "Service files:"
|
|
49
|
+
for file in $SERVICE_FILES; do
|
|
50
|
+
if [[ -f "$SCRIPT_DIR/service/$file" ]]; then
|
|
51
|
+
cp "$SCRIPT_DIR/service/$file" "$SERVICE_DIR/$file"
|
|
52
|
+
echo " Installed: service/$file -> $SERVICE_DIR/$file"
|
|
53
|
+
fi
|
|
54
|
+
done
|
|
55
|
+
else
|
|
56
|
+
# Remote install - download from GitHub
|
|
57
|
+
echo "Downloading plugin files from GitHub..."
|
|
58
|
+
|
|
59
|
+
for file in $PLUGIN_FILES; do
|
|
60
|
+
echo " Downloading: plugin/$file"
|
|
61
|
+
if curl -fsSL "https://raw.githubusercontent.com/$REPO/main/plugin/$file" -o "$PLUGIN_DIR/$file"; then
|
|
62
|
+
echo " Installed: $file"
|
|
63
|
+
else
|
|
64
|
+
echo " ERROR: Failed to download $file"
|
|
65
|
+
exit 1
|
|
66
|
+
fi
|
|
67
|
+
done
|
|
68
|
+
|
|
69
|
+
echo ""
|
|
70
|
+
echo "Downloading service files from GitHub..."
|
|
71
|
+
|
|
72
|
+
for file in $SERVICE_FILES; do
|
|
73
|
+
echo " Downloading: service/$file"
|
|
74
|
+
if curl -fsSL "https://raw.githubusercontent.com/$REPO/main/service/$file" -o "$SERVICE_DIR/$file"; then
|
|
75
|
+
echo " Installed: $file"
|
|
76
|
+
else
|
|
77
|
+
echo " ERROR: Failed to download $file"
|
|
78
|
+
exit 1
|
|
79
|
+
fi
|
|
80
|
+
done
|
|
81
|
+
fi
|
|
82
|
+
|
|
83
|
+
echo ""
|
|
84
|
+
echo "Plugin files installed to: $PLUGIN_DIR"
|
|
85
|
+
echo "Service files installed to: $SERVICE_DIR"
|
|
86
|
+
|
|
87
|
+
# Install LaunchAgent plist (macOS only)
|
|
88
|
+
if [[ "$(uname)" == "Darwin" ]]; then
|
|
89
|
+
echo ""
|
|
90
|
+
echo "Installing LaunchAgent for callback service..."
|
|
91
|
+
|
|
92
|
+
mkdir -p "$PLIST_DIR"
|
|
93
|
+
|
|
94
|
+
# Find node path (handle both Intel and Apple Silicon Macs)
|
|
95
|
+
NODE_PATH=$(command -v node 2>/dev/null)
|
|
96
|
+
if [[ -z "$NODE_PATH" ]]; then
|
|
97
|
+
# Try Homebrew paths
|
|
98
|
+
if [[ -x "/opt/homebrew/bin/node" ]]; then
|
|
99
|
+
NODE_PATH="/opt/homebrew/bin/node"
|
|
100
|
+
elif [[ -x "/usr/local/bin/node" ]]; then
|
|
101
|
+
NODE_PATH="/usr/local/bin/node"
|
|
102
|
+
else
|
|
103
|
+
echo " WARNING: node not found, please install Node.js"
|
|
104
|
+
NODE_PATH="/usr/local/bin/node"
|
|
105
|
+
fi
|
|
106
|
+
fi
|
|
107
|
+
|
|
108
|
+
# Generate plist with correct paths
|
|
109
|
+
cat > "$PLIST_DIR/$PLIST_NAME" << EOF
|
|
110
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
111
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
112
|
+
<plist version="1.0">
|
|
113
|
+
<dict>
|
|
114
|
+
<key>Label</key>
|
|
115
|
+
<string>io.opencode.ntfy</string>
|
|
116
|
+
|
|
117
|
+
<key>ProgramArguments</key>
|
|
118
|
+
<array>
|
|
119
|
+
<string>$NODE_PATH</string>
|
|
120
|
+
<string>$SERVICE_DIR/server.js</string>
|
|
121
|
+
</array>
|
|
122
|
+
|
|
123
|
+
<key>RunAtLoad</key>
|
|
124
|
+
<true/>
|
|
125
|
+
|
|
126
|
+
<key>KeepAlive</key>
|
|
127
|
+
<true/>
|
|
128
|
+
|
|
129
|
+
<key>StandardOutPath</key>
|
|
130
|
+
<string>$HOME/.local/share/opencode-ntfy/opencode-ntfy.log</string>
|
|
131
|
+
|
|
132
|
+
<key>StandardErrorPath</key>
|
|
133
|
+
<string>$HOME/.local/share/opencode-ntfy/opencode-ntfy.log</string>
|
|
134
|
+
|
|
135
|
+
<key>WorkingDirectory</key>
|
|
136
|
+
<string>$SERVICE_DIR</string>
|
|
137
|
+
</dict>
|
|
138
|
+
</plist>
|
|
139
|
+
EOF
|
|
140
|
+
|
|
141
|
+
echo " LaunchAgent installed to: $PLIST_DIR/$PLIST_NAME"
|
|
142
|
+
echo ""
|
|
143
|
+
echo " To start the callback service:"
|
|
144
|
+
echo " launchctl load $PLIST_DIR/$PLIST_NAME"
|
|
145
|
+
echo ""
|
|
146
|
+
echo " To stop the callback service:"
|
|
147
|
+
echo " launchctl unload $PLIST_DIR/$PLIST_NAME"
|
|
148
|
+
fi
|
|
149
|
+
|
|
150
|
+
# Configure opencode.json
|
|
151
|
+
echo ""
|
|
152
|
+
echo "Configuring OpenCode..."
|
|
153
|
+
|
|
154
|
+
if [[ -f "$CONFIG_FILE" ]]; then
|
|
155
|
+
# Check if plugin already configured
|
|
156
|
+
if grep -q "$PLUGIN_DIR" "$CONFIG_FILE" 2>/dev/null; then
|
|
157
|
+
echo " Plugin already configured in opencode.json"
|
|
158
|
+
else
|
|
159
|
+
echo ""
|
|
160
|
+
echo " Would you like to add the plugin to opencode.json? [Y/n]"
|
|
161
|
+
read -r response </dev/tty || response="y"
|
|
162
|
+
if [[ "$response" != "n" && "$response" != "N" ]]; then
|
|
163
|
+
# Use node to update JSON safely
|
|
164
|
+
if command -v node >/dev/null 2>&1; then
|
|
165
|
+
node -e "
|
|
166
|
+
const fs = require('fs');
|
|
167
|
+
const configPath = '$CONFIG_FILE';
|
|
168
|
+
const pluginDir = '$PLUGIN_DIR';
|
|
169
|
+
|
|
170
|
+
let config;
|
|
171
|
+
try {
|
|
172
|
+
config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
173
|
+
} catch {
|
|
174
|
+
config = {};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
config.plugin = config.plugin || [];
|
|
178
|
+
if (!config.plugin.includes(pluginDir)) {
|
|
179
|
+
config.plugin.push(pluginDir);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
|
|
183
|
+
"
|
|
184
|
+
echo " Plugin added to opencode.json"
|
|
185
|
+
else
|
|
186
|
+
echo " WARNING: node not found, please manually add to opencode.json:"
|
|
187
|
+
echo ""
|
|
188
|
+
echo " \"plugin\": [\"$PLUGIN_DIR\"]"
|
|
189
|
+
fi
|
|
190
|
+
else
|
|
191
|
+
echo " Skipped. You can manually add the plugin path to opencode.json later."
|
|
192
|
+
fi
|
|
193
|
+
fi
|
|
194
|
+
else
|
|
195
|
+
echo ""
|
|
196
|
+
echo " No opencode.json found. Create one with the plugin configured? [Y/n]"
|
|
197
|
+
read -r response </dev/tty || response="y"
|
|
198
|
+
if [[ "$response" != "n" && "$response" != "N" ]]; then
|
|
199
|
+
mkdir -p "$(dirname "$CONFIG_FILE")"
|
|
200
|
+
cat > "$CONFIG_FILE" << EOF
|
|
201
|
+
{
|
|
202
|
+
"plugin": ["$PLUGIN_DIR"]
|
|
203
|
+
}
|
|
204
|
+
EOF
|
|
205
|
+
echo " Created $CONFIG_FILE"
|
|
206
|
+
else
|
|
207
|
+
echo " Skipped. You can create opencode.json later with:"
|
|
208
|
+
echo ""
|
|
209
|
+
echo " {\"plugin\": [\"$PLUGIN_DIR\"]}"
|
|
210
|
+
fi
|
|
211
|
+
fi
|
|
212
|
+
|
|
213
|
+
# Environment variable check and guidance
|
|
214
|
+
echo ""
|
|
215
|
+
echo "========================================"
|
|
216
|
+
echo " Installation complete!"
|
|
217
|
+
echo "========================================"
|
|
218
|
+
echo ""
|
|
219
|
+
|
|
220
|
+
# Check if NTFY_TOPIC is set
|
|
221
|
+
if [[ -n "${NTFY_TOPIC:-}" ]]; then
|
|
222
|
+
echo "NTFY_TOPIC is set: $NTFY_TOPIC"
|
|
223
|
+
echo ""
|
|
224
|
+
echo "The plugin is ready to use!"
|
|
225
|
+
else
|
|
226
|
+
echo "REQUIRED: Set NTFY_TOPIC in your environment."
|
|
227
|
+
echo ""
|
|
228
|
+
echo "Add to ~/.env (if using direnv) or your shell profile:"
|
|
229
|
+
echo ""
|
|
230
|
+
echo " export NTFY_TOPIC=your-secret-topic"
|
|
231
|
+
fi
|
|
232
|
+
|
|
233
|
+
echo ""
|
|
234
|
+
echo "Optional configuration:"
|
|
235
|
+
echo " NTFY_SERVER=https://ntfy.sh # ntfy server (default: ntfy.sh)"
|
|
236
|
+
echo " NTFY_TOKEN=tk_xxx # ntfy access token for protected topics"
|
|
237
|
+
echo " NTFY_CALLBACK_HOST=host.ts.net # Callback host for interactive notifications"
|
|
238
|
+
echo " NTFY_CALLBACK_PORT=4097 # Callback server port"
|
|
239
|
+
echo " NTFY_IDLE_DELAY_MS=300000 # Idle notification delay (5 min)"
|
|
240
|
+
|
|
241
|
+
echo ""
|
|
242
|
+
echo "For interactive permissions:"
|
|
243
|
+
echo " 1. Set NTFY_CALLBACK_HOST to your machine's hostname (e.g., via Tailscale)"
|
|
244
|
+
echo " 2. Start the callback service: launchctl load ~/Library/LaunchAgents/$PLIST_NAME"
|
|
245
|
+
echo " 3. Ensure your phone can reach the callback URL"
|
|
246
|
+
echo ""
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "opencode-pilot",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Automation layer for OpenCode - notifications, mobile UI, and workflow orchestration",
|
|
6
|
+
"main": "plugin/index.js",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/athal7/opencode-pilot.git"
|
|
10
|
+
},
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"provenance": true,
|
|
13
|
+
"access": "public"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"opencode",
|
|
17
|
+
"plugin",
|
|
18
|
+
"notifications",
|
|
19
|
+
"automation",
|
|
20
|
+
"ntfy"
|
|
21
|
+
],
|
|
22
|
+
"author": "Andrew Thal <467872+athal7@users.noreply.github.com>",
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"bin": {
|
|
25
|
+
"opencode-pilot": "./bin/opencode-pilot"
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"test": "node --test test/unit/*.test.js"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@semantic-release/git": "^10.0.1",
|
|
32
|
+
"@semantic-release/github": "^9.2.6",
|
|
33
|
+
"@semantic-release/npm": "^12.0.0",
|
|
34
|
+
"semantic-release": "^25.0.2"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@modelcontextprotocol/sdk": "^1.25.1",
|
|
38
|
+
"yaml": "^2.8.2"
|
|
39
|
+
}
|
|
40
|
+
}
|
package/plugin/config.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// Configuration management for opencode-pilot
|
|
2
|
+
// Reads from ~/.config/opencode-pilot/config.yaml
|
|
3
|
+
//
|
|
4
|
+
// Example config file (~/.config/opencode-pilot/config.yaml):
|
|
5
|
+
// notifications:
|
|
6
|
+
// topic: my-secret-topic
|
|
7
|
+
// server: https://ntfy.sh
|
|
8
|
+
// idle_delay_ms: 300000
|
|
9
|
+
// debug: true
|
|
10
|
+
|
|
11
|
+
import { readFileSync, existsSync } from 'fs'
|
|
12
|
+
import { join } from 'path'
|
|
13
|
+
import { homedir } from 'os'
|
|
14
|
+
import YAML from 'yaml'
|
|
15
|
+
|
|
16
|
+
const DEFAULT_CONFIG_PATH = join(homedir(), '.config', 'opencode-pilot', 'config.yaml')
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Load configuration from config file
|
|
20
|
+
* @param {string} [configPath] - Optional path to config file (for testing)
|
|
21
|
+
*/
|
|
22
|
+
export function loadConfig(configPath) {
|
|
23
|
+
const actualPath = configPath || DEFAULT_CONFIG_PATH
|
|
24
|
+
|
|
25
|
+
// Load config.yaml if it exists
|
|
26
|
+
let fileConfig = {}
|
|
27
|
+
if (existsSync(actualPath)) {
|
|
28
|
+
try {
|
|
29
|
+
const content = readFileSync(actualPath, 'utf8')
|
|
30
|
+
const parsed = YAML.parse(content)
|
|
31
|
+
// Extract notifications section
|
|
32
|
+
fileConfig = parsed?.notifications || {}
|
|
33
|
+
} catch (err) {
|
|
34
|
+
// Silently ignore parse errors
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Helper to get value with default
|
|
39
|
+
const get = (key, defaultValue) => {
|
|
40
|
+
if (fileConfig[key] !== undefined && fileConfig[key] !== '') {
|
|
41
|
+
return fileConfig[key]
|
|
42
|
+
}
|
|
43
|
+
return defaultValue
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Helper to parse boolean
|
|
47
|
+
const getBool = (key, defaultValue) => {
|
|
48
|
+
const value = get(key, undefined)
|
|
49
|
+
if (value === undefined) return defaultValue
|
|
50
|
+
if (typeof value === 'boolean') return value
|
|
51
|
+
return String(value).toLowerCase() !== 'false' && String(value) !== '0'
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Helper to parse int
|
|
55
|
+
const getInt = (key, defaultValue) => {
|
|
56
|
+
const value = get(key, undefined)
|
|
57
|
+
if (value === undefined) return defaultValue
|
|
58
|
+
if (typeof value === 'number') return value
|
|
59
|
+
const parsed = parseInt(String(value), 10)
|
|
60
|
+
return isNaN(parsed) ? defaultValue : parsed
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
topic: get('topic', null),
|
|
65
|
+
server: get('server', 'https://ntfy.sh'),
|
|
66
|
+
authToken: get('token', null),
|
|
67
|
+
idleDelayMs: getInt('idle_delay_ms', 300000),
|
|
68
|
+
errorNotify: getBool('error_notify', true),
|
|
69
|
+
errorDebounceMs: getInt('error_debounce_ms', 60000),
|
|
70
|
+
retryNotifyFirst: getBool('retry_notify_first', true),
|
|
71
|
+
retryNotifyAfter: getInt('retry_notify_after', 3),
|
|
72
|
+
idleNotify: getBool('idle_notify', true),
|
|
73
|
+
debug: getBool('debug', false),
|
|
74
|
+
debugPath: get('debug_path', null),
|
|
75
|
+
}
|
|
76
|
+
}
|