opencode-pilot 0.1.0 → 0.2.1

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.
@@ -9,6 +9,7 @@ on:
9
9
  permissions:
10
10
  contents: write
11
11
  id-token: write # Enable OIDC for npm trusted publishing
12
+ issues: write # For semantic-release failure reporting
12
13
 
13
14
  jobs:
14
15
  test:
@@ -56,12 +57,18 @@ jobs:
56
57
  with:
57
58
  node-version: '22'
58
59
 
60
+ - name: Upgrade npm for trusted publishing
61
+ run: npm install -g npm@latest
62
+
63
+ - name: Configure npm registry
64
+ run: npm config set registry https://registry.npmjs.org/
65
+
59
66
  - name: Install Node.js dependencies
60
67
  run: npm install
61
68
 
62
69
  - name: Run semantic-release
63
70
  id: semantic
64
- uses: cycjimmy/semantic-release-action@v4
71
+ run: npx semantic-release
65
72
  env:
66
73
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
67
74
  NPM_CONFIG_PROVENANCE: true
package/.releaserc.cjs CHANGED
@@ -5,7 +5,16 @@ module.exports = {
5
5
 
6
6
  plugins: [
7
7
  // Analyze commits to determine release type
8
- '@semantic-release/commit-analyzer',
8
+ // While in 0.x, breaking changes bump minor (not major) per semver spec
9
+ ['@semantic-release/commit-analyzer', {
10
+ releaseRules: [
11
+ { breaking: true, release: 'minor' },
12
+ { type: 'feat', release: 'minor' },
13
+ { type: 'fix', release: 'patch' },
14
+ { type: 'perf', release: 'patch' },
15
+ { type: 'refactor', release: 'patch' },
16
+ ]
17
+ }],
9
18
 
10
19
  // Generate release notes
11
20
  '@semantic-release/release-notes-generator',
package/AGENTS.md CHANGED
@@ -7,7 +7,6 @@ Before committing changes, verify documentation is updated to reflect code chang
7
7
  1. **README.md** - Update if changes affect:
8
8
  - Configuration options (config.yaml keys)
9
9
  - CLI commands (`opencode-pilot <command>`)
10
- - Notification types or behavior
11
10
  - Installation or setup steps
12
11
  - Service management
13
12
  - Sources or polling behavior
@@ -15,7 +14,6 @@ Before committing changes, verify documentation is updated to reflect code chang
15
14
  2. **CONTRIBUTING.md** - Update if changes affect:
16
15
  - Development setup or workflow
17
16
  - Test commands or patterns
18
- - Plugin architecture
19
17
 
20
18
  ## Post-PR: Release and Upgrade Workflow
21
19
 
@@ -38,20 +36,16 @@ gh release list -R athal7/opencode-pilot -L 1
38
36
  npm view opencode-pilot version
39
37
  ```
40
38
 
41
- ### 3. Restart OpenCode
39
+ ### 3. Restart Service
42
40
 
43
- OpenCode auto-updates npm plugins. Simply restart any running OpenCode sessions to get the latest version.
44
-
45
- ### 4. Restart Service
46
-
47
- If the callback service is running, restart it:
41
+ If the service is running, restart it:
48
42
 
49
43
  ```bash
50
44
  # Stop current service (Ctrl+C) and restart
51
45
  npx opencode-pilot start
52
46
  ```
53
47
 
54
- ### 5. Verify Upgrade
48
+ ### 4. Verify Upgrade
55
49
 
56
50
  ```bash
57
51
  npx opencode-pilot status
@@ -61,10 +55,10 @@ npx opencode-pilot status
61
55
 
62
56
  Config file: `~/.config/opencode-pilot/config.yaml`
63
57
 
64
- Configuration has three sections:
65
- - `notifications` - ntfy settings (topic, server, callback, etc.)
66
- - `repos` - per-repository settings (use YAML anchors to share config)
58
+ Configuration has these sections:
59
+ - `tools` - field mappings for MCP servers
67
60
  - `sources` - polling sources with generic MCP tool references
61
+ - `repos` - per-repository settings (use YAML anchors to share config)
68
62
 
69
63
  Template files: `~/.config/opencode-pilot/templates/*.md`
70
64
 
package/README.md CHANGED
@@ -1,38 +1,42 @@
1
1
  # opencode-pilot
2
2
 
3
- Automation layer for [OpenCode](https://github.com/sst/opencode) - notifications and workflow orchestration.
3
+ Automation daemon for [OpenCode](https://github.com/sst/opencode) - polls for work and spawns sessions.
4
4
 
5
5
  > **Note**: This is a community project and is not built by or affiliated with the OpenCode team.
6
6
 
7
7
  ## Features
8
8
 
9
- - **Idle notifications** - Get notified when OpenCode has been waiting for input
10
- - **Error alerts** - Stay informed when something needs attention
11
9
  - **Polling automation** - Automatically start sessions from GitHub issues, Linear tickets, etc.
10
+ - **Readiness evaluation** - Check labels, dependencies, and priority before starting work
11
+ - **Template-based prompts** - Customize prompts with placeholders for issue data
12
12
 
13
13
  ## Installation
14
14
 
15
- Add the plugin to your `~/.config/opencode/opencode.json`:
16
-
17
- ```json
18
- {
19
- "plugin": ["opencode-pilot"]
20
- }
15
+ ```bash
16
+ npm install -g opencode-pilot
21
17
  ```
22
18
 
23
- OpenCode auto-installs npm plugins on startup.
24
-
25
19
  ## Quick Start
26
20
 
27
21
  1. **Create config** - Copy [examples/config.yaml](examples/config.yaml) to `~/.config/opencode-pilot/config.yaml` and customize
28
22
 
29
- 2. **Start the service** (in a separate terminal):
23
+ 2. **Create templates** - Add prompt templates to `~/.config/opencode-pilot/templates/`
30
24
 
31
- ```bash
32
- npx opencode-pilot start
25
+ 3. **Enable the plugin** - Add to your `opencode.json`:
26
+
27
+ ```json
28
+ {
29
+ "plugin": ["opencode-pilot"]
30
+ }
33
31
  ```
34
32
 
35
- 3. **Run OpenCode** - notifications will be sent to your ntfy topic!
33
+ The daemon will auto-start when OpenCode launches.
34
+
35
+ Or start manually:
36
+
37
+ ```bash
38
+ opencode-pilot start
39
+ ```
36
40
 
37
41
  ## Configuration
38
42
 
@@ -40,28 +44,30 @@ See [examples/config.yaml](examples/config.yaml) for a complete example with all
40
44
 
41
45
  ### Key Sections
42
46
 
43
- - **`notifications`** - ntfy settings (topic, server, idle/error settings)
44
- - **`repos`** - Repository paths and settings (use YAML anchors to share config)
45
47
  - **`sources`** - What to poll (GitHub issues, Linear tickets, etc.)
46
48
  - **`tools`** - Field mappings to normalize different MCP APIs
49
+ - **`repos`** - Repository paths and settings (use YAML anchors to share config)
47
50
 
48
51
  ### Prompt Templates
49
52
 
50
53
  Create prompt templates as markdown files in `~/.config/opencode-pilot/templates/`. Templates support placeholders like `{title}`, `{body}`, `{number}`, `{html_url}`, etc.
51
54
 
52
- ## Service Management
55
+ ## CLI Commands
53
56
 
54
57
  ```bash
55
- npx opencode-pilot start # Start the service (foreground)
56
- npx opencode-pilot status # Check status
57
- npx opencode-pilot test-source NAME # Test a source
58
+ opencode-pilot start # Start the service (foreground)
59
+ opencode-pilot status # Check status
60
+ opencode-pilot config # Validate and show config
61
+ opencode-pilot test-source NAME # Test a source
62
+ opencode-pilot test-mapping MCP # Test field mappings
58
63
  ```
59
64
 
60
- ## Troubleshooting
65
+ ## How It Works
61
66
 
62
- 1. Check ntfy topic: `curl -d "test" ntfy.sh/your-topic`
63
- 2. Verify config: `npx opencode-pilot config`
64
- 3. Enable debug logging: set `notifications.debug: true` in config
67
+ 1. **Poll sources** - Periodically fetch items from configured MCP tools (GitHub, Linear, etc.)
68
+ 2. **Evaluate readiness** - Check labels, dependencies, and calculate priority
69
+ 3. **Spawn sessions** - Start `opencode run` with the appropriate prompt template
70
+ 4. **Track state** - Remember which items have been processed
65
71
 
66
72
  ## Related
67
73
 
@@ -1,18 +1,22 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * opencode-pilot - CLI for opencode-pilot automation
3
+ * opencode-pilot - CLI for opencode-pilot automation daemon
4
4
  *
5
5
  * Commands:
6
- * setup Copy plugin files and configure OpenCode
7
- * status Show installation and service status
6
+ * start Start the polling service
7
+ * status Show service status
8
+ * config Validate and show configuration
9
+ * test-source Test a polling source
10
+ * test-mapping Test field mappings
8
11
  * help Show help
9
12
  */
10
13
 
11
14
  import { fileURLToPath } from "url";
12
15
  import { dirname, join } from "path";
13
- import { existsSync, readFileSync, writeFileSync, mkdirSync, cpSync, rmSync } from "fs";
16
+ import { existsSync, readFileSync } from "fs";
14
17
  import { execSync } from "child_process";
15
18
  import os from "os";
19
+ import YAML from "yaml";
16
20
 
17
21
  // Get script directory for relative imports
18
22
  const __filename = fileURLToPath(import.meta.url);
@@ -32,30 +36,34 @@ function findServiceDir() {
32
36
  return candidates[0];
33
37
  }
34
38
 
35
- // Find plugin directory
36
- function findPluginDir() {
37
- const candidates = [
38
- join(__dirname, "..", "plugin"), // Development
39
- join(__dirname, "..", "lib", "opencode-pilot", "plugin"), // Homebrew
40
- ];
41
- for (const dir of candidates) {
42
- if (existsSync(join(dir, "index.js"))) {
43
- return dir;
44
- }
45
- }
46
- return null;
47
- }
48
-
49
39
  const serviceDir = findServiceDir();
50
40
 
51
41
  // Paths
52
- const OPENCODE_CONFIG_DIR = join(os.homedir(), ".config/opencode");
53
- const PLUGIN_NAME = "opencode-pilot";
54
- const PLUGIN_DEST = join(OPENCODE_CONFIG_DIR, "plugins", PLUGIN_NAME);
55
- const OPENCODE_CONFIG_FILE = join(OPENCODE_CONFIG_DIR, "opencode.json");
56
42
  const PILOT_CONFIG_FILE = join(os.homedir(), ".config/opencode-pilot/config.yaml");
57
43
  const PILOT_TEMPLATES_DIR = join(os.homedir(), ".config/opencode-pilot/templates");
58
44
 
45
+ // Default port
46
+ const DEFAULT_PORT = 4097;
47
+
48
+ /**
49
+ * Load port from config file
50
+ * @returns {number} Port number
51
+ */
52
+ function getPortFromConfig() {
53
+ try {
54
+ if (existsSync(PILOT_CONFIG_FILE)) {
55
+ const content = readFileSync(PILOT_CONFIG_FILE, "utf8");
56
+ const config = YAML.parse(content);
57
+ if (config?.port && typeof config.port === "number") {
58
+ return config.port;
59
+ }
60
+ }
61
+ } catch {
62
+ // Ignore errors, use default
63
+ }
64
+ return DEFAULT_PORT;
65
+ }
66
+
59
67
  // Parse command line arguments
60
68
  function parseArgs(args) {
61
69
  const result = {
@@ -99,27 +107,25 @@ function parseArgs(args) {
99
107
 
100
108
  // Help text
101
109
  function showHelp() {
102
- console.log(`opencode-pilot - Automation layer for OpenCode
110
+ console.log(`opencode-pilot - Automation daemon for OpenCode
103
111
 
104
112
  Usage:
105
113
  opencode-pilot <command> [options]
106
114
 
107
115
  Commands:
108
- start Start the callback service (foreground)
109
- setup Copy plugin files and configure OpenCode
110
- status Show installation and service status
116
+ start Start the polling service (foreground)
117
+ status Show service status
111
118
  config Validate and show configuration
112
119
  test-source NAME Test a source by fetching items and showing mappings
113
120
  test-mapping MCP Test field mappings with sample JSON input
114
121
  help Show this help message
115
122
 
116
123
  The service handles:
117
- - Notification callbacks from ntfy
118
124
  - Polling for GitHub/Linear issues to work on
125
+ - Spawning OpenCode sessions for ready items
119
126
 
120
127
  Examples:
121
128
  opencode-pilot start # Start service (foreground)
122
- opencode-pilot setup # Initial setup
123
129
  opencode-pilot status # Check status
124
130
  opencode-pilot config # Validate and show config
125
131
  opencode-pilot test-source my-issues # Test a source
@@ -140,14 +146,13 @@ async function startCommand() {
140
146
  process.exit(1);
141
147
  }
142
148
 
143
- console.log("[opencode-pilot] Starting callback service...");
149
+ console.log("[opencode-pilot] Starting polling service...");
144
150
 
145
151
  // Dynamic import of the service module
146
152
  const { startService, stopService } = await import(serverPath);
147
153
 
148
154
  const config = {
149
- httpPort: parseInt(process.env.NTFY_CALLBACK_PORT || "4097", 10),
150
- socketPath: process.env.NTFY_SOCKET_PATH || "/tmp/opencode-pilot.sock",
155
+ httpPort: getPortFromConfig(),
151
156
  };
152
157
 
153
158
  const service = await startService(config);
@@ -169,180 +174,34 @@ async function startCommand() {
169
174
  await new Promise(() => {});
170
175
  }
171
176
 
172
- // ============================================================================
173
- // Setup Command
174
- // ============================================================================
175
-
176
- async function setupCommand() {
177
- console.log("Setting up opencode-pilot...");
178
- console.log("");
179
-
180
- const pluginSource = findPluginDir();
181
- if (!pluginSource) {
182
- console.error("ERROR: Could not find plugin source files");
183
- console.error("Install via: brew install athal7/tap/opencode-pilot");
184
- process.exit(1);
185
- }
186
-
187
- console.log(`Source: ${pluginSource}`);
188
- console.log(`Destination: ${PLUGIN_DEST}`);
189
- console.log("");
190
-
191
- // Backup existing config
192
- if (existsSync(OPENCODE_CONFIG_FILE)) {
193
- const backupFile = `${OPENCODE_CONFIG_FILE}.backup.${Date.now()}`;
194
- try {
195
- cpSync(OPENCODE_CONFIG_FILE, backupFile);
196
- console.log(`Backup created: ${backupFile}`);
197
- } catch (err) {
198
- console.error("ERROR: Failed to create backup of opencode.json");
199
- process.exit(1);
200
- }
201
- }
202
-
203
- // Copy plugin files
204
- mkdirSync(join(OPENCODE_CONFIG_DIR, "plugins"), { recursive: true });
205
- if (existsSync(PLUGIN_DEST)) {
206
- rmSync(PLUGIN_DEST, { recursive: true });
207
- }
208
- cpSync(pluginSource, PLUGIN_DEST, { recursive: true });
209
- console.log("Plugin files copied.");
210
-
211
- // Update opencode.json
212
- let config = {};
213
- if (existsSync(OPENCODE_CONFIG_FILE)) {
214
- try {
215
- config = JSON.parse(readFileSync(OPENCODE_CONFIG_FILE, "utf8"));
216
- } catch {
217
- config = {};
218
- }
219
- }
220
-
221
- config.plugin = config.plugin || [];
222
- const alreadyRegistered = config.plugin.some(p => p.includes(`plugins/${PLUGIN_NAME}`));
223
-
224
- if (alreadyRegistered) {
225
- console.log("Plugin already in opencode.json");
226
- } else {
227
- config.plugin.push(PLUGIN_DEST);
228
- const tempFile = `${OPENCODE_CONFIG_FILE}.tmp`;
229
- writeFileSync(tempFile, JSON.stringify(config, null, 2) + "\n");
230
- const { renameSync } = await import("fs");
231
- renameSync(tempFile, OPENCODE_CONFIG_FILE);
232
- console.log("Added plugin to opencode.json");
233
- }
234
-
235
- // Try to restart service
236
- try {
237
- execSync("brew services restart opencode-pilot", { stdio: "pipe" });
238
- console.log("");
239
- console.log("Service restarted.");
240
- } catch {
241
- // Not installed via brew or service not available
242
- }
243
-
244
- console.log("");
245
- console.log("Setup complete!");
246
- console.log("");
247
- console.log("Next steps:");
248
- console.log(" 1. Edit ~/.config/opencode-pilot/config.yaml");
249
- console.log(" 2. Run: opencode-pilot start");
250
- }
251
-
252
177
  // ============================================================================
253
178
  // Status Command
254
179
  // ============================================================================
255
180
 
256
- function getConfigValue(envVar, configKey, defaultValue = "") {
257
- // Check env var first
258
- if (process.env[envVar]) {
259
- return process.env[envVar];
260
- }
261
-
262
- // Check config file
263
- if (existsSync(PILOT_CONFIG_FILE)) {
264
- try {
265
- const config = JSON.parse(readFileSync(PILOT_CONFIG_FILE, "utf8"));
266
- if (config[configKey] !== undefined && config[configKey] !== "") {
267
- return config[configKey];
268
- }
269
- } catch {
270
- // Ignore
271
- }
272
- }
273
-
274
- return defaultValue;
275
- }
276
-
277
181
  function statusCommand() {
278
182
  console.log("opencode-pilot status");
279
183
  console.log("=====================");
280
184
  console.log("");
281
185
 
282
- // Plugin installed?
283
- if (existsSync(PLUGIN_DEST)) {
284
- console.log(`Plugin: installed at ${PLUGIN_DEST}`);
285
- } else {
286
- console.log("Plugin: NOT INSTALLED (run: opencode-pilot setup)");
287
- }
288
-
289
- // Config?
290
- if (existsSync(OPENCODE_CONFIG_FILE)) {
291
- try {
292
- const config = JSON.parse(readFileSync(OPENCODE_CONFIG_FILE, "utf8"));
293
- const registered = (config.plugin || []).some(p => p.includes(`plugins/${PLUGIN_NAME}`));
294
- if (registered) {
295
- console.log("Config: plugin registered in opencode.json");
296
- } else {
297
- console.log("Config: plugin NOT in opencode.json");
298
- }
299
- } catch {
300
- console.log("Config: could not parse opencode.json");
301
- }
302
- } else {
303
- console.log("Config: opencode.json not found");
304
- }
305
-
306
- // Service running? Check if socket exists and HTTP responds
307
- const socketPath = "/tmp/opencode-pilot.sock";
308
- const servicePort = getConfigValue("NTFY_CALLBACK_PORT", "callbackPort", "4097");
186
+ // Service running? Check if HTTP responds
187
+ const servicePort = getPortFromConfig();
309
188
 
310
- if (existsSync(socketPath)) {
311
- // Socket exists, try health check
312
- try {
313
- const res = execSync(`curl -s -o /dev/null -w "%{http_code}" http://localhost:${servicePort}/health`, { encoding: "utf8", timeout: 2000 });
314
- if (res.trim() === "200") {
315
- console.log("Service: running");
316
- } else {
317
- console.log("Service: socket exists but not responding (run: opencode-pilot start)");
318
- }
319
- } catch {
320
- console.log("Service: socket exists but health check failed");
189
+ try {
190
+ const res = execSync(`curl -s -o /dev/null -w "%{http_code}" http://localhost:${servicePort}/health`, { encoding: "utf8", timeout: 2000 });
191
+ if (res.trim() === "200") {
192
+ console.log("Service: running");
193
+ } else {
194
+ console.log("Service: not responding (run: opencode-pilot start)");
321
195
  }
322
- } else {
196
+ } catch {
323
197
  console.log("Service: not running (run: opencode-pilot start)");
324
198
  }
325
199
 
326
- // Notification configuration
327
- console.log("");
328
- console.log("Notification Configuration:");
329
-
330
- const topic = getConfigValue("NTFY_TOPIC", "topic", "");
331
- const server = getConfigValue("NTFY_SERVER", "server", "https://ntfy.sh");
332
- const callbackHost = getConfigValue("NTFY_CALLBACK_HOST", "callbackHost", "");
333
- const callbackPort = getConfigValue("NTFY_CALLBACK_PORT", "callbackPort", "4097");
334
-
335
- console.log(` topic: ${topic || "<not set>"}`);
336
- console.log(` server: ${server}`);
337
- console.log(` callbackHost: ${callbackHost || "<not set>"}`);
338
- console.log(` callbackPort: ${callbackPort}`);
339
-
340
200
  // Polling configuration
341
201
  console.log("");
342
- console.log("Polling Configuration:");
202
+ console.log("Configuration:");
343
203
  if (existsSync(PILOT_CONFIG_FILE)) {
344
204
  console.log(` config.yaml: ${PILOT_CONFIG_FILE}`);
345
- console.log(" polling: enabled (handled by service)");
346
205
  } else {
347
206
  console.log(` config.yaml: not found at ${PILOT_CONFIG_FILE}`);
348
207
  console.log(" polling: disabled");
@@ -379,22 +238,6 @@ async function configCommand() {
379
238
  process.exit(1);
380
239
  }
381
240
 
382
- // Validate notifications section
383
- console.log("");
384
- console.log("Notifications:");
385
- const notifications = config.notifications || {};
386
- if (notifications.topic) {
387
- console.log(` ✓ topic: ${notifications.topic.substring(0, 8)}...`);
388
- } else {
389
- console.log(" ✗ topic: not set (required for notifications)");
390
- }
391
- console.log(` server: ${notifications.server || "https://ntfy.sh"}`);
392
- if (notifications.callback_host) {
393
- console.log(` ✓ callback_host: ${notifications.callback_host}`);
394
- } else {
395
- console.log(" ⚠ callback_host: not set (interactive notifications disabled)");
396
- }
397
-
398
241
  // Validate defaults section
399
242
  console.log("");
400
243
  console.log("Defaults:");
@@ -599,7 +442,6 @@ async function testSourceCommand(sourceName) {
599
442
  // Import poller
600
443
  const pollerPath = join(serviceDir, "poller.js");
601
444
  const { pollGenericSource, applyMappings } = await import(pollerPath);
602
- const { getToolMappings } = await import(join(serviceDir, "repo-config.js"));
603
445
 
604
446
  // Fetch items with mappings applied
605
447
  const items = await pollGenericSource(source, { mappings });
@@ -763,7 +605,7 @@ async function testMappingCommand(mcpName) {
763
605
 
764
606
  async function main() {
765
607
  const args = process.argv.slice(2);
766
- const { command, subcommand, flags } = parseArgs(args);
608
+ const { command, subcommand } = parseArgs(args);
767
609
 
768
610
  if (!command || command === "help") {
769
611
  showHelp();
@@ -775,10 +617,6 @@ async function main() {
775
617
  await startCommand();
776
618
  break;
777
619
 
778
- case "setup":
779
- await setupCommand();
780
- break;
781
-
782
620
  case "status":
783
621
  statusCommand();
784
622
  break;
@@ -4,16 +4,9 @@
4
4
  # Also create templates/ directory with prompt template files
5
5
 
6
6
  # =============================================================================
7
- # NOTIFICATIONS - ntfy settings for mobile notifications
7
+ # SERVICE - Daemon configuration
8
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
9
+ # port: 4097 # HTTP port for health checks (default: 4097)
17
10
 
18
11
  # =============================================================================
19
12
  # TOOLS - Field mappings for MCP servers (normalize different APIs)
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "opencode-pilot",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "type": "module",
5
- "description": "Automation layer for OpenCode - notifications, mobile UI, and workflow orchestration",
6
5
  "main": "plugin/index.js",
6
+ "description": "Automation daemon for OpenCode - polls for work and spawns sessions",
7
7
  "repository": {
8
8
  "type": "git",
9
9
  "url": "https://github.com/athal7/opencode-pilot.git"
@@ -14,10 +14,10 @@
14
14
  },
15
15
  "keywords": [
16
16
  "opencode",
17
- "plugin",
18
- "notifications",
19
17
  "automation",
20
- "ntfy"
18
+ "polling",
19
+ "github",
20
+ "linear"
21
21
  ],
22
22
  "author": "Andrew Thal <467872+athal7@users.noreply.github.com>",
23
23
  "license": "MIT",
@@ -30,7 +30,7 @@
30
30
  "devDependencies": {
31
31
  "@semantic-release/git": "^10.0.1",
32
32
  "@semantic-release/github": "^9.2.6",
33
- "@semantic-release/npm": "^12.0.0",
33
+ "@semantic-release/npm": "^13.1.0",
34
34
  "semantic-release": "^25.0.2"
35
35
  },
36
36
  "dependencies": {