opencode-termux-notify 0.1.1-dev.0 → 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 CHANGED
@@ -1,28 +1,7 @@
1
- # Changelog
1
+ # Changelogs
2
2
 
3
- All notable changes to this project will be documented in this file.
3
+ ## 0.1.1
4
+ - Removed V1 compatibility code — now V2-only via `@opencode-ai/plugin/v2/promise`.
4
5
 
5
- The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
- and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
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`. **Default is V1 (`opencode`), V2 (`opencode2`) via `opencode-termux-notify/v2`.**
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
  [![npm version](https://img.shields.io/npm/v/opencode-termux-notify)](https://www.npmjs.com/package/opencode-termux-notify)
6
6
  [![license](https://img.shields.io/npm/l/opencode-termux-notify)](./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` from Opencode
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 # latest
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.json`:
27
+ Add to `opencode.jsonc`:
31
28
 
32
29
  ```jsonc
33
- // ~/.config/opencode/opencode.json (V1: singular "plugin")
30
+ // ~/.config/opencode/opencode.jsonc
34
31
  {
35
32
  "$schema": "https://opencode.ai/config.json",
36
- "plugin": ["opencode-termux-notify"]
33
+ "plugins": ["opencode-termux-notify"]
37
34
  }
38
35
  ```
39
36
 
40
- **V2 `opencode2` (beta):**
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/v2"]
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 (same for V1/V2, note `plugin` vs `plugins` and `opencode.json` vs `opencode.jsonc`):
48
+ With options:
68
49
 
69
50
  ```jsonc
70
- // V1 — opencode.json
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/v2",
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 the opencode after editing config:
64
+ Restart OpenCode after editing config:
92
65
 
93
66
  ```bash
94
- opencode2 service restart
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/audio.d.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  import type { ResolvedOpts } from "./types.js";
2
+ export declare function getAccessibleDir(): string;
3
+ export declare function ensureAccessibleAudio(source: string, file: string): Promise<string>;
2
4
  export declare function resolveAudioPath(file: string): string;
3
5
  export declare function playAudio(soundName: string, opts: ResolvedOpts): Promise<void>;
4
6
  //# sourceMappingURL=audio.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"audio.d.ts","sourceRoot":"","sources":["../src/audio.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAE9C,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAgBrD;AAED,wBAAsB,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAwBpF"}
1
+ {"version":3,"file":"audio.d.ts","sourceRoot":"","sources":["../src/audio.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAK9C,wBAAgB,gBAAgB,IAAI,MAAM,CASzC;AAED,wBAAsB,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAgBzF;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAgBrD;AAED,wBAAsB,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAyBpF"}
package/dist/audio.js CHANGED
@@ -1,8 +1,42 @@
1
1
  import { existsSync } from "node:fs";
2
+ import { copyFile, mkdir, chmod } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
2
4
  import { spawn } from "node:child_process";
3
5
  import { join, dirname } from "node:path";
4
6
  import { fileURLToPath } from "node:url";
5
7
  import { SOUND_FILES } from "./constants.js";
8
+ const TERMUX_USR_TMP = "/data/data/com.termux/files/usr/tmp";
9
+ const TERMUX_FILES_PREFIX = "/data/data/com.termux/files/";
10
+ export function getAccessibleDir() {
11
+ const envTmp = process.env.TMPDIR;
12
+ if (envTmp && envTmp.startsWith(TERMUX_FILES_PREFIX) && existsSync(envTmp)) {
13
+ return join(envTmp, "termux-notify-audio");
14
+ }
15
+ if (existsSync(TERMUX_USR_TMP)) {
16
+ return join(TERMUX_USR_TMP, "termux-notify-audio");
17
+ }
18
+ return join(tmpdir(), "termux-notify-audio");
19
+ }
20
+ export async function ensureAccessibleAudio(source, file) {
21
+ if (source.startsWith(TERMUX_FILES_PREFIX))
22
+ return source;
23
+ const dir = getAccessibleDir();
24
+ try {
25
+ await mkdir(dir, { recursive: true });
26
+ const dest = join(dir, file);
27
+ if (!existsSync(dest)) {
28
+ await copyFile(source, dest);
29
+ try {
30
+ await chmod(dest, 0o644);
31
+ }
32
+ catch { }
33
+ }
34
+ return dest;
35
+ }
36
+ catch {
37
+ return source;
38
+ }
39
+ }
6
40
  export function resolveAudioPath(file) {
7
41
  try {
8
42
  const here = dirname(fileURLToPath(import.meta.url));
@@ -29,11 +63,12 @@ export async function playAudio(soundName, opts) {
29
63
  const file = SOUND_FILES[soundName];
30
64
  if (!file)
31
65
  return;
32
- const path = resolveAudioPath(file);
33
- if (!existsSync(path)) {
34
- console.warn(`[termux-notify] audio file not found: ${path} for ${soundName}`);
66
+ const source = resolveAudioPath(file);
67
+ if (!existsSync(source)) {
68
+ console.warn(`[termux-notify] audio file not found: ${source} for ${soundName}`);
35
69
  return;
36
70
  }
71
+ const path = await ensureAccessibleAudio(source, file);
37
72
  const bin = existsSync(opts.mediaBin) ? opts.mediaBin : "termux-media-player";
38
73
  await new Promise((resolve) => {
39
74
  const p = spawn(bin, ["play", path], { stdio: "ignore" });
package/dist/audio.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"audio.js","sourceRoot":"","sources":["../src/audio.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AACpC,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAA;AAC1C,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AACxC,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAG5C,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,OAAO,CAAC,aAAa,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;QACpD,MAAM,UAAU,GAAG;YACjB,IAAI,CAAC,IAAI,EAAE,iBAAiB,EAAE,IAAI,CAAC;YACnC,IAAI,CAAC,IAAI,EAAE,cAAc,EAAE,IAAI,CAAC;YAChC,IAAI,CAAC,IAAI,EAAE,oBAAoB,EAAE,IAAI,CAAC;YACtC,IAAI,CAAC,IAAI,EAAE,qBAAqB,EAAE,IAAI,CAAC;YACvC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,IAAI,CAAC;YACzC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,kBAAkB,EAAE,IAAI,CAAC;SAC9C,CAAA;QACD,KAAK,MAAM,CAAC,IAAI,UAAU;YAAE,IAAI,UAAU,CAAC,CAAC,CAAC;gBAAE,OAAO,CAAC,CAAA;QACvD,OAAO,UAAU,CAAC,CAAC,CAAE,CAAA;IACvB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAA;IACb,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,SAAiB,EAAE,IAAkB;IACnE,IAAI,CAAC,IAAI,CAAC,SAAS;QAAE,OAAM;IAC3B,MAAM,IAAI,GAAG,WAAW,CAAC,SAAS,CAAC,CAAA;IACnC,IAAI,CAAC,IAAI;QAAE,OAAM;IACjB,MAAM,IAAI,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAA;IACnC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACtB,OAAO,CAAC,IAAI,CAAC,yCAAyC,IAAI,QAAQ,SAAS,EAAE,CAAC,CAAA;QAC9E,OAAM;IACR,CAAC;IACD,MAAM,GAAG,GAAG,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,qBAAqB,CAAA;IAC7E,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;QAClC,MAAM,CAAC,GAAG,KAAK,CAAC,GAAG,EAAE,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAA;QACzD,CAAC,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;YACpB,OAAO,CAAC,KAAK,CAAC,yCAAyC,SAAS,GAAG,EAAG,GAAa,CAAC,OAAO,CAAC,CAAA;YAC5F,OAAO,EAAE,CAAA;QACX,CAAC,CAAC,CAAA;QACF,CAAC,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAA;QAC9B,UAAU,CAAC,GAAG,EAAE;YACd,IAAI,CAAC;gBACH,CAAC;gBAAC,CAAS,CAAC,KAAK,EAAE,EAAE,CAAA;YACvB,CAAC;YAAC,MAAM,CAAC,CAAA,CAAC;YACV,OAAO,EAAE,CAAA;QACX,CAAC,EAAE,IAAI,CAAC,CAAA;IACV,CAAC,CAAC,CAAA;AACJ,CAAC"}
1
+ {"version":3,"file":"audio.js","sourceRoot":"","sources":["../src/audio.ts"],"names":[],"mappings":"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"}
@@ -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,CAY1E;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"}
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
- const enabledKinds = new Set(Array.isArray(userOpts.kinds) && userOpts.kinds.length
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 = {
@@ -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,MAAM,YAAY,GAAG,IAAI,GAAG,CAC1B,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;IACD,IAAI,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;QAC7B,YAAY,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;QAC3B,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;QACxB,YAAY,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;IAC7B,CAAC;IACD,OAAO,YAAY,CAAA;AACrB,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"}
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" | "idle">;
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;
@@ -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,GAAG,MAAM,CAAC,CAAA;IAClG,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;IAEtB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;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"}
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
@@ -1,4 +1,2 @@
1
1
  export { default } from "./dist/index.js"
2
2
  export { DEFAULTS, PRIORITY_BY_KIND, SOUND_FILES, VIBRATE_PATTERNS } from "./dist/constants.js"
3
- export { default as V1 } from "./dist/v1.js"
4
- export { default as V2 } from "./dist/index.js"
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-dev.0",
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": "dev"
54
+ "tag": "latest"
63
55
  },
64
56
  "dependencies": {
65
57
  "@opencode-ai/plugin": "^1.18.27"
package/src/audio.ts CHANGED
@@ -1,10 +1,44 @@
1
1
  import { existsSync } from "node:fs"
2
+ import { copyFile, mkdir, chmod } from "node:fs/promises"
3
+ import { tmpdir } from "node:os"
2
4
  import { spawn } from "node:child_process"
3
5
  import { join, dirname } from "node:path"
4
6
  import { fileURLToPath } from "node:url"
5
7
  import { SOUND_FILES } from "./constants.js"
6
8
  import type { ResolvedOpts } from "./types.js"
7
9
 
10
+ const TERMUX_USR_TMP = "/data/data/com.termux/files/usr/tmp"
11
+ const TERMUX_FILES_PREFIX = "/data/data/com.termux/files/"
12
+
13
+ export function getAccessibleDir(): string {
14
+ const envTmp = process.env.TMPDIR
15
+ if (envTmp && envTmp.startsWith(TERMUX_FILES_PREFIX) && existsSync(envTmp)) {
16
+ return join(envTmp, "termux-notify-audio")
17
+ }
18
+ if (existsSync(TERMUX_USR_TMP)) {
19
+ return join(TERMUX_USR_TMP, "termux-notify-audio")
20
+ }
21
+ return join(tmpdir(), "termux-notify-audio")
22
+ }
23
+
24
+ export async function ensureAccessibleAudio(source: string, file: string): Promise<string> {
25
+ if (source.startsWith(TERMUX_FILES_PREFIX)) return source
26
+ const dir = getAccessibleDir()
27
+ try {
28
+ await mkdir(dir, { recursive: true })
29
+ const dest = join(dir, file)
30
+ if (!existsSync(dest)) {
31
+ await copyFile(source, dest)
32
+ try {
33
+ await chmod(dest, 0o644)
34
+ } catch {}
35
+ }
36
+ return dest
37
+ } catch {
38
+ return source
39
+ }
40
+ }
41
+
8
42
  export function resolveAudioPath(file: string): string {
9
43
  try {
10
44
  const here = dirname(fileURLToPath(import.meta.url))
@@ -27,11 +61,12 @@ export async function playAudio(soundName: string, opts: ResolvedOpts): Promise<
27
61
  if (!opts.playSound) return
28
62
  const file = SOUND_FILES[soundName]
29
63
  if (!file) return
30
- const path = resolveAudioPath(file)
31
- if (!existsSync(path)) {
32
- console.warn(`[termux-notify] audio file not found: ${path} for ${soundName}`)
64
+ const source = resolveAudioPath(file)
65
+ if (!existsSync(source)) {
66
+ console.warn(`[termux-notify] audio file not found: ${source} for ${soundName}`)
33
67
  return
34
68
  }
69
+ const path = await ensureAccessibleAudio(source, file)
35
70
  const bin = existsSync(opts.mediaBin) ? opts.mediaBin : "termux-media-player"
36
71
  await new Promise<void>((resolve) => {
37
72
  const p = spawn(bin, ["play", path], { stdio: "ignore" })
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
- const enabledKinds = new Set<string>(
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" | "idle">
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