@staff0rd/assist 0.81.0 → 0.83.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 CHANGED
@@ -76,6 +76,7 @@ After installation, the `assist` command will be available globally.
76
76
  - `assist config get <key>` - Get a config value
77
77
  - `assist config list` - List all config values
78
78
  - `assist verify` - Run all verify:* commands in parallel (from run configs in assist.yml and scripts in package.json)
79
+ - `assist verify all` - Run all checks, ignoring diff-based filters
79
80
  - `assist verify init` - Add verify scripts to a project
80
81
  - `assist verify hardcoded-colors` - Check for hardcoded hex colors in src/ (supports `hardcodedColors.ignore` globs in config)
81
82
  - `assist lint` - Run lint checks for conventions not enforced by biomejs
@@ -3,3 +3,5 @@ description: Run all verification commands in parallel
3
3
  ---
4
4
 
5
5
  Run `assist verify 2>&1` (not npm run, not npx - just `assist verify 2>&1` directly). If it fails, fix all errors and run again until it passes.
6
+
7
+ Use `assist verify all 2>&1` to bypass diff-based filters and run every check regardless of which files changed.
@@ -24,7 +24,10 @@ class AudioCapture:
24
24
  self._queue.put(indata[:, 0].copy())
25
25
 
26
26
  def start(self) -> None:
27
- log("audio_start", f"device={self._device}, rate={SAMPLE_RATE}, block={BLOCK_SIZE}")
27
+ log(
28
+ "audio_start",
29
+ f"device={self._device}, rate={SAMPLE_RATE}, block={BLOCK_SIZE}",
30
+ )
28
31
  self._stream = sd.InputStream(
29
32
  samplerate=SAMPLE_RATE,
30
33
  channels=1,
@@ -60,12 +60,15 @@ class VoiceDaemon:
60
60
  self._running = True
61
61
  self._state = IDLE
62
62
  self._audio_buffer: list[np.ndarray] = []
63
+ self._submit_word = os.environ.get("VOICE_SUBMIT_WORD", "").strip().lower()
63
64
 
64
65
  log("daemon_init", "Initializing models...")
65
66
  self._mic = AudioCapture()
66
67
  self._vad = SileroVAD()
67
68
  self._smart_turn = SmartTurn()
68
69
  self._stt = ParakeetSTT()
70
+ if self._submit_word:
71
+ log("daemon_init", f"Submit word: '{self._submit_word}'")
69
72
  log("daemon_ready")
70
73
 
71
74
  # Incremental typing state
@@ -93,25 +96,51 @@ class VoiceDaemon:
93
96
 
94
97
  if self._state == ACTIVATED:
95
98
  # Already activated — everything is the command, no wake word needed
96
- if text.strip() != self._typed_text:
99
+ partial = self._hide_submit_word(text.strip())
100
+ if partial and partial != self._typed_text:
97
101
  if self._typed_text:
98
- self._update_typed_text(text.strip())
102
+ self._update_typed_text(partial)
99
103
  else:
100
- keyboard.type_text(text.strip())
101
- self._typed_text = text.strip()
104
+ keyboard.type_text(partial)
105
+ self._typed_text = partial
102
106
  elif not self._wake_detected:
103
107
  found, command = check_wake_word(text)
104
108
  if found and command:
105
- self._wake_detected = True
106
- log("wake_word_detected", command)
107
- if DEBUG:
108
- print(f" Wake word! Typing: {command}", file=sys.stderr)
109
- keyboard.type_text(command)
110
- self._typed_text = command
109
+ partial = self._hide_submit_word(command)
110
+ if partial:
111
+ self._wake_detected = True
112
+ log("wake_word_detected", partial)
113
+ if DEBUG:
114
+ print(f" Wake word! Typing: {partial}", file=sys.stderr)
115
+ keyboard.type_text(partial)
116
+ self._typed_text = partial
111
117
  else:
112
118
  found, command = check_wake_word(text)
113
- if found and command and command != self._typed_text:
114
- self._update_typed_text(command)
119
+ if found and command:
120
+ partial = self._hide_submit_word(command)
121
+ if partial and partial != self._typed_text:
122
+ self._update_typed_text(partial)
123
+
124
+ def _strip_submit_word(self, text: str) -> tuple[bool, str]:
125
+ """Check if text ends with the submit word.
126
+
127
+ Returns (should_submit, stripped_text).
128
+ If no submit word is configured, always returns (True, text).
129
+ """
130
+ if not self._submit_word:
131
+ return True, text
132
+ words = text.rsplit(None, 1)
133
+ if len(words) >= 1 and words[-1].lower().rstrip(".,!?") == self._submit_word:
134
+ stripped = text[: text.lower().rfind(words[-1].lower())].rstrip()
135
+ return True, stripped
136
+ return False, text
137
+
138
+ def _hide_submit_word(self, text: str) -> str:
139
+ """Strip trailing submit word from partial text so it's never typed."""
140
+ if not self._submit_word:
141
+ return text
142
+ _, stripped = self._strip_submit_word(text)
143
+ return stripped
115
144
 
116
145
  def _update_typed_text(self, new_text: str) -> None:
117
146
  """Diff old typed text vs new, backspace + type the difference."""
@@ -132,6 +161,29 @@ class VoiceDaemon:
132
161
  keyboard.type_text(to_type)
133
162
  self._typed_text = new_text
134
163
 
164
+ def _process_audio_chunk(
165
+ self, chunk, prob: float, sample_count: int, trailing_silence: int
166
+ ) -> tuple[int, int]:
167
+ """Buffer audio chunk, run partial STT, and check for segment end."""
168
+ self._audio_buffer.append(chunk)
169
+ sample_count += len(chunk)
170
+
171
+ if prob > self._vad.threshold:
172
+ trailing_silence = 0
173
+ else:
174
+ trailing_silence += 1
175
+
176
+ if sample_count - self._last_partial_at >= PARTIAL_STT_INTERVAL:
177
+ self._last_partial_at = sample_count
178
+ self._run_partial_stt()
179
+
180
+ if self._check_segment_end(sample_count, trailing_silence):
181
+ self._finalize_utterance()
182
+ sample_count = 0
183
+ trailing_silence = 0
184
+
185
+ return sample_count, trailing_silence
186
+
135
187
  def _check_segment_end(self, sample_count: int, trailing_silence: int) -> bool:
136
188
  """Check if the current segment is done.
137
189
 
@@ -160,6 +212,27 @@ class VoiceDaemon:
160
212
  log("smart_turn_incomplete", "Continuing to listen...")
161
213
  return False
162
214
 
215
+ def _dispatch_result(self, should_submit: bool, stripped: str) -> None:
216
+ """Log and optionally submit a recognized command."""
217
+ if stripped:
218
+ if should_submit:
219
+ log("dispatch_enter", stripped)
220
+ if DEBUG:
221
+ print(f" Final: {stripped} [Enter]", file=sys.stderr)
222
+ keyboard.press_enter()
223
+ else:
224
+ log("dispatch_typed", stripped)
225
+ if DEBUG:
226
+ print(f" Final: {stripped} (no submit)", file=sys.stderr)
227
+ elif should_submit:
228
+ # Submit word only — erase it and press enter
229
+ if self._typed_text:
230
+ keyboard.backspace(len(self._typed_text))
231
+ log("dispatch_enter", "(submit word only)")
232
+ if DEBUG:
233
+ print(" Submit word only [Enter]", file=sys.stderr)
234
+ keyboard.press_enter()
235
+
163
236
  def _finalize_utterance(self) -> None:
164
237
  """End of turn: final STT, correct typed text, press Enter."""
165
238
  if not self._audio_buffer:
@@ -179,15 +252,14 @@ class VoiceDaemon:
179
252
  # Activated mode — full text is the command
180
253
  command = text.strip()
181
254
  if command:
182
- if command != self._typed_text:
183
- if self._typed_text:
184
- self._update_typed_text(command)
185
- else:
186
- keyboard.type_text(command)
187
- log("dispatch_enter", command)
188
- if DEBUG:
189
- print(f" Final: {command} [Enter]", file=sys.stderr)
190
- keyboard.press_enter()
255
+ should_submit, stripped = self._strip_submit_word(command)
256
+ if stripped:
257
+ if stripped != self._typed_text:
258
+ if self._typed_text:
259
+ self._update_typed_text(stripped)
260
+ else:
261
+ keyboard.type_text(stripped)
262
+ self._dispatch_result(should_submit, stripped)
191
263
  else:
192
264
  if self._typed_text:
193
265
  keyboard.backspace(len(self._typed_text))
@@ -199,30 +271,50 @@ class VoiceDaemon:
199
271
  # Correct final text and submit
200
272
  found, command = check_wake_word(text)
201
273
  if found and command:
202
- if command != self._typed_text:
203
- self._update_typed_text(command)
204
- log("dispatch_enter", command)
205
- if DEBUG:
206
- print(f" Final: {command} [Enter]", file=sys.stderr)
207
- keyboard.press_enter()
208
- elif self._typed_text:
209
- # Wake word but no command — clear what we typed
210
- keyboard.backspace(len(self._typed_text))
274
+ should_submit, stripped = self._strip_submit_word(command)
275
+ if stripped:
276
+ if stripped != self._typed_text:
277
+ self._update_typed_text(stripped)
278
+ self._dispatch_result(should_submit, stripped)
279
+ elif found:
280
+ # Wake word found but no command text after it
281
+ if self._typed_text:
282
+ keyboard.backspace(len(self._typed_text))
211
283
  log("dispatch_cancelled", "No command after wake word")
284
+ elif self._typed_text:
285
+ # Final transcription lost the wake word (e.g. audio clipping
286
+ # turned "computer" into "uter"); fall back to the command
287
+ # captured during streaming
288
+ should_submit, stripped = self._strip_submit_word(self._typed_text)
289
+ self._dispatch_result(should_submit, stripped or self._typed_text)
212
290
  else:
213
291
  # Check final transcription for wake word
214
292
  found, command = check_wake_word(text)
215
293
  if found and command:
216
- log("wake_word_detected", command)
217
- if DEBUG:
218
- print(f" Wake word! Final: {command} [Enter]", file=sys.stderr)
219
- keyboard.type_text(command)
220
- keyboard.press_enter()
221
- elif found:
294
+ should_submit, stripped = self._strip_submit_word(command)
295
+ if stripped:
296
+ log("wake_word_detected", stripped)
297
+ if DEBUG:
298
+ label = "[Enter]" if should_submit else "(no submit)"
299
+ print(
300
+ f" Wake word! Final: {stripped} {label}", file=sys.stderr
301
+ )
302
+ keyboard.type_text(stripped)
303
+ if should_submit:
304
+ keyboard.press_enter()
305
+ else:
306
+ # Submit word only — just press enter
307
+ log("dispatch_enter", "(submit word only)")
308
+ if DEBUG:
309
+ print(" Wake word + submit word only [Enter]", file=sys.stderr)
310
+ keyboard.press_enter()
311
+ if found and not command:
222
312
  # Wake word only — enter ACTIVATED state for next utterance
223
313
  log("wake_word_only", "Listening for command...")
224
314
  if DEBUG:
225
- print(" Wake word heard — listening for command...", file=sys.stderr)
315
+ print(
316
+ " Wake word heard — listening for command...", file=sys.stderr
317
+ )
226
318
  self._audio_buffer.clear()
227
319
  self._vad.reset()
228
320
  self._wake_detected = False
@@ -231,7 +323,7 @@ class VoiceDaemon:
231
323
  self._activated_at = time.monotonic()
232
324
  self._state = ACTIVATED
233
325
  return # don't reset to IDLE
234
- else:
326
+ elif not found:
235
327
  log("no_wake_word", text)
236
328
  if DEBUG:
237
329
  print(f" No wake word: {text}", file=sys.stderr)
@@ -300,42 +392,14 @@ class VoiceDaemon:
300
392
  log("speech_start", "command after activation")
301
393
 
302
394
  if prob > self._vad.threshold or self._audio_buffer:
303
- self._audio_buffer.append(chunk)
304
- sample_count += len(chunk)
305
-
306
- if prob > self._vad.threshold:
307
- trailing_silence = 0
308
- else:
309
- trailing_silence += 1
310
-
311
- # Periodic STT for incremental typing
312
- if sample_count - self._last_partial_at >= PARTIAL_STT_INTERVAL:
313
- self._last_partial_at = sample_count
314
- self._run_partial_stt()
315
-
316
- if self._check_segment_end(sample_count, trailing_silence):
317
- self._finalize_utterance()
318
- sample_count = 0
319
- trailing_silence = 0
395
+ sample_count, trailing_silence = self._process_audio_chunk(
396
+ chunk, prob, sample_count, trailing_silence
397
+ )
320
398
 
321
399
  elif self._state == LISTENING:
322
- self._audio_buffer.append(chunk)
323
- sample_count += len(chunk)
324
-
325
- if prob > self._vad.threshold:
326
- trailing_silence = 0
327
- else:
328
- trailing_silence += 1
329
-
330
- # Periodic STT for incremental typing
331
- if sample_count - self._last_partial_at >= PARTIAL_STT_INTERVAL:
332
- self._last_partial_at = sample_count
333
- self._run_partial_stt()
334
-
335
- if self._check_segment_end(sample_count, trailing_silence):
336
- self._finalize_utterance()
337
- sample_count = 0
338
- trailing_silence = 0
400
+ sample_count, trailing_silence = self._process_audio_chunk(
401
+ chunk, prob, sample_count, trailing_silence
402
+ )
339
403
 
340
404
  finally:
341
405
  if DEBUG:
@@ -344,7 +408,26 @@ class VoiceDaemon:
344
408
  log("daemon_stop", "Voice daemon stopped")
345
409
 
346
410
 
411
+ def _setup_console() -> None:
412
+ """On Windows, reopen stdout/stderr to the process's own console (CONOUT$).
413
+
414
+ When launched as a detached background process, Node.js redirects stdio to
415
+ NUL. Windows still allocates a console window for the process, but nothing
416
+ appears in it. Opening CONOUT$ gives us a handle to that console so all
417
+ debug output shows up there.
418
+ """
419
+ if sys.platform != "win32":
420
+ return
421
+ try:
422
+ con = open("CONOUT$", "w", encoding="utf-8")
423
+ sys.stdout = con
424
+ sys.stderr = con
425
+ except OSError:
426
+ pass
427
+
428
+
347
429
  def main() -> None:
430
+ _setup_console()
348
431
  log("daemon_launch", f"PID={os.getpid()}")
349
432
  try:
350
433
  daemon = VoiceDaemon()
@@ -4,7 +4,7 @@ import os
4
4
 
5
5
  from logger import log
6
6
 
7
- DEFAULT_WAKE_WORDS = ["hi claude"]
7
+ DEFAULT_WAKE_WORDS = ["computer"]
8
8
 
9
9
 
10
10
  def get_wake_words() -> list[str]:
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import { Command } from "commander";
6
6
  // package.json
7
7
  var package_default = {
8
8
  name: "@staff0rd/assist",
9
- version: "0.81.0",
9
+ version: "0.83.0",
10
10
  type: "module",
11
11
  main: "dist/index.js",
12
12
  bin: {
@@ -22,14 +22,7 @@ var package_default = {
22
22
  scripts: {
23
23
  build: "tsup",
24
24
  start: "node dist/index.js",
25
- "verify:lint": "biome check --write .",
26
- "verify:build": "tsup",
27
- "verify:types": "tsc --noEmit",
28
- "verify:knip": "knip --no-progress --treat-config-hints-as-errors",
29
- "verify:duplicate-code": "jscpd --format 'typescript,tsx,python' --exitCode 1 --ignore '**/*.test.*' -r consoleFull src",
30
- "verify:maintainability": "assist complexity maintainability ./src --threshold 70",
31
- "verify:custom-lint": "assist lint",
32
- "verify:react-build": 'esbuild src/commands/backlog/web/ui/App.tsx --bundle --minify --format=iife --target=es2020 --outfile=dist/commands/backlog/web/bundle.js --jsx=automatic --jsx-import-source=react "--define:process.env.NODE_ENV=\\"production\\""'
25
+ "check:types": "tsc --noEmit"
33
26
  },
34
27
  keywords: [],
35
28
  author: "",
@@ -90,7 +83,8 @@ import { z } from "zod";
90
83
  var runConfigSchema = z.strictObject({
91
84
  name: z.string(),
92
85
  command: z.string(),
93
- args: z.array(z.string()).optional()
86
+ args: z.array(z.string()).optional(),
87
+ filter: z.string().optional()
94
88
  });
95
89
  var transcriptConfigSchema = z.strictObject({
96
90
  vttDir: z.string(),
@@ -132,11 +126,12 @@ var assistConfigSchema = z.strictObject({
132
126
  run: z.array(runConfigSchema).optional(),
133
127
  transcript: transcriptConfigSchema.optional(),
134
128
  voice: z.strictObject({
135
- wakeWords: z.array(z.string()).default(["claude"]),
129
+ wakeWords: z.array(z.string()).default(["computer"]),
136
130
  mic: z.string().optional(),
137
131
  cwd: z.string().optional(),
138
132
  modelsDir: z.string().optional(),
139
133
  lockDir: z.string().optional(),
134
+ submitWord: z.string().optional(),
140
135
  models: z.strictObject({
141
136
  vad: z.string().optional(),
142
137
  smartTurn: z.string().optional(),
@@ -1347,7 +1342,7 @@ function lint() {
1347
1342
  }
1348
1343
 
1349
1344
  // src/commands/new/registerNew/newCli/index.ts
1350
- import { execSync as execSync8 } from "child_process";
1345
+ import { execSync as execSync9 } from "child_process";
1351
1346
  import { basename as basename2, resolve } from "path";
1352
1347
 
1353
1348
  // src/commands/verify/hardcodedColors.ts
@@ -1407,7 +1402,8 @@ function getRunEntries() {
1407
1402
  if (!run3) return [];
1408
1403
  return run3.filter((r) => r.name.startsWith("verify:")).map((r) => ({
1409
1404
  name: r.name,
1410
- fullCommand: buildFullCommand(r.command, r.args)
1405
+ fullCommand: buildFullCommand(r.command, r.args),
1406
+ filter: r.filter
1411
1407
  }));
1412
1408
  }
1413
1409
  function getPackageJsonEntries() {
@@ -1478,6 +1474,32 @@ function initTaskStatuses(scripts) {
1478
1474
  return scripts.map((script) => ({ script, startTime: Date.now() }));
1479
1475
  }
1480
1476
 
1477
+ // src/commands/verify/run/filterByChangedFiles.ts
1478
+ import { minimatch as minimatch2 } from "minimatch";
1479
+
1480
+ // src/commands/verify/run/getChangedFiles.ts
1481
+ import { execSync as execSync6 } from "child_process";
1482
+ function getChangedFiles() {
1483
+ const output = execSync6("git diff --name-only HEAD", {
1484
+ encoding: "utf-8"
1485
+ }).trim();
1486
+ if (output === "") return [];
1487
+ return output.split("\n");
1488
+ }
1489
+
1490
+ // src/commands/verify/run/filterByChangedFiles.ts
1491
+ function filterByChangedFiles(entries) {
1492
+ const hasFilters = entries.some((e) => e.filter);
1493
+ if (!hasFilters) return entries;
1494
+ const changedFiles = getChangedFiles();
1495
+ return entries.filter((entry) => {
1496
+ const { filter } = entry;
1497
+ if (!filter) return true;
1498
+ if (changedFiles.length === 0) return false;
1499
+ return changedFiles.some((file) => minimatch2(file, filter));
1500
+ });
1501
+ }
1502
+
1481
1503
  // src/commands/verify/run/spawnCommand.ts
1482
1504
  import { spawn } from "child_process";
1483
1505
  var isClaudeCode = !!process.env.CLAUDECODE;
@@ -1547,31 +1569,36 @@ async function run(options2 = {}) {
1547
1569
  console.log("No verify commands found");
1548
1570
  return;
1549
1571
  }
1550
- printEntryList(allEntries);
1551
- const results = await runAllEntries(allEntries, options2.timer ?? false);
1552
- handleResults(results, allEntries.length);
1572
+ const entries = options2.all ? allEntries : filterByChangedFiles(allEntries);
1573
+ if (entries.length === 0) {
1574
+ console.log("No verify commands matched changed files \u2014 skipping");
1575
+ return;
1576
+ }
1577
+ printEntryList(entries);
1578
+ const results = await runAllEntries(entries, options2.timer ?? false);
1579
+ handleResults(results, entries.length);
1553
1580
  }
1554
1581
 
1555
1582
  // src/commands/new/registerNew/initGit.ts
1556
- import { execSync as execSync6 } from "child_process";
1583
+ import { execSync as execSync7 } from "child_process";
1557
1584
  import { writeFileSync as writeFileSync6 } from "fs";
1558
1585
  function initGit() {
1559
1586
  console.log("Initializing git repository...");
1560
- execSync6("git init", { stdio: "inherit" });
1587
+ execSync7("git init", { stdio: "inherit" });
1561
1588
  writeFileSync6(".gitignore", "dist\nnode_modules\n");
1562
1589
  }
1563
1590
 
1564
1591
  // src/commands/new/registerNew/newCli/initPackageJson.ts
1565
- import { execSync as execSync7 } from "child_process";
1592
+ import { execSync as execSync8 } from "child_process";
1566
1593
  function initPackageJson(name) {
1567
1594
  console.log("Initializing package.json...");
1568
- execSync7("npm init -y", { stdio: "inherit" });
1595
+ execSync8("npm init -y", { stdio: "inherit" });
1569
1596
  console.log("Configuring package.json...");
1570
- execSync7("npm pkg delete main", { stdio: "inherit" });
1571
- execSync7("npm pkg set type=module", { stdio: "inherit" });
1572
- execSync7(`npm pkg set bin.${name}=./dist/index.js`, { stdio: "inherit" });
1573
- execSync7("npm pkg set scripts.build=tsup", { stdio: "inherit" });
1574
- execSync7('npm pkg set scripts.start="node dist/index.js"', {
1597
+ execSync8("npm pkg delete main", { stdio: "inherit" });
1598
+ execSync8("npm pkg set type=module", { stdio: "inherit" });
1599
+ execSync8(`npm pkg set bin.${name}=./dist/index.js`, { stdio: "inherit" });
1600
+ execSync8("npm pkg set scripts.build=tsup", { stdio: "inherit" });
1601
+ execSync8('npm pkg set scripts.start="node dist/index.js"', {
1575
1602
  stdio: "inherit"
1576
1603
  });
1577
1604
  }
@@ -1636,8 +1663,8 @@ async function newCli() {
1636
1663
  initGit();
1637
1664
  initPackageJson(name);
1638
1665
  console.log("Installing dependencies...");
1639
- execSync8("npm install commander", { stdio: "inherit" });
1640
- execSync8("npm install -D tsup typescript @types/node", {
1666
+ execSync9("npm install commander", { stdio: "inherit" });
1667
+ execSync9("npm install -D tsup typescript @types/node", {
1641
1668
  stdio: "inherit"
1642
1669
  });
1643
1670
  writeCliTemplate(name);
@@ -1646,11 +1673,11 @@ async function newCli() {
1646
1673
  }
1647
1674
 
1648
1675
  // src/commands/new/registerNew/newProject.ts
1649
- import { execSync as execSync10 } from "child_process";
1676
+ import { execSync as execSync11 } from "child_process";
1650
1677
  import { existsSync as existsSync10, readFileSync as readFileSync8, writeFileSync as writeFileSync9 } from "fs";
1651
1678
 
1652
1679
  // src/commands/deploy/init/index.ts
1653
- import { execSync as execSync9 } from "child_process";
1680
+ import { execSync as execSync10 } from "child_process";
1654
1681
  import chalk21 from "chalk";
1655
1682
  import enquirer3 from "enquirer";
1656
1683
 
@@ -1703,7 +1730,7 @@ Created ${WORKFLOW_PATH}`));
1703
1730
  // src/commands/deploy/init/index.ts
1704
1731
  async function ensureNetlifyCli() {
1705
1732
  try {
1706
- execSync9("netlify sites:create --disable-linking", { stdio: "inherit" });
1733
+ execSync10("netlify sites:create --disable-linking", { stdio: "inherit" });
1707
1734
  } catch (error) {
1708
1735
  if (!(error instanceof Error) || !error.message.includes("command not found"))
1709
1736
  throw error;
@@ -1718,9 +1745,9 @@ async function ensureNetlifyCli() {
1718
1745
  process.exit(1);
1719
1746
  }
1720
1747
  console.log(chalk21.dim("\nInstalling netlify-cli...\n"));
1721
- execSync9("npm install -g netlify-cli", { stdio: "inherit" });
1748
+ execSync10("npm install -g netlify-cli", { stdio: "inherit" });
1722
1749
  console.log();
1723
- execSync9("netlify sites:create --disable-linking", { stdio: "inherit" });
1750
+ execSync10("netlify sites:create --disable-linking", { stdio: "inherit" });
1724
1751
  }
1725
1752
  }
1726
1753
  function printSetupInstructions() {
@@ -1763,7 +1790,7 @@ async function init5() {
1763
1790
  // src/commands/new/registerNew/newProject.ts
1764
1791
  async function newProject() {
1765
1792
  console.log("Initializing Vite with react-ts template...");
1766
- execSync10("npm create vite@latest . -- --template react-ts", {
1793
+ execSync11("npm create vite@latest . -- --template react-ts", {
1767
1794
  stdio: "inherit"
1768
1795
  });
1769
1796
  initGit();
@@ -2401,11 +2428,11 @@ import ts5 from "typescript";
2401
2428
  // src/commands/complexity/findSourceFiles.ts
2402
2429
  import fs10 from "fs";
2403
2430
  import path15 from "path";
2404
- import { minimatch as minimatch2 } from "minimatch";
2431
+ import { minimatch as minimatch3 } from "minimatch";
2405
2432
  function applyIgnoreGlobs(files) {
2406
2433
  const { complexity } = loadConfig();
2407
2434
  return files.filter(
2408
- (f) => !complexity.ignore.some((glob) => minimatch2(f, glob))
2435
+ (f) => !complexity.ignore.some((glob) => minimatch3(f, glob))
2409
2436
  );
2410
2437
  }
2411
2438
  function walk(dir, results) {
@@ -2429,7 +2456,7 @@ function findSourceFiles2(pattern2, baseDir = ".") {
2429
2456
  const results = [];
2430
2457
  if (pattern2.includes("*")) {
2431
2458
  walk(baseDir, results);
2432
- return applyIgnoreGlobs(results.filter((f) => minimatch2(f, pattern2)));
2459
+ return applyIgnoreGlobs(results.filter((f) => minimatch3(f, pattern2)));
2433
2460
  }
2434
2461
  if (fs10.existsSync(pattern2) && fs10.statSync(pattern2).isFile()) {
2435
2462
  return [pattern2];
@@ -2439,7 +2466,7 @@ function findSourceFiles2(pattern2, baseDir = ".") {
2439
2466
  return applyIgnoreGlobs(results);
2440
2467
  }
2441
2468
  walk(baseDir, results);
2442
- return applyIgnoreGlobs(results.filter((f) => minimatch2(f, pattern2)));
2469
+ return applyIgnoreGlobs(results.filter((f) => minimatch3(f, pattern2)));
2443
2470
  }
2444
2471
 
2445
2472
  // src/commands/complexity/shared/getNodeName.ts
@@ -2916,11 +2943,11 @@ function registerDeploy(program2) {
2916
2943
  }
2917
2944
 
2918
2945
  // src/commands/devlog/list/index.ts
2919
- import { execSync as execSync12 } from "child_process";
2946
+ import { execSync as execSync13 } from "child_process";
2920
2947
  import { basename as basename3 } from "path";
2921
2948
 
2922
2949
  // src/commands/devlog/shared.ts
2923
- import { execSync as execSync11 } from "child_process";
2950
+ import { execSync as execSync12 } from "child_process";
2924
2951
  import chalk37 from "chalk";
2925
2952
 
2926
2953
  // src/commands/devlog/loadDevlogEntries.ts
@@ -2964,7 +2991,7 @@ function loadDevlogEntries(repoName) {
2964
2991
  // src/commands/devlog/shared.ts
2965
2992
  function getCommitFiles(hash) {
2966
2993
  try {
2967
- const output = execSync11(`git show --name-only --format="" ${hash}`, {
2994
+ const output = execSync12(`git show --name-only --format="" ${hash}`, {
2968
2995
  encoding: "utf-8"
2969
2996
  });
2970
2997
  return output.trim().split("\n").filter(Boolean);
@@ -3035,7 +3062,7 @@ function list3(options2) {
3035
3062
  const devlogEntries = loadDevlogEntries(repoName);
3036
3063
  const reverseFlag = options2.reverse ? "--reverse " : "";
3037
3064
  const limitFlag = options2.reverse ? "" : "-n 500 ";
3038
- const output = execSync12(
3065
+ const output = execSync13(
3039
3066
  `git log ${reverseFlag}${limitFlag}--pretty=format:'%ad|%h|%s' --date=short`,
3040
3067
  { encoding: "utf-8" }
3041
3068
  );
@@ -3061,11 +3088,11 @@ function list3(options2) {
3061
3088
  }
3062
3089
 
3063
3090
  // src/commands/devlog/getLastVersionInfo.ts
3064
- import { execSync as execSync13 } from "child_process";
3091
+ import { execSync as execSync14 } from "child_process";
3065
3092
  import semver from "semver";
3066
3093
  function getVersionAtCommit(hash) {
3067
3094
  try {
3068
- const content = execSync13(`git show ${hash}:package.json`, {
3095
+ const content = execSync14(`git show ${hash}:package.json`, {
3069
3096
  encoding: "utf-8"
3070
3097
  });
3071
3098
  const pkg = JSON.parse(content);
@@ -3080,7 +3107,7 @@ function stripToMinor(version2) {
3080
3107
  }
3081
3108
  function getLastVersionInfoFromGit() {
3082
3109
  try {
3083
- const output = execSync13(
3110
+ const output = execSync14(
3084
3111
  "git log -1 --pretty=format:'%ad|%h' --date=short",
3085
3112
  {
3086
3113
  encoding: "utf-8"
@@ -3123,7 +3150,7 @@ function bumpVersion(version2, type) {
3123
3150
  }
3124
3151
 
3125
3152
  // src/commands/devlog/next/displayNextEntry/index.ts
3126
- import { execSync as execSync14 } from "child_process";
3153
+ import { execSync as execSync15 } from "child_process";
3127
3154
  import chalk40 from "chalk";
3128
3155
 
3129
3156
  // src/commands/devlog/next/displayNextEntry/displayVersion.ts
@@ -3157,7 +3184,7 @@ function findTargetDate(commitsByDate, skipDays) {
3157
3184
  return Array.from(commitsByDate.keys()).filter((d) => !skipDays.has(d)).sort()[0];
3158
3185
  }
3159
3186
  function fetchCommitsByDate(ignore2, lastDate) {
3160
- const output = execSync14(
3187
+ const output = execSync15(
3161
3188
  "git log --pretty=format:'%ad|%h|%s' --date=short -n 500",
3162
3189
  { encoding: "utf-8" }
3163
3190
  );
@@ -3288,7 +3315,7 @@ import { tmpdir as tmpdir2 } from "os";
3288
3315
  import { join as join12 } from "path";
3289
3316
 
3290
3317
  // src/commands/prs/shared.ts
3291
- import { execSync as execSync15 } from "child_process";
3318
+ import { execSync as execSync16 } from "child_process";
3292
3319
  function isGhNotInstalled(error) {
3293
3320
  if (error instanceof Error) {
3294
3321
  const msg = error.message.toLowerCase();
@@ -3304,14 +3331,14 @@ function isNotFound(error) {
3304
3331
  }
3305
3332
  function getRepoInfo() {
3306
3333
  const repoInfo = JSON.parse(
3307
- execSync15("gh repo view --json owner,name", { encoding: "utf-8" })
3334
+ execSync16("gh repo view --json owner,name", { encoding: "utf-8" })
3308
3335
  );
3309
3336
  return { org: repoInfo.owner.login, repo: repoInfo.name };
3310
3337
  }
3311
3338
  function getCurrentPrNumber() {
3312
3339
  try {
3313
3340
  const prInfo = JSON.parse(
3314
- execSync15("gh pr view --json number", { encoding: "utf-8" })
3341
+ execSync16("gh pr view --json number", { encoding: "utf-8" })
3315
3342
  );
3316
3343
  return prInfo.number;
3317
3344
  } catch (error) {
@@ -3325,7 +3352,7 @@ function getCurrentPrNumber() {
3325
3352
  function getCurrentPrNodeId() {
3326
3353
  try {
3327
3354
  const prInfo = JSON.parse(
3328
- execSync15("gh pr view --json id", { encoding: "utf-8" })
3355
+ execSync16("gh pr view --json id", { encoding: "utf-8" })
3329
3356
  );
3330
3357
  return prInfo.id;
3331
3358
  } catch (error) {
@@ -3396,10 +3423,10 @@ function comment(path31, line, body) {
3396
3423
  }
3397
3424
 
3398
3425
  // src/commands/prs/fixed.ts
3399
- import { execSync as execSync17 } from "child_process";
3426
+ import { execSync as execSync18 } from "child_process";
3400
3427
 
3401
3428
  // src/commands/prs/resolveCommentWithReply.ts
3402
- import { execSync as execSync16 } from "child_process";
3429
+ import { execSync as execSync17 } from "child_process";
3403
3430
  import { unlinkSync as unlinkSync5, writeFileSync as writeFileSync14 } from "fs";
3404
3431
  import { tmpdir as tmpdir3 } from "os";
3405
3432
  import { join as join14 } from "path";
@@ -3429,7 +3456,7 @@ function deleteCommentsCache(prNumber) {
3429
3456
 
3430
3457
  // src/commands/prs/resolveCommentWithReply.ts
3431
3458
  function replyToComment(org, repo, prNumber, commentId, message) {
3432
- execSync16(
3459
+ execSync17(
3433
3460
  `gh api repos/${org}/${repo}/pulls/${prNumber}/comments -f body="${message.replace(/"/g, '\\"')}" -F in_reply_to=${commentId}`,
3434
3461
  { stdio: "inherit" }
3435
3462
  );
@@ -3439,7 +3466,7 @@ function resolveThread(threadId) {
3439
3466
  const queryFile = join14(tmpdir3(), `gh-mutation-${Date.now()}.graphql`);
3440
3467
  writeFileSync14(queryFile, mutation);
3441
3468
  try {
3442
- execSync16(
3469
+ execSync17(
3443
3470
  `gh api graphql -F query=@${queryFile} -f threadId="${threadId}"`,
3444
3471
  { stdio: "inherit" }
3445
3472
  );
@@ -3491,7 +3518,7 @@ function resolveCommentWithReply(commentId, message) {
3491
3518
  // src/commands/prs/fixed.ts
3492
3519
  function verifySha(sha) {
3493
3520
  try {
3494
- return execSync17(`git rev-parse --verify ${sha}`, {
3521
+ return execSync18(`git rev-parse --verify ${sha}`, {
3495
3522
  encoding: "utf-8"
3496
3523
  }).trim();
3497
3524
  } catch {
@@ -3527,7 +3554,7 @@ function isClaudeCode2() {
3527
3554
  }
3528
3555
 
3529
3556
  // src/commands/prs/fetchThreadIds.ts
3530
- import { execSync as execSync18 } from "child_process";
3557
+ import { execSync as execSync19 } from "child_process";
3531
3558
  import { unlinkSync as unlinkSync6, writeFileSync as writeFileSync15 } from "fs";
3532
3559
  import { tmpdir as tmpdir4 } from "os";
3533
3560
  import { join as join15 } from "path";
@@ -3536,7 +3563,7 @@ function fetchThreadIds(org, repo, prNumber) {
3536
3563
  const queryFile = join15(tmpdir4(), `gh-query-${Date.now()}.graphql`);
3537
3564
  writeFileSync15(queryFile, THREAD_QUERY);
3538
3565
  try {
3539
- const result = execSync18(
3566
+ const result = execSync19(
3540
3567
  `gh api graphql -F query=@${queryFile} -F owner="${org}" -F repo="${repo}" -F prNumber=${prNumber}`,
3541
3568
  { encoding: "utf-8" }
3542
3569
  );
@@ -3558,9 +3585,9 @@ function fetchThreadIds(org, repo, prNumber) {
3558
3585
  }
3559
3586
 
3560
3587
  // src/commands/prs/listComments/fetchReviewComments.ts
3561
- import { execSync as execSync19 } from "child_process";
3588
+ import { execSync as execSync20 } from "child_process";
3562
3589
  function fetchJson(endpoint) {
3563
- const result = execSync19(`gh api ${endpoint}`, { encoding: "utf-8" });
3590
+ const result = execSync20(`gh api ${endpoint}`, { encoding: "utf-8" });
3564
3591
  if (!result.trim()) return [];
3565
3592
  return JSON.parse(result);
3566
3593
  }
@@ -3686,7 +3713,7 @@ async function listComments() {
3686
3713
  }
3687
3714
 
3688
3715
  // src/commands/prs/prs/index.ts
3689
- import { execSync as execSync20 } from "child_process";
3716
+ import { execSync as execSync21 } from "child_process";
3690
3717
 
3691
3718
  // src/commands/prs/prs/displayPaginated/index.ts
3692
3719
  import enquirer5 from "enquirer";
@@ -3792,7 +3819,7 @@ async function displayPaginated(pullRequests) {
3792
3819
  async function prs(options2) {
3793
3820
  const state = options2.open ? "open" : options2.closed ? "closed" : "all";
3794
3821
  try {
3795
- const result = execSync20(
3822
+ const result = execSync21(
3796
3823
  `gh pr list --state ${state} --json number,title,url,author,createdAt,mergedAt,closedAt,state,changedFiles --limit 100`,
3797
3824
  { encoding: "utf-8" }
3798
3825
  );
@@ -3815,7 +3842,7 @@ async function prs(options2) {
3815
3842
  }
3816
3843
 
3817
3844
  // src/commands/prs/wontfix.ts
3818
- import { execSync as execSync21 } from "child_process";
3845
+ import { execSync as execSync22 } from "child_process";
3819
3846
  function validateReason(reason) {
3820
3847
  const lowerReason = reason.toLowerCase();
3821
3848
  if (lowerReason.includes("claude") || lowerReason.includes("opus")) {
@@ -3832,7 +3859,7 @@ function validateShaReferences(reason) {
3832
3859
  const invalidShas = [];
3833
3860
  for (const sha of shas) {
3834
3861
  try {
3835
- execSync21(`git cat-file -t ${sha}`, { stdio: "pipe" });
3862
+ execSync22(`git cat-file -t ${sha}`, { stdio: "pipe" });
3836
3863
  } catch {
3837
3864
  invalidShas.push(sha);
3838
3865
  }
@@ -3936,9 +3963,9 @@ Refactor check failed:
3936
3963
  }
3937
3964
 
3938
3965
  // src/commands/refactor/check/getViolations/index.ts
3939
- import { execSync as execSync22 } from "child_process";
3966
+ import { execSync as execSync23 } from "child_process";
3940
3967
  import fs15 from "fs";
3941
- import { minimatch as minimatch3 } from "minimatch";
3968
+ import { minimatch as minimatch4 } from "minimatch";
3942
3969
 
3943
3970
  // src/commands/refactor/check/getViolations/getIgnoredFiles.ts
3944
3971
  import fs14 from "fs";
@@ -3986,7 +4013,7 @@ function getGitFiles(options2) {
3986
4013
  }
3987
4014
  const files = /* @__PURE__ */ new Set();
3988
4015
  if (options2.staged || options2.modified) {
3989
- const staged = execSync22("git diff --cached --name-only", {
4016
+ const staged = execSync23("git diff --cached --name-only", {
3990
4017
  encoding: "utf-8"
3991
4018
  });
3992
4019
  for (const file of staged.trim().split("\n").filter(Boolean)) {
@@ -3994,7 +4021,7 @@ function getGitFiles(options2) {
3994
4021
  }
3995
4022
  }
3996
4023
  if (options2.unstaged || options2.modified) {
3997
- const unstaged = execSync22("git diff --name-only", { encoding: "utf-8" });
4024
+ const unstaged = execSync23("git diff --name-only", { encoding: "utf-8" });
3998
4025
  for (const file of unstaged.trim().split("\n").filter(Boolean)) {
3999
4026
  files.add(file);
4000
4027
  }
@@ -4006,7 +4033,7 @@ function getViolations(pattern2, options2 = {}, maxLines = DEFAULT_MAX_LINES) {
4006
4033
  const ignoredFiles = getIgnoredFiles();
4007
4034
  const gitFiles = getGitFiles(options2);
4008
4035
  if (pattern2) {
4009
- sourceFiles = sourceFiles.filter((f) => minimatch3(f, pattern2));
4036
+ sourceFiles = sourceFiles.filter((f) => minimatch4(f, pattern2));
4010
4037
  }
4011
4038
  if (gitFiles) {
4012
4039
  sourceFiles = sourceFiles.filter((f) => gitFiles.has(f));
@@ -5257,7 +5284,18 @@ function registerTranscript(program2) {
5257
5284
 
5258
5285
  // src/commands/registerVerify.ts
5259
5286
  function registerVerify(program2) {
5260
- const verifyCommand = program2.command("verify").description("Run all verify:* commands in parallel").option("--timer", "Show timing information for each task as they complete").action((options2) => run(options2));
5287
+ const verifyCommand = program2.command("verify").description("Run all verify:* commands in parallel").argument(
5288
+ "[scope]",
5289
+ 'Use "all" to run all checks, ignoring diff-based filters'
5290
+ ).option("--timer", "Show timing information for each task as they complete").action((scope, options2) => {
5291
+ if (scope && scope !== "all") {
5292
+ console.error(
5293
+ `Unknown scope: "${scope}". Use "all" to run all checks.`
5294
+ );
5295
+ process.exit(1);
5296
+ }
5297
+ run({ ...options2, all: scope === "all" });
5298
+ });
5261
5299
  verifyCommand.command("list").description("List configured verify commands").action(list);
5262
5300
  verifyCommand.command("init").description("Add verify scripts to a project").action(init2);
5263
5301
  verifyCommand.command("hardcoded-colors").description("Check for hardcoded hex colors in src/").action(hardcodedColors);
@@ -5307,7 +5345,7 @@ function logs(options2) {
5307
5345
  console.log("No voice log file found");
5308
5346
  return;
5309
5347
  }
5310
- const count = Number.parseInt(options2.lines ?? "20", 10);
5348
+ const count = Number.parseInt(options2.lines ?? "150", 10);
5311
5349
  const content = readFileSync17(voicePaths.log, "utf-8").trim();
5312
5350
  if (!content) {
5313
5351
  console.log("Voice log is empty");
@@ -5329,67 +5367,14 @@ function logs(options2) {
5329
5367
  }
5330
5368
 
5331
5369
  // src/commands/voice/setup.ts
5332
- import { execSync as execSync23, spawnSync as spawnSync4 } from "child_process";
5333
- import { existsSync as existsSync24, mkdirSync as mkdirSync7 } from "fs";
5334
- import { join as join25 } from "path";
5335
- function setup() {
5336
- mkdirSync7(voicePaths.dir, { recursive: true });
5337
- if (!existsSync24(getVenvPython())) {
5338
- console.log("Creating Python virtual environment...");
5339
- execSync23(`uv venv "${voicePaths.venv}"`, { stdio: "inherit" });
5340
- console.log("Installing dependencies...");
5341
- const pythonDir = getPythonDir();
5342
- execSync23(
5343
- `uv pip install --python "${getVenvPython()}" -e "${pythonDir}[dev]"`,
5344
- { stdio: "inherit" }
5345
- );
5346
- }
5347
- console.log("\nDownloading models...\n");
5348
- const script = join25(getPythonDir(), "setup_models.py");
5349
- const result = spawnSync4(getVenvPython(), [script], {
5350
- stdio: "inherit",
5351
- env: { ...process.env, VOICE_LOG_FILE: voicePaths.log }
5352
- });
5353
- if (result.status !== 0) {
5354
- console.error("Model setup failed");
5355
- process.exit(1);
5356
- }
5357
- }
5358
-
5359
- // src/commands/voice/start.ts
5360
- import { spawn as spawn4 } from "child_process";
5361
- import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync19 } from "fs";
5362
- import { join as join27 } from "path";
5363
-
5364
- // src/commands/voice/buildDaemonEnv.ts
5365
- var ENV_MAP = {
5366
- VOICE_WAKE_WORDS: (v) => v.wakeWords?.join(","),
5367
- VOICE_MIC: (v) => v.mic,
5368
- VOICE_CWD: (v) => v.cwd,
5369
- VOICE_MODELS_DIR: (v) => v.modelsDir,
5370
- VOICE_MODEL_VAD: (v) => v.models?.vad,
5371
- VOICE_MODEL_SMART_TURN: (v) => v.models?.smartTurn,
5372
- VOICE_MODEL_STT: (v) => v.models?.stt
5373
- };
5374
- function buildDaemonEnv(options2) {
5375
- const config = loadConfig();
5376
- const env = { ...process.env };
5377
- const voice = config.voice;
5378
- if (voice) {
5379
- for (const [key, getter] of Object.entries(ENV_MAP)) {
5380
- const value = getter(voice);
5381
- if (value) env[key] = value;
5382
- }
5383
- }
5384
- env.VOICE_LOG_FILE = voicePaths.log;
5385
- if (options2?.debug) env.VOICE_DEBUG = "1";
5386
- return env;
5387
- }
5370
+ import { spawnSync as spawnSync4 } from "child_process";
5371
+ import { mkdirSync as mkdirSync8 } from "fs";
5372
+ import { join as join26 } from "path";
5388
5373
 
5389
5374
  // src/commands/voice/checkLockFile.ts
5390
5375
  import { execSync as execSync24 } from "child_process";
5391
- import { existsSync as existsSync25, mkdirSync as mkdirSync8, readFileSync as readFileSync18, writeFileSync as writeFileSync18 } from "fs";
5392
- import { join as join26 } from "path";
5376
+ import { existsSync as existsSync24, mkdirSync as mkdirSync7, readFileSync as readFileSync18, writeFileSync as writeFileSync18 } from "fs";
5377
+ import { join as join25 } from "path";
5393
5378
  function isProcessAlive(pid) {
5394
5379
  try {
5395
5380
  process.kill(pid, 0);
@@ -5400,7 +5385,7 @@ function isProcessAlive(pid) {
5400
5385
  }
5401
5386
  function checkLockFile() {
5402
5387
  const lockFile = getLockFile();
5403
- if (!existsSync25(lockFile)) return;
5388
+ if (!existsSync24(lockFile)) return;
5404
5389
  try {
5405
5390
  const lock = JSON.parse(readFileSync18(lockFile, "utf-8"));
5406
5391
  if (lock.pid && isProcessAlive(lock.pid)) {
@@ -5413,7 +5398,7 @@ function checkLockFile() {
5413
5398
  }
5414
5399
  }
5415
5400
  function bootstrapVenv() {
5416
- if (existsSync25(getVenvPython())) return;
5401
+ if (existsSync24(getVenvPython())) return;
5417
5402
  console.log("Creating Python virtual environment...");
5418
5403
  execSync24(`uv venv "${voicePaths.venv}"`, { stdio: "inherit" });
5419
5404
  console.log("Installing dependencies...");
@@ -5425,7 +5410,7 @@ function bootstrapVenv() {
5425
5410
  }
5426
5411
  function writeLockFile(pid) {
5427
5412
  const lockFile = getLockFile();
5428
- mkdirSync8(join26(lockFile, ".."), { recursive: true });
5413
+ mkdirSync7(join25(lockFile, ".."), { recursive: true });
5429
5414
  writeFileSync18(
5430
5415
  lockFile,
5431
5416
  JSON.stringify({
@@ -5436,6 +5421,53 @@ function writeLockFile(pid) {
5436
5421
  );
5437
5422
  }
5438
5423
 
5424
+ // src/commands/voice/setup.ts
5425
+ function setup() {
5426
+ mkdirSync8(voicePaths.dir, { recursive: true });
5427
+ bootstrapVenv();
5428
+ console.log("\nDownloading models...\n");
5429
+ const script = join26(getPythonDir(), "setup_models.py");
5430
+ const result = spawnSync4(getVenvPython(), [script], {
5431
+ stdio: "inherit",
5432
+ env: { ...process.env, VOICE_LOG_FILE: voicePaths.log }
5433
+ });
5434
+ if (result.status !== 0) {
5435
+ console.error("Model setup failed");
5436
+ process.exit(1);
5437
+ }
5438
+ }
5439
+
5440
+ // src/commands/voice/start.ts
5441
+ import { spawn as spawn4 } from "child_process";
5442
+ import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync19 } from "fs";
5443
+ import { join as join27 } from "path";
5444
+
5445
+ // src/commands/voice/buildDaemonEnv.ts
5446
+ var ENV_MAP = {
5447
+ VOICE_WAKE_WORDS: (v) => v.wakeWords?.join(","),
5448
+ VOICE_MIC: (v) => v.mic,
5449
+ VOICE_CWD: (v) => v.cwd,
5450
+ VOICE_MODELS_DIR: (v) => v.modelsDir,
5451
+ VOICE_MODEL_VAD: (v) => v.models?.vad,
5452
+ VOICE_MODEL_SMART_TURN: (v) => v.models?.smartTurn,
5453
+ VOICE_MODEL_STT: (v) => v.models?.stt,
5454
+ VOICE_SUBMIT_WORD: (v) => v.submitWord
5455
+ };
5456
+ function buildDaemonEnv(options2) {
5457
+ const config = loadConfig();
5458
+ const env = { ...process.env };
5459
+ const voice = config.voice;
5460
+ if (voice) {
5461
+ for (const [key, getter] of Object.entries(ENV_MAP)) {
5462
+ const value = getter(voice);
5463
+ if (value) env[key] = value;
5464
+ }
5465
+ }
5466
+ env.VOICE_LOG_FILE = voicePaths.log;
5467
+ if (options2?.debug) env.VOICE_DEBUG = "1";
5468
+ return env;
5469
+ }
5470
+
5439
5471
  // src/commands/voice/start.ts
5440
5472
  function spawnForeground(python, script, env) {
5441
5473
  console.log("Starting voice daemon in foreground...");
@@ -5462,7 +5494,7 @@ function start2(options2) {
5462
5494
  mkdirSync9(voicePaths.dir, { recursive: true });
5463
5495
  checkLockFile();
5464
5496
  bootstrapVenv();
5465
- const debug = options2.debug || options2.foreground;
5497
+ const debug = options2.debug || options2.foreground || process.platform === "win32";
5466
5498
  const env = buildDaemonEnv({ debug });
5467
5499
  const script = join27(getPythonDir(), "voice_daemon.py");
5468
5500
  const python = getVenvPython();
@@ -5474,7 +5506,7 @@ function start2(options2) {
5474
5506
  }
5475
5507
 
5476
5508
  // src/commands/voice/status.ts
5477
- import { existsSync as existsSync26, readFileSync as readFileSync19 } from "fs";
5509
+ import { existsSync as existsSync25, readFileSync as readFileSync19 } from "fs";
5478
5510
  function isProcessAlive2(pid) {
5479
5511
  try {
5480
5512
  process.kill(pid, 0);
@@ -5484,12 +5516,12 @@ function isProcessAlive2(pid) {
5484
5516
  }
5485
5517
  }
5486
5518
  function readRecentLogs(count) {
5487
- if (!existsSync26(voicePaths.log)) return [];
5519
+ if (!existsSync25(voicePaths.log)) return [];
5488
5520
  const lines = readFileSync19(voicePaths.log, "utf-8").trim().split("\n");
5489
5521
  return lines.slice(-count);
5490
5522
  }
5491
5523
  function status() {
5492
- if (!existsSync26(voicePaths.pid)) {
5524
+ if (!existsSync25(voicePaths.pid)) {
5493
5525
  console.log("Voice daemon: not running (no PID file)");
5494
5526
  return;
5495
5527
  }
@@ -5512,9 +5544,9 @@ function status() {
5512
5544
  }
5513
5545
 
5514
5546
  // src/commands/voice/stop.ts
5515
- import { existsSync as existsSync27, readFileSync as readFileSync20, unlinkSync as unlinkSync7 } from "fs";
5547
+ import { existsSync as existsSync26, readFileSync as readFileSync20, unlinkSync as unlinkSync7 } from "fs";
5516
5548
  function stop() {
5517
- if (!existsSync27(voicePaths.pid)) {
5549
+ if (!existsSync26(voicePaths.pid)) {
5518
5550
  console.log("Voice daemon is not running (no PID file)");
5519
5551
  return;
5520
5552
  }
@@ -5531,7 +5563,7 @@ function stop() {
5531
5563
  }
5532
5564
  try {
5533
5565
  const lockFile = getLockFile();
5534
- if (existsSync27(lockFile)) unlinkSync7(lockFile);
5566
+ if (existsSync26(lockFile)) unlinkSync7(lockFile);
5535
5567
  } catch {
5536
5568
  }
5537
5569
  console.log("Voice daemon stopped");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@staff0rd/assist",
3
- "version": "0.81.0",
3
+ "version": "0.83.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -16,14 +16,7 @@
16
16
  "scripts": {
17
17
  "build": "tsup",
18
18
  "start": "node dist/index.js",
19
- "verify:lint": "biome check --write .",
20
- "verify:build": "tsup",
21
- "verify:types": "tsc --noEmit",
22
- "verify:knip": "knip --no-progress --treat-config-hints-as-errors",
23
- "verify:duplicate-code": "jscpd --format 'typescript,tsx,python' --exitCode 1 --ignore '**/*.test.*' -r consoleFull src",
24
- "verify:maintainability": "assist complexity maintainability ./src --threshold 70",
25
- "verify:custom-lint": "assist lint",
26
- "verify:react-build": "esbuild src/commands/backlog/web/ui/App.tsx --bundle --minify --format=iife --target=es2020 --outfile=dist/commands/backlog/web/bundle.js --jsx=automatic --jsx-import-source=react \"--define:process.env.NODE_ENV=\\\"production\\\"\""
19
+ "check:types": "tsc --noEmit"
27
20
  },
28
21
  "keywords": [],
29
22
  "author": "",