@mclean-capital/neura 3.1.0 → 3.2.0
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 +8 -20
- package/core/version.txt +1 -1
- package/dist/audio/capture.d.ts +5 -2
- package/dist/audio/capture.d.ts.map +1 -1
- package/dist/audio/capture.js +127 -31
- package/dist/audio/capture.js.map +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -21,28 +21,15 @@ Requires **Node.js >= 22**.
|
|
|
21
21
|
| Platform | Supported |
|
|
22
22
|
| ----------------------------------- | :-------: |
|
|
23
23
|
| macOS — Apple Silicon (M1/M2/M3/M4) | Yes |
|
|
24
|
-
| macOS — Intel (x64) |
|
|
24
|
+
| macOS — Intel (x64) | Yes |
|
|
25
25
|
| Windows — x64 / arm64 | Yes |
|
|
26
26
|
| Linux — x64 / arm64 | Yes |
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
`Cannot find module '../bin/napi-v6/darwin/x64/onnxruntime_binding.node'`.
|
|
34
|
-
|
|
35
|
-
If you're on an Apple Silicon Mac but see the `darwin/x64` error, you've
|
|
36
|
-
installed the Intel build of Node under Rosetta. Reinstall Node as arm64:
|
|
37
|
-
|
|
38
|
-
```bash
|
|
39
|
-
nvm uninstall <version>
|
|
40
|
-
arch -arm64 nvm install <version>
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
For true Intel Macs, there's no workaround short of self-building against
|
|
44
|
-
an older onnxruntime-node — we recommend running Neura on a supported
|
|
45
|
-
machine instead.
|
|
28
|
+
All platforms are fully supported. On Intel Macs, native binaries for
|
|
29
|
+
ONNX runtime and PortAudio (decibri) are unavailable, so Neura
|
|
30
|
+
automatically falls back to WASM-based wake-word detection and
|
|
31
|
+
`@picovoice/pvrecorder-node` for mic capture. No manual configuration
|
|
32
|
+
needed — the fallbacks are transparent.
|
|
46
33
|
|
|
47
34
|
## Quick start
|
|
48
35
|
|
|
@@ -134,7 +121,8 @@ After this one-time step, `neura update` works normally for all future upgrades.
|
|
|
134
121
|
|
|
135
122
|
`neura listen` uses optional native audio dependencies:
|
|
136
123
|
|
|
137
|
-
- **`decibri`** — microphone capture via PortAudio
|
|
124
|
+
- **`decibri`** (optional) — microphone capture via PortAudio (arm64 prebuilts)
|
|
125
|
+
- **`@picovoice/pvrecorder-node`** — microphone capture fallback (prebuilts for all platforms incl. Intel Mac)
|
|
138
126
|
- **`speaker`**, **`@picovoice/pvspeaker-node`**, or **`sox`** — speaker playback
|
|
139
127
|
|
|
140
128
|
These are marked as `optionalDependencies` so `npm install -g @mclean-capital/neura` won't fail if your platform lacks the required build tools. If audio init fails at runtime, `neura listen` will print install instructions for your platform.
|
package/core/version.txt
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.
|
|
1
|
+
3.2.0
|
package/dist/audio/capture.d.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Audio capture abstraction for mic input.
|
|
3
3
|
*
|
|
4
|
-
* Primary:
|
|
5
|
-
*
|
|
4
|
+
* Primary: decibri (PortAudio, prebuilt arm64 binaries, 24kHz native)
|
|
5
|
+
* Fallback: @picovoice/pvrecorder-node (prebuilt for all platforms incl.
|
|
6
|
+
* Intel Mac, 16kHz fixed — resampled to 24kHz before emission)
|
|
7
|
+
*
|
|
8
|
+
* The implementation lazy-loads both so `neura chat` works without either.
|
|
6
9
|
*/
|
|
7
10
|
export interface AudioCapture {
|
|
8
11
|
start(): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"capture.d.ts","sourceRoot":"","sources":["../../src/audio/capture.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"capture.d.ts","sourceRoot":"","sources":["../../src/audio/capture.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,MAAM,WAAW,YAAY;IAC3B,KAAK,IAAI,IAAI,CAAC;IACd,IAAI,IAAI,IAAI,CAAC;IACb,MAAM,EAAE,CAAC,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC;CAC9C;AAED,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,YAAY,CAAC,CAmBhE;AAED,yCAAyC;AACzC,wBAAsB,gBAAgB,IAAI,OAAO,CAC/C;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,EAAE,CACtD,CAmBA"}
|
package/dist/audio/capture.js
CHANGED
|
@@ -1,52 +1,148 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Audio capture abstraction for mic input.
|
|
3
3
|
*
|
|
4
|
-
* Primary:
|
|
5
|
-
*
|
|
4
|
+
* Primary: decibri (PortAudio, prebuilt arm64 binaries, 24kHz native)
|
|
5
|
+
* Fallback: @picovoice/pvrecorder-node (prebuilt for all platforms incl.
|
|
6
|
+
* Intel Mac, 16kHz fixed — resampled to 24kHz before emission)
|
|
7
|
+
*
|
|
8
|
+
* The implementation lazy-loads both so `neura chat` works without either.
|
|
6
9
|
*/
|
|
7
10
|
import { audioInstallHint } from './install-hints.js';
|
|
8
11
|
export async function createAudioCapture() {
|
|
12
|
+
// 1. Try decibri (native 24kHz, event-driven)
|
|
9
13
|
try {
|
|
10
|
-
|
|
11
|
-
let onData = null;
|
|
12
|
-
let mic = null;
|
|
13
|
-
return {
|
|
14
|
-
get onData() {
|
|
15
|
-
return onData;
|
|
16
|
-
},
|
|
17
|
-
set onData(handler) {
|
|
18
|
-
onData = handler;
|
|
19
|
-
},
|
|
20
|
-
start() {
|
|
21
|
-
mic = new Decibri({ sampleRate: 24000, channels: 1, format: 'int16' });
|
|
22
|
-
mic.on('data', (chunk) => {
|
|
23
|
-
if (onData) {
|
|
24
|
-
onData(chunk.toString('base64'));
|
|
25
|
-
}
|
|
26
|
-
});
|
|
27
|
-
// Prevent unhandled 'error' from crashing the process
|
|
28
|
-
mic.on('error', (err) => {
|
|
29
|
-
console.error(`Mic error: ${err.message}`);
|
|
30
|
-
});
|
|
31
|
-
},
|
|
32
|
-
stop() {
|
|
33
|
-
mic?.stop();
|
|
34
|
-
mic = null;
|
|
35
|
-
},
|
|
36
|
-
};
|
|
14
|
+
return await createDecibriCapture();
|
|
37
15
|
}
|
|
38
16
|
catch {
|
|
39
|
-
|
|
17
|
+
// decibri unavailable — try pvrecorder
|
|
40
18
|
}
|
|
19
|
+
// 2. Try pvrecorder (16kHz polling, resampled to 24kHz)
|
|
20
|
+
try {
|
|
21
|
+
return await createPvRecorderCapture();
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
// pvrecorder also unavailable
|
|
25
|
+
}
|
|
26
|
+
throw new Error('Mic capture requires "decibri" or "@picovoice/pvrecorder-node".\n' +
|
|
27
|
+
audioInstallHint('decibri'));
|
|
41
28
|
}
|
|
42
29
|
/** List available audio input devices */
|
|
43
30
|
export async function listInputDevices() {
|
|
31
|
+
// Try decibri first (richer device info)
|
|
44
32
|
try {
|
|
45
33
|
const { default: Decibri } = await import('decibri');
|
|
46
34
|
return Decibri.devices();
|
|
47
35
|
}
|
|
48
36
|
catch {
|
|
49
|
-
|
|
37
|
+
// fall through
|
|
38
|
+
}
|
|
39
|
+
// Try pvrecorder (returns string[] of device names)
|
|
40
|
+
try {
|
|
41
|
+
const { PvRecorder } = await import('@picovoice/pvrecorder-node');
|
|
42
|
+
const names = PvRecorder.getAvailableDevices();
|
|
43
|
+
return names.map((name, index) => ({ index, name, isDefault: index === 0 }));
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
// fall through
|
|
47
|
+
}
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
// ── Decibri backend ─────────────────────────────────────────────
|
|
51
|
+
async function createDecibriCapture() {
|
|
52
|
+
const { default: Decibri } = await import('decibri');
|
|
53
|
+
let onData = null;
|
|
54
|
+
let mic = null;
|
|
55
|
+
return {
|
|
56
|
+
get onData() {
|
|
57
|
+
return onData;
|
|
58
|
+
},
|
|
59
|
+
set onData(handler) {
|
|
60
|
+
onData = handler;
|
|
61
|
+
},
|
|
62
|
+
start() {
|
|
63
|
+
mic = new Decibri({ sampleRate: 24000, channels: 1, format: 'int16' });
|
|
64
|
+
mic.on('data', (chunk) => {
|
|
65
|
+
if (onData) {
|
|
66
|
+
onData(chunk.toString('base64'));
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
mic.on('error', (err) => {
|
|
70
|
+
console.error(`Mic error: ${err.message}`);
|
|
71
|
+
});
|
|
72
|
+
},
|
|
73
|
+
stop() {
|
|
74
|
+
mic?.stop();
|
|
75
|
+
mic = null;
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
// ── PvRecorder backend ──────────────────────────────────────────
|
|
80
|
+
/** Frame length in samples at 16kHz (~32ms per frame) */
|
|
81
|
+
const PV_FRAME_LENGTH = 512;
|
|
82
|
+
async function createPvRecorderCapture() {
|
|
83
|
+
const { PvRecorder } = await import('@picovoice/pvrecorder-node');
|
|
84
|
+
let onData = null;
|
|
85
|
+
let recorder = null;
|
|
86
|
+
let running = false;
|
|
87
|
+
async function pollLoop() {
|
|
88
|
+
while (running && recorder) {
|
|
89
|
+
try {
|
|
90
|
+
const frame = await recorder.read(); // Int16Array, 16kHz
|
|
91
|
+
if (!running || !onData)
|
|
92
|
+
continue;
|
|
93
|
+
const resampled = resample16kTo24k(frame);
|
|
94
|
+
onData(resampled.toString('base64'));
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
// recorder was stopped or released mid-read
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
get onData() {
|
|
104
|
+
return onData;
|
|
105
|
+
},
|
|
106
|
+
set onData(handler) {
|
|
107
|
+
onData = handler;
|
|
108
|
+
},
|
|
109
|
+
start() {
|
|
110
|
+
recorder = new PvRecorder(PV_FRAME_LENGTH);
|
|
111
|
+
running = true;
|
|
112
|
+
recorder.start();
|
|
113
|
+
void pollLoop();
|
|
114
|
+
},
|
|
115
|
+
stop() {
|
|
116
|
+
running = false;
|
|
117
|
+
if (recorder) {
|
|
118
|
+
try {
|
|
119
|
+
recorder.stop();
|
|
120
|
+
recorder.release();
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
/* already released */
|
|
124
|
+
}
|
|
125
|
+
recorder = null;
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Resample Int16 PCM from 16kHz to 24kHz using linear interpolation.
|
|
132
|
+
* Returns a Buffer of Int16LE samples ready for base64 encoding.
|
|
133
|
+
*/
|
|
134
|
+
function resample16kTo24k(input) {
|
|
135
|
+
const ratio = 16000 / 24000; // 0.6667
|
|
136
|
+
const outputLen = Math.ceil(input.length / ratio);
|
|
137
|
+
const output = Buffer.alloc(outputLen * 2);
|
|
138
|
+
for (let i = 0; i < outputLen; i++) {
|
|
139
|
+
const srcIdx = i * ratio;
|
|
140
|
+
const lo = Math.floor(srcIdx);
|
|
141
|
+
const hi = Math.min(lo + 1, input.length - 1);
|
|
142
|
+
const frac = srcIdx - lo;
|
|
143
|
+
const sample = Math.round(input[lo] * (1 - frac) + input[hi] * frac);
|
|
144
|
+
output.writeInt16LE(Math.max(-32768, Math.min(32767, sample)), i * 2);
|
|
50
145
|
}
|
|
146
|
+
return output;
|
|
51
147
|
}
|
|
52
148
|
//# sourceMappingURL=capture.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"capture.js","sourceRoot":"","sources":["../../src/audio/capture.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"capture.js","sourceRoot":"","sources":["../../src/audio/capture.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mclean-capital/neura",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.2.0",
|
|
4
4
|
"description": "Neura — CLI for installing and managing the Neura AI assistant core service. Includes text chat and voice listen clients.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"neura",
|
|
@@ -53,6 +53,7 @@
|
|
|
53
53
|
"@electric-sql/pglite": "0.4.3",
|
|
54
54
|
"@inquirer/prompts": "^7",
|
|
55
55
|
"@mariozechner/pi-coding-agent": "^0.66.1",
|
|
56
|
+
"@picovoice/pvrecorder-node": "^1.2.8",
|
|
56
57
|
"chalk": "^5",
|
|
57
58
|
"chokidar": "^5.0.0",
|
|
58
59
|
"commander": "^13",
|