speech-opencode 1.1.1 → 1.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/dist/index.d.ts CHANGED
@@ -8,28 +8,9 @@ export interface VoicePluginOptions {
8
8
  silenceDuration?: number;
9
9
  /** Maximum recording duration in seconds as a safety timeout (default 300 = 5 minutes) */
10
10
  maxDuration?: number;
11
+ /** Enable wake word trigger file watching (default true) */
12
+ enableWakeWord?: boolean;
11
13
  }
12
- /**
13
- * OpenCode Voice Plugin
14
- *
15
- * Adds a 'voice' tool that records audio from the microphone and transcribes it
16
- * using OpenAI's Whisper API.
17
- *
18
- * @example
19
- * ```ts
20
- * // In opencode.json
21
- * {
22
- * "plugin": ["opencode-voice"]
23
- * }
24
- * ```
25
- *
26
- * @example
27
- * ```ts
28
- * // With options in .opencode/plugin/voice.ts
29
- * import { VoicePlugin } from "opencode-voice"
30
- * export default VoicePlugin({ language: "en", defaultDuration: 10 })
31
- * ```
32
- */
33
14
  export declare const VoicePlugin: (options?: VoicePluginOptions) => Plugin;
34
15
  declare const _default: Plugin;
35
16
  export default _default;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAQ,MAAM,qBAAqB,CAAA;AAwGvD,MAAM,WAAW,kBAAkB;IACjC,yDAAyD;IACzD,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,8FAA8F;IAC9F,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,+DAA+D;IAC/D,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,0FAA0F;IAC1F,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,WAAW,GACrB,UAAS,kBAAuB,KAAG,MA4DnC,CAAA;;AAGH,wBAA4B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAQ,MAAM,qBAAqB,CAAA;AA2GvD,MAAM,WAAW,kBAAkB;IACjC,yDAAyD;IACzD,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,8FAA8F;IAC9F,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,+DAA+D;IAC/D,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,0FAA0F;IAC1F,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,4DAA4D;IAC5D,cAAc,CAAC,EAAE,OAAO,CAAA;CACzB;AAmID,eAAO,MAAM,WAAW,GACrB,UAAS,kBAAuB,KAAG,MAyDnC,CAAA;;AAGH,wBAA4B"}
package/dist/index.js CHANGED
@@ -1,9 +1,11 @@
1
1
  import { tool } from "@opencode-ai/plugin";
2
2
  import OpenAI from "openai";
3
3
  import { spawn } from "child_process";
4
- import { unlinkSync, readFileSync } from "fs";
5
- import { tmpdir } from "os";
6
- import { join } from "path";
4
+ import { unlinkSync, readFileSync, existsSync, watch, mkdirSync } from "fs";
5
+ import { tmpdir, homedir } from "os";
6
+ import { join, dirname } from "path";
7
+ // Trigger file path for wake word integration
8
+ const TRIGGER_FILE = join(homedir(), ".cache", "opencode", "voice_trigger");
7
9
  /**
8
10
  * Records audio from the microphone with automatic silence detection.
9
11
  * Recording stops after the specified silence duration.
@@ -99,11 +101,101 @@ async function transcribeAudio(audioFilePath, apiKey, language) {
99
101
  * export default VoicePlugin({ language: "en", defaultDuration: 10 })
100
102
  * ```
101
103
  */
104
+ /**
105
+ * Records and transcribes audio, returning the transcription
106
+ */
107
+ async function recordAndTranscribe(apiKey, maxDuration, silenceDuration, language) {
108
+ let audioFile = null;
109
+ try {
110
+ audioFile = await recordAudio(maxDuration, silenceDuration);
111
+ const transcription = await transcribeAudio(audioFile, apiKey, language);
112
+ if (!transcription || transcription.trim() === "") {
113
+ return "No speech detected. Please try again and speak clearly into your microphone.";
114
+ }
115
+ return transcription;
116
+ }
117
+ finally {
118
+ if (audioFile) {
119
+ try {
120
+ unlinkSync(audioFile);
121
+ }
122
+ catch {
123
+ // Ignore cleanup errors
124
+ }
125
+ }
126
+ }
127
+ }
128
+ /**
129
+ * Clears the wake word trigger file
130
+ */
131
+ function clearTriggerFile() {
132
+ try {
133
+ if (existsSync(TRIGGER_FILE)) {
134
+ unlinkSync(TRIGGER_FILE);
135
+ }
136
+ }
137
+ catch {
138
+ // Ignore errors
139
+ }
140
+ }
141
+ /**
142
+ * Sets up wake word trigger file watching
143
+ * When the trigger file is written, it records audio, transcribes it, and appends to the TUI prompt
144
+ */
145
+ function setupWakeWordWatcher(apiKey, maxDuration, silenceDuration, language, client // OpenCode SDK client
146
+ ) {
147
+ // Ensure the directory exists
148
+ const triggerDir = dirname(TRIGGER_FILE);
149
+ mkdirSync(triggerDir, { recursive: true });
150
+ // Clear any existing trigger
151
+ clearTriggerFile();
152
+ console.log("[Voice Plugin] Wake word watcher enabled");
153
+ console.log(`[Voice Plugin] Watching: ${TRIGGER_FILE}`);
154
+ console.log("[Voice Plugin] Run 'python wakeword/listener.py' to enable 'Hey Jarvis' wake word");
155
+ // Watch the directory for the trigger file
156
+ let isRecording = false;
157
+ watch(triggerDir, async (eventType, filename) => {
158
+ if (filename !== "voice_trigger" || isRecording)
159
+ return;
160
+ if (!existsSync(TRIGGER_FILE))
161
+ return;
162
+ isRecording = true;
163
+ console.log("[Voice Plugin] Wake word triggered! Recording...");
164
+ try {
165
+ // Clear the trigger file immediately
166
+ clearTriggerFile();
167
+ // Record and transcribe
168
+ const transcription = await recordAndTranscribe(apiKey, maxDuration, silenceDuration, language);
169
+ if (transcription && !transcription.startsWith("No speech detected")) {
170
+ console.log(`[Voice Plugin] Transcribed: "${transcription}"`);
171
+ // Append transcription to the TUI prompt
172
+ try {
173
+ await client.tui.appendPrompt({ body: { text: transcription } });
174
+ // Auto-submit the prompt
175
+ await client.tui.submitPrompt();
176
+ }
177
+ catch (err) {
178
+ console.error("[Voice Plugin] Failed to send to TUI:", err);
179
+ }
180
+ }
181
+ }
182
+ catch (error) {
183
+ console.error("[Voice Plugin] Error:", error);
184
+ }
185
+ finally {
186
+ isRecording = false;
187
+ }
188
+ });
189
+ }
102
190
  export const VoicePlugin = (options = {}) => async (ctx) => {
103
- const { apiKey = process.env.OPENAI_API_KEY, language, silenceDuration = 7, maxDuration = 300, } = options;
191
+ const { apiKey = process.env.OPENAI_API_KEY, language, silenceDuration = 7, maxDuration = 300, enableWakeWord = true, } = options;
104
192
  if (!apiKey) {
105
193
  console.warn("[Voice Plugin] Warning: OPENAI_API_KEY not set. Voice transcription will fail.");
106
194
  }
195
+ // Set up wake word watcher if enabled
196
+ if (enableWakeWord && apiKey && ctx.client) {
197
+ setupWakeWordWatcher(apiKey, maxDuration, silenceDuration, language, ctx.client);
198
+ }
107
199
  return {
108
200
  tool: {
109
201
  voice: tool({
@@ -115,29 +207,14 @@ export const VoicePlugin = (options = {}) => async (ctx) => {
115
207
  if (!apiKey) {
116
208
  return "Error: OPENAI_API_KEY environment variable is not set. Please set it to use voice transcription.";
117
209
  }
118
- let audioFile = null;
119
210
  try {
120
- audioFile = await recordAudio(maxDuration, silenceDuration);
121
- const transcription = await transcribeAudio(audioFile, apiKey, language);
122
- if (!transcription || transcription.trim() === "") {
123
- return "No speech detected. Please try again and speak clearly into your microphone.";
124
- }
211
+ const transcription = await recordAndTranscribe(apiKey, maxDuration, silenceDuration, language);
125
212
  return `Transcribed speech: "${transcription}"`;
126
213
  }
127
214
  catch (error) {
128
215
  const errorMessage = error instanceof Error ? error.message : String(error);
129
216
  return `Voice recording/transcription failed: ${errorMessage}`;
130
217
  }
131
- finally {
132
- if (audioFile) {
133
- try {
134
- unlinkSync(audioFile);
135
- }
136
- catch {
137
- // Ignore cleanup errors
138
- }
139
- }
140
- }
141
218
  },
142
219
  }),
143
220
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "speech-opencode",
3
- "version": "1.1.1",
3
+ "version": "1.1.2",
4
4
  "description": "Voice input plugin for OpenCode using OpenAI Whisper",
5
5
  "keywords": [
6
6
  "opencode",