@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(
|
|
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
|
-
|
|
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(
|
|
102
|
+
self._update_typed_text(partial)
|
|
99
103
|
else:
|
|
100
|
-
keyboard.type_text(
|
|
101
|
-
self._typed_text =
|
|
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
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
|
114
|
-
self.
|
|
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
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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
|
-
|
|
217
|
-
if
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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(
|
|
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
|
-
|
|
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.
|
|
304
|
-
|
|
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.
|
|
323
|
-
|
|
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()
|
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,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(["
|
|
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
|
|
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
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
1592
|
+
import { execSync as execSync8 } from "child_process";
|
|
1566
1593
|
function initPackageJson(name) {
|
|
1567
1594
|
console.log("Initializing package.json...");
|
|
1568
|
-
|
|
1595
|
+
execSync8("npm init -y", { stdio: "inherit" });
|
|
1569
1596
|
console.log("Configuring package.json...");
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
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
|
-
|
|
1640
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1748
|
+
execSync10("npm install -g netlify-cli", { stdio: "inherit" });
|
|
1722
1749
|
console.log();
|
|
1723
|
-
|
|
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
|
-
|
|
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
|
|
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) =>
|
|
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) =>
|
|
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) =>
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
3426
|
+
import { execSync as execSync18 } from "child_process";
|
|
3400
3427
|
|
|
3401
3428
|
// src/commands/prs/resolveCommentWithReply.ts
|
|
3402
|
-
import { execSync as
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
3588
|
+
import { execSync as execSync20 } from "child_process";
|
|
3562
3589
|
function fetchJson(endpoint) {
|
|
3563
|
-
const result =
|
|
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
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
|
3966
|
+
import { execSync as execSync23 } from "child_process";
|
|
3940
3967
|
import fs15 from "fs";
|
|
3941
|
-
import { minimatch as
|
|
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 =
|
|
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 =
|
|
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) =>
|
|
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").
|
|
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 ?? "
|
|
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 {
|
|
5333
|
-
import {
|
|
5334
|
-
import { join as
|
|
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
|
|
5392
|
-
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";
|
|
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 (!
|
|
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 (
|
|
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
|
-
|
|
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
|
|
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 (!
|
|
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 (!
|
|
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
|
|
5547
|
+
import { existsSync as existsSync26, readFileSync as readFileSync20, unlinkSync as unlinkSync7 } from "fs";
|
|
5516
5548
|
function stop() {
|
|
5517
|
-
if (!
|
|
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 (
|
|
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.
|
|
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": "",
|