voicecc 1.0.8 → 1.0.9

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/bin/voicecc.js CHANGED
@@ -3,31 +3,28 @@
3
3
  /**
4
4
  * CLI entry point for the voicecc command.
5
5
  *
6
- * Resolves the package install directory and spawns `tsx run.ts`
7
- * with inherited stdio so the dashboard server runs in the foreground.
8
- *
9
- * Responsibilities:
10
- * - Resolve the package root from this script's location
11
- * - Spawn tsx with run.ts in the correct working directory
12
- * - Forward signals (SIGINT, SIGTERM) so Ctrl+C stops the server cleanly
6
+ * Checks if first-run setup is needed (compile mic-vpio, Python venv, etc.)
7
+ * and runs it with visible output. Then spawns `tsx run.ts` for the dashboard.
13
8
  */
14
9
 
15
10
  import { spawn } from "node:child_process";
16
11
  import { dirname, join } from "node:path";
17
12
  import { fileURLToPath } from "node:url";
18
13
 
19
- // ============================================================================
20
- // CONSTANTS
21
- // ============================================================================
22
-
23
14
  const __dirname = dirname(fileURLToPath(import.meta.url));
24
15
  const PKG_ROOT = join(__dirname, "..");
25
16
  const TSX_BIN = join(PKG_ROOT, "node_modules", ".bin", "tsx");
26
17
 
27
- // ============================================================================
28
- // MAIN ENTRYPOINT
29
- // ============================================================================
18
+ // Run setup if needed (first run or incomplete install)
19
+ process.chdir(PKG_ROOT);
20
+ const { needsSetup, runSetup } = await import("../scripts/postinstall.js");
21
+
22
+ if (needsSetup()) {
23
+ console.log("[voicecc] Running first-time setup...\n");
24
+ runSetup();
25
+ }
30
26
 
27
+ // Start the dashboard
31
28
  const child = spawn(TSX_BIN, ["run.ts"], {
32
29
  cwd: PKG_ROOT,
33
30
  stdio: "inherit",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "voicecc",
3
- "version": "1.0.8",
3
+ "version": "1.0.9",
4
4
  "description": "Voice mode plugin for Claude Code -- hands-free interaction via local STT/TTS/VAD",
5
5
  "type": "module",
6
6
  "bin": {
@@ -10,8 +10,7 @@
10
10
  "start": "tsx run.ts",
11
11
  "dev:dashboard": "cd dashboard && npx vite",
12
12
  "build:dashboard": "cd dashboard && npx vite build",
13
- "prepublishOnly": "npm run build:dashboard",
14
- "postinstall": "node scripts/postinstall.js"
13
+ "prepublishOnly": "npm run build:dashboard"
15
14
  },
16
15
  "files": [
17
16
  "bin/",
@@ -1,16 +1,11 @@
1
1
  /**
2
- * Postinstall script that runs after `npm install`.
2
+ * Setup script for voicecc.
3
3
  *
4
4
  * Compiles the mic-vpio Swift binary (macOS VPIO echo cancellation),
5
5
  * checks for required system dependencies (espeak-ng), then sets up
6
6
  * the Python virtual environment and installs TTS dependencies.
7
7
  *
8
- * Responsibilities:
9
- * - Compile the mic-vpio Swift binary for echo-cancelled audio I/O
10
- * - Verify espeak-ng is installed
11
- * - Create Python venv in sidecar/.venv (if not already present)
12
- * - Install Python TTS packages (mlx-audio, misaki, etc.)
13
- * - Download spaCy English model
8
+ * Called from bin/voicecc.js on first run (or when setup is incomplete).
14
9
  */
15
10
 
16
11
  import { execSync } from "child_process";
@@ -24,6 +19,7 @@ import { join } from "path";
24
19
  const VENV_DIR = join("sidecar", ".venv");
25
20
  const PIP = join(VENV_DIR, "bin", "pip");
26
21
  const PYTHON = join(VENV_DIR, "bin", "python3");
22
+ const MIC_VPIO = join("sidecar", "mic-vpio");
27
23
 
28
24
  const PYTHON_PACKAGES = [
29
25
  "mlx-audio",
@@ -34,10 +30,24 @@ const PYTHON_PACKAGES = [
34
30
  ];
35
31
 
36
32
  // ============================================================================
37
- // MAIN ENTRYPOINT
33
+ // PUBLIC API
38
34
  // ============================================================================
39
35
 
40
- function main() {
36
+ /**
37
+ * Returns true if any setup step is incomplete.
38
+ */
39
+ export function needsSetup() {
40
+ return (
41
+ !existsSync(MIC_VPIO) ||
42
+ !existsSync(PYTHON) ||
43
+ !existsSync(join("dashboard", "dist", "index.html"))
44
+ );
45
+ }
46
+
47
+ /**
48
+ * Run all setup steps. Shows progress to stdout.
49
+ */
50
+ export function runSetup() {
41
51
  installClaudeMd();
42
52
  buildDashboard();
43
53
  compileMicVpio();
@@ -48,20 +58,15 @@ function main() {
48
58
 
49
59
  console.log("");
50
60
  console.log("========================================");
51
- console.log(" VOICECC INSTALLED ");
61
+ console.log(" VOICECC SETUP COMPLETE ");
52
62
  console.log("========================================");
53
63
  console.log("");
54
- console.log(" Run 'voicecc' in terminal to start the server!");
55
- console.log("");
56
64
  }
57
65
 
58
66
  // ============================================================================
59
67
  // HELPER FUNCTIONS
60
68
  // ============================================================================
61
69
 
62
- /**
63
- * Build the dashboard frontend via Vite.
64
- */
65
70
  function buildDashboard() {
66
71
  if (existsSync(join("dashboard", "dist", "index.html"))) {
67
72
  console.log("Dashboard already built, skipping.");
@@ -78,16 +83,11 @@ function buildDashboard() {
78
83
  console.log("Dashboard built successfully");
79
84
  }
80
85
 
81
- /**
82
- * Copy the project CLAUDE.md from init/ to the project root.
83
- * Overwrites any existing CLAUDE.md to keep it in sync with the repo.
84
- */
85
86
  function installClaudeMd() {
86
87
  const src = join("init", "CLAUDE.md");
87
88
  const dest = "CLAUDE.md";
88
89
 
89
90
  if (!existsSync(src)) {
90
- console.warn("init/CLAUDE.md not found, skipping.");
91
91
  return;
92
92
  }
93
93
 
@@ -95,13 +95,13 @@ function installClaudeMd() {
95
95
  console.log("Installed CLAUDE.md to project root.");
96
96
  }
97
97
 
98
- /**
99
- * Compile the mic-vpio Swift binary for macOS VPIO echo cancellation.
100
- * Skips compilation if the binary already exists and is newer than the source.
101
- */
102
98
  function compileMicVpio() {
99
+ if (existsSync(MIC_VPIO)) {
100
+ console.log("mic-vpio already compiled, skipping.");
101
+ return;
102
+ }
103
+
103
104
  const source = join("sidecar", "mic-vpio.swift");
104
- const binary = join("sidecar", "mic-vpio");
105
105
 
106
106
  if (process.platform !== "darwin") {
107
107
  console.error("\n[voicecc] ERROR: macOS is required.");
@@ -118,7 +118,7 @@ function compileMicVpio() {
118
118
 
119
119
  console.log("Compiling mic-vpio (VPIO echo cancellation)...");
120
120
  try {
121
- run(`swiftc -O -o ${binary} ${source} -framework AudioToolbox -framework CoreAudio`);
121
+ run(`swiftc -O -o ${MIC_VPIO} ${source} -framework AudioToolbox -framework CoreAudio`);
122
122
  } catch (err) {
123
123
  console.error("\n[voicecc] ERROR: Failed to compile mic-vpio.swift.");
124
124
  console.error(" Make sure Xcode Command Line Tools are installed: xcode-select --install\n");
@@ -127,10 +127,6 @@ function compileMicVpio() {
127
127
  console.log("mic-vpio compiled successfully");
128
128
  }
129
129
 
130
- /**
131
- * Check that required system binaries are available.
132
- * Exits with a clear error message if any are missing.
133
- */
134
130
  function checkSystemDeps() {
135
131
  const missing = [];
136
132
 
@@ -139,16 +135,13 @@ function checkSystemDeps() {
139
135
  if (missing.length > 0) {
140
136
  console.error(`\n[voicecc] ERROR: Missing system dependencies: ${missing.join(", ")}`);
141
137
  console.error(` Install with: brew install ${missing.join(" ")}`);
142
- console.error(` Then re-run: npm install\n`);
138
+ console.error(` Then re-run: voicecc\n`);
143
139
  process.exit(1);
144
140
  }
145
141
 
146
142
  console.log("System dependencies OK (espeak-ng)");
147
143
  }
148
144
 
149
- /**
150
- * Create the Python virtual environment if it doesn't exist.
151
- */
152
145
  function setupPythonVenv() {
153
146
  if (existsSync(PIP)) {
154
147
  console.log(`Python venv already exists at ${VENV_DIR}`);
@@ -171,9 +164,6 @@ function setupPythonVenv() {
171
164
  }
172
165
  }
173
166
 
174
- /**
175
- * Install Python TTS packages into the venv.
176
- */
177
167
  function installPythonPackages() {
178
168
  console.log("Installing Python TTS packages...");
179
169
  try {
@@ -182,14 +172,11 @@ function installPythonPackages() {
182
172
  console.error("\n[voicecc] ERROR: Failed to install Python TTS packages.");
183
173
  console.error(" This may be due to missing build tools or incompatible Python version.");
184
174
  console.error(" Required packages: " + PYTHON_PACKAGES.join(", "));
185
- console.error(" Try deleting sidecar/.venv and re-running: npm install\n");
175
+ console.error(" Try deleting sidecar/.venv and re-running: voicecc\n");
186
176
  process.exit(1);
187
177
  }
188
178
  }
189
179
 
190
- /**
191
- * Download the spaCy English language model.
192
- */
193
180
  function downloadSpacyModel() {
194
181
  console.log("Downloading spaCy English model...");
195
182
  try {
@@ -201,12 +188,6 @@ function downloadSpacyModel() {
201
188
  }
202
189
  }
203
190
 
204
- /**
205
- * Check if a command exists on the system PATH.
206
- *
207
- * @param {string} cmd - Command name to check
208
- * @returns {boolean} True if the command exists
209
- */
210
191
  function commandExists(cmd) {
211
192
  try {
212
193
  execSync(`which ${cmd}`, { stdio: "ignore" });
@@ -216,17 +197,6 @@ function commandExists(cmd) {
216
197
  }
217
198
  }
218
199
 
219
- /**
220
- * Run a shell command with inherited stdio (output visible to user).
221
- *
222
- * @param {string} cmd - Shell command to execute
223
- */
224
200
  function run(cmd) {
225
201
  execSync(cmd, { stdio: "inherit" });
226
202
  }
227
-
228
- // ============================================================================
229
- // ENTRY POINT
230
- // ============================================================================
231
-
232
- main();