@jack-yang/opencode-bark-notify 1.0.2 → 1.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/README.md CHANGED
@@ -1,12 +1,11 @@
1
1
  # opencode-bark-notify
2
2
 
3
- Focus-aware [Bark](https://github.com/Finb/Bark) push notifications for [OpenCode](https://opencode.ai).
3
+ [Bark](https://github.com/Finb/Bark) push notifications for [OpenCode](https://opencode.ai).
4
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.
5
+ Get **detailed agent messages** on your iPhone: task finished, permission needed, or a question waiting for you.
6
6
 
7
7
  ## Features
8
8
 
9
- - **Focus-aware** — Suppresses notifications when your terminal is the frontmost app
10
9
  - **Detailed content** — Sends the agent's actual last message, not just "task completed"
11
10
  - **Category badges** — iOS subtitle shows what happened: ✅ Responded / 🔐 Wait for Permission / ❓ Input Needed
12
11
  - **Session-aware** — Includes the session title in every notification
@@ -59,7 +58,6 @@ Restart OpenCode.
59
58
  | `barkUrl` | string | `https://api.day.app` | Bark server URL (for self-hosted) |
60
59
  | `level` | string | `timeSensitive` | iOS notification level |
61
60
  | `group` | string | `opencode` | iOS notification group |
62
- | `terminals` | string[] | `["Ghostty", "Terminal", "iTerm2", ...]` | Terminal app names for focus detection |
63
61
 
64
62
  ### Example with all options
65
63
 
@@ -70,24 +68,17 @@ Restart OpenCode.
70
68
  "barkKey": "YOUR_KEY",
71
69
  "barkUrl": "https://your-bark-server.com",
72
70
  "level": "active",
73
- "group": "my-agent",
74
- "terminals": ["Ghostty", "WezTerm"]
71
+ "group": "my-agent"
75
72
  }]
76
73
  ]
77
74
  }
78
75
  ```
79
76
 
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
77
  ## Notification types
87
78
 
88
79
  | Event | Badge | Body |
89
80
  |-------|-------|------|
90
- | Session complete | **✅ Responded** | Agent's last message (up to 500 chars) |
81
+ | Session complete | **✅ Responded** | The agent's last reply (up to 500 chars) |
91
82
  | Permission request | **🔐 Wait for Permission** | Permission type + patterns |
92
83
  | Question | **❓ Input Needed** | The question text |
93
84
 
@@ -95,10 +86,10 @@ On non-macOS, focus detection is disabled (notifications always fire).
95
86
 
96
87
  This plugin complements [@mohak34/opencode-notifier](https://github.com/mohak34/opencode-notifier) (desktop notifications). Use both together:
97
88
 
98
- - **Desktop** (osascript/sound/bell) → when at computer but in another app
99
- - **Bark** (iPhone push) → when away from computer entirely
89
+ - **Desktop** (osascript/sound/bell) → on the computer
90
+ - **Bark** (iPhone push) → on your phone
100
91
 
101
- Both independently check focus, so there's no conflict.
92
+ This plugin always sends; if you want desktop notifications only when the terminal is in the background, set `suppressWhenFocused` in opencode-notifier.
102
93
 
103
94
  ## License
104
95
 
package/README.zh-CN.md CHANGED
@@ -1,12 +1,11 @@
1
1
  # opencode-bark-notify
2
2
 
3
- 为 [OpenCode](https://opencode.ai) 提供基于 [Bark](https://github.com/Finb/Bark) 的手机推送通知,**智能感知终端焦点**。
3
+ 为 [OpenCode](https://opencode.ai) 提供基于 [Bark](https://github.com/Finb/Bark) 的手机推送通知。
4
4
 
5
- 离开电脑时收到 **agent 的实际消息内容**,在终端前时自动静默——不打扰。
5
+ 任务完成、需要授权、等你回答时,iPhone 上收到 **agent 的实际消息内容**。
6
6
 
7
7
  ## 功能
8
8
 
9
- - **焦点感知** — 终端在前台时不发通知,切走或离开时才推
10
9
  - **详细内容** — 推送 agent 的最后一条消息,不只是"任务完成"
11
10
  - **分类标识** — iOS 副标题显示事件类型:✅ Responded / 🔐 Wait for Permission / ❓ Input Needed
12
11
  - **Session 感知** — 每条通知包含 session 标题
@@ -59,13 +58,12 @@ export BARK_KEY="你的BARK设备KEY"
59
58
  | `barkUrl` | string | `https://api.day.app` | Bark 服务器地址(自建服务器用) |
60
59
  | `level` | string | `timeSensitive` | iOS 通知级别 |
61
60
  | `group` | string | `opencode` | iOS 通知分组 |
62
- | `terminals` | string[] | `["Ghostty", "Terminal", "iTerm2", ...]` | 焦点检测的终端 App 名称 |
63
61
 
64
62
  ## 通知类型
65
63
 
66
64
  | 事件 | 标识 | 内容 |
67
65
  |------|------|------|
68
- | Session 完成 | **✅ Responded** | agent 最后一条消息(≤500字) |
66
+ | Session 完成 | **✅ Responded** | agent 最后一条回复(≤500字) |
69
67
  | 需要权限 | **🔐 Wait for Permission** | 权限类型 + 匹配模式 |
70
68
  | 需要回答 | **❓ Input Needed** | 问题内容 |
71
69
 
@@ -73,10 +71,10 @@ export BARK_KEY="你的BARK设备KEY"
73
71
 
74
72
  本插件与 [@mohak34/opencode-notifier](https://github.com/mohak34/opencode-notifier)(桌面通知)互补:
75
73
 
76
- - **桌面通知**(声音/弹窗)→ 在电脑前但切到了其他窗口
77
- - **Bark 推送**(手机)→ 完全离开电脑
74
+ - **桌面通知**(声音/弹窗)→ 电脑上
75
+ - **Bark 推送**(手机)→ 手机上
78
76
 
79
- 两者各自独立检测焦点,互不冲突。
77
+ 本插件总是推送;如果希望桌面通知只在终端不在前台时弹,在 opencode-notifier 里设置 `suppressWhenFocused`。
80
78
 
81
79
  ## 许可证
82
80
 
package/index.ts CHANGED
@@ -5,27 +5,11 @@ interface BarkConfig {
5
5
  barkUrl?: string
6
6
  level?: string
7
7
  group?: string
8
- terminals?: string[]
9
8
  }
10
9
 
11
- const DEFAULT_TERMINALS = ["Ghostty", "Terminal", "iTerm2", "Alacritty", "kitty", "WezTerm"]
12
10
  const DEFAULT_BARK_URL = "https://api.day.app"
13
11
  const MAX_BODY_LENGTH = 500
14
12
 
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
13
  const sendBark = async (
30
14
  barkUrl: string,
31
15
  barkKey: string,
@@ -49,7 +33,12 @@ const truncate = (text: string, max: number): string => {
49
33
  return clean.length > max ? clean.slice(0, max) + "..." : clean
50
34
  }
51
35
 
52
- export const BarkNotifyPlugin: Plugin = async ({ project, client }, config: BarkConfig = {}) => {
36
+ // Every notification is sent, whether or not the terminal is in front. Focus detection
37
+ // (osascript) proved fragile across macOS releases and silently dropped notifications.
38
+ //
39
+ // Only the plugin itself may be exported: opencode calls every exported function of a
40
+ // plugin module as a plugin, so an exported helper would be invoked with the plugin context.
41
+ export const BarkNotifyPlugin: Plugin = async ({ project, client, directory }, config: BarkConfig = {}) => {
53
42
  const barkKey = config.barkKey || process.env.BARK_KEY
54
43
  if (!barkKey) {
55
44
  console.warn(
@@ -61,54 +50,61 @@ export const BarkNotifyPlugin: Plugin = async ({ project, client }, config: Bark
61
50
  const barkUrl = config.barkUrl || process.env.BARK_URL || DEFAULT_BARK_URL
62
51
  const level = config.level || "timeSensitive"
63
52
  const group = config.group || "opencode"
64
- const terminals = config.terminals || DEFAULT_TERMINALS
65
53
 
54
+ // project.id is "global" outside a git repository; the folder name says more.
55
+ const folder = String(directory || (project as any)?.worktree || "").split("/").filter(Boolean).pop()
56
+ let currentTitle = `OpenCode · ${folder || "Session"}`
57
+
58
+ // Only assistant text is worth sending; user prompts arrive as text parts too.
59
+ const assistantMessages = new Set<string>()
66
60
  let lastMessage = ""
67
- let currentTitle = `OpenCode · ${project?.id || "Session"}`
68
61
 
69
62
  const send = (subtitle: string, body: string) =>
70
63
  sendBark(barkUrl, barkKey, currentTitle, subtitle, body, level, group)
71
64
 
72
65
  return {
73
- event: async ({ event }) => {
66
+ event: async ({ event }: { event: any }) => {
67
+ if (event.type === "message.updated") {
68
+ const info = event.properties?.info
69
+ if (info?.role === "assistant" && info.id) assistantMessages.add(info.id)
70
+ }
71
+
74
72
  if (event.type === "message.part.updated") {
75
73
  const part = event.properties?.part
76
- if (part?.type === "text" && part.text) {
74
+ if (part?.type === "text" && part.text && !part.synthetic && assistantMessages.has(part.messageID)) {
77
75
  lastMessage = part.text
78
76
  }
79
77
  if (part?.type === "tool" && part.tool === "question" && part.state?.status === "running") {
80
- if (await isFocused(terminals)) return
81
78
  const questions = part.state?.input?.questions || []
82
79
  const q = questions[0]?.question || "Input needed"
83
80
  await send("❓ Input Needed", q)
84
81
  }
85
82
  }
86
83
 
87
- if (event.type === "session.idle") {
88
- if (await isFocused(terminals)) return
84
+ // Permission requests arrive as an event; the old "permission.ask" hook is gone in opencode 1.19+.
85
+ if (event.type === "permission.asked") {
86
+ const p = event.properties || {}
87
+ const patterns: string[] = p.patterns || []
88
+ let body = p.permission || "permission"
89
+ if (patterns.length) body += `: ${patterns.join(", ")}`
90
+ await send("🔐 Wait for Permission", truncate(body, MAX_BODY_LENGTH))
91
+ }
89
92
 
93
+ if (event.type === "session.idle") {
90
94
  const sid = event.properties?.sessionID
91
95
  if (sid) {
92
96
  try {
93
- const sessions = await client.session.list({ limit: 50 })
94
- const s = sessions.data?.find((s: any) => s.id === sid)
97
+ const sessions = await client.session.list({ limit: 50 } as any)
98
+ const s = (sessions as any).data?.find((s: any) => s.id === sid)
95
99
  if (s?.title) currentTitle = `OpenCode · ${s.title}`
96
100
  } catch {}
97
101
  }
98
102
 
99
103
  const body = lastMessage ? truncate(lastMessage, MAX_BODY_LENGTH) : "Task completed"
104
+ lastMessage = ""
100
105
  await send("✅ Responded", body)
101
106
  }
102
107
  },
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
108
  }
113
109
  }
114
110
 
package/package.json CHANGED
@@ -1,12 +1,16 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "@jack-yang/opencode-bark-notify",
4
- "version": "1.0.2",
5
- "description": "Focus-aware Bark push notifications for OpenCode — get detailed agent messages on your phone when you step away",
4
+ "version": "1.1.0",
5
+ "description": "Bark push notifications for OpenCode — get the agent's actual messages on your iPhone",
6
6
  "type": "module",
7
+ "main": "./index.ts",
7
8
  "exports": {
8
9
  ".": {
9
10
  "import": "./index.ts"
11
+ },
12
+ "./server": {
13
+ "import": "./index.ts"
10
14
  }
11
15
  },
12
16
  "engines": {
@@ -33,8 +37,7 @@
33
37
  "bark",
34
38
  "notification",
35
39
  "push-notification",
36
- "ios",
37
- "focus-aware"
40
+ "ios"
38
41
  ],
39
42
  "license": "MIT",
40
43
  "repository": {