@hxnnxs/opencode-voice 0.1.5 → 0.1.6

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
@@ -2,6 +2,12 @@
2
2
 
3
3
  All notable changes to this project are documented here.
4
4
 
5
+ ## 0.1.6 - 2026-06-17
6
+
7
+ ### Fixed
8
+
9
+ - Fixed Windows recorder startup to use resolved recorder command paths (including bundled ffmpeg) directly when spawning, preventing startup failures when ffmpeg is present only by absolute path.
10
+
5
11
  ## 0.1.5 - 2026-06-17
6
12
 
7
13
  ### Fixed
@@ -162,7 +162,13 @@ function printEngineRetry({ error, nextAttempt, attempts }) {
162
162
 
163
163
  async function installCommand() {
164
164
  const pluginArgs = args.filter((arg) => arg !== "--no-engine");
165
- const result = spawnSync("opencode", ["plugin", packageName(), ...pluginArgs], { stdio: "inherit" });
165
+ const spawnOptions = { stdio: "inherit" };
166
+ if (process.platform === "win32") spawnOptions.shell = true;
167
+ const result = spawnSync("opencode", ["plugin", packageName(), ...pluginArgs], spawnOptions);
168
+ if (result.error) {
169
+ console.error(`Failed to run opencode: ${result.error.message}`);
170
+ process.exit(1);
171
+ }
166
172
  if ((result.status ?? 1) !== 0) process.exit(result.status ?? 1);
167
173
 
168
174
  if (!hasFlag("--no-engine")) {
package/lib/download.js CHANGED
@@ -70,7 +70,7 @@ function contentRangeStart(value) {
70
70
  return match ? Number(match[1]) : null;
71
71
  }
72
72
 
73
- async function replaceFile(source, destination) {
73
+ export async function replaceFile(source, destination) {
74
74
  await fs.promises.unlink(destination).catch(() => {});
75
75
 
76
76
  for (let attempt = 1; attempt <= 5; attempt++) {
package/lib/engine.js CHANGED
@@ -182,8 +182,11 @@ function parseWindowsMicrophones(stderr) {
182
182
  }
183
183
 
184
184
  export function listMicrophones() {
185
- if (process.platform === "linux" && commandExists("arecord")) {
186
- const result = spawnSync("arecord", ["-L"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
185
+ const arecordCommand = resolveCommand("arecord");
186
+ const ffmpegCommand = resolveCommand("ffmpeg");
187
+
188
+ if (process.platform === "linux" && arecordCommand) {
189
+ const result = spawnSync(arecordCommand, ["-L"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
187
190
  const devices = result.stdout
188
191
  .split(/\r?\n/)
189
192
  .map((line) => line.trim())
@@ -191,8 +194,8 @@ export function listMicrophones() {
191
194
  return ["default", ...devices.filter((item) => item !== "default")];
192
195
  }
193
196
 
194
- if (process.platform === "darwin" && commandExists("ffmpeg")) {
195
- const result = spawnSync("ffmpeg", ["-hide_banner", "-f", "avfoundation", "-list_devices", "true", "-i", ""], {
197
+ if (process.platform === "darwin" && ffmpegCommand) {
198
+ const result = spawnSync(ffmpegCommand, ["-hide_banner", "-f", "avfoundation", "-list_devices", "true", "-i", ""], {
196
199
  encoding: "utf8",
197
200
  stdio: ["ignore", "ignore", "pipe"],
198
201
  });
@@ -203,8 +206,8 @@ export function listMicrophones() {
203
206
  .map((id) => `:${id}`);
204
207
  }
205
208
 
206
- if (process.platform === "win32" && commandExists("ffmpeg")) {
207
- const result = spawnSync("ffmpeg", ["-hide_banner", "-f", "dshow", "-list_devices", "true", "-i", "dummy"], {
209
+ if (process.platform === "win32" && ffmpegCommand) {
210
+ const result = spawnSync(ffmpegCommand, ["-hide_banner", "-f", "dshow", "-list_devices", "true", "-i", "dummy"], {
208
211
  encoding: "utf8",
209
212
  stdio: ["ignore", "ignore", "pipe"],
210
213
  });
@@ -218,54 +221,60 @@ export function listMicrophones() {
218
221
  function buildRecorders(file, settings = {}) {
219
222
  const mic = settings.mic || "";
220
223
  const recorders = [];
224
+ const arecordCommand = resolveCommand("arecord", settings);
225
+ const ffmpegCommand = resolveCommand("ffmpeg", settings);
226
+ const soxCommand = resolveCommand("sox", settings);
221
227
 
222
- if (process.platform === "linux" && commandExists("arecord")) {
228
+ if (process.platform === "linux" && arecordCommand) {
223
229
  recorders.push({
224
230
  label: mic ? `arecord (${mic})` : "arecord (default)",
225
- command: "arecord",
231
+ command: arecordCommand,
226
232
  args: ["-q", "-f", "S16_LE", "-r", "16000", "-c", "1", "-t", "wav", ...(mic ? ["-D", mic] : []), file],
227
233
  });
228
234
  }
229
235
 
230
- if (process.platform === "linux" && commandExists("ffmpeg")) {
236
+ if (process.platform === "linux" && ffmpegCommand) {
231
237
  if (!mic) {
232
238
  recorders.push({
233
239
  label: "ffmpeg pulse (default)",
234
- command: "ffmpeg",
240
+ command: ffmpegCommand,
235
241
  args: ["-hide_banner", "-loglevel", "error", "-y", "-f", "pulse", "-i", "default", "-ac", "1", "-ar", "16000", file],
236
242
  });
237
243
  }
238
244
 
239
245
  recorders.push({
240
246
  label: `ffmpeg alsa (${mic || "default"})`,
241
- command: "ffmpeg",
247
+ command: ffmpegCommand,
242
248
  args: ["-hide_banner", "-loglevel", "error", "-y", "-f", "alsa", "-i", mic || "default", "-ac", "1", "-ar", "16000", file],
243
249
  });
244
250
  }
245
251
 
246
- if (process.platform === "darwin" && commandExists("ffmpeg")) {
252
+ if (process.platform === "darwin" && ffmpegCommand) {
247
253
  recorders.push({
248
254
  label: `ffmpeg avfoundation (${mic || ":0"})`,
249
- command: "ffmpeg",
255
+ command: ffmpegCommand,
250
256
  args: ["-hide_banner", "-loglevel", "error", "-y", "-f", "avfoundation", "-i", mic || ":0", "-ac", "1", "-ar", "16000", file],
251
257
  });
252
258
  }
253
259
 
254
- if (process.platform === "win32" && commandExists("ffmpeg")) {
255
- const inputs = [...new Set([normalizeWindowsAudioInput(mic), "audio=default"])];
256
- for (const input of inputs) {
257
- recorders.push({
258
- label: `ffmpeg dshow (${input.replace(/^audio=/, "")})`,
259
- command: "ffmpeg",
260
- args: ["-hide_banner", "-loglevel", "error", "-y", "-f", "dshow", "-i", input, "-ac", "1", "-ar", "16000", file],
261
- });
260
+ if (process.platform === "win32" && ffmpegCommand) {
261
+ const ffmpegCmd = ffmpegCommand;
262
+ if (ffmpegCmd) {
263
+ const inputs = [...new Set([normalizeWindowsAudioInput(mic), "audio=default"])];
264
+ for (const input of inputs) {
265
+ recorders.push({
266
+ label: `ffmpeg dshow (${input.replace(/^audio=/, "")})`,
267
+ command: ffmpegCmd,
268
+ args: ["-hide_banner", "-loglevel", "error", "-y", "-f", "dshow", "-i", input, "-ac", "1", "-ar", "16000", file],
269
+ });
270
+ }
262
271
  }
263
272
  }
264
273
 
265
- if (commandExists("sox")) {
274
+ if (soxCommand) {
266
275
  recorders.push({
267
276
  label: "sox default",
268
- command: "sox",
277
+ command: soxCommand,
269
278
  args: ["-d", "-r", "16000", "-c", "1", "-b", "16", file],
270
279
  });
271
280
  }
package/lib/engines.js CHANGED
@@ -5,7 +5,7 @@ import { createGunzip } from "node:zlib";
5
5
  import { spawn } from "node:child_process";
6
6
  import { Readable, Transform } from "node:stream";
7
7
  import { pipeline } from "node:stream/promises";
8
- import { sha256, ensureDir } from "./download.js";
8
+ import { sha256, ensureDir, replaceFile } from "./download.js";
9
9
  import { getCacheDir } from "./models.js";
10
10
  import { getBundledEngineDir, resolveCommand } from "./engine.js";
11
11
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hxnnxs/opencode-voice",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Local voice input plugin for OpenCode",
5
5
  "type": "module",
6
6
  "license": "MIT",