oc-sounds 0.1.1 → 0.1.2

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
@@ -95,7 +95,8 @@ hooks. The built-in tool palette is:
95
95
  | --- | --- |
96
96
  | Read files | `page` |
97
97
  | Glob and grep | `release` |
98
- | Web search and fetch | `arrival` |
98
+ | Web search | `arrival` |
99
+ | Web fetch | `loading` |
99
100
  | Write and edit | `toggle` |
100
101
  | Patch a file | `arrival` |
101
102
  | Shell / code execution | `tick` |
@@ -104,6 +105,7 @@ hooks. The built-in tool palette is:
104
105
  | Ask the user a question | `chime` |
105
106
  | Other tools | `tick` |
106
107
  | Tool succeeds / fails | `release` / `error` |
108
+ | Web fetch succeeds | `ready` |
107
109
 
108
110
  Session actions also have cues. The option keys are listed below:
109
111
 
@@ -147,9 +149,9 @@ lifecycle cues.
147
149
  ## Customize
148
150
 
149
151
  **Every action's sound is configurable.** Use `cues` for session actions such as
150
- thinking, replying, and completion, and `tools` for individual tool-start sounds
151
- such as read, edit, and web fetch. Set a value to any Cuelume sound name, or `false`
152
- to mute it. Omitted settings keep their defaults.
152
+ thinking, replying, and completion, `tools` for individual tool-start sounds,
153
+ and `returns` for successful per-tool return sounds. Set a value to any Cuelume
154
+ sound name, or `false` to mute it. Omitted settings keep their defaults.
153
155
 
154
156
  Edit the existing plugin entry in `~/.config/opencode/opencode.jsonc` (global)
155
157
  or your project's `opencode.jsonc`. Merge it with your other settings. If you use
@@ -180,9 +182,12 @@ a local checkout, keep its directory path in `package`:
180
182
  "read": "tick",
181
183
  "edit": "toggle",
182
184
  "shell": "pulse",
183
- "webfetch": "arrival",
185
+ "webfetch": "loading",
184
186
  "websearch": "arrival",
185
187
  "my_mcp_tool": "sparkle"
188
+ },
189
+ "returns": {
190
+ "webfetch": "ready"
186
191
  }
187
192
  }
188
193
  }
@@ -215,6 +220,10 @@ does not require rebuilding or reinstalling the plugin.
215
220
  name (`read` also matches `functions.read` / `functions_read`). Exact names
216
221
  take precedence. `false` mutes that tool's starting cue; completion and error
217
222
  cues are controlled separately. Tool overrides take precedence over `cues.tool`.
223
+ - `returns`: override successful return cues by exact effective tool name or
224
+ built-in short name. The default `webfetch` return is `ready`; other successful
225
+ tools fall back to `cues.toolComplete`. Set a value to `false` to mute that
226
+ tool's successful return without muting its starting cue.
218
227
 
219
228
  All 17 Cuelume sounds are available:
220
229
  `chime`, `sparkle`, `droplet`, `bloom`, `whisper`, `tick`, `press`, `release`,
package/dist/config.d.ts CHANGED
@@ -23,6 +23,7 @@ export declare const defaultCues: {
23
23
  };
24
24
  export type Action = keyof typeof defaultCues;
25
25
  export declare const defaultToolCues: Record<string, SoundName>;
26
+ export declare const defaultToolReturnCues: Record<string, SoundName>;
26
27
  export type PlayerName = "auto" | "paplay" | "pw-play" | "afplay" | "ffplay";
27
28
  export declare const defaultPlayback: {
28
29
  minIntervalMs: number;
@@ -39,5 +40,6 @@ export interface Options {
39
40
  playback: PlaybackOptions;
40
41
  cues: Record<Action, SoundName | false>;
41
42
  tools: Record<string, SoundName | false>;
43
+ returns: Record<string, SoundName | false>;
42
44
  }
43
45
  export declare function parseOptions(input: unknown): Options;
package/dist/config.js CHANGED
@@ -29,7 +29,7 @@ export const defaultToolCues = {
29
29
  glob: "release",
30
30
  grep: "release",
31
31
  websearch: "arrival",
32
- webfetch: "arrival",
32
+ webfetch: "loading",
33
33
  write: "toggle",
34
34
  edit: "toggle",
35
35
  patch: "arrival",
@@ -41,6 +41,9 @@ export const defaultToolCues = {
41
41
  task: "arrival",
42
42
  skill: "sparkle",
43
43
  };
44
+ export const defaultToolReturnCues = {
45
+ webfetch: "ready",
46
+ };
44
47
  export const defaultPlayback = { minIntervalMs: 180, dedupeWindowMs: 300 };
45
48
  function object(value) {
46
49
  if (value && typeof value === "object" && !Array.isArray(value)) {
@@ -56,7 +59,7 @@ function cue(value) {
56
59
  export function parseOptions(input) {
57
60
  const raw = object(input);
58
61
  for (const key of Object.keys(raw)) {
59
- if (!["enabled", "volume", "player", "playback", "cues", "tools"].includes(key)) {
62
+ if (!["enabled", "volume", "player", "playback", "cues", "tools", "returns"].includes(key)) {
60
63
  throw new Error(`oc-sounds: unknown option ${key}`);
61
64
  }
62
65
  }
@@ -80,6 +83,9 @@ export function parseOptions(input) {
80
83
  const tools = Object.create(null);
81
84
  for (const [key, value] of Object.entries(object(raw.tools ?? {})))
82
85
  tools[key] = cue(value);
86
+ const returns = Object.create(null);
87
+ for (const [key, value] of Object.entries(object(raw.returns ?? {})))
88
+ returns[key] = cue(value);
83
89
  const playback = { ...defaultPlayback };
84
90
  for (const [key, value] of Object.entries(object(raw.playback ?? {}))) {
85
91
  if (!Object.hasOwn(playback, key))
@@ -89,5 +95,5 @@ export function parseOptions(input) {
89
95
  }
90
96
  playback[key] = value;
91
97
  }
92
- return { enabled, volume, player: player, playback, cues, tools };
98
+ return { enabled, volume, player: player, playback, cues, tools, returns };
93
99
  }
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { Plugin } from "@opencode/plugin";
2
2
  import { type Action, type Options } from "./config.js";
3
3
  import { type Player } from "./player.js";
4
- export { defaultCues, defaultToolCues, defaultPlayback, sounds } from "./config.js";
4
+ export { defaultCues, defaultToolCues, defaultToolReturnCues, defaultPlayback, sounds } from "./config.js";
5
5
  export type { Action, Options, PlaybackOptions, SoundName } from "./config.js";
6
6
  type Event = ReturnType<Plugin.Context["event"]["subscribe"]> extends AsyncIterable<infer E> ? E : never;
7
7
  export declare function actionForEvent(event: Event): Action | undefined;
package/dist/index.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import { Plugin } from "@opencode/plugin";
2
2
  import { resolve } from "node:path";
3
3
  import { setTimeout as delay } from "node:timers/promises";
4
- import { defaultToolCues, parseOptions } from "./config.js";
4
+ import { defaultToolCues, defaultToolReturnCues, parseOptions } from "./config.js";
5
5
  import { createPlayer } from "./player.js";
6
6
  import { schedulePlayer } from "./scheduler.js";
7
- export { defaultCues, defaultToolCues, defaultPlayback, sounds } from "./config.js";
7
+ export { defaultCues, defaultToolCues, defaultToolReturnCues, defaultPlayback, sounds } from "./config.js";
8
8
  const eventActions = {
9
9
  "session.inbox.enqueued": "prompt",
10
10
  "session.execution.started": "start",
@@ -26,6 +26,9 @@ const eventActions = {
26
26
  export function actionForEvent(event) {
27
27
  return eventActions[event.type];
28
28
  }
29
+ function shortToolName(tool) {
30
+ return tool.split(".").at(-1).replace(/^(functions|tools)_/, "");
31
+ }
29
32
  const importantActions = new Set([
30
33
  "complete", "error", "toolError", "permission", "question", "interrupt",
31
34
  ]);
@@ -37,21 +40,31 @@ export async function setup(ctx, makePlayer = createPlayer) {
37
40
  const player = schedulePlayer(makePlayer(options.player, options.volume), options.playback);
38
41
  const controller = new AbortController();
39
42
  const registrations = [];
43
+ const calls = new Map();
40
44
  const emit = (action, tool) => {
41
45
  if (controller.signal.aborted)
42
46
  return;
43
47
  let sound = options.cues[action];
44
48
  if (tool !== undefined) {
45
- const name = tool.split(".").at(-1);
46
- const short = name.replace(/^(functions|tools)_/, "");
47
- if (Object.hasOwn(options.tools, tool))
48
- sound = options.tools[tool];
49
- else if (Object.hasOwn(options.tools, short))
50
- sound = options.tools[short];
51
- // An explicit tool cue replaces the built-in palette for all tools.
52
- else if (action === "tool" && !Object.hasOwn(ctx.options.cues ?? {}, "tool")) {
53
- if (Object.hasOwn(defaultToolCues, short))
54
- sound = defaultToolCues[short];
49
+ const short = shortToolName(tool);
50
+ if (action === "tool") {
51
+ if (Object.hasOwn(options.tools, tool))
52
+ sound = options.tools[tool];
53
+ else if (Object.hasOwn(options.tools, short))
54
+ sound = options.tools[short];
55
+ // An explicit general cue replaces the built-in palette for all tools.
56
+ else if (!Object.hasOwn(ctx.options.cues ?? {}, "tool")) {
57
+ if (Object.hasOwn(defaultToolCues, short))
58
+ sound = defaultToolCues[short];
59
+ }
60
+ }
61
+ else if (action === "toolComplete") {
62
+ if (Object.hasOwn(options.returns, tool))
63
+ sound = options.returns[tool];
64
+ else if (Object.hasOwn(options.returns, short))
65
+ sound = options.returns[short];
66
+ else if (Object.hasOwn(defaultToolReturnCues, short))
67
+ sound = defaultToolReturnCues[short];
55
68
  }
56
69
  }
57
70
  if (sound)
@@ -64,11 +77,14 @@ export async function setup(ctx, makePlayer = createPlayer) {
64
77
  };
65
78
  try {
66
79
  registrations.push(await ctx.tool.hook("execute.before", (event) => {
67
- const name = event.tool.split(/[._]/).at(-1);
80
+ const name = shortToolName(event.tool);
81
+ calls.set(event.id, event.tool);
68
82
  emit(name === "question" ? "question" : "tool", event.tool);
69
83
  }));
70
84
  registrations.push(await ctx.tool.hook("execute.after", (event) => {
71
- emit(event.status === "error" ? "toolError" : "toolComplete");
85
+ const tool = calls.get(event.id) ?? event.tool;
86
+ calls.delete(event.id);
87
+ emit(event.status === "error" ? "toolError" : "toolComplete", typeof tool === "string" ? tool : undefined);
72
88
  }));
73
89
  }
74
90
  catch (error) {
@@ -37,7 +37,7 @@
37
37
  "glob": "release",
38
38
  "grep": "release",
39
39
  "websearch": "arrival",
40
- "webfetch": "arrival",
40
+ "webfetch": "loading",
41
41
  "write": "toggle",
42
42
  "edit": "toggle",
43
43
  "patch": "arrival",
@@ -48,6 +48,9 @@
48
48
  "subagent": "arrival",
49
49
  "task": "arrival",
50
50
  "skill": "sparkle"
51
+ },
52
+ "returns": {
53
+ "webfetch": "ready"
51
54
  }
52
55
  }
53
56
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oc-sounds",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Cuelume sound feedback for OpenCode V2 agent actions",
5
5
  "type": "module",
6
6
  "license": "MIT",