@staff0rd/assist 0.82.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 +1 -0
- package/claude/commands/verify.md +2 -0
- package/dist/commands/voice/python/voice_daemon.py +81 -71
- package/dist/commands/voice/python/wake_word.py +1 -1
- package/dist/index.js +169 -139
- package/package.json +2 -9
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.
|
|
@@ -161,6 +161,29 @@ class VoiceDaemon:
|
|
|
161
161
|
keyboard.type_text(to_type)
|
|
162
162
|
self._typed_text = new_text
|
|
163
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
|
+
|
|
164
187
|
def _check_segment_end(self, sample_count: int, trailing_silence: int) -> bool:
|
|
165
188
|
"""Check if the current segment is done.
|
|
166
189
|
|
|
@@ -189,6 +212,27 @@ class VoiceDaemon:
|
|
|
189
212
|
log("smart_turn_incomplete", "Continuing to listen...")
|
|
190
213
|
return False
|
|
191
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
|
+
|
|
192
236
|
def _finalize_utterance(self) -> None:
|
|
193
237
|
"""End of turn: final STT, correct typed text, press Enter."""
|
|
194
238
|
if not self._audio_buffer:
|
|
@@ -215,23 +259,7 @@ class VoiceDaemon:
|
|
|
215
259
|
self._update_typed_text(stripped)
|
|
216
260
|
else:
|
|
217
261
|
keyboard.type_text(stripped)
|
|
218
|
-
|
|
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()
|
|
262
|
+
self._dispatch_result(should_submit, stripped)
|
|
235
263
|
else:
|
|
236
264
|
if self._typed_text:
|
|
237
265
|
keyboard.backspace(len(self._typed_text))
|
|
@@ -247,27 +275,18 @@ class VoiceDaemon:
|
|
|
247
275
|
if stripped:
|
|
248
276
|
if stripped != self._typed_text:
|
|
249
277
|
self._update_typed_text(stripped)
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
else:
|
|
256
|
-
log("dispatch_typed", stripped)
|
|
257
|
-
if DEBUG:
|
|
258
|
-
print(f" Final: {stripped} (no submit)", file=sys.stderr)
|
|
259
|
-
elif should_submit:
|
|
260
|
-
# Submit word only — erase it and press enter
|
|
261
|
-
if self._typed_text:
|
|
262
|
-
keyboard.backspace(len(self._typed_text))
|
|
263
|
-
log("dispatch_enter", "(submit word only)")
|
|
264
|
-
if DEBUG:
|
|
265
|
-
print(" Submit word only [Enter]", file=sys.stderr)
|
|
266
|
-
keyboard.press_enter()
|
|
267
|
-
elif self._typed_text:
|
|
268
|
-
# Wake word but no command — clear what we typed
|
|
269
|
-
keyboard.backspace(len(self._typed_text))
|
|
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))
|
|
270
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)
|
|
271
290
|
else:
|
|
272
291
|
# Check final transcription for wake word
|
|
273
292
|
found, command = check_wake_word(text)
|
|
@@ -373,42 +392,14 @@ class VoiceDaemon:
|
|
|
373
392
|
log("speech_start", "command after activation")
|
|
374
393
|
|
|
375
394
|
if prob > self._vad.threshold or self._audio_buffer:
|
|
376
|
-
self.
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
if prob > self._vad.threshold:
|
|
380
|
-
trailing_silence = 0
|
|
381
|
-
else:
|
|
382
|
-
trailing_silence += 1
|
|
383
|
-
|
|
384
|
-
# Periodic STT for incremental typing
|
|
385
|
-
if sample_count - self._last_partial_at >= PARTIAL_STT_INTERVAL:
|
|
386
|
-
self._last_partial_at = sample_count
|
|
387
|
-
self._run_partial_stt()
|
|
388
|
-
|
|
389
|
-
if self._check_segment_end(sample_count, trailing_silence):
|
|
390
|
-
self._finalize_utterance()
|
|
391
|
-
sample_count = 0
|
|
392
|
-
trailing_silence = 0
|
|
395
|
+
sample_count, trailing_silence = self._process_audio_chunk(
|
|
396
|
+
chunk, prob, sample_count, trailing_silence
|
|
397
|
+
)
|
|
393
398
|
|
|
394
399
|
elif self._state == LISTENING:
|
|
395
|
-
self.
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
if prob > self._vad.threshold:
|
|
399
|
-
trailing_silence = 0
|
|
400
|
-
else:
|
|
401
|
-
trailing_silence += 1
|
|
402
|
-
|
|
403
|
-
# Periodic STT for incremental typing
|
|
404
|
-
if sample_count - self._last_partial_at >= PARTIAL_STT_INTERVAL:
|
|
405
|
-
self._last_partial_at = sample_count
|
|
406
|
-
self._run_partial_stt()
|
|
407
|
-
|
|
408
|
-
if self._check_segment_end(sample_count, trailing_silence):
|
|
409
|
-
self._finalize_utterance()
|
|
410
|
-
sample_count = 0
|
|
411
|
-
trailing_silence = 0
|
|
400
|
+
sample_count, trailing_silence = self._process_audio_chunk(
|
|
401
|
+
chunk, prob, sample_count, trailing_silence
|
|
402
|
+
)
|
|
412
403
|
|
|
413
404
|
finally:
|
|
414
405
|
if DEBUG:
|
|
@@ -417,7 +408,26 @@ class VoiceDaemon:
|
|
|
417
408
|
log("daemon_stop", "Voice daemon stopped")
|
|
418
409
|
|
|
419
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
|
+
|
|
420
429
|
def main() -> None:
|
|
430
|
+
_setup_console()
|
|
421
431
|
log("daemon_launch", f"PID={os.getpid()}")
|
|
422
432
|
try:
|
|
423
433
|
daemon = VoiceDaemon()
|
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.
|
|
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
|
-
"
|
|
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,7 +126,7 @@ 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(["
|
|
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(),
|
|
@@ -1348,7 +1342,7 @@ function lint() {
|
|
|
1348
1342
|
}
|
|
1349
1343
|
|
|
1350
1344
|
// src/commands/new/registerNew/newCli/index.ts
|
|
1351
|
-
import { execSync as
|
|
1345
|
+
import { execSync as execSync9 } from "child_process";
|
|
1352
1346
|
import { basename as basename2, resolve } from "path";
|
|
1353
1347
|
|
|
1354
1348
|
// src/commands/verify/hardcodedColors.ts
|
|
@@ -1408,7 +1402,8 @@ function getRunEntries() {
|
|
|
1408
1402
|
if (!run3) return [];
|
|
1409
1403
|
return run3.filter((r) => r.name.startsWith("verify:")).map((r) => ({
|
|
1410
1404
|
name: r.name,
|
|
1411
|
-
fullCommand: buildFullCommand(r.command, r.args)
|
|
1405
|
+
fullCommand: buildFullCommand(r.command, r.args),
|
|
1406
|
+
filter: r.filter
|
|
1412
1407
|
}));
|
|
1413
1408
|
}
|
|
1414
1409
|
function getPackageJsonEntries() {
|
|
@@ -1479,6 +1474,32 @@ function initTaskStatuses(scripts) {
|
|
|
1479
1474
|
return scripts.map((script) => ({ script, startTime: Date.now() }));
|
|
1480
1475
|
}
|
|
1481
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
|
+
|
|
1482
1503
|
// src/commands/verify/run/spawnCommand.ts
|
|
1483
1504
|
import { spawn } from "child_process";
|
|
1484
1505
|
var isClaudeCode = !!process.env.CLAUDECODE;
|
|
@@ -1548,31 +1569,36 @@ async function run(options2 = {}) {
|
|
|
1548
1569
|
console.log("No verify commands found");
|
|
1549
1570
|
return;
|
|
1550
1571
|
}
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
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);
|
|
1554
1580
|
}
|
|
1555
1581
|
|
|
1556
1582
|
// src/commands/new/registerNew/initGit.ts
|
|
1557
|
-
import { execSync as
|
|
1583
|
+
import { execSync as execSync7 } from "child_process";
|
|
1558
1584
|
import { writeFileSync as writeFileSync6 } from "fs";
|
|
1559
1585
|
function initGit() {
|
|
1560
1586
|
console.log("Initializing git repository...");
|
|
1561
|
-
|
|
1587
|
+
execSync7("git init", { stdio: "inherit" });
|
|
1562
1588
|
writeFileSync6(".gitignore", "dist\nnode_modules\n");
|
|
1563
1589
|
}
|
|
1564
1590
|
|
|
1565
1591
|
// src/commands/new/registerNew/newCli/initPackageJson.ts
|
|
1566
|
-
import { execSync as
|
|
1592
|
+
import { execSync as execSync8 } from "child_process";
|
|
1567
1593
|
function initPackageJson(name) {
|
|
1568
1594
|
console.log("Initializing package.json...");
|
|
1569
|
-
|
|
1595
|
+
execSync8("npm init -y", { stdio: "inherit" });
|
|
1570
1596
|
console.log("Configuring package.json...");
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
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"', {
|
|
1576
1602
|
stdio: "inherit"
|
|
1577
1603
|
});
|
|
1578
1604
|
}
|
|
@@ -1637,8 +1663,8 @@ async function newCli() {
|
|
|
1637
1663
|
initGit();
|
|
1638
1664
|
initPackageJson(name);
|
|
1639
1665
|
console.log("Installing dependencies...");
|
|
1640
|
-
|
|
1641
|
-
|
|
1666
|
+
execSync9("npm install commander", { stdio: "inherit" });
|
|
1667
|
+
execSync9("npm install -D tsup typescript @types/node", {
|
|
1642
1668
|
stdio: "inherit"
|
|
1643
1669
|
});
|
|
1644
1670
|
writeCliTemplate(name);
|
|
@@ -1647,11 +1673,11 @@ async function newCli() {
|
|
|
1647
1673
|
}
|
|
1648
1674
|
|
|
1649
1675
|
// src/commands/new/registerNew/newProject.ts
|
|
1650
|
-
import { execSync as
|
|
1676
|
+
import { execSync as execSync11 } from "child_process";
|
|
1651
1677
|
import { existsSync as existsSync10, readFileSync as readFileSync8, writeFileSync as writeFileSync9 } from "fs";
|
|
1652
1678
|
|
|
1653
1679
|
// src/commands/deploy/init/index.ts
|
|
1654
|
-
import { execSync as
|
|
1680
|
+
import { execSync as execSync10 } from "child_process";
|
|
1655
1681
|
import chalk21 from "chalk";
|
|
1656
1682
|
import enquirer3 from "enquirer";
|
|
1657
1683
|
|
|
@@ -1704,7 +1730,7 @@ Created ${WORKFLOW_PATH}`));
|
|
|
1704
1730
|
// src/commands/deploy/init/index.ts
|
|
1705
1731
|
async function ensureNetlifyCli() {
|
|
1706
1732
|
try {
|
|
1707
|
-
|
|
1733
|
+
execSync10("netlify sites:create --disable-linking", { stdio: "inherit" });
|
|
1708
1734
|
} catch (error) {
|
|
1709
1735
|
if (!(error instanceof Error) || !error.message.includes("command not found"))
|
|
1710
1736
|
throw error;
|
|
@@ -1719,9 +1745,9 @@ async function ensureNetlifyCli() {
|
|
|
1719
1745
|
process.exit(1);
|
|
1720
1746
|
}
|
|
1721
1747
|
console.log(chalk21.dim("\nInstalling netlify-cli...\n"));
|
|
1722
|
-
|
|
1748
|
+
execSync10("npm install -g netlify-cli", { stdio: "inherit" });
|
|
1723
1749
|
console.log();
|
|
1724
|
-
|
|
1750
|
+
execSync10("netlify sites:create --disable-linking", { stdio: "inherit" });
|
|
1725
1751
|
}
|
|
1726
1752
|
}
|
|
1727
1753
|
function printSetupInstructions() {
|
|
@@ -1764,7 +1790,7 @@ async function init5() {
|
|
|
1764
1790
|
// src/commands/new/registerNew/newProject.ts
|
|
1765
1791
|
async function newProject() {
|
|
1766
1792
|
console.log("Initializing Vite with react-ts template...");
|
|
1767
|
-
|
|
1793
|
+
execSync11("npm create vite@latest . -- --template react-ts", {
|
|
1768
1794
|
stdio: "inherit"
|
|
1769
1795
|
});
|
|
1770
1796
|
initGit();
|
|
@@ -2402,11 +2428,11 @@ import ts5 from "typescript";
|
|
|
2402
2428
|
// src/commands/complexity/findSourceFiles.ts
|
|
2403
2429
|
import fs10 from "fs";
|
|
2404
2430
|
import path15 from "path";
|
|
2405
|
-
import { minimatch as
|
|
2431
|
+
import { minimatch as minimatch3 } from "minimatch";
|
|
2406
2432
|
function applyIgnoreGlobs(files) {
|
|
2407
2433
|
const { complexity } = loadConfig();
|
|
2408
2434
|
return files.filter(
|
|
2409
|
-
(f) => !complexity.ignore.some((glob) =>
|
|
2435
|
+
(f) => !complexity.ignore.some((glob) => minimatch3(f, glob))
|
|
2410
2436
|
);
|
|
2411
2437
|
}
|
|
2412
2438
|
function walk(dir, results) {
|
|
@@ -2430,7 +2456,7 @@ function findSourceFiles2(pattern2, baseDir = ".") {
|
|
|
2430
2456
|
const results = [];
|
|
2431
2457
|
if (pattern2.includes("*")) {
|
|
2432
2458
|
walk(baseDir, results);
|
|
2433
|
-
return applyIgnoreGlobs(results.filter((f) =>
|
|
2459
|
+
return applyIgnoreGlobs(results.filter((f) => minimatch3(f, pattern2)));
|
|
2434
2460
|
}
|
|
2435
2461
|
if (fs10.existsSync(pattern2) && fs10.statSync(pattern2).isFile()) {
|
|
2436
2462
|
return [pattern2];
|
|
@@ -2440,7 +2466,7 @@ function findSourceFiles2(pattern2, baseDir = ".") {
|
|
|
2440
2466
|
return applyIgnoreGlobs(results);
|
|
2441
2467
|
}
|
|
2442
2468
|
walk(baseDir, results);
|
|
2443
|
-
return applyIgnoreGlobs(results.filter((f) =>
|
|
2469
|
+
return applyIgnoreGlobs(results.filter((f) => minimatch3(f, pattern2)));
|
|
2444
2470
|
}
|
|
2445
2471
|
|
|
2446
2472
|
// src/commands/complexity/shared/getNodeName.ts
|
|
@@ -2917,11 +2943,11 @@ function registerDeploy(program2) {
|
|
|
2917
2943
|
}
|
|
2918
2944
|
|
|
2919
2945
|
// src/commands/devlog/list/index.ts
|
|
2920
|
-
import { execSync as
|
|
2946
|
+
import { execSync as execSync13 } from "child_process";
|
|
2921
2947
|
import { basename as basename3 } from "path";
|
|
2922
2948
|
|
|
2923
2949
|
// src/commands/devlog/shared.ts
|
|
2924
|
-
import { execSync as
|
|
2950
|
+
import { execSync as execSync12 } from "child_process";
|
|
2925
2951
|
import chalk37 from "chalk";
|
|
2926
2952
|
|
|
2927
2953
|
// src/commands/devlog/loadDevlogEntries.ts
|
|
@@ -2965,7 +2991,7 @@ function loadDevlogEntries(repoName) {
|
|
|
2965
2991
|
// src/commands/devlog/shared.ts
|
|
2966
2992
|
function getCommitFiles(hash) {
|
|
2967
2993
|
try {
|
|
2968
|
-
const output =
|
|
2994
|
+
const output = execSync12(`git show --name-only --format="" ${hash}`, {
|
|
2969
2995
|
encoding: "utf-8"
|
|
2970
2996
|
});
|
|
2971
2997
|
return output.trim().split("\n").filter(Boolean);
|
|
@@ -3036,7 +3062,7 @@ function list3(options2) {
|
|
|
3036
3062
|
const devlogEntries = loadDevlogEntries(repoName);
|
|
3037
3063
|
const reverseFlag = options2.reverse ? "--reverse " : "";
|
|
3038
3064
|
const limitFlag = options2.reverse ? "" : "-n 500 ";
|
|
3039
|
-
const output =
|
|
3065
|
+
const output = execSync13(
|
|
3040
3066
|
`git log ${reverseFlag}${limitFlag}--pretty=format:'%ad|%h|%s' --date=short`,
|
|
3041
3067
|
{ encoding: "utf-8" }
|
|
3042
3068
|
);
|
|
@@ -3062,11 +3088,11 @@ function list3(options2) {
|
|
|
3062
3088
|
}
|
|
3063
3089
|
|
|
3064
3090
|
// src/commands/devlog/getLastVersionInfo.ts
|
|
3065
|
-
import { execSync as
|
|
3091
|
+
import { execSync as execSync14 } from "child_process";
|
|
3066
3092
|
import semver from "semver";
|
|
3067
3093
|
function getVersionAtCommit(hash) {
|
|
3068
3094
|
try {
|
|
3069
|
-
const content =
|
|
3095
|
+
const content = execSync14(`git show ${hash}:package.json`, {
|
|
3070
3096
|
encoding: "utf-8"
|
|
3071
3097
|
});
|
|
3072
3098
|
const pkg = JSON.parse(content);
|
|
@@ -3081,7 +3107,7 @@ function stripToMinor(version2) {
|
|
|
3081
3107
|
}
|
|
3082
3108
|
function getLastVersionInfoFromGit() {
|
|
3083
3109
|
try {
|
|
3084
|
-
const output =
|
|
3110
|
+
const output = execSync14(
|
|
3085
3111
|
"git log -1 --pretty=format:'%ad|%h' --date=short",
|
|
3086
3112
|
{
|
|
3087
3113
|
encoding: "utf-8"
|
|
@@ -3124,7 +3150,7 @@ function bumpVersion(version2, type) {
|
|
|
3124
3150
|
}
|
|
3125
3151
|
|
|
3126
3152
|
// src/commands/devlog/next/displayNextEntry/index.ts
|
|
3127
|
-
import { execSync as
|
|
3153
|
+
import { execSync as execSync15 } from "child_process";
|
|
3128
3154
|
import chalk40 from "chalk";
|
|
3129
3155
|
|
|
3130
3156
|
// src/commands/devlog/next/displayNextEntry/displayVersion.ts
|
|
@@ -3158,7 +3184,7 @@ function findTargetDate(commitsByDate, skipDays) {
|
|
|
3158
3184
|
return Array.from(commitsByDate.keys()).filter((d) => !skipDays.has(d)).sort()[0];
|
|
3159
3185
|
}
|
|
3160
3186
|
function fetchCommitsByDate(ignore2, lastDate) {
|
|
3161
|
-
const output =
|
|
3187
|
+
const output = execSync15(
|
|
3162
3188
|
"git log --pretty=format:'%ad|%h|%s' --date=short -n 500",
|
|
3163
3189
|
{ encoding: "utf-8" }
|
|
3164
3190
|
);
|
|
@@ -3289,7 +3315,7 @@ import { tmpdir as tmpdir2 } from "os";
|
|
|
3289
3315
|
import { join as join12 } from "path";
|
|
3290
3316
|
|
|
3291
3317
|
// src/commands/prs/shared.ts
|
|
3292
|
-
import { execSync as
|
|
3318
|
+
import { execSync as execSync16 } from "child_process";
|
|
3293
3319
|
function isGhNotInstalled(error) {
|
|
3294
3320
|
if (error instanceof Error) {
|
|
3295
3321
|
const msg = error.message.toLowerCase();
|
|
@@ -3305,14 +3331,14 @@ function isNotFound(error) {
|
|
|
3305
3331
|
}
|
|
3306
3332
|
function getRepoInfo() {
|
|
3307
3333
|
const repoInfo = JSON.parse(
|
|
3308
|
-
|
|
3334
|
+
execSync16("gh repo view --json owner,name", { encoding: "utf-8" })
|
|
3309
3335
|
);
|
|
3310
3336
|
return { org: repoInfo.owner.login, repo: repoInfo.name };
|
|
3311
3337
|
}
|
|
3312
3338
|
function getCurrentPrNumber() {
|
|
3313
3339
|
try {
|
|
3314
3340
|
const prInfo = JSON.parse(
|
|
3315
|
-
|
|
3341
|
+
execSync16("gh pr view --json number", { encoding: "utf-8" })
|
|
3316
3342
|
);
|
|
3317
3343
|
return prInfo.number;
|
|
3318
3344
|
} catch (error) {
|
|
@@ -3326,7 +3352,7 @@ function getCurrentPrNumber() {
|
|
|
3326
3352
|
function getCurrentPrNodeId() {
|
|
3327
3353
|
try {
|
|
3328
3354
|
const prInfo = JSON.parse(
|
|
3329
|
-
|
|
3355
|
+
execSync16("gh pr view --json id", { encoding: "utf-8" })
|
|
3330
3356
|
);
|
|
3331
3357
|
return prInfo.id;
|
|
3332
3358
|
} catch (error) {
|
|
@@ -3397,10 +3423,10 @@ function comment(path31, line, body) {
|
|
|
3397
3423
|
}
|
|
3398
3424
|
|
|
3399
3425
|
// src/commands/prs/fixed.ts
|
|
3400
|
-
import { execSync as
|
|
3426
|
+
import { execSync as execSync18 } from "child_process";
|
|
3401
3427
|
|
|
3402
3428
|
// src/commands/prs/resolveCommentWithReply.ts
|
|
3403
|
-
import { execSync as
|
|
3429
|
+
import { execSync as execSync17 } from "child_process";
|
|
3404
3430
|
import { unlinkSync as unlinkSync5, writeFileSync as writeFileSync14 } from "fs";
|
|
3405
3431
|
import { tmpdir as tmpdir3 } from "os";
|
|
3406
3432
|
import { join as join14 } from "path";
|
|
@@ -3430,7 +3456,7 @@ function deleteCommentsCache(prNumber) {
|
|
|
3430
3456
|
|
|
3431
3457
|
// src/commands/prs/resolveCommentWithReply.ts
|
|
3432
3458
|
function replyToComment(org, repo, prNumber, commentId, message) {
|
|
3433
|
-
|
|
3459
|
+
execSync17(
|
|
3434
3460
|
`gh api repos/${org}/${repo}/pulls/${prNumber}/comments -f body="${message.replace(/"/g, '\\"')}" -F in_reply_to=${commentId}`,
|
|
3435
3461
|
{ stdio: "inherit" }
|
|
3436
3462
|
);
|
|
@@ -3440,7 +3466,7 @@ function resolveThread(threadId) {
|
|
|
3440
3466
|
const queryFile = join14(tmpdir3(), `gh-mutation-${Date.now()}.graphql`);
|
|
3441
3467
|
writeFileSync14(queryFile, mutation);
|
|
3442
3468
|
try {
|
|
3443
|
-
|
|
3469
|
+
execSync17(
|
|
3444
3470
|
`gh api graphql -F query=@${queryFile} -f threadId="${threadId}"`,
|
|
3445
3471
|
{ stdio: "inherit" }
|
|
3446
3472
|
);
|
|
@@ -3492,7 +3518,7 @@ function resolveCommentWithReply(commentId, message) {
|
|
|
3492
3518
|
// src/commands/prs/fixed.ts
|
|
3493
3519
|
function verifySha(sha) {
|
|
3494
3520
|
try {
|
|
3495
|
-
return
|
|
3521
|
+
return execSync18(`git rev-parse --verify ${sha}`, {
|
|
3496
3522
|
encoding: "utf-8"
|
|
3497
3523
|
}).trim();
|
|
3498
3524
|
} catch {
|
|
@@ -3528,7 +3554,7 @@ function isClaudeCode2() {
|
|
|
3528
3554
|
}
|
|
3529
3555
|
|
|
3530
3556
|
// src/commands/prs/fetchThreadIds.ts
|
|
3531
|
-
import { execSync as
|
|
3557
|
+
import { execSync as execSync19 } from "child_process";
|
|
3532
3558
|
import { unlinkSync as unlinkSync6, writeFileSync as writeFileSync15 } from "fs";
|
|
3533
3559
|
import { tmpdir as tmpdir4 } from "os";
|
|
3534
3560
|
import { join as join15 } from "path";
|
|
@@ -3537,7 +3563,7 @@ function fetchThreadIds(org, repo, prNumber) {
|
|
|
3537
3563
|
const queryFile = join15(tmpdir4(), `gh-query-${Date.now()}.graphql`);
|
|
3538
3564
|
writeFileSync15(queryFile, THREAD_QUERY);
|
|
3539
3565
|
try {
|
|
3540
|
-
const result =
|
|
3566
|
+
const result = execSync19(
|
|
3541
3567
|
`gh api graphql -F query=@${queryFile} -F owner="${org}" -F repo="${repo}" -F prNumber=${prNumber}`,
|
|
3542
3568
|
{ encoding: "utf-8" }
|
|
3543
3569
|
);
|
|
@@ -3559,9 +3585,9 @@ function fetchThreadIds(org, repo, prNumber) {
|
|
|
3559
3585
|
}
|
|
3560
3586
|
|
|
3561
3587
|
// src/commands/prs/listComments/fetchReviewComments.ts
|
|
3562
|
-
import { execSync as
|
|
3588
|
+
import { execSync as execSync20 } from "child_process";
|
|
3563
3589
|
function fetchJson(endpoint) {
|
|
3564
|
-
const result =
|
|
3590
|
+
const result = execSync20(`gh api ${endpoint}`, { encoding: "utf-8" });
|
|
3565
3591
|
if (!result.trim()) return [];
|
|
3566
3592
|
return JSON.parse(result);
|
|
3567
3593
|
}
|
|
@@ -3687,7 +3713,7 @@ async function listComments() {
|
|
|
3687
3713
|
}
|
|
3688
3714
|
|
|
3689
3715
|
// src/commands/prs/prs/index.ts
|
|
3690
|
-
import { execSync as
|
|
3716
|
+
import { execSync as execSync21 } from "child_process";
|
|
3691
3717
|
|
|
3692
3718
|
// src/commands/prs/prs/displayPaginated/index.ts
|
|
3693
3719
|
import enquirer5 from "enquirer";
|
|
@@ -3793,7 +3819,7 @@ async function displayPaginated(pullRequests) {
|
|
|
3793
3819
|
async function prs(options2) {
|
|
3794
3820
|
const state = options2.open ? "open" : options2.closed ? "closed" : "all";
|
|
3795
3821
|
try {
|
|
3796
|
-
const result =
|
|
3822
|
+
const result = execSync21(
|
|
3797
3823
|
`gh pr list --state ${state} --json number,title,url,author,createdAt,mergedAt,closedAt,state,changedFiles --limit 100`,
|
|
3798
3824
|
{ encoding: "utf-8" }
|
|
3799
3825
|
);
|
|
@@ -3816,7 +3842,7 @@ async function prs(options2) {
|
|
|
3816
3842
|
}
|
|
3817
3843
|
|
|
3818
3844
|
// src/commands/prs/wontfix.ts
|
|
3819
|
-
import { execSync as
|
|
3845
|
+
import { execSync as execSync22 } from "child_process";
|
|
3820
3846
|
function validateReason(reason) {
|
|
3821
3847
|
const lowerReason = reason.toLowerCase();
|
|
3822
3848
|
if (lowerReason.includes("claude") || lowerReason.includes("opus")) {
|
|
@@ -3833,7 +3859,7 @@ function validateShaReferences(reason) {
|
|
|
3833
3859
|
const invalidShas = [];
|
|
3834
3860
|
for (const sha of shas) {
|
|
3835
3861
|
try {
|
|
3836
|
-
|
|
3862
|
+
execSync22(`git cat-file -t ${sha}`, { stdio: "pipe" });
|
|
3837
3863
|
} catch {
|
|
3838
3864
|
invalidShas.push(sha);
|
|
3839
3865
|
}
|
|
@@ -3937,9 +3963,9 @@ Refactor check failed:
|
|
|
3937
3963
|
}
|
|
3938
3964
|
|
|
3939
3965
|
// src/commands/refactor/check/getViolations/index.ts
|
|
3940
|
-
import { execSync as
|
|
3966
|
+
import { execSync as execSync23 } from "child_process";
|
|
3941
3967
|
import fs15 from "fs";
|
|
3942
|
-
import { minimatch as
|
|
3968
|
+
import { minimatch as minimatch4 } from "minimatch";
|
|
3943
3969
|
|
|
3944
3970
|
// src/commands/refactor/check/getViolations/getIgnoredFiles.ts
|
|
3945
3971
|
import fs14 from "fs";
|
|
@@ -3987,7 +4013,7 @@ function getGitFiles(options2) {
|
|
|
3987
4013
|
}
|
|
3988
4014
|
const files = /* @__PURE__ */ new Set();
|
|
3989
4015
|
if (options2.staged || options2.modified) {
|
|
3990
|
-
const staged =
|
|
4016
|
+
const staged = execSync23("git diff --cached --name-only", {
|
|
3991
4017
|
encoding: "utf-8"
|
|
3992
4018
|
});
|
|
3993
4019
|
for (const file of staged.trim().split("\n").filter(Boolean)) {
|
|
@@ -3995,7 +4021,7 @@ function getGitFiles(options2) {
|
|
|
3995
4021
|
}
|
|
3996
4022
|
}
|
|
3997
4023
|
if (options2.unstaged || options2.modified) {
|
|
3998
|
-
const unstaged =
|
|
4024
|
+
const unstaged = execSync23("git diff --name-only", { encoding: "utf-8" });
|
|
3999
4025
|
for (const file of unstaged.trim().split("\n").filter(Boolean)) {
|
|
4000
4026
|
files.add(file);
|
|
4001
4027
|
}
|
|
@@ -4007,7 +4033,7 @@ function getViolations(pattern2, options2 = {}, maxLines = DEFAULT_MAX_LINES) {
|
|
|
4007
4033
|
const ignoredFiles = getIgnoredFiles();
|
|
4008
4034
|
const gitFiles = getGitFiles(options2);
|
|
4009
4035
|
if (pattern2) {
|
|
4010
|
-
sourceFiles = sourceFiles.filter((f) =>
|
|
4036
|
+
sourceFiles = sourceFiles.filter((f) => minimatch4(f, pattern2));
|
|
4011
4037
|
}
|
|
4012
4038
|
if (gitFiles) {
|
|
4013
4039
|
sourceFiles = sourceFiles.filter((f) => gitFiles.has(f));
|
|
@@ -5258,7 +5284,18 @@ function registerTranscript(program2) {
|
|
|
5258
5284
|
|
|
5259
5285
|
// src/commands/registerVerify.ts
|
|
5260
5286
|
function registerVerify(program2) {
|
|
5261
|
-
const verifyCommand = program2.command("verify").description("Run all verify:* commands in parallel").
|
|
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
|
+
});
|
|
5262
5299
|
verifyCommand.command("list").description("List configured verify commands").action(list);
|
|
5263
5300
|
verifyCommand.command("init").description("Add verify scripts to a project").action(init2);
|
|
5264
5301
|
verifyCommand.command("hardcoded-colors").description("Check for hardcoded hex colors in src/").action(hardcodedColors);
|
|
@@ -5308,7 +5345,7 @@ function logs(options2) {
|
|
|
5308
5345
|
console.log("No voice log file found");
|
|
5309
5346
|
return;
|
|
5310
5347
|
}
|
|
5311
|
-
const count = Number.parseInt(options2.lines ?? "
|
|
5348
|
+
const count = Number.parseInt(options2.lines ?? "150", 10);
|
|
5312
5349
|
const content = readFileSync17(voicePaths.log, "utf-8").trim();
|
|
5313
5350
|
if (!content) {
|
|
5314
5351
|
console.log("Voice log is empty");
|
|
@@ -5330,68 +5367,14 @@ function logs(options2) {
|
|
|
5330
5367
|
}
|
|
5331
5368
|
|
|
5332
5369
|
// src/commands/voice/setup.ts
|
|
5333
|
-
import {
|
|
5334
|
-
import {
|
|
5335
|
-
import { join as
|
|
5336
|
-
function setup() {
|
|
5337
|
-
mkdirSync7(voicePaths.dir, { recursive: true });
|
|
5338
|
-
if (!existsSync24(getVenvPython())) {
|
|
5339
|
-
console.log("Creating Python virtual environment...");
|
|
5340
|
-
execSync23(`uv venv "${voicePaths.venv}"`, { stdio: "inherit" });
|
|
5341
|
-
console.log("Installing dependencies...");
|
|
5342
|
-
const pythonDir = getPythonDir();
|
|
5343
|
-
execSync23(
|
|
5344
|
-
`uv pip install --python "${getVenvPython()}" -e "${pythonDir}[dev]"`,
|
|
5345
|
-
{ stdio: "inherit" }
|
|
5346
|
-
);
|
|
5347
|
-
}
|
|
5348
|
-
console.log("\nDownloading models...\n");
|
|
5349
|
-
const script = join25(getPythonDir(), "setup_models.py");
|
|
5350
|
-
const result = spawnSync4(getVenvPython(), [script], {
|
|
5351
|
-
stdio: "inherit",
|
|
5352
|
-
env: { ...process.env, VOICE_LOG_FILE: voicePaths.log }
|
|
5353
|
-
});
|
|
5354
|
-
if (result.status !== 0) {
|
|
5355
|
-
console.error("Model setup failed");
|
|
5356
|
-
process.exit(1);
|
|
5357
|
-
}
|
|
5358
|
-
}
|
|
5359
|
-
|
|
5360
|
-
// src/commands/voice/start.ts
|
|
5361
|
-
import { spawn as spawn4 } from "child_process";
|
|
5362
|
-
import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync19 } from "fs";
|
|
5363
|
-
import { join as join27 } from "path";
|
|
5364
|
-
|
|
5365
|
-
// src/commands/voice/buildDaemonEnv.ts
|
|
5366
|
-
var ENV_MAP = {
|
|
5367
|
-
VOICE_WAKE_WORDS: (v) => v.wakeWords?.join(","),
|
|
5368
|
-
VOICE_MIC: (v) => v.mic,
|
|
5369
|
-
VOICE_CWD: (v) => v.cwd,
|
|
5370
|
-
VOICE_MODELS_DIR: (v) => v.modelsDir,
|
|
5371
|
-
VOICE_MODEL_VAD: (v) => v.models?.vad,
|
|
5372
|
-
VOICE_MODEL_SMART_TURN: (v) => v.models?.smartTurn,
|
|
5373
|
-
VOICE_MODEL_STT: (v) => v.models?.stt,
|
|
5374
|
-
VOICE_SUBMIT_WORD: (v) => v.submitWord
|
|
5375
|
-
};
|
|
5376
|
-
function buildDaemonEnv(options2) {
|
|
5377
|
-
const config = loadConfig();
|
|
5378
|
-
const env = { ...process.env };
|
|
5379
|
-
const voice = config.voice;
|
|
5380
|
-
if (voice) {
|
|
5381
|
-
for (const [key, getter] of Object.entries(ENV_MAP)) {
|
|
5382
|
-
const value = getter(voice);
|
|
5383
|
-
if (value) env[key] = value;
|
|
5384
|
-
}
|
|
5385
|
-
}
|
|
5386
|
-
env.VOICE_LOG_FILE = voicePaths.log;
|
|
5387
|
-
if (options2?.debug) env.VOICE_DEBUG = "1";
|
|
5388
|
-
return env;
|
|
5389
|
-
}
|
|
5370
|
+
import { spawnSync as spawnSync4 } from "child_process";
|
|
5371
|
+
import { mkdirSync as mkdirSync8 } from "fs";
|
|
5372
|
+
import { join as join26 } from "path";
|
|
5390
5373
|
|
|
5391
5374
|
// src/commands/voice/checkLockFile.ts
|
|
5392
5375
|
import { execSync as execSync24 } from "child_process";
|
|
5393
|
-
import { existsSync as
|
|
5394
|
-
import { join as
|
|
5376
|
+
import { existsSync as existsSync24, mkdirSync as mkdirSync7, readFileSync as readFileSync18, writeFileSync as writeFileSync18 } from "fs";
|
|
5377
|
+
import { join as join25 } from "path";
|
|
5395
5378
|
function isProcessAlive(pid) {
|
|
5396
5379
|
try {
|
|
5397
5380
|
process.kill(pid, 0);
|
|
@@ -5402,7 +5385,7 @@ function isProcessAlive(pid) {
|
|
|
5402
5385
|
}
|
|
5403
5386
|
function checkLockFile() {
|
|
5404
5387
|
const lockFile = getLockFile();
|
|
5405
|
-
if (!
|
|
5388
|
+
if (!existsSync24(lockFile)) return;
|
|
5406
5389
|
try {
|
|
5407
5390
|
const lock = JSON.parse(readFileSync18(lockFile, "utf-8"));
|
|
5408
5391
|
if (lock.pid && isProcessAlive(lock.pid)) {
|
|
@@ -5415,7 +5398,7 @@ function checkLockFile() {
|
|
|
5415
5398
|
}
|
|
5416
5399
|
}
|
|
5417
5400
|
function bootstrapVenv() {
|
|
5418
|
-
if (
|
|
5401
|
+
if (existsSync24(getVenvPython())) return;
|
|
5419
5402
|
console.log("Creating Python virtual environment...");
|
|
5420
5403
|
execSync24(`uv venv "${voicePaths.venv}"`, { stdio: "inherit" });
|
|
5421
5404
|
console.log("Installing dependencies...");
|
|
@@ -5427,7 +5410,7 @@ function bootstrapVenv() {
|
|
|
5427
5410
|
}
|
|
5428
5411
|
function writeLockFile(pid) {
|
|
5429
5412
|
const lockFile = getLockFile();
|
|
5430
|
-
|
|
5413
|
+
mkdirSync7(join25(lockFile, ".."), { recursive: true });
|
|
5431
5414
|
writeFileSync18(
|
|
5432
5415
|
lockFile,
|
|
5433
5416
|
JSON.stringify({
|
|
@@ -5438,6 +5421,53 @@ function writeLockFile(pid) {
|
|
|
5438
5421
|
);
|
|
5439
5422
|
}
|
|
5440
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
|
+
|
|
5441
5471
|
// src/commands/voice/start.ts
|
|
5442
5472
|
function spawnForeground(python, script, env) {
|
|
5443
5473
|
console.log("Starting voice daemon in foreground...");
|
|
@@ -5464,7 +5494,7 @@ function start2(options2) {
|
|
|
5464
5494
|
mkdirSync9(voicePaths.dir, { recursive: true });
|
|
5465
5495
|
checkLockFile();
|
|
5466
5496
|
bootstrapVenv();
|
|
5467
|
-
const debug = options2.debug || options2.foreground;
|
|
5497
|
+
const debug = options2.debug || options2.foreground || process.platform === "win32";
|
|
5468
5498
|
const env = buildDaemonEnv({ debug });
|
|
5469
5499
|
const script = join27(getPythonDir(), "voice_daemon.py");
|
|
5470
5500
|
const python = getVenvPython();
|
|
@@ -5476,7 +5506,7 @@ function start2(options2) {
|
|
|
5476
5506
|
}
|
|
5477
5507
|
|
|
5478
5508
|
// src/commands/voice/status.ts
|
|
5479
|
-
import { existsSync as
|
|
5509
|
+
import { existsSync as existsSync25, readFileSync as readFileSync19 } from "fs";
|
|
5480
5510
|
function isProcessAlive2(pid) {
|
|
5481
5511
|
try {
|
|
5482
5512
|
process.kill(pid, 0);
|
|
@@ -5486,12 +5516,12 @@ function isProcessAlive2(pid) {
|
|
|
5486
5516
|
}
|
|
5487
5517
|
}
|
|
5488
5518
|
function readRecentLogs(count) {
|
|
5489
|
-
if (!
|
|
5519
|
+
if (!existsSync25(voicePaths.log)) return [];
|
|
5490
5520
|
const lines = readFileSync19(voicePaths.log, "utf-8").trim().split("\n");
|
|
5491
5521
|
return lines.slice(-count);
|
|
5492
5522
|
}
|
|
5493
5523
|
function status() {
|
|
5494
|
-
if (!
|
|
5524
|
+
if (!existsSync25(voicePaths.pid)) {
|
|
5495
5525
|
console.log("Voice daemon: not running (no PID file)");
|
|
5496
5526
|
return;
|
|
5497
5527
|
}
|
|
@@ -5514,9 +5544,9 @@ function status() {
|
|
|
5514
5544
|
}
|
|
5515
5545
|
|
|
5516
5546
|
// src/commands/voice/stop.ts
|
|
5517
|
-
import { existsSync as
|
|
5547
|
+
import { existsSync as existsSync26, readFileSync as readFileSync20, unlinkSync as unlinkSync7 } from "fs";
|
|
5518
5548
|
function stop() {
|
|
5519
|
-
if (!
|
|
5549
|
+
if (!existsSync26(voicePaths.pid)) {
|
|
5520
5550
|
console.log("Voice daemon is not running (no PID file)");
|
|
5521
5551
|
return;
|
|
5522
5552
|
}
|
|
@@ -5533,7 +5563,7 @@ function stop() {
|
|
|
5533
5563
|
}
|
|
5534
5564
|
try {
|
|
5535
5565
|
const lockFile = getLockFile();
|
|
5536
|
-
if (
|
|
5566
|
+
if (existsSync26(lockFile)) unlinkSync7(lockFile);
|
|
5537
5567
|
} catch {
|
|
5538
5568
|
}
|
|
5539
5569
|
console.log("Voice daemon stopped");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@staff0rd/assist",
|
|
3
|
-
"version": "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
|
-
"
|
|
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": "",
|