opencode-termux-notify 0.1.1-dev.1 → 0.1.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.
- package/CHANGELOG.md +5 -26
- package/README.md +14 -41
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +1 -7
- package/dist/config.js.map +1 -1
- package/dist/types.d.ts +1 -7
- package/dist/types.d.ts.map +1 -1
- package/index.js +0 -2
- package/package.json +2 -10
- package/src/config.ts +1 -7
- package/src/types.ts +1 -8
- package/dist/v1.d.ts +0 -21
- package/dist/v1.d.ts.map +0 -1
- package/dist/v1.js +0 -133
- package/dist/v1.js.map +0 -1
- package/src/v1.ts +0 -140
package/CHANGELOG.md
CHANGED
|
@@ -1,28 +1,7 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Changelogs
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
## 0.1.1
|
|
4
|
+
- Removed V1 compatibility code — now V2-only via `@opencode-ai/plugin/v2/promise`.
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
## [0.1.0] - 2026-09-04
|
|
9
|
-
|
|
10
|
-
### Added
|
|
11
|
-
- Initial npm release, extracted from `~/.config/opencode/plugins/termux-notify`
|
|
12
|
-
- `Plugin.define` entrypoint using `@opencode-ai/plugin` (`^1.18.27`, `v2/promise`)
|
|
13
|
-
- Cross-instance deduplication via shared JSON file + stable `termux-notification --id`
|
|
14
|
-
- Per-session cooldown (5s), global throttle (1s), `event.id` TTL (60s) + `active`/`errored` tracking (mirrors `packages/tui/src/feature-plugins/system/notifications.ts:59-86`)
|
|
15
|
-
- Session-aware titles via `ctx.session.get` + `parentID` check for `subagent_done`
|
|
16
|
-
- 6 wired attention sounds (from `packages/tui/src/attention.ts:19-60` + `packages/ui/src/assets/audio/`):
|
|
17
|
-
- `default` → `bip-bop-01.mp3` (400,200,400)
|
|
18
|
-
- `question` → `bip-bop-03.mp3` (500,250,500) on `question.asked`/`v2`
|
|
19
|
-
- `permission` → `staplebops-06.mp3` (600,200,600) on `permission.asked`/`v2`
|
|
20
|
-
- `error` → `nope-03.mp3` (800,300,800,300,800)
|
|
21
|
-
- `done` → `bip-bop-01.mp3` (400,200,400)
|
|
22
|
-
- `subagent_done` → `yup-01.mp3` (200,100,200) — obeys `notifySubagents` (default true, `false` = sound-only like TUI)
|
|
23
|
-
- Bundled mp3 assets at `assets/audio/*.mp3` + playback via `termux-media-player play` (`termux-notification --sound` is boolean only — see `termux-notification -h`)
|
|
24
|
-
- Configurable via `opencode.json` `options`: `bin`, `mediaBin`, `sharedPath`, `seenTTL`, `sessionCooldown`, `globalCooldown`, `sound`, `playSound`, `vibrate`, `priority`, `requireTermux`, `kinds` (6), `notifySubagents`, `title_<kind>`/`content_<kind>`
|
|
25
|
-
- TypeScript source (`src/index.ts`) + runtime JS (`src/index.js`) + types (`src/index.d.ts`)
|
|
26
|
-
- Professional package scaffolding: README, LICENSE (MIT), CHANGELOG, `.gitignore`, `tsconfig.json`
|
|
27
|
-
|
|
28
|
-
[0.1.0]: https://github.com/Victozee26/opencode-termux-notify/releases/tag/v0.1.0
|
|
6
|
+
## 0.1.0
|
|
7
|
+
- Initial release: Termux notifications via `termux-notification` + `termux-media-player` with 6 sounds, deduplication, and session-aware titles.
|
package/README.md
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# opencode-termux-notify
|
|
2
2
|
|
|
3
|
-
Termux notification plugin for [OpenCode](https://opencode.ai) on Android. Routes OpenCode attention events to `termux-notification` and `termux-media-player
|
|
3
|
+
Termux notification plugin for [OpenCode](https://opencode.ai) on Android. Routes OpenCode attention events to `termux-notification` and `termux-media-player` via the OpenCode V2 plugin API (`@opencode-ai/plugin/v2/promise`).
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/opencode-termux-notify)
|
|
6
6
|
[](./LICENSE)
|
|
7
7
|
|
|
8
8
|
## Features
|
|
9
9
|
|
|
10
|
-
- 6 notification kinds (default, question, permission, error, done, subagent_done) with distinct vibration and audio via `termux-media-player`
|
|
10
|
+
- 6 notification kinds (default, question, permission, error, done, subagent_done) with distinct vibration and audio via `termux-media-player`
|
|
11
11
|
- Cross-instance deduplication via shared JSON file and stable notification IDs
|
|
12
12
|
- Spam prevention via event TTL (60s), per-session cooldown (5s), and global throttle (1s)
|
|
13
13
|
- Session-aware titles resolved from OpenCode session metadata
|
|
@@ -16,69 +16,42 @@ Termux notification plugin for [OpenCode](https://opencode.ai) on Android. Route
|
|
|
16
16
|
## Requirements
|
|
17
17
|
|
|
18
18
|
- Android + Termux with `termux-api` (`pkg install termux-api` and install Termux:API app)
|
|
19
|
-
- Node >= 20, OpenCode >= 1.18.27
|
|
19
|
+
- Node >= 20, OpenCode >= 1.18.27 (V2 plugin API)
|
|
20
20
|
|
|
21
21
|
## Install
|
|
22
22
|
|
|
23
|
-
**V1 (default) — `opencode` (1.18.27+):**
|
|
24
|
-
|
|
25
23
|
```bash
|
|
26
|
-
opencode plugin opencode-termux-notify
|
|
27
|
-
opencode plugin opencode-termux-notify -g # install globally instead of per-project
|
|
24
|
+
opencode plugin add opencode-termux-notify
|
|
28
25
|
```
|
|
29
26
|
|
|
30
|
-
Add to `opencode.
|
|
27
|
+
Add to `opencode.jsonc`:
|
|
31
28
|
|
|
32
29
|
```jsonc
|
|
33
|
-
// ~/.config/opencode/opencode.
|
|
30
|
+
// ~/.config/opencode/opencode.jsonc
|
|
34
31
|
{
|
|
35
32
|
"$schema": "https://opencode.ai/config.json",
|
|
36
|
-
"
|
|
33
|
+
"plugins": ["opencode-termux-notify"]
|
|
37
34
|
}
|
|
38
35
|
```
|
|
39
36
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
```bash
|
|
43
|
-
opencode2 plugin add opencode-termux-notify/v2
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
Add to `opencode.jsonc` (`opencode.json(c)`):
|
|
47
|
-
|
|
48
|
-
```jsonc
|
|
49
|
-
// ~/.config/opencode/opencode.jsonc (V2: plural "plugins")
|
|
50
|
-
{
|
|
51
|
-
"$schema": "https://opencode.ai/config.json",
|
|
52
|
-
"plugins": ["opencode-termux-notify/v2"]
|
|
53
|
-
}
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
Local path (this repo) for V2 — must be a directory (`opencode.jsonc`):
|
|
37
|
+
Local path (this repo):
|
|
57
38
|
|
|
58
39
|
```jsonc
|
|
59
40
|
// opencode.jsonc
|
|
60
41
|
{
|
|
61
|
-
"plugins": ["/data/data/com.termux/files/home/opencode-termux-notify
|
|
42
|
+
"plugins": ["/data/data/com.termux/files/home/opencode-termux-notify"]
|
|
62
43
|
}
|
|
63
44
|
```
|
|
64
45
|
|
|
65
46
|
## Usage
|
|
66
47
|
|
|
67
|
-
With options
|
|
48
|
+
With options:
|
|
68
49
|
|
|
69
50
|
```jsonc
|
|
70
|
-
//
|
|
71
|
-
{
|
|
72
|
-
"plugin": [["opencode-termux-notify", {
|
|
73
|
-
"kinds": ["question", "permission", "error", "done", "subagent_done"],
|
|
74
|
-
"notifySubagents": true,
|
|
75
|
-
"priority": "high"
|
|
76
|
-
}]]
|
|
77
|
-
}
|
|
78
|
-
// V2 — opencode.jsonc
|
|
51
|
+
// opencode.jsonc
|
|
79
52
|
{
|
|
80
53
|
"plugins": [{
|
|
81
|
-
"package": "opencode-termux-notify
|
|
54
|
+
"package": "opencode-termux-notify",
|
|
82
55
|
"options": {
|
|
83
56
|
"kinds": ["question", "permission", "error", "done", "subagent_done"],
|
|
84
57
|
"notifySubagents": true,
|
|
@@ -88,10 +61,10 @@ With options (same for V1/V2, note `plugin` vs `plugins` and `opencode.json` vs
|
|
|
88
61
|
}
|
|
89
62
|
```
|
|
90
63
|
|
|
91
|
-
Restart
|
|
64
|
+
Restart OpenCode after editing config:
|
|
92
65
|
|
|
93
66
|
```bash
|
|
94
|
-
|
|
67
|
+
opencode service restart
|
|
95
68
|
```
|
|
96
69
|
|
|
97
70
|
Grant notification permission on Android 13+ under Settings > Apps > Termux > Notifications. Verify with:
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAA;AAEnE,wBAAgB,WAAW,CAAC,QAAQ,EAAE,mBAAmB,GAAG,YAAY,CAMvE;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,mBAAmB,GAAG,GAAG,CAAC,MAAM,CAAC,
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAA;AAEnE,wBAAgB,WAAW,CAAC,QAAQ,EAAE,mBAAmB,GAAG,YAAY,CAMvE;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,mBAAmB,GAAG,GAAG,CAAC,MAAM,CAAC,CAM1E;AAED,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,mBAAmB,GAAG,MAAM,CAWjG;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,mBAAmB,GAAG,MAAM,CAW9E"}
|
package/dist/config.js
CHANGED
|
@@ -10,15 +10,9 @@ export function resolveOpts(userOpts) {
|
|
|
10
10
|
return opts;
|
|
11
11
|
}
|
|
12
12
|
export function getEnabledKinds(userOpts) {
|
|
13
|
-
|
|
13
|
+
return new Set(Array.isArray(userOpts.kinds) && userOpts.kinds.length
|
|
14
14
|
? userOpts.kinds
|
|
15
15
|
: ["default", "question", "permission", "error", "done", "subagent_done"]);
|
|
16
|
-
if (enabledKinds.has("idle")) {
|
|
17
|
-
enabledKinds.delete("idle");
|
|
18
|
-
enabledKinds.add("done");
|
|
19
|
-
enabledKinds.add("default");
|
|
20
|
-
}
|
|
21
|
-
return enabledKinds;
|
|
22
16
|
}
|
|
23
17
|
export function getTitle(kind, sessionName, userOpts) {
|
|
24
18
|
const titles = {
|
package/dist/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAGzC,MAAM,UAAU,WAAW,CAAC,QAA6B;IACvD,MAAM,IAAI,GAAiB,EAAE,GAAG,QAAQ,EAAE,GAAG,QAAQ,EAAkB,CAAA;IACvE,IAAI,OAAO,QAAQ,CAAC,UAAU,KAAK,QAAQ,IAAI,QAAQ,CAAC,UAAU,CAAC,IAAI,EAAE;QAAE,IAAI,CAAC,UAAU,GAAG,QAAQ,CAAC,UAAU,CAAA;IAChH,IAAI,OAAQ,QAAgB,CAAC,GAAG,KAAK,QAAQ,IAAK,QAAgB,CAAC,GAAG,CAAC,IAAI,EAAE;QAAE,IAAI,CAAC,GAAG,GAAI,QAAgB,CAAC,GAAG,CAAA;IAC/G,IAAI,OAAO,QAAQ,CAAC,QAAQ,KAAK,QAAQ,IAAI,QAAQ,CAAC,QAAQ,CAAC,IAAI,EAAE;QAAE,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC,QAAQ,CAAA;IACxG,OAAO,IAAI,CAAA;AACb,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,QAA6B;IAC3D,
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAGzC,MAAM,UAAU,WAAW,CAAC,QAA6B;IACvD,MAAM,IAAI,GAAiB,EAAE,GAAG,QAAQ,EAAE,GAAG,QAAQ,EAAkB,CAAA;IACvE,IAAI,OAAO,QAAQ,CAAC,UAAU,KAAK,QAAQ,IAAI,QAAQ,CAAC,UAAU,CAAC,IAAI,EAAE;QAAE,IAAI,CAAC,UAAU,GAAG,QAAQ,CAAC,UAAU,CAAA;IAChH,IAAI,OAAQ,QAAgB,CAAC,GAAG,KAAK,QAAQ,IAAK,QAAgB,CAAC,GAAG,CAAC,IAAI,EAAE;QAAE,IAAI,CAAC,GAAG,GAAI,QAAgB,CAAC,GAAG,CAAA;IAC/G,IAAI,OAAO,QAAQ,CAAC,QAAQ,KAAK,QAAQ,IAAI,QAAQ,CAAC,QAAQ,CAAC,IAAI,EAAE;QAAE,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC,QAAQ,CAAA;IACxG,OAAO,IAAI,CAAA;AACb,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,QAA6B;IAC3D,OAAO,IAAI,GAAG,CACZ,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,QAAQ,CAAC,KAAK,CAAC,MAAM;QACpD,CAAC,CAAE,QAAQ,CAAC,KAAkB;QAC9B,CAAC,CAAC,CAAC,SAAS,EAAE,UAAU,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,EAAE,eAAe,CAAC,CAC5E,CAAA;AACH,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,IAAY,EAAE,WAAmB,EAAE,QAA6B;IACvF,MAAM,MAAM,GAA2B;QACrC,OAAO,EAAE,cAAc,WAAW,EAAE;QACpC,IAAI,EAAE,cAAc,WAAW,EAAE;QACjC,aAAa,EAAE,yBAAyB,WAAW,EAAE;QACrD,QAAQ,EAAE,uBAAuB,WAAW,EAAE;QAC9C,UAAU,EAAE,yBAAyB,WAAW,EAAE;QAClD,KAAK,EAAE,oBAAoB,WAAW,EAAE;KACzC,CAAA;IACD,MAAM,GAAG,GAAG,SAAS,IAAI,EAA+B,CAAA;IACxD,OAAO,OAAQ,QAAgB,CAAC,GAAG,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAE,QAAgB,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,OAAQ,CAAA;AAC9G,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,IAAY,EAAE,QAA6B;IACpE,MAAM,QAAQ,GAA2B;QACvC,OAAO,EAAE,kBAAkB;QAC3B,IAAI,EAAE,cAAc;QACpB,aAAa,EAAE,eAAe;QAC9B,QAAQ,EAAE,sBAAsB;QAChC,UAAU,EAAE,wBAAwB;QACpC,KAAK,EAAE,iBAAiB;KACzB,CAAA;IACD,MAAM,GAAG,GAAG,WAAW,IAAI,EAA+B,CAAA;IAC1D,OAAO,OAAQ,QAAgB,CAAC,GAAG,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAE,QAAgB,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,QAAQ,CAAC,OAAQ,CAAA;AAClH,CAAC"}
|
package/dist/types.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ export interface TermuxNotifyOptions {
|
|
|
11
11
|
vibrate?: boolean;
|
|
12
12
|
requireTermux?: boolean;
|
|
13
13
|
priority?: "high" | "low" | "default" | "max" | (string & {});
|
|
14
|
-
kinds?: Array<"default" | "question" | "permission" | "error" | "done" | "subagent_done"
|
|
14
|
+
kinds?: Array<"default" | "question" | "permission" | "error" | "done" | "subagent_done">;
|
|
15
15
|
notifySubagents?: boolean;
|
|
16
16
|
title_default?: string;
|
|
17
17
|
title_done?: string;
|
|
@@ -25,12 +25,6 @@ export interface TermuxNotifyOptions {
|
|
|
25
25
|
content_question?: string;
|
|
26
26
|
content_permission?: string;
|
|
27
27
|
content_error?: string;
|
|
28
|
-
vibrateIdle?: string;
|
|
29
|
-
vibrateError?: string;
|
|
30
|
-
titleIdle?: string;
|
|
31
|
-
titleError?: string;
|
|
32
|
-
contentIdle?: string;
|
|
33
|
-
contentError?: string;
|
|
34
28
|
}
|
|
35
29
|
export type NotifyKind = "default" | "question" | "permission" | "error" | "done" | "subagent_done";
|
|
36
30
|
export type ResolvedOpts = typeof DEFAULTS;
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAE9C,MAAM,WAAW,mBAAmB;IAClC,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,QAAQ,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,SAAS,GAAG,KAAK,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAA;IAC7D,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,GAAG,UAAU,GAAG,YAAY,GAAG,OAAO,GAAG,MAAM,GAAG,eAAe,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAE9C,MAAM,WAAW,mBAAmB;IAClC,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,QAAQ,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,SAAS,GAAG,KAAK,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAA;IAC7D,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,GAAG,UAAU,GAAG,YAAY,GAAG,OAAO,GAAG,MAAM,GAAG,eAAe,CAAC,CAAA;IACzF,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,UAAU,GAAG,YAAY,GAAG,OAAO,GAAG,MAAM,GAAG,eAAe,CAAA;AACnG,MAAM,MAAM,YAAY,GAAG,OAAO,QAAQ,CAAA"}
|
package/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
3
|
"name": "opencode-termux-notify",
|
|
4
|
-
"version": "0.1.1
|
|
4
|
+
"version": "0.1.1",
|
|
5
5
|
"description": "Termux-native notifications for OpenCode — 6 attention sounds (default/question/permission/error/done/subagent_done) via termux-notification + termux-media-player, with cross-instance deduplication.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"keywords": [
|
|
@@ -33,14 +33,6 @@
|
|
|
33
33
|
"import": "./dist/index.js",
|
|
34
34
|
"types": "./dist/index.d.ts"
|
|
35
35
|
},
|
|
36
|
-
"./v1": {
|
|
37
|
-
"import": "./dist/v1.js",
|
|
38
|
-
"types": "./dist/v1.d.ts"
|
|
39
|
-
},
|
|
40
|
-
"./v2": {
|
|
41
|
-
"import": "./dist/index.js",
|
|
42
|
-
"types": "./dist/index.d.ts"
|
|
43
|
-
},
|
|
44
36
|
"./package.json": "./package.json"
|
|
45
37
|
},
|
|
46
38
|
"main": "./dist/index.js",
|
|
@@ -59,7 +51,7 @@
|
|
|
59
51
|
},
|
|
60
52
|
"publishConfig": {
|
|
61
53
|
"access": "public",
|
|
62
|
-
"tag": "
|
|
54
|
+
"tag": "latest"
|
|
63
55
|
},
|
|
64
56
|
"dependencies": {
|
|
65
57
|
"@opencode-ai/plugin": "^1.18.27"
|
package/src/config.ts
CHANGED
|
@@ -10,17 +10,11 @@ export function resolveOpts(userOpts: TermuxNotifyOptions): ResolvedOpts {
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
export function getEnabledKinds(userOpts: TermuxNotifyOptions): Set<string> {
|
|
13
|
-
|
|
13
|
+
return new Set<string>(
|
|
14
14
|
Array.isArray(userOpts.kinds) && userOpts.kinds.length
|
|
15
15
|
? (userOpts.kinds as string[])
|
|
16
16
|
: ["default", "question", "permission", "error", "done", "subagent_done"],
|
|
17
17
|
)
|
|
18
|
-
if (enabledKinds.has("idle")) {
|
|
19
|
-
enabledKinds.delete("idle")
|
|
20
|
-
enabledKinds.add("done")
|
|
21
|
-
enabledKinds.add("default")
|
|
22
|
-
}
|
|
23
|
-
return enabledKinds
|
|
24
18
|
}
|
|
25
19
|
|
|
26
20
|
export function getTitle(kind: string, sessionName: string, userOpts: TermuxNotifyOptions): string {
|
package/src/types.ts
CHANGED
|
@@ -12,7 +12,7 @@ export interface TermuxNotifyOptions {
|
|
|
12
12
|
vibrate?: boolean
|
|
13
13
|
requireTermux?: boolean
|
|
14
14
|
priority?: "high" | "low" | "default" | "max" | (string & {})
|
|
15
|
-
kinds?: Array<"default" | "question" | "permission" | "error" | "done" | "subagent_done"
|
|
15
|
+
kinds?: Array<"default" | "question" | "permission" | "error" | "done" | "subagent_done">
|
|
16
16
|
notifySubagents?: boolean
|
|
17
17
|
title_default?: string
|
|
18
18
|
title_done?: string
|
|
@@ -26,13 +26,6 @@ export interface TermuxNotifyOptions {
|
|
|
26
26
|
content_question?: string
|
|
27
27
|
content_permission?: string
|
|
28
28
|
content_error?: string
|
|
29
|
-
// legacy
|
|
30
|
-
vibrateIdle?: string
|
|
31
|
-
vibrateError?: string
|
|
32
|
-
titleIdle?: string
|
|
33
|
-
titleError?: string
|
|
34
|
-
contentIdle?: string
|
|
35
|
-
contentError?: string
|
|
36
29
|
}
|
|
37
30
|
|
|
38
31
|
export type NotifyKind = "default" | "question" | "permission" | "error" | "done" | "subagent_done"
|
package/dist/v1.d.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* opencode-termux-notify — Termux-native notifications for OpenCode (V1)
|
|
3
|
-
*
|
|
4
|
-
* V1 plugin API: export const TermuxNotify: Plugin = async (input, options) => ({ event })
|
|
5
|
-
* Loaded via `plugin: ["opencode-termux-notify"]` or `["opencode-termux-notify/v1"]`
|
|
6
|
-
* V2 is available at `opencode-termux-notify/v2`
|
|
7
|
-
*
|
|
8
|
-
* 6 builtin attention sounds:
|
|
9
|
-
* default → bip-bop-01.mp3
|
|
10
|
-
* question → bip-bop-03.mp3
|
|
11
|
-
* permission → staplebops-06.mp3
|
|
12
|
-
* error → nope-03.mp3
|
|
13
|
-
* done → bip-bop-01.mp3
|
|
14
|
-
* subagent_done → yup-01.mp3
|
|
15
|
-
*/
|
|
16
|
-
import type { Plugin } from "@opencode-ai/plugin";
|
|
17
|
-
export type { TermuxNotifyOptions } from "./types.js";
|
|
18
|
-
export { DEFAULTS, PRIORITY_BY_KIND, SOUND_FILES, VIBRATE_PATTERNS } from "./constants.js";
|
|
19
|
-
export declare const TermuxNotify: Plugin;
|
|
20
|
-
export default TermuxNotify;
|
|
21
|
-
//# sourceMappingURL=v1.d.ts.map
|
package/dist/v1.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"v1.d.ts","sourceRoot":"","sources":["../src/v1.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AASjD,YAAY,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAA;AACrD,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AAE1F,eAAO,MAAM,YAAY,EAAE,MA6G1B,CAAA;eAEc,YAAY"}
|
package/dist/v1.js
DELETED
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* opencode-termux-notify — Termux-native notifications for OpenCode (V1)
|
|
3
|
-
*
|
|
4
|
-
* V1 plugin API: export const TermuxNotify: Plugin = async (input, options) => ({ event })
|
|
5
|
-
* Loaded via `plugin: ["opencode-termux-notify"]` or `["opencode-termux-notify/v1"]`
|
|
6
|
-
* V2 is available at `opencode-termux-notify/v2`
|
|
7
|
-
*
|
|
8
|
-
* 6 builtin attention sounds:
|
|
9
|
-
* default → bip-bop-01.mp3
|
|
10
|
-
* question → bip-bop-03.mp3
|
|
11
|
-
* permission → staplebops-06.mp3
|
|
12
|
-
* error → nope-03.mp3
|
|
13
|
-
* done → bip-bop-01.mp3
|
|
14
|
-
* subagent_done → yup-01.mp3
|
|
15
|
-
*/
|
|
16
|
-
import { playAudio } from "./audio.js";
|
|
17
|
-
import { getEnabledKinds, getContent, getTitle, resolveOpts } from "./config.js";
|
|
18
|
-
import { shouldNotifyShared } from "./dedup.js";
|
|
19
|
-
import { isTermuxEnvironment } from "./env.js";
|
|
20
|
-
import { classifyEvent } from "./events.js";
|
|
21
|
-
import { notify } from "./notify.js";
|
|
22
|
-
export { DEFAULTS, PRIORITY_BY_KIND, SOUND_FILES, VIBRATE_PATTERNS } from "./constants.js";
|
|
23
|
-
export const TermuxNotify = async (input, options) => {
|
|
24
|
-
const userOpts = (options ?? {});
|
|
25
|
-
const opts = resolveOpts(userOpts);
|
|
26
|
-
const enabledKinds = getEnabledKinds(userOpts);
|
|
27
|
-
if (opts.requireTermux && !isTermuxEnvironment()) {
|
|
28
|
-
console.warn("[termux-notify] Not in Termux (TERMUX_VERSION/PREFIX missing) — plugin will still listen but notifications may fail. Set { requireTermux:false } to silence this.");
|
|
29
|
-
}
|
|
30
|
-
const active = new Set();
|
|
31
|
-
const errored = new Set();
|
|
32
|
-
// Adapter so classifyEvent and session-title logic can use V2-style ctx.session.get
|
|
33
|
-
// V1 client uses client.session.get({ path: { id } }) -> { data: Session }
|
|
34
|
-
const ctxAdapter = {
|
|
35
|
-
session: {
|
|
36
|
-
get: async ({ sessionID }) => {
|
|
37
|
-
try {
|
|
38
|
-
const client = input.client;
|
|
39
|
-
if (!client?.session?.get)
|
|
40
|
-
return undefined;
|
|
41
|
-
let raw;
|
|
42
|
-
// try V2-style first, then V1-style
|
|
43
|
-
try {
|
|
44
|
-
raw = await client.session.get({ sessionID });
|
|
45
|
-
if (raw && (raw.info || raw.data || raw.title || raw.id))
|
|
46
|
-
return raw;
|
|
47
|
-
}
|
|
48
|
-
catch { }
|
|
49
|
-
try {
|
|
50
|
-
raw = await client.session.get({ path: { id: sessionID } });
|
|
51
|
-
}
|
|
52
|
-
catch { }
|
|
53
|
-
// SDK gen returns { data, ... } or direct
|
|
54
|
-
const data = raw?.data ?? raw;
|
|
55
|
-
return data;
|
|
56
|
-
}
|
|
57
|
-
catch {
|
|
58
|
-
return undefined;
|
|
59
|
-
}
|
|
60
|
-
},
|
|
61
|
-
},
|
|
62
|
-
};
|
|
63
|
-
return {
|
|
64
|
-
event: async ({ event }) => {
|
|
65
|
-
if (!event?.type)
|
|
66
|
-
return;
|
|
67
|
-
// replicate V2 status tracking to avoid duplicate idle notifications after errors
|
|
68
|
-
if (event.type === "session.status") {
|
|
69
|
-
const sid = event.properties?.sessionID;
|
|
70
|
-
if (!sid)
|
|
71
|
-
return;
|
|
72
|
-
const st = event.properties?.status?.type;
|
|
73
|
-
if (st === "busy" || st === "retry") {
|
|
74
|
-
active.add(sid);
|
|
75
|
-
errored.delete(sid);
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
if (st !== "idle")
|
|
79
|
-
return;
|
|
80
|
-
if (!active.has(sid))
|
|
81
|
-
return;
|
|
82
|
-
active.delete(sid);
|
|
83
|
-
if (errored.has(sid)) {
|
|
84
|
-
errored.delete(sid);
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
if (event.type === "session.error") {
|
|
89
|
-
const sid = event.properties?.sessionID;
|
|
90
|
-
if (sid && active.has(sid))
|
|
91
|
-
errored.add(sid);
|
|
92
|
-
}
|
|
93
|
-
const classified = await classifyEvent(event, ctxAdapter);
|
|
94
|
-
if (!classified)
|
|
95
|
-
return;
|
|
96
|
-
const { kind, sound, sessionID } = classified;
|
|
97
|
-
if (!enabledKinds.has(kind) && !enabledKinds.has(sound))
|
|
98
|
-
return;
|
|
99
|
-
const evtId = classified.evtId;
|
|
100
|
-
const sessionKey = `${sessionID}:${kind}`;
|
|
101
|
-
const ok = await shouldNotifyShared(evtId, sessionKey, opts);
|
|
102
|
-
if (!ok)
|
|
103
|
-
return;
|
|
104
|
-
const nid = `opencode-${sessionID}-${kind}`;
|
|
105
|
-
let sessionName = sessionID.slice(0, 8);
|
|
106
|
-
try {
|
|
107
|
-
const info = await ctxAdapter.session.get({ sessionID });
|
|
108
|
-
const raw = info?.info || info?.data || info;
|
|
109
|
-
const candidate = raw?.title || raw?.slug || raw?.summary?.title || raw?.id;
|
|
110
|
-
if (candidate && typeof candidate === "string" && candidate.trim()) {
|
|
111
|
-
sessionName = candidate.trim().slice(0, 40);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
catch { }
|
|
115
|
-
const title = getTitle(kind, sessionName, userOpts);
|
|
116
|
-
const content = getContent(kind, userOpts);
|
|
117
|
-
const notifySubagents = userOpts.notifySubagents !== false;
|
|
118
|
-
if (kind === "subagent_done" && !notifySubagents) {
|
|
119
|
-
await playAudio(sound, opts);
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
try {
|
|
123
|
-
await notify(title, content, nid, sound, opts);
|
|
124
|
-
}
|
|
125
|
-
catch (err) {
|
|
126
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
127
|
-
console.error(`[termux-notify] Failed to send ${kind} notification:`, msg);
|
|
128
|
-
}
|
|
129
|
-
},
|
|
130
|
-
};
|
|
131
|
-
};
|
|
132
|
-
export default TermuxNotify;
|
|
133
|
-
//# sourceMappingURL=v1.js.map
|
package/dist/v1.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"v1.js","sourceRoot":"","sources":["../src/v1.ts"],"names":[],"mappings":"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"}
|
package/src/v1.ts
DELETED
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* opencode-termux-notify — Termux-native notifications for OpenCode (V1)
|
|
3
|
-
*
|
|
4
|
-
* V1 plugin API: export const TermuxNotify: Plugin = async (input, options) => ({ event })
|
|
5
|
-
* Loaded via `plugin: ["opencode-termux-notify"]` or `["opencode-termux-notify/v1"]`
|
|
6
|
-
* V2 is available at `opencode-termux-notify/v2`
|
|
7
|
-
*
|
|
8
|
-
* 6 builtin attention sounds:
|
|
9
|
-
* default → bip-bop-01.mp3
|
|
10
|
-
* question → bip-bop-03.mp3
|
|
11
|
-
* permission → staplebops-06.mp3
|
|
12
|
-
* error → nope-03.mp3
|
|
13
|
-
* done → bip-bop-01.mp3
|
|
14
|
-
* subagent_done → yup-01.mp3
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import type { Plugin } from "@opencode-ai/plugin"
|
|
18
|
-
import { playAudio } from "./audio.js"
|
|
19
|
-
import { getEnabledKinds, getContent, getTitle, resolveOpts } from "./config.js"
|
|
20
|
-
import { shouldNotifyShared } from "./dedup.js"
|
|
21
|
-
import { isTermuxEnvironment } from "./env.js"
|
|
22
|
-
import { classifyEvent } from "./events.js"
|
|
23
|
-
import { notify } from "./notify.js"
|
|
24
|
-
import type { TermuxNotifyOptions } from "./types.js"
|
|
25
|
-
|
|
26
|
-
export type { TermuxNotifyOptions } from "./types.js"
|
|
27
|
-
export { DEFAULTS, PRIORITY_BY_KIND, SOUND_FILES, VIBRATE_PATTERNS } from "./constants.js"
|
|
28
|
-
|
|
29
|
-
export const TermuxNotify: Plugin = async (input, options) => {
|
|
30
|
-
const userOpts: TermuxNotifyOptions = (options ?? {}) as TermuxNotifyOptions
|
|
31
|
-
const opts = resolveOpts(userOpts)
|
|
32
|
-
const enabledKinds = getEnabledKinds(userOpts)
|
|
33
|
-
|
|
34
|
-
if (opts.requireTermux && !isTermuxEnvironment()) {
|
|
35
|
-
console.warn(
|
|
36
|
-
"[termux-notify] Not in Termux (TERMUX_VERSION/PREFIX missing) — plugin will still listen but notifications may fail. Set { requireTermux:false } to silence this.",
|
|
37
|
-
)
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const active = new Set<string>()
|
|
41
|
-
const errored = new Set<string>()
|
|
42
|
-
|
|
43
|
-
// Adapter so classifyEvent and session-title logic can use V2-style ctx.session.get
|
|
44
|
-
// V1 client uses client.session.get({ path: { id } }) -> { data: Session }
|
|
45
|
-
const ctxAdapter: any = {
|
|
46
|
-
session: {
|
|
47
|
-
get: async ({ sessionID }: { sessionID: string }) => {
|
|
48
|
-
try {
|
|
49
|
-
const client: any = (input as any).client
|
|
50
|
-
if (!client?.session?.get) return undefined
|
|
51
|
-
let raw: any
|
|
52
|
-
// try V2-style first, then V1-style
|
|
53
|
-
try {
|
|
54
|
-
raw = await client.session.get({ sessionID })
|
|
55
|
-
if (raw && (raw.info || raw.data || raw.title || raw.id)) return raw
|
|
56
|
-
} catch {}
|
|
57
|
-
try {
|
|
58
|
-
raw = await client.session.get({ path: { id: sessionID } })
|
|
59
|
-
} catch {}
|
|
60
|
-
// SDK gen returns { data, ... } or direct
|
|
61
|
-
const data = raw?.data ?? raw
|
|
62
|
-
return data
|
|
63
|
-
} catch {
|
|
64
|
-
return undefined
|
|
65
|
-
}
|
|
66
|
-
},
|
|
67
|
-
},
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
return {
|
|
71
|
-
event: async ({ event }: { event: any }) => {
|
|
72
|
-
if (!event?.type) return
|
|
73
|
-
|
|
74
|
-
// replicate V2 status tracking to avoid duplicate idle notifications after errors
|
|
75
|
-
if (event.type === "session.status") {
|
|
76
|
-
const sid: string | undefined = event.properties?.sessionID
|
|
77
|
-
if (!sid) return
|
|
78
|
-
const st: string | undefined = event.properties?.status?.type
|
|
79
|
-
if (st === "busy" || st === "retry") {
|
|
80
|
-
active.add(sid)
|
|
81
|
-
errored.delete(sid)
|
|
82
|
-
return
|
|
83
|
-
}
|
|
84
|
-
if (st !== "idle") return
|
|
85
|
-
if (!active.has(sid)) return
|
|
86
|
-
active.delete(sid)
|
|
87
|
-
if (errored.has(sid)) {
|
|
88
|
-
errored.delete(sid)
|
|
89
|
-
return
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
if (event.type === "session.error") {
|
|
93
|
-
const sid: string | undefined = event.properties?.sessionID
|
|
94
|
-
if (sid && active.has(sid)) errored.add(sid)
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const classified = await classifyEvent(event, ctxAdapter)
|
|
98
|
-
if (!classified) return
|
|
99
|
-
|
|
100
|
-
const { kind, sound, sessionID } = classified
|
|
101
|
-
if (!enabledKinds.has(kind) && !enabledKinds.has(sound)) return
|
|
102
|
-
|
|
103
|
-
const evtId: string = classified.evtId
|
|
104
|
-
const sessionKey = `${sessionID}:${kind}`
|
|
105
|
-
|
|
106
|
-
const ok = await shouldNotifyShared(evtId, sessionKey, opts)
|
|
107
|
-
if (!ok) return
|
|
108
|
-
|
|
109
|
-
const nid = `opencode-${sessionID}-${kind}`
|
|
110
|
-
|
|
111
|
-
let sessionName = sessionID.slice(0, 8)
|
|
112
|
-
try {
|
|
113
|
-
const info: any = await ctxAdapter.session.get({ sessionID })
|
|
114
|
-
const raw = info?.info || info?.data || info
|
|
115
|
-
const candidate: string | undefined = raw?.title || raw?.slug || raw?.summary?.title || raw?.id
|
|
116
|
-
if (candidate && typeof candidate === "string" && candidate.trim()) {
|
|
117
|
-
sessionName = candidate.trim().slice(0, 40)
|
|
118
|
-
}
|
|
119
|
-
} catch {}
|
|
120
|
-
|
|
121
|
-
const title = getTitle(kind, sessionName, userOpts)
|
|
122
|
-
const content = getContent(kind, userOpts)
|
|
123
|
-
|
|
124
|
-
const notifySubagents: boolean = (userOpts as any).notifySubagents !== false
|
|
125
|
-
if (kind === "subagent_done" && !notifySubagents) {
|
|
126
|
-
await playAudio(sound, opts)
|
|
127
|
-
return
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
try {
|
|
131
|
-
await notify(title, content, nid, sound, opts)
|
|
132
|
-
} catch (err: unknown) {
|
|
133
|
-
const msg = err instanceof Error ? err.message : String(err)
|
|
134
|
-
console.error(`[termux-notify] Failed to send ${kind} notification:`, msg)
|
|
135
|
-
}
|
|
136
|
-
},
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
export default TermuxNotify
|