@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 +105 -0
- package/README.zh-CN.md +83 -0
- package/index.ts +115 -0
- package/package.json +51 -0
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
|
package/README.zh-CN.md
ADDED
|
@@ -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
|
+
}
|