@jack-yang/opencode-bark-notify 1.0.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/README.md ADDED
@@ -0,0 +1,105 @@
1
+ # opencode-bark-notify
2
+
3
+ Focus-aware [Bark](https://github.com/Finb/Bark) push notifications for [OpenCode](https://opencode.ai).
4
+
5
+ Get **detailed agent messages** on your iPhone when you step away from your computer. When you're at the terminal, notifications are suppressed — no spam.
6
+
7
+ ## Features
8
+
9
+ - **Focus-aware** — Suppresses notifications when your terminal is the frontmost app
10
+ - **Detailed content** — Sends the agent's actual last message, not just "task completed"
11
+ - **Category badges** — iOS subtitle shows what happened: ✅ Responded / 🔐 Wait for Permission / ❓ Input Needed
12
+ - **Session-aware** — Includes the session title in every notification
13
+ - **Zero dependencies** — Pure TypeScript, runs on Bun
14
+
15
+ ## Prerequisites
16
+
17
+ - [Bark](https://apps.apple.com/app/id1403753865) app installed on iOS
18
+ - Your Bark device key (found in the Bark app homepage, e.g. `BrHXXCACzphzrB63kNJMFF`)
19
+
20
+ ## Install
21
+
22
+ ### Option 1: With config (recommended)
23
+
24
+ Add to your `~/.config/opencode/opencode.json`:
25
+
26
+ ```json
27
+ {
28
+ "plugin": [
29
+ ["@jack-yang/opencode-bark-notify", {
30
+ "barkKey": "YOUR_BARK_DEVICE_KEY"
31
+ }]
32
+ ]
33
+ }
34
+ ```
35
+
36
+ ### Option 2: With env var
37
+
38
+ Add to your `~/.zshrc`:
39
+
40
+ ```bash
41
+ export BARK_KEY="YOUR_BARK_DEVICE_KEY"
42
+ ```
43
+
44
+ Then in `opencode.json`:
45
+
46
+ ```json
47
+ {
48
+ "plugin": ["@jack-yang/opencode-bark-notify"]
49
+ }
50
+ ```
51
+
52
+ Restart OpenCode.
53
+
54
+ ## Configuration
55
+
56
+ | Option | Type | Default | Description |
57
+ |--------|------|---------|-------------|
58
+ | `barkKey` | string | *(env: `BARK_KEY`)* | Bark device key (**required**) |
59
+ | `barkUrl` | string | `https://api.day.app` | Bark server URL (for self-hosted) |
60
+ | `level` | string | `timeSensitive` | iOS notification level |
61
+ | `group` | string | `opencode` | iOS notification group |
62
+ | `terminals` | string[] | `["Ghostty", "Terminal", "iTerm2", ...]` | Terminal app names for focus detection |
63
+
64
+ ### Example with all options
65
+
66
+ ```json
67
+ {
68
+ "plugin": [
69
+ ["@jack-yang/opencode-bark-notify", {
70
+ "barkKey": "YOUR_KEY",
71
+ "barkUrl": "https://your-bark-server.com",
72
+ "level": "active",
73
+ "group": "my-agent",
74
+ "terminals": ["Ghostty", "WezTerm"]
75
+ }]
76
+ ]
77
+ }
78
+ ```
79
+
80
+ ## How focus detection works
81
+
82
+ On macOS, the plugin uses `osascript` to check if a terminal app is the frontmost application. If it is, notifications are suppressed — you're already looking at the terminal. If you've switched to another app or stepped away, notifications fire.
83
+
84
+ On non-macOS, focus detection is disabled (notifications always fire).
85
+
86
+ ## Notification types
87
+
88
+ | Event | Badge | Body |
89
+ |-------|-------|------|
90
+ | Session complete | **✅ Responded** | Agent's last message (up to 500 chars) |
91
+ | Permission request | **🔐 Wait for Permission** | Permission type + patterns |
92
+ | Question | **❓ Input Needed** | The question text |
93
+
94
+ ## Pairing with desktop notifications
95
+
96
+ This plugin complements [@mohak34/opencode-notifier](https://github.com/mohak34/opencode-notifier) (desktop notifications). Use both together:
97
+
98
+ - **Desktop** (osascript/sound/bell) → when at computer but in another app
99
+ - **Bark** (iPhone push) → when away from computer entirely
100
+
101
+ Both independently check focus, so there's no conflict.
102
+
103
+ ## License
104
+
105
+ MIT
@@ -0,0 +1,83 @@
1
+ # opencode-bark-notify
2
+
3
+ 为 [OpenCode](https://opencode.ai) 提供基于 [Bark](https://github.com/Finb/Bark) 的手机推送通知,**智能感知终端焦点**。
4
+
5
+ 离开电脑时收到 **agent 的实际消息内容**,在终端前时自动静默——不打扰。
6
+
7
+ ## 功能
8
+
9
+ - **焦点感知** — 终端在前台时不发通知,切走或离开时才推
10
+ - **详细内容** — 推送 agent 的最后一条消息,不只是"任务完成"
11
+ - **分类标识** — iOS 副标题显示事件类型:✅ Responded / 🔐 Wait for Permission / ❓ Input Needed
12
+ - **Session 感知** — 每条通知包含 session 标题
13
+ - **零依赖** — 纯 TypeScript,Bun 直接运行
14
+
15
+ ## 前置条件
16
+
17
+ - iOS 设备安装 [Bark](https://apps.apple.com/app/id1403753865)
18
+ - Bark device key(在 Bark App 首页查看,如 `BrHXXCACzphzrB63kNJMFF`)
19
+
20
+ ## 安装
21
+
22
+ ### 方式一:带配置(推荐)
23
+
24
+ 在 `~/.config/opencode/opencode.json` 中添加:
25
+
26
+ ```json
27
+ {
28
+ "plugin": [
29
+ ["@jack-yang/opencode-bark-notify", {
30
+ "barkKey": "你的BARK设备KEY"
31
+ }]
32
+ ]
33
+ }
34
+ ```
35
+
36
+ ### 方式二:环境变量
37
+
38
+ 在 `~/.zshrc` 中设置:
39
+
40
+ ```bash
41
+ export BARK_KEY="你的BARK设备KEY"
42
+ ```
43
+
44
+ 然后在 `opencode.json` 中:
45
+
46
+ ```json
47
+ {
48
+ "plugin": ["@jack-yang/opencode-bark-notify"]
49
+ }
50
+ ```
51
+
52
+ 重启 OpenCode 生效。
53
+
54
+ ## 配置项
55
+
56
+ | 选项 | 类型 | 默认值 | 说明 |
57
+ |------|------|--------|------|
58
+ | `barkKey` | string | *(环境变量 `BARK_KEY`)* | Bark 设备 Key(**必填**) |
59
+ | `barkUrl` | string | `https://api.day.app` | Bark 服务器地址(自建服务器用) |
60
+ | `level` | string | `timeSensitive` | iOS 通知级别 |
61
+ | `group` | string | `opencode` | iOS 通知分组 |
62
+ | `terminals` | string[] | `["Ghostty", "Terminal", "iTerm2", ...]` | 焦点检测的终端 App 名称 |
63
+
64
+ ## 通知类型
65
+
66
+ | 事件 | 标识 | 内容 |
67
+ |------|------|------|
68
+ | Session 完成 | **✅ Responded** | agent 最后一条消息(≤500字) |
69
+ | 需要权限 | **🔐 Wait for Permission** | 权限类型 + 匹配模式 |
70
+ | 需要回答 | **❓ Input Needed** | 问题内容 |
71
+
72
+ ## 与桌面通知配合
73
+
74
+ 本插件与 [@mohak34/opencode-notifier](https://github.com/mohak34/opencode-notifier)(桌面通知)互补:
75
+
76
+ - **桌面通知**(声音/弹窗)→ 在电脑前但切到了其他窗口
77
+ - **Bark 推送**(手机)→ 完全离开电脑
78
+
79
+ 两者各自独立检测焦点,互不冲突。
80
+
81
+ ## 许可证
82
+
83
+ MIT
package/index.ts ADDED
@@ -0,0 +1,115 @@
1
+ import type { Plugin } from "@opencode-ai/plugin"
2
+
3
+ interface BarkConfig {
4
+ barkKey?: string
5
+ barkUrl?: string
6
+ level?: string
7
+ group?: string
8
+ terminals?: string[]
9
+ }
10
+
11
+ const DEFAULT_TERMINALS = ["Ghostty", "Terminal", "iTerm2", "Alacritty", "kitty", "WezTerm"]
12
+ const DEFAULT_BARK_URL = "https://api.day.app"
13
+ const MAX_BODY_LENGTH = 500
14
+
15
+ const isFocused = async (terminals: string[]): Promise<boolean> => {
16
+ if (process.platform !== "darwin") return false
17
+ try {
18
+ const proc = Bun.spawn(
19
+ ["osascript", "-e", 'tell application "System Events" to name of first application whose frontmost is true'],
20
+ { stdout: "pipe", stderr: "pipe" },
21
+ )
22
+ const output = await new Response(proc.stdout).text()
23
+ return terminals.includes(output.trim())
24
+ } catch {
25
+ return false
26
+ }
27
+ }
28
+
29
+ const sendBark = async (
30
+ barkUrl: string,
31
+ barkKey: string,
32
+ title: string,
33
+ subtitle: string,
34
+ body: string,
35
+ level: string,
36
+ group: string,
37
+ ) => {
38
+ try {
39
+ await fetch(`${barkUrl}/${barkKey}`, {
40
+ method: "POST",
41
+ headers: { "Content-Type": "application/json; charset=utf-8" },
42
+ body: JSON.stringify({ title, subtitle, body, group, level }),
43
+ })
44
+ } catch {}
45
+ }
46
+
47
+ const truncate = (text: string, max: number): string => {
48
+ const clean = text.replace(/\n+/g, " ").trim()
49
+ return clean.length > max ? clean.slice(0, max) + "..." : clean
50
+ }
51
+
52
+ export const BarkNotifyPlugin: Plugin = async ({ project, client }, config: BarkConfig = {}) => {
53
+ const barkKey = config.barkKey || process.env.BARK_KEY
54
+ if (!barkKey) {
55
+ console.warn(
56
+ "[opencode-bark-notify] No barkKey configured. Set barkKey in plugin config or BARK_KEY env var.",
57
+ )
58
+ return {}
59
+ }
60
+
61
+ const barkUrl = config.barkUrl || process.env.BARK_URL || DEFAULT_BARK_URL
62
+ const level = config.level || "timeSensitive"
63
+ const group = config.group || "opencode"
64
+ const terminals = config.terminals || DEFAULT_TERMINALS
65
+
66
+ let lastMessage = ""
67
+ let currentTitle = `OpenCode · ${project?.id || "Session"}`
68
+
69
+ const send = (subtitle: string, body: string) =>
70
+ sendBark(barkUrl, barkKey, currentTitle, subtitle, body, level, group)
71
+
72
+ return {
73
+ event: async ({ event }) => {
74
+ if (event.type === "message.part.updated") {
75
+ const part = event.properties?.part
76
+ if (part?.type === "text" && part.text) {
77
+ lastMessage = part.text
78
+ }
79
+ if (part?.type === "tool" && part.tool === "question" && part.state?.status === "running") {
80
+ if (await isFocused(terminals)) return
81
+ const questions = part.state?.input?.questions || []
82
+ const q = questions[0]?.question || "Input needed"
83
+ await send("❓ Input Needed", q)
84
+ }
85
+ }
86
+
87
+ if (event.type === "session.idle") {
88
+ if (await isFocused(terminals)) return
89
+
90
+ const sid = event.properties?.sessionID
91
+ if (sid) {
92
+ try {
93
+ const sessions = await client.session.list({ limit: 50 })
94
+ const s = sessions.data?.find((s: any) => s.id === sid)
95
+ if (s?.title) currentTitle = `OpenCode · ${s.title}`
96
+ } catch {}
97
+ }
98
+
99
+ const body = lastMessage ? truncate(lastMessage, MAX_BODY_LENGTH) : "Task completed"
100
+ await send("✅ Responded", body)
101
+ }
102
+ },
103
+
104
+ "permission.ask": async (input: any) => {
105
+ if (await isFocused(terminals)) return
106
+ const permType = input?.type || "permission"
107
+ const patterns = input?.patterns || []
108
+ let body = permType
109
+ if (patterns.length) body += `: ${patterns.join(", ")}`
110
+ await send("🔐 Wait for Permission", body)
111
+ },
112
+ }
113
+ }
114
+
115
+ export default BarkNotifyPlugin
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/package.json",
3
+ "name": "@jack-yang/opencode-bark-notify",
4
+ "version": "1.0.0",
5
+ "description": "Focus-aware Bark push notifications for OpenCode — get detailed agent messages on your phone when you step away",
6
+ "type": "module",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./index.ts"
10
+ }
11
+ },
12
+ "engines": {
13
+ "opencode": ">=1.4.3"
14
+ },
15
+ "peerDependencies": {
16
+ "@opencode-ai/plugin": ">=1.4.3"
17
+ },
18
+ "devDependencies": {
19
+ "vitest": "^3.2.4",
20
+ "typescript": "^5.9.2"
21
+ },
22
+ "scripts": {
23
+ "test": "vitest run",
24
+ "test:watch": "vitest"
25
+ },
26
+ "files": [
27
+ "index.ts",
28
+ "README.md"
29
+ ],
30
+ "keywords": [
31
+ "opencode",
32
+ "opencode-plugin",
33
+ "bark",
34
+ "notification",
35
+ "push-notification",
36
+ "ios",
37
+ "focus-aware"
38
+ ],
39
+ "license": "MIT",
40
+ "repository": {
41
+ "type": "git",
42
+ "url": "git+https://github.com/yangwudong/opencode-bark-notify.git"
43
+ },
44
+ "homepage": "https://github.com/yangwudong/opencode-bark-notify#readme",
45
+ "bugs": {
46
+ "url": "https://github.com/yangwudong/opencode-bark-notify/issues"
47
+ },
48
+ "publishConfig": {
49
+ "access": "public"
50
+ }
51
+ }