pi-voicekit 0.1.1 → 0.1.3

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
@@ -2,7 +2,7 @@
2
2
 
3
3
  # pi-voicekit
4
4
 
5
- > **Community continuation of [`codexstar69/pi-listen`](https://github.com/codexstar69/pi-listen)** (archived upstream, MIT).
5
+ > **Community continuation of [`codexstar69/pi-listen`](https://github.com/codexstar69/pi-listen)** (upstream, MIT — dormant since v7.2.2 in May 2026).
6
6
  > Not affiliated with the original author. Old name: `pi-listen`.
7
7
 
8
8
  <p align="center">
@@ -109,9 +109,9 @@ Toggle between Deepgram (cloud, live streaming) and Local (offline, batch mode).
109
109
 
110
110
  ### Models — browse, search, install
111
111
 
112
- <img src="https://raw.githubusercontent.com/CyFeng16/pi-voicekit/main/assets/settings-models.png" alt="Models tab — browse 19 models with accuracy/speed ratings" width="600" />
112
+ <img src="https://raw.githubusercontent.com/CyFeng16/pi-voicekit/main/assets/settings-models.png" alt="Models tab — browse 21 models with accuracy/speed ratings" width="600" />
113
113
 
114
- Browse 19 models from Parakeet, Whisper, Moonshine, SenseVoice, and GigaAM. Each model shows accuracy and speed ratings (●●●●○/●●●●○), fitness badges, and download status. Fuzzy search to find models fast. Press Enter to activate and download.
114
+ Browse 21 models from Parakeet, Whisper, Moonshine, SenseVoice, GigaAM, Paraformer, and Qwen3. Each model shows accuracy and speed ratings (●●●●○/●●●●○), fitness badges, and download status. Fuzzy search to find models fast. Press Enter to activate and download.
115
115
 
116
116
  ### Downloaded — manage installed models
117
117
 
@@ -189,7 +189,7 @@ in front):
189
189
 
190
190
  ## Local Models
191
191
 
192
- 19 models across 5 families. Sorted by quality — best models first.
192
+ 21 models across 7 families. Sorted by quality — best models first.
193
193
 
194
194
  ### Top picks
195
195
 
@@ -240,7 +240,7 @@ Models from [Handy](https://github.com/cjpais/handy) (`~/Library/Application Sup
240
240
  | Feature | Description |
241
241
  | -------------------------------- | ---------------------------------------------------------------------------------------- |
242
242
  | **Dual backend** | Deepgram (cloud, live streaming) or local models (offline, batch) — switch in settings |
243
- | **19 local models** | Parakeet, Whisper, Moonshine, SenseVoice, GigaAM — with accuracy/speed ratings |
243
+ | **21 local models** | Parakeet, Whisper, Moonshine, SenseVoice, GigaAM, Paraformer, Qwen3 — with accuracy/speed ratings |
244
244
  | **Unified settings panel** | One overlay panel for all configuration — `/voice-settings` |
245
245
  | **Device-aware recommendations** | Scores models against your hardware. Only best-in-class models get [recommended]. |
246
246
  | **Enterprise download pipeline** | Pre-checks (disk, network, permissions), live progress with speed/ETA, post-verification |
@@ -264,7 +264,7 @@ extensions/voice.ts Main extension — state machine, recording,
264
264
  extensions/voice/config.ts Config loading, saving, migration
265
265
  extensions/voice/onboarding.ts First-run wizard, language picker
266
266
  extensions/voice/deepgram.ts Deepgram URL builder, API key resolver
267
- extensions/voice/local.ts Model catalog (19 models), in-process transcription
267
+ extensions/voice/local.ts Model catalog (21 models), in-process transcription
268
268
  extensions/voice/device.ts Device profiling — RAM, GPU, CPU, container detection
269
269
  extensions/voice/model-download.ts Download manager — resume, progress, verification, Handy import
270
270
  extensions/voice/sherpa-engine.ts sherpa-onnx bindings — recognizer lifecycle, inference
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Capture-tool preference.
3
+ *
4
+ * SoX' `rec` is the best default for local capture, but it is known to stall on
5
+ * network PulseAudio servers: over an SSH audio tunnel (`PULSE_SERVER` pointing at
6
+ * a forwarded TCP port, e.g. `tcp:127.0.0.1:4713`) the same `rec` invocation
7
+ * returned zero bytes in 40% of runs on this stack, while ffmpeg was reliable in
8
+ * every run. So when a remote Pulse server is configured, probe ffmpeg first and
9
+ * keep SoX as the fallback; local capture keeps SoX first.
10
+ */
11
+
12
+ export type AudioToolName = "ffmpeg" | "sox" | "arecord";
13
+
14
+ export function audioToolOrder(env: { PULSE_SERVER?: string | undefined } = process.env): AudioToolName[] {
15
+ return env.PULSE_SERVER ? ["ffmpeg", "sox", "arecord"] : ["sox", "ffmpeg", "arecord"];
16
+ }
@@ -100,6 +100,7 @@ import {
100
100
  } from "./voice/local";
101
101
  import { shouldArmReleaseDetectOnRepeat, decideRecordingStartTimer } from "./voice/hold-to-talk";
102
102
  import { GapTimer, type TimerPort } from "./voice/release-controller";
103
+ import { audioToolOrder, type AudioToolName } from "./voice/audio-tool";
103
104
 
104
105
  /** Adapter for the real event loop — lets GapTimer run under the real setTimeout. */
105
106
  const realTimerPort: TimerPort = {
@@ -245,87 +246,95 @@ interface AudioCaptureTool {
245
246
  args: string[];
246
247
  }
247
248
 
248
- // Try available audio capture tools in order of preference
249
+ // Try available audio capture tools in the order given by audioToolOrder()
249
250
  let _cachedAudioTool: AudioCaptureTool | null | undefined;
250
- function detectAudioCaptureTool(): AudioCaptureTool | null {
251
- if (_cachedAudioTool !== undefined) return _cachedAudioTool;
252
251
 
253
- // 1. SoX rec — purpose-built for recording, best quality
254
- if (commandExists("rec")) {
255
- _cachedAudioTool = {
256
- name: "sox",
257
- cmd: "rec",
258
- args: [
259
- "-q",
260
- "--buffer",
261
- "4096",
262
- "-c",
263
- String(CHANNELS),
264
- "-b",
265
- "16",
266
- "-e",
267
- "signed-integer",
268
- "-t",
269
- "raw",
270
- "-",
271
- "rate",
272
- String(SAMPLE_RATE),
273
- ],
274
- };
275
- return _cachedAudioTool;
276
- }
252
+ function probeSox(): AudioCaptureTool | null {
253
+ if (!commandExists("rec")) return null;
254
+ return {
255
+ name: "sox",
256
+ cmd: "rec",
257
+ args: [
258
+ "-q",
259
+ "--buffer",
260
+ "4096",
261
+ "-c",
262
+ String(CHANNELS),
263
+ "-b",
264
+ "16",
265
+ "-e",
266
+ "signed-integer",
267
+ "-t",
268
+ "raw",
269
+ "-",
270
+ "rate",
271
+ String(SAMPLE_RATE),
272
+ ],
273
+ };
274
+ }
277
275
 
278
- // 2. ffmpeg — widely installed, captures from default mic
279
- if (commandExists("ffmpeg")) {
280
- const isLinux = process.platform === "linux";
281
- const isMac = process.platform === "darwin";
282
- const isWin = process.platform === "win32";
283
- // Input device varies by platform
284
- let inputArgs: string[];
285
- if (isMac) {
286
- inputArgs = ["-f", "avfoundation", "-i", ":default"];
287
- } else if (isLinux) {
288
- inputArgs = ["-f", "pulse", "-i", "default"];
289
- } else if (isWin) {
290
- // DirectShow has no "default" alias — enumerate devices and pick the first audio device
291
- const dshowDevice = detectWindowsAudioDevice();
292
- inputArgs = dshowDevice
293
- ? ["-f", "dshow", "-i", `audio=${dshowDevice}`]
294
- : ["-f", "dshow", "-i", "audio=Microphone"]; // last-resort guess
295
- } else {
296
- inputArgs = ["-f", "pulse", "-i", "default"]; // fallback for other platforms
297
- }
298
- _cachedAudioTool = {
299
- name: "ffmpeg",
300
- cmd: "ffmpeg",
301
- args: [
302
- ...inputArgs,
303
- "-ac",
304
- String(CHANNELS),
305
- "-ar",
306
- String(SAMPLE_RATE),
307
- "-sample_fmt",
308
- "s16",
309
- "-f",
310
- "s16le",
311
- "-loglevel",
312
- "error",
313
- "pipe:1",
314
- ],
315
- };
316
- return _cachedAudioTool;
276
+ function probeFfmpeg(): AudioCaptureTool | null {
277
+ if (!commandExists("ffmpeg")) return null;
278
+ const isLinux = process.platform === "linux";
279
+ const isMac = process.platform === "darwin";
280
+ const isWin = process.platform === "win32";
281
+ // Input device varies by platform
282
+ let inputArgs: string[];
283
+ if (isMac) {
284
+ inputArgs = ["-f", "avfoundation", "-i", ":default"];
285
+ } else if (isLinux) {
286
+ inputArgs = ["-f", "pulse", "-i", "default"];
287
+ } else if (isWin) {
288
+ // DirectShow has no "default" alias — enumerate devices and pick the first audio device
289
+ const dshowDevice = detectWindowsAudioDevice();
290
+ inputArgs = dshowDevice ? ["-f", "dshow", "-i", `audio=${dshowDevice}`] : ["-f", "dshow", "-i", "audio=Microphone"]; // last-resort guess
291
+ } else {
292
+ inputArgs = ["-f", "pulse", "-i", "default"]; // fallback for other platforms
317
293
  }
294
+ return {
295
+ name: "ffmpeg",
296
+ cmd: "ffmpeg",
297
+ args: [
298
+ ...inputArgs,
299
+ "-ac",
300
+ String(CHANNELS),
301
+ "-ar",
302
+ String(SAMPLE_RATE),
303
+ "-sample_fmt",
304
+ "s16",
305
+ "-f",
306
+ "s16le",
307
+ "-loglevel",
308
+ "error",
309
+ "pipe:1",
310
+ ],
311
+ };
312
+ }
318
313
 
319
- // 3. arecord — built into Linux ALSA, zero install
320
- if (process.platform === "linux" && commandExists("arecord")) {
321
- _cachedAudioTool = {
322
- name: "arecord",
323
- cmd: "arecord",
324
- args: ["-q", "-f", "S16_LE", "-r", String(SAMPLE_RATE), "-c", String(CHANNELS), "-t", "raw"],
325
- };
326
- return _cachedAudioTool;
327
- }
314
+ function probeArecord(): AudioCaptureTool | null {
315
+ if (process.platform !== "linux" || !commandExists("arecord")) return null;
316
+ return {
317
+ name: "arecord",
318
+ cmd: "arecord",
319
+ args: ["-q", "-f", "S16_LE", "-r", String(SAMPLE_RATE), "-c", String(CHANNELS), "-t", "raw"],
320
+ };
321
+ }
328
322
 
323
+ const audioProbes: Record<AudioToolName, () => AudioCaptureTool | null> = {
324
+ sox: probeSox,
325
+ ffmpeg: probeFfmpeg,
326
+ arecord: probeArecord,
327
+ };
328
+
329
+ function detectAudioCaptureTool(): AudioCaptureTool | null {
330
+ if (_cachedAudioTool !== undefined) return _cachedAudioTool;
331
+ for (const name of audioToolOrder()) {
332
+ const tool = audioProbes[name]();
333
+ if (tool) {
334
+ _cachedAudioTool = tool;
335
+ return tool;
336
+ }
337
+ }
329
338
  _cachedAudioTool = null;
330
339
  return null;
331
340
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-voicekit",
3
- "version": "0.1.1",
4
- "description": "Voice in + voice out for Pi CLI — hold-to-talk STT (Deepgram or 19 offline models) plus TTS (Kitten Nano, Piper, Kokoro, or Deepgram Aura)",
3
+ "version": "0.1.3",
4
+ "description": "Voice in + voice out for Pi CLI — hold-to-talk STT (Deepgram streaming or 21 offline models) plus TTS (Kitten Nano, Piper, Kokoro, or Deepgram Aura)",
5
5
  "type": "module",
6
6
  "keywords": [
7
7
  "pi-package",
@@ -46,6 +46,8 @@
46
46
  "typecheck": "bunx tsc -p tsconfig.json",
47
47
  "test": "bun test",
48
48
  "check": "bun run typecheck && bun run test",
49
+ "format": "bunx prettier@3.3.3 --write \"**/*.{ts,json,yml,yaml}\"",
50
+ "format:check": "bunx prettier@3.3.3 --check \"**/*.{ts,json,yml,yaml}\"",
49
51
  "release:dry": "bun run check && bun publish --dry-run",
50
52
  "release": "bun run check && bun publish --access public"
51
53
  },