@staff0rd/assist 0.83.1 → 0.85.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/dist/commands/voice/python/CLAUDE.md +10 -0
- package/dist/commands/voice/python/voice_daemon.py +119 -102
- package/dist/index.js +141 -97
- package/package.json +1 -1
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Never use `pip install` directly. All Python dependency management uses `uv`.
|
|
2
|
+
|
|
3
|
+
**The venv MUST live in `~/.assist/voice/.venv`, NEVER in this directory or anywhere in the repo.**
|
|
4
|
+
|
|
5
|
+
Every `uv` command that touches this project MUST set `UV_PROJECT_ENVIRONMENT` to point to the home dir venv. This includes `uv sync`, `uv run`, and any other uv command. There must be no `.venv/`, `__pycache__/`, `*.egg-info/`, or any other generated artifacts in this directory.
|
|
6
|
+
|
|
7
|
+
- Dependencies are declared in `pyproject.toml`
|
|
8
|
+
- Bootstrap uses `uv sync --project <dir> --no-install-project` with `UV_PROJECT_ENVIRONMENT=~/.assist/voice/.venv`
|
|
9
|
+
- To add a dependency: edit `pyproject.toml`, then run `UV_PROJECT_ENVIRONMENT=~/.assist/voice/.venv uv lock --project src/commands/voice/python`
|
|
10
|
+
- No build system — this is a virtual project, not an installable package
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
"""Voice daemon entry point — main loop and signal handling."""
|
|
2
2
|
|
|
3
|
+
import ctypes
|
|
4
|
+
import json
|
|
3
5
|
import os
|
|
4
6
|
import signal
|
|
7
|
+
import subprocess
|
|
5
8
|
import sys
|
|
6
9
|
import time
|
|
7
10
|
|
|
@@ -16,6 +19,73 @@ from wake_word import check_wake_word
|
|
|
16
19
|
|
|
17
20
|
import keyboard
|
|
18
21
|
|
|
22
|
+
|
|
23
|
+
def _load_voice_config() -> dict:
|
|
24
|
+
"""Load voice config by calling ``assist config get voice``.
|
|
25
|
+
|
|
26
|
+
Sets environment variables so other Python modules (audio_capture,
|
|
27
|
+
vad, smart_turn, stt, wake_word) can read them as before.
|
|
28
|
+
Returns the parsed config dict.
|
|
29
|
+
"""
|
|
30
|
+
try:
|
|
31
|
+
result = subprocess.run(
|
|
32
|
+
["assist", "config", "get", "voice"],
|
|
33
|
+
capture_output=True,
|
|
34
|
+
text=True,
|
|
35
|
+
check=True,
|
|
36
|
+
shell=True,
|
|
37
|
+
)
|
|
38
|
+
config = json.loads(result.stdout)
|
|
39
|
+
except (subprocess.CalledProcessError, json.JSONDecodeError) as exc:
|
|
40
|
+
log("config_error", str(exc), level="error")
|
|
41
|
+
return {}
|
|
42
|
+
|
|
43
|
+
env_map: dict[str, str | None] = {
|
|
44
|
+
"VOICE_WAKE_WORDS": ",".join(config.get("wakeWords", [])) or None,
|
|
45
|
+
"VOICE_MIC": config.get("mic"),
|
|
46
|
+
"VOICE_MODELS_DIR": os.path.expanduser(v)
|
|
47
|
+
if (v := config.get("modelsDir"))
|
|
48
|
+
else None,
|
|
49
|
+
"VOICE_MODEL_VAD": (config.get("models") or {}).get("vad"),
|
|
50
|
+
"VOICE_MODEL_SMART_TURN": (config.get("models") or {}).get("smartTurn"),
|
|
51
|
+
}
|
|
52
|
+
for key, value in env_map.items():
|
|
53
|
+
if value:
|
|
54
|
+
os.environ[key] = value
|
|
55
|
+
|
|
56
|
+
log("config_loaded", json.dumps(config))
|
|
57
|
+
return config
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _get_foreground_window_info() -> str:
|
|
61
|
+
"""Return the title and process name of the currently focused window."""
|
|
62
|
+
user32 = ctypes.windll.user32
|
|
63
|
+
kernel32 = ctypes.windll.kernel32
|
|
64
|
+
hwnd = user32.GetForegroundWindow()
|
|
65
|
+
|
|
66
|
+
# Window title
|
|
67
|
+
buf = ctypes.create_unicode_buffer(256)
|
|
68
|
+
user32.GetWindowTextW(hwnd, buf, 256)
|
|
69
|
+
title = buf.value
|
|
70
|
+
|
|
71
|
+
# Process name from window handle
|
|
72
|
+
pid = ctypes.c_ulong()
|
|
73
|
+
user32.GetWindowThreadProcessId(hwnd, ctypes.byref(pid))
|
|
74
|
+
process_name = ""
|
|
75
|
+
PROCESS_QUERY_LIMITED_INFORMATION = 0x1000
|
|
76
|
+
handle = kernel32.OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, False, pid.value)
|
|
77
|
+
if handle:
|
|
78
|
+
exe_buf = ctypes.create_unicode_buffer(260)
|
|
79
|
+
size = ctypes.c_ulong(260)
|
|
80
|
+
if kernel32.QueryFullProcessImageNameW(handle, 0, exe_buf, ctypes.byref(size)):
|
|
81
|
+
process_name = exe_buf.value.rsplit("\\", 1)[-1]
|
|
82
|
+
kernel32.CloseHandle(handle)
|
|
83
|
+
|
|
84
|
+
if process_name:
|
|
85
|
+
return f"{process_name}: {title}"
|
|
86
|
+
return title
|
|
87
|
+
|
|
88
|
+
|
|
19
89
|
# States
|
|
20
90
|
IDLE = "idle"
|
|
21
91
|
LISTENING = "listening"
|
|
@@ -36,23 +106,9 @@ STOP_CHUNKS = (STOP_MS * 16000) // (BLOCK_SIZE * 1000) # ~31 chunks
|
|
|
36
106
|
ACTIVATED_TIMEOUT = 10.0
|
|
37
107
|
|
|
38
108
|
|
|
39
|
-
def
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
"""Print a live VAD meter to stderr when debug mode is on."""
|
|
43
|
-
rms = float(np.sqrt(np.mean(chunk**2)))
|
|
44
|
-
peak = float(np.max(np.abs(chunk)))
|
|
45
|
-
width = 40
|
|
46
|
-
filled = int(prob * width)
|
|
47
|
-
bar = "█" * filled + "░" * (width - filled)
|
|
48
|
-
marker = ">" if prob > threshold else " "
|
|
49
|
-
print(
|
|
50
|
-
f"\r {marker} VAD {prob:.2f} [{bar}] "
|
|
51
|
-
f"rms={rms:.4f} peak={peak:.4f} {state:10s}",
|
|
52
|
-
end="",
|
|
53
|
-
file=sys.stderr,
|
|
54
|
-
flush=True,
|
|
55
|
-
)
|
|
109
|
+
def _print_state(state: str) -> None:
|
|
110
|
+
"""Print the current daemon state to stderr when debug mode is on."""
|
|
111
|
+
print(f"\r {state:10s}", end="", file=sys.stderr, flush=True)
|
|
56
112
|
|
|
57
113
|
|
|
58
114
|
class VoiceDaemon:
|
|
@@ -60,15 +116,17 @@ class VoiceDaemon:
|
|
|
60
116
|
self._running = True
|
|
61
117
|
self._state = IDLE
|
|
62
118
|
self._audio_buffer: list[np.ndarray] = []
|
|
63
|
-
|
|
119
|
+
|
|
120
|
+
config = _load_voice_config()
|
|
121
|
+
self._submit_windows: set[str] = set(config.get("submitWindows") or [])
|
|
64
122
|
|
|
65
123
|
log("daemon_init", "Initializing models...")
|
|
66
124
|
self._mic = AudioCapture()
|
|
67
125
|
self._vad = SileroVAD()
|
|
68
126
|
self._smart_turn = SmartTurn()
|
|
69
127
|
self._stt = ParakeetSTT()
|
|
70
|
-
if self.
|
|
71
|
-
log("daemon_init", f"Submit
|
|
128
|
+
if self._submit_windows:
|
|
129
|
+
log("daemon_init", f"Submit windows: {self._submit_windows}")
|
|
72
130
|
log("daemon_ready")
|
|
73
131
|
|
|
74
132
|
# Incremental typing state
|
|
@@ -96,7 +154,7 @@ class VoiceDaemon:
|
|
|
96
154
|
|
|
97
155
|
if self._state == ACTIVATED:
|
|
98
156
|
# Already activated — everything is the command, no wake word needed
|
|
99
|
-
partial =
|
|
157
|
+
partial = text.strip()
|
|
100
158
|
if partial and partial != self._typed_text:
|
|
101
159
|
if self._typed_text:
|
|
102
160
|
self._update_typed_text(partial)
|
|
@@ -106,41 +164,25 @@ class VoiceDaemon:
|
|
|
106
164
|
elif not self._wake_detected:
|
|
107
165
|
found, command = check_wake_word(text)
|
|
108
166
|
if found and command:
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
keyboard.type_text(partial)
|
|
116
|
-
self._typed_text = partial
|
|
167
|
+
self._wake_detected = True
|
|
168
|
+
log("wake_word_detected", command)
|
|
169
|
+
if DEBUG:
|
|
170
|
+
print(f" Wake word! Typing: {command}", file=sys.stderr)
|
|
171
|
+
keyboard.type_text(command)
|
|
172
|
+
self._typed_text = command
|
|
117
173
|
else:
|
|
118
174
|
found, command = check_wake_word(text)
|
|
119
175
|
if found and command:
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
self._update_typed_text(partial)
|
|
176
|
+
if command != self._typed_text:
|
|
177
|
+
self._update_typed_text(command)
|
|
123
178
|
|
|
124
|
-
def
|
|
125
|
-
"""Check if
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
"""
|
|
130
|
-
|
|
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
|
|
179
|
+
def _should_submit(self) -> bool:
|
|
180
|
+
"""Check if the foreground window matches the submit allowlist."""
|
|
181
|
+
if not self._submit_windows:
|
|
182
|
+
return True
|
|
183
|
+
info = _get_foreground_window_info()
|
|
184
|
+
process_name = info.split(":")[0].strip() if ":" in info else ""
|
|
185
|
+
return process_name in self._submit_windows
|
|
144
186
|
|
|
145
187
|
def _update_typed_text(self, new_text: str) -> None:
|
|
146
188
|
"""Diff old typed text vs new, backspace + type the difference."""
|
|
@@ -212,26 +254,18 @@ class VoiceDaemon:
|
|
|
212
254
|
log("smart_turn_incomplete", "Continuing to listen...")
|
|
213
255
|
return False
|
|
214
256
|
|
|
215
|
-
def _dispatch_result(self,
|
|
257
|
+
def _dispatch_result(self, text: str) -> None:
|
|
216
258
|
"""Log and optionally submit a recognized command."""
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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)")
|
|
259
|
+
should_submit = self._should_submit()
|
|
260
|
+
if should_submit:
|
|
261
|
+
log("dispatch_enter", text)
|
|
232
262
|
if DEBUG:
|
|
233
|
-
print("
|
|
263
|
+
print(f" Final: {text} [Enter]", file=sys.stderr)
|
|
234
264
|
keyboard.press_enter()
|
|
265
|
+
else:
|
|
266
|
+
log("dispatch_typed", text)
|
|
267
|
+
if DEBUG:
|
|
268
|
+
print(f" Final: {text} (no submit)", file=sys.stderr)
|
|
235
269
|
|
|
236
270
|
def _finalize_utterance(self) -> None:
|
|
237
271
|
"""End of turn: final STT, correct typed text, press Enter."""
|
|
@@ -252,14 +286,12 @@ class VoiceDaemon:
|
|
|
252
286
|
# Activated mode — full text is the command
|
|
253
287
|
command = text.strip()
|
|
254
288
|
if command:
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
keyboard.type_text(stripped)
|
|
262
|
-
self._dispatch_result(should_submit, stripped)
|
|
289
|
+
if command != self._typed_text:
|
|
290
|
+
if self._typed_text:
|
|
291
|
+
self._update_typed_text(command)
|
|
292
|
+
else:
|
|
293
|
+
keyboard.type_text(command)
|
|
294
|
+
self._dispatch_result(command)
|
|
263
295
|
else:
|
|
264
296
|
if self._typed_text:
|
|
265
297
|
keyboard.backspace(len(self._typed_text))
|
|
@@ -271,11 +303,9 @@ class VoiceDaemon:
|
|
|
271
303
|
# Correct final text and submit
|
|
272
304
|
found, command = check_wake_word(text)
|
|
273
305
|
if found and command:
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
self._update_typed_text(stripped)
|
|
278
|
-
self._dispatch_result(should_submit, stripped)
|
|
306
|
+
if command != self._typed_text:
|
|
307
|
+
self._update_typed_text(command)
|
|
308
|
+
self._dispatch_result(command)
|
|
279
309
|
elif found:
|
|
280
310
|
# Wake word found but no command text after it
|
|
281
311
|
if self._typed_text:
|
|
@@ -285,29 +315,16 @@ class VoiceDaemon:
|
|
|
285
315
|
# Final transcription lost the wake word (e.g. audio clipping
|
|
286
316
|
# turned "computer" into "uter"); fall back to the command
|
|
287
317
|
# captured during streaming
|
|
288
|
-
|
|
289
|
-
self._dispatch_result(should_submit, stripped or self._typed_text)
|
|
318
|
+
self._dispatch_result(self._typed_text)
|
|
290
319
|
else:
|
|
291
320
|
# Check final transcription for wake word
|
|
292
321
|
found, command = check_wake_word(text)
|
|
293
322
|
if found and command:
|
|
294
|
-
|
|
295
|
-
if
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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()
|
|
323
|
+
log("wake_word_detected", command)
|
|
324
|
+
if DEBUG:
|
|
325
|
+
print(f" Wake word! Final: {command}", file=sys.stderr)
|
|
326
|
+
keyboard.type_text(command)
|
|
327
|
+
self._dispatch_result(command)
|
|
311
328
|
if found and not command:
|
|
312
329
|
# Wake word only — enter ACTIVATED state for next utterance
|
|
313
330
|
log("wake_word_only", "Listening for command...")
|
|
@@ -367,7 +384,7 @@ class VoiceDaemon:
|
|
|
367
384
|
prob = self._vad.process(chunk)
|
|
368
385
|
|
|
369
386
|
if DEBUG:
|
|
370
|
-
|
|
387
|
+
_print_state(self._state)
|
|
371
388
|
|
|
372
389
|
if self._state == IDLE:
|
|
373
390
|
if prob > self._vad.threshold:
|
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.85.0",
|
|
10
10
|
type: "module",
|
|
11
11
|
main: "dist/index.js",
|
|
12
12
|
bin: {
|
|
@@ -84,6 +84,7 @@ var runConfigSchema = z.strictObject({
|
|
|
84
84
|
name: z.string(),
|
|
85
85
|
command: z.string(),
|
|
86
86
|
args: z.array(z.string()).optional(),
|
|
87
|
+
env: z.record(z.string(), z.string()).optional(),
|
|
87
88
|
filter: z.string().optional()
|
|
88
89
|
});
|
|
89
90
|
var transcriptConfigSchema = z.strictObject({
|
|
@@ -91,6 +92,8 @@ var transcriptConfigSchema = z.strictObject({
|
|
|
91
92
|
transcriptsDir: z.string(),
|
|
92
93
|
summaryDir: z.string()
|
|
93
94
|
});
|
|
95
|
+
var DEFAULT_WAKE_WORDS = ["computer"];
|
|
96
|
+
var DEFAULT_MODELS_DIR = "~/.assist/voice/models";
|
|
94
97
|
var assistConfigSchema = z.strictObject({
|
|
95
98
|
commit: z.strictObject({
|
|
96
99
|
conventional: z.boolean().default(false),
|
|
@@ -126,18 +129,21 @@ var assistConfigSchema = z.strictObject({
|
|
|
126
129
|
run: z.array(runConfigSchema).optional(),
|
|
127
130
|
transcript: transcriptConfigSchema.optional(),
|
|
128
131
|
voice: z.strictObject({
|
|
129
|
-
wakeWords: z.array(z.string()).default(
|
|
132
|
+
wakeWords: z.array(z.string()).default(DEFAULT_WAKE_WORDS),
|
|
130
133
|
mic: z.string().optional(),
|
|
131
134
|
cwd: z.string().optional(),
|
|
132
|
-
modelsDir: z.string().
|
|
135
|
+
modelsDir: z.string().default(DEFAULT_MODELS_DIR),
|
|
133
136
|
lockDir: z.string().optional(),
|
|
134
|
-
|
|
137
|
+
submitWindows: z.array(z.string()).optional(),
|
|
135
138
|
models: z.strictObject({
|
|
136
139
|
vad: z.string().optional(),
|
|
137
|
-
smartTurn: z.string().optional()
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
140
|
+
smartTurn: z.string().optional()
|
|
141
|
+
}).default({})
|
|
142
|
+
}).default({
|
|
143
|
+
wakeWords: DEFAULT_WAKE_WORDS,
|
|
144
|
+
modelsDir: DEFAULT_MODELS_DIR,
|
|
145
|
+
models: {}
|
|
146
|
+
})
|
|
141
147
|
});
|
|
142
148
|
|
|
143
149
|
// src/shared/loadConfig.ts
|
|
@@ -453,6 +459,7 @@ var expectedScripts = {
|
|
|
453
459
|
"verify:duplicate-code": "jscpd --format 'typescript,tsx' --exitCode 1 --ignore '**/*.test.*' -r consoleFull src",
|
|
454
460
|
"verify:test": "vitest run --reporter=dot --silent",
|
|
455
461
|
"verify:hardcoded-colors": "assist verify hardcoded-colors",
|
|
462
|
+
"verify:no-venv": "assist verify no-venv",
|
|
456
463
|
"verify:maintainability": "assist complexity maintainability ./src --threshold 60"
|
|
457
464
|
};
|
|
458
465
|
|
|
@@ -1342,7 +1349,7 @@ function lint() {
|
|
|
1342
1349
|
}
|
|
1343
1350
|
|
|
1344
1351
|
// src/commands/new/registerNew/newCli/index.ts
|
|
1345
|
-
import { execSync as
|
|
1352
|
+
import { execSync as execSync10 } from "child_process";
|
|
1346
1353
|
import { basename as basename2, resolve } from "path";
|
|
1347
1354
|
|
|
1348
1355
|
// src/commands/verify/hardcodedColors.ts
|
|
@@ -1403,6 +1410,7 @@ function getRunEntries() {
|
|
|
1403
1410
|
return run3.filter((r) => r.name.startsWith("verify:")).map((r) => ({
|
|
1404
1411
|
name: r.name,
|
|
1405
1412
|
fullCommand: buildFullCommand(r.command, r.args),
|
|
1413
|
+
env: r.env,
|
|
1406
1414
|
filter: r.filter
|
|
1407
1415
|
}));
|
|
1408
1416
|
}
|
|
@@ -1432,6 +1440,38 @@ function list() {
|
|
|
1432
1440
|
}
|
|
1433
1441
|
}
|
|
1434
1442
|
|
|
1443
|
+
// src/commands/verify/noVenv.ts
|
|
1444
|
+
import { execSync as execSync6 } from "child_process";
|
|
1445
|
+
function noVenv() {
|
|
1446
|
+
try {
|
|
1447
|
+
const output = execSync6(
|
|
1448
|
+
"find . -type d -name venv -not -path '*/node_modules/*'",
|
|
1449
|
+
{
|
|
1450
|
+
encoding: "utf-8"
|
|
1451
|
+
}
|
|
1452
|
+
).trim();
|
|
1453
|
+
if (output.length === 0) {
|
|
1454
|
+
console.log("No venv folders found.");
|
|
1455
|
+
process.exit(0);
|
|
1456
|
+
}
|
|
1457
|
+
const folders = output.split("\n");
|
|
1458
|
+
console.log("venv folders found:\n");
|
|
1459
|
+
for (const folder of folders) {
|
|
1460
|
+
console.log(` ${folder}`);
|
|
1461
|
+
}
|
|
1462
|
+
console.log(`
|
|
1463
|
+
Total: ${folders.length} venv folder(s)`);
|
|
1464
|
+
console.log(
|
|
1465
|
+
"\nRemove venv folders and use a project-level virtual environment manager instead."
|
|
1466
|
+
);
|
|
1467
|
+
console.log("Add 'venv/' to .gitignore if needed.");
|
|
1468
|
+
process.exit(1);
|
|
1469
|
+
} catch {
|
|
1470
|
+
console.log("No venv folders found.");
|
|
1471
|
+
process.exit(0);
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1435
1475
|
// src/commands/verify/run/createTimerCallback/printTaskStatuses.ts
|
|
1436
1476
|
function formatDuration(ms) {
|
|
1437
1477
|
if (ms < 1e3) {
|
|
@@ -1478,9 +1518,9 @@ function initTaskStatuses(scripts) {
|
|
|
1478
1518
|
import { minimatch as minimatch2 } from "minimatch";
|
|
1479
1519
|
|
|
1480
1520
|
// src/commands/verify/run/getChangedFiles.ts
|
|
1481
|
-
import { execSync as
|
|
1521
|
+
import { execSync as execSync7 } from "child_process";
|
|
1482
1522
|
function getChangedFiles() {
|
|
1483
|
-
const output =
|
|
1523
|
+
const output = execSync7("git diff --name-only HEAD", {
|
|
1484
1524
|
encoding: "utf-8"
|
|
1485
1525
|
}).trim();
|
|
1486
1526
|
if (output === "") return [];
|
|
@@ -1502,12 +1542,26 @@ function filterByChangedFiles(entries) {
|
|
|
1502
1542
|
|
|
1503
1543
|
// src/commands/verify/run/spawnCommand.ts
|
|
1504
1544
|
import { spawn } from "child_process";
|
|
1545
|
+
|
|
1546
|
+
// src/shared/expandEnv.ts
|
|
1547
|
+
import { homedir as homedir2 } from "os";
|
|
1548
|
+
function expandTilde(value) {
|
|
1549
|
+
return value.startsWith("~/") ? homedir2() + value.slice(1) : value;
|
|
1550
|
+
}
|
|
1551
|
+
function expandEnv(env) {
|
|
1552
|
+
return Object.fromEntries(
|
|
1553
|
+
Object.entries(env).map(([k, v]) => [k, expandTilde(v)])
|
|
1554
|
+
);
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
// src/commands/verify/run/spawnCommand.ts
|
|
1505
1558
|
var isClaudeCode = !!process.env.CLAUDECODE;
|
|
1506
|
-
function spawnCommand(fullCommand, cwd) {
|
|
1559
|
+
function spawnCommand(fullCommand, cwd, env) {
|
|
1507
1560
|
return spawn(fullCommand, [], {
|
|
1508
1561
|
stdio: isClaudeCode ? "pipe" : "inherit",
|
|
1509
1562
|
shell: true,
|
|
1510
|
-
cwd: cwd ?? process.cwd()
|
|
1563
|
+
cwd: cwd ?? process.cwd(),
|
|
1564
|
+
env: env ? { ...process.env, ...expandEnv(env) } : void 0
|
|
1511
1565
|
});
|
|
1512
1566
|
}
|
|
1513
1567
|
function collectOutput(child) {
|
|
@@ -1526,7 +1580,7 @@ function flushIfFailed(exitCode, chunks) {
|
|
|
1526
1580
|
// src/commands/verify/run/index.ts
|
|
1527
1581
|
function runEntry(entry, onComplete) {
|
|
1528
1582
|
return new Promise((resolve3) => {
|
|
1529
|
-
const child = spawnCommand(entry.fullCommand, entry.cwd);
|
|
1583
|
+
const child = spawnCommand(entry.fullCommand, entry.cwd, entry.env);
|
|
1530
1584
|
const chunks = collectOutput(child);
|
|
1531
1585
|
child.on("close", (code) => {
|
|
1532
1586
|
const exitCode = code ?? 1;
|
|
@@ -1580,25 +1634,25 @@ async function run(options2 = {}) {
|
|
|
1580
1634
|
}
|
|
1581
1635
|
|
|
1582
1636
|
// src/commands/new/registerNew/initGit.ts
|
|
1583
|
-
import { execSync as
|
|
1637
|
+
import { execSync as execSync8 } from "child_process";
|
|
1584
1638
|
import { writeFileSync as writeFileSync6 } from "fs";
|
|
1585
1639
|
function initGit() {
|
|
1586
1640
|
console.log("Initializing git repository...");
|
|
1587
|
-
|
|
1641
|
+
execSync8("git init", { stdio: "inherit" });
|
|
1588
1642
|
writeFileSync6(".gitignore", "dist\nnode_modules\n");
|
|
1589
1643
|
}
|
|
1590
1644
|
|
|
1591
1645
|
// src/commands/new/registerNew/newCli/initPackageJson.ts
|
|
1592
|
-
import { execSync as
|
|
1646
|
+
import { execSync as execSync9 } from "child_process";
|
|
1593
1647
|
function initPackageJson(name) {
|
|
1594
1648
|
console.log("Initializing package.json...");
|
|
1595
|
-
|
|
1649
|
+
execSync9("npm init -y", { stdio: "inherit" });
|
|
1596
1650
|
console.log("Configuring package.json...");
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1651
|
+
execSync9("npm pkg delete main", { stdio: "inherit" });
|
|
1652
|
+
execSync9("npm pkg set type=module", { stdio: "inherit" });
|
|
1653
|
+
execSync9(`npm pkg set bin.${name}=./dist/index.js`, { stdio: "inherit" });
|
|
1654
|
+
execSync9("npm pkg set scripts.build=tsup", { stdio: "inherit" });
|
|
1655
|
+
execSync9('npm pkg set scripts.start="node dist/index.js"', {
|
|
1602
1656
|
stdio: "inherit"
|
|
1603
1657
|
});
|
|
1604
1658
|
}
|
|
@@ -1663,8 +1717,8 @@ async function newCli() {
|
|
|
1663
1717
|
initGit();
|
|
1664
1718
|
initPackageJson(name);
|
|
1665
1719
|
console.log("Installing dependencies...");
|
|
1666
|
-
|
|
1667
|
-
|
|
1720
|
+
execSync10("npm install commander", { stdio: "inherit" });
|
|
1721
|
+
execSync10("npm install -D tsup typescript @types/node", {
|
|
1668
1722
|
stdio: "inherit"
|
|
1669
1723
|
});
|
|
1670
1724
|
writeCliTemplate(name);
|
|
@@ -1673,11 +1727,11 @@ async function newCli() {
|
|
|
1673
1727
|
}
|
|
1674
1728
|
|
|
1675
1729
|
// src/commands/new/registerNew/newProject.ts
|
|
1676
|
-
import { execSync as
|
|
1730
|
+
import { execSync as execSync12 } from "child_process";
|
|
1677
1731
|
import { existsSync as existsSync10, readFileSync as readFileSync8, writeFileSync as writeFileSync9 } from "fs";
|
|
1678
1732
|
|
|
1679
1733
|
// src/commands/deploy/init/index.ts
|
|
1680
|
-
import { execSync as
|
|
1734
|
+
import { execSync as execSync11 } from "child_process";
|
|
1681
1735
|
import chalk21 from "chalk";
|
|
1682
1736
|
import enquirer3 from "enquirer";
|
|
1683
1737
|
|
|
@@ -1730,7 +1784,7 @@ Created ${WORKFLOW_PATH}`));
|
|
|
1730
1784
|
// src/commands/deploy/init/index.ts
|
|
1731
1785
|
async function ensureNetlifyCli() {
|
|
1732
1786
|
try {
|
|
1733
|
-
|
|
1787
|
+
execSync11("netlify sites:create --disable-linking", { stdio: "inherit" });
|
|
1734
1788
|
} catch (error) {
|
|
1735
1789
|
if (!(error instanceof Error) || !error.message.includes("command not found"))
|
|
1736
1790
|
throw error;
|
|
@@ -1745,9 +1799,9 @@ async function ensureNetlifyCli() {
|
|
|
1745
1799
|
process.exit(1);
|
|
1746
1800
|
}
|
|
1747
1801
|
console.log(chalk21.dim("\nInstalling netlify-cli...\n"));
|
|
1748
|
-
|
|
1802
|
+
execSync11("npm install -g netlify-cli", { stdio: "inherit" });
|
|
1749
1803
|
console.log();
|
|
1750
|
-
|
|
1804
|
+
execSync11("netlify sites:create --disable-linking", { stdio: "inherit" });
|
|
1751
1805
|
}
|
|
1752
1806
|
}
|
|
1753
1807
|
function printSetupInstructions() {
|
|
@@ -1790,7 +1844,7 @@ async function init5() {
|
|
|
1790
1844
|
// src/commands/new/registerNew/newProject.ts
|
|
1791
1845
|
async function newProject() {
|
|
1792
1846
|
console.log("Initializing Vite with react-ts template...");
|
|
1793
|
-
|
|
1847
|
+
execSync12("npm create vite@latest . -- --template react-ts", {
|
|
1794
1848
|
stdio: "inherit"
|
|
1795
1849
|
});
|
|
1796
1850
|
initGit();
|
|
@@ -2943,18 +2997,18 @@ function registerDeploy(program2) {
|
|
|
2943
2997
|
}
|
|
2944
2998
|
|
|
2945
2999
|
// src/commands/devlog/list/index.ts
|
|
2946
|
-
import { execSync as
|
|
3000
|
+
import { execSync as execSync14 } from "child_process";
|
|
2947
3001
|
import { basename as basename3 } from "path";
|
|
2948
3002
|
|
|
2949
3003
|
// src/commands/devlog/shared.ts
|
|
2950
|
-
import { execSync as
|
|
3004
|
+
import { execSync as execSync13 } from "child_process";
|
|
2951
3005
|
import chalk37 from "chalk";
|
|
2952
3006
|
|
|
2953
3007
|
// src/commands/devlog/loadDevlogEntries.ts
|
|
2954
3008
|
import { readdirSync, readFileSync as readFileSync13 } from "fs";
|
|
2955
|
-
import { homedir as
|
|
3009
|
+
import { homedir as homedir3 } from "os";
|
|
2956
3010
|
import { join as join11 } from "path";
|
|
2957
|
-
var DEVLOG_DIR = join11(
|
|
3011
|
+
var DEVLOG_DIR = join11(homedir3(), "git/blog/src/content/devlog");
|
|
2958
3012
|
function loadDevlogEntries(repoName) {
|
|
2959
3013
|
const entries = /* @__PURE__ */ new Map();
|
|
2960
3014
|
try {
|
|
@@ -2991,7 +3045,7 @@ function loadDevlogEntries(repoName) {
|
|
|
2991
3045
|
// src/commands/devlog/shared.ts
|
|
2992
3046
|
function getCommitFiles(hash) {
|
|
2993
3047
|
try {
|
|
2994
|
-
const output =
|
|
3048
|
+
const output = execSync13(`git show --name-only --format="" ${hash}`, {
|
|
2995
3049
|
encoding: "utf-8"
|
|
2996
3050
|
});
|
|
2997
3051
|
return output.trim().split("\n").filter(Boolean);
|
|
@@ -3062,7 +3116,7 @@ function list3(options2) {
|
|
|
3062
3116
|
const devlogEntries = loadDevlogEntries(repoName);
|
|
3063
3117
|
const reverseFlag = options2.reverse ? "--reverse " : "";
|
|
3064
3118
|
const limitFlag = options2.reverse ? "" : "-n 500 ";
|
|
3065
|
-
const output =
|
|
3119
|
+
const output = execSync14(
|
|
3066
3120
|
`git log ${reverseFlag}${limitFlag}--pretty=format:'%ad|%h|%s' --date=short`,
|
|
3067
3121
|
{ encoding: "utf-8" }
|
|
3068
3122
|
);
|
|
@@ -3088,11 +3142,11 @@ function list3(options2) {
|
|
|
3088
3142
|
}
|
|
3089
3143
|
|
|
3090
3144
|
// src/commands/devlog/getLastVersionInfo.ts
|
|
3091
|
-
import { execSync as
|
|
3145
|
+
import { execSync as execSync15 } from "child_process";
|
|
3092
3146
|
import semver from "semver";
|
|
3093
3147
|
function getVersionAtCommit(hash) {
|
|
3094
3148
|
try {
|
|
3095
|
-
const content =
|
|
3149
|
+
const content = execSync15(`git show ${hash}:package.json`, {
|
|
3096
3150
|
encoding: "utf-8"
|
|
3097
3151
|
});
|
|
3098
3152
|
const pkg = JSON.parse(content);
|
|
@@ -3107,7 +3161,7 @@ function stripToMinor(version2) {
|
|
|
3107
3161
|
}
|
|
3108
3162
|
function getLastVersionInfoFromGit() {
|
|
3109
3163
|
try {
|
|
3110
|
-
const output =
|
|
3164
|
+
const output = execSync15(
|
|
3111
3165
|
"git log -1 --pretty=format:'%ad|%h' --date=short",
|
|
3112
3166
|
{
|
|
3113
3167
|
encoding: "utf-8"
|
|
@@ -3150,7 +3204,7 @@ function bumpVersion(version2, type) {
|
|
|
3150
3204
|
}
|
|
3151
3205
|
|
|
3152
3206
|
// src/commands/devlog/next/displayNextEntry/index.ts
|
|
3153
|
-
import { execSync as
|
|
3207
|
+
import { execSync as execSync16 } from "child_process";
|
|
3154
3208
|
import chalk40 from "chalk";
|
|
3155
3209
|
|
|
3156
3210
|
// src/commands/devlog/next/displayNextEntry/displayVersion.ts
|
|
@@ -3184,7 +3238,7 @@ function findTargetDate(commitsByDate, skipDays) {
|
|
|
3184
3238
|
return Array.from(commitsByDate.keys()).filter((d) => !skipDays.has(d)).sort()[0];
|
|
3185
3239
|
}
|
|
3186
3240
|
function fetchCommitsByDate(ignore2, lastDate) {
|
|
3187
|
-
const output =
|
|
3241
|
+
const output = execSync16(
|
|
3188
3242
|
"git log --pretty=format:'%ad|%h|%s' --date=short -n 500",
|
|
3189
3243
|
{ encoding: "utf-8" }
|
|
3190
3244
|
);
|
|
@@ -3315,7 +3369,7 @@ import { tmpdir as tmpdir2 } from "os";
|
|
|
3315
3369
|
import { join as join12 } from "path";
|
|
3316
3370
|
|
|
3317
3371
|
// src/commands/prs/shared.ts
|
|
3318
|
-
import { execSync as
|
|
3372
|
+
import { execSync as execSync17 } from "child_process";
|
|
3319
3373
|
function isGhNotInstalled(error) {
|
|
3320
3374
|
if (error instanceof Error) {
|
|
3321
3375
|
const msg = error.message.toLowerCase();
|
|
@@ -3331,14 +3385,14 @@ function isNotFound(error) {
|
|
|
3331
3385
|
}
|
|
3332
3386
|
function getRepoInfo() {
|
|
3333
3387
|
const repoInfo = JSON.parse(
|
|
3334
|
-
|
|
3388
|
+
execSync17("gh repo view --json owner,name", { encoding: "utf-8" })
|
|
3335
3389
|
);
|
|
3336
3390
|
return { org: repoInfo.owner.login, repo: repoInfo.name };
|
|
3337
3391
|
}
|
|
3338
3392
|
function getCurrentPrNumber() {
|
|
3339
3393
|
try {
|
|
3340
3394
|
const prInfo = JSON.parse(
|
|
3341
|
-
|
|
3395
|
+
execSync17("gh pr view --json number", { encoding: "utf-8" })
|
|
3342
3396
|
);
|
|
3343
3397
|
return prInfo.number;
|
|
3344
3398
|
} catch (error) {
|
|
@@ -3352,7 +3406,7 @@ function getCurrentPrNumber() {
|
|
|
3352
3406
|
function getCurrentPrNodeId() {
|
|
3353
3407
|
try {
|
|
3354
3408
|
const prInfo = JSON.parse(
|
|
3355
|
-
|
|
3409
|
+
execSync17("gh pr view --json id", { encoding: "utf-8" })
|
|
3356
3410
|
);
|
|
3357
3411
|
return prInfo.id;
|
|
3358
3412
|
} catch (error) {
|
|
@@ -3423,10 +3477,10 @@ function comment(path31, line, body) {
|
|
|
3423
3477
|
}
|
|
3424
3478
|
|
|
3425
3479
|
// src/commands/prs/fixed.ts
|
|
3426
|
-
import { execSync as
|
|
3480
|
+
import { execSync as execSync19 } from "child_process";
|
|
3427
3481
|
|
|
3428
3482
|
// src/commands/prs/resolveCommentWithReply.ts
|
|
3429
|
-
import { execSync as
|
|
3483
|
+
import { execSync as execSync18 } from "child_process";
|
|
3430
3484
|
import { unlinkSync as unlinkSync5, writeFileSync as writeFileSync14 } from "fs";
|
|
3431
3485
|
import { tmpdir as tmpdir3 } from "os";
|
|
3432
3486
|
import { join as join14 } from "path";
|
|
@@ -3456,7 +3510,7 @@ function deleteCommentsCache(prNumber) {
|
|
|
3456
3510
|
|
|
3457
3511
|
// src/commands/prs/resolveCommentWithReply.ts
|
|
3458
3512
|
function replyToComment(org, repo, prNumber, commentId, message) {
|
|
3459
|
-
|
|
3513
|
+
execSync18(
|
|
3460
3514
|
`gh api repos/${org}/${repo}/pulls/${prNumber}/comments -f body="${message.replace(/"/g, '\\"')}" -F in_reply_to=${commentId}`,
|
|
3461
3515
|
{ stdio: "inherit" }
|
|
3462
3516
|
);
|
|
@@ -3466,7 +3520,7 @@ function resolveThread(threadId) {
|
|
|
3466
3520
|
const queryFile = join14(tmpdir3(), `gh-mutation-${Date.now()}.graphql`);
|
|
3467
3521
|
writeFileSync14(queryFile, mutation);
|
|
3468
3522
|
try {
|
|
3469
|
-
|
|
3523
|
+
execSync18(
|
|
3470
3524
|
`gh api graphql -F query=@${queryFile} -f threadId="${threadId}"`,
|
|
3471
3525
|
{ stdio: "inherit" }
|
|
3472
3526
|
);
|
|
@@ -3518,7 +3572,7 @@ function resolveCommentWithReply(commentId, message) {
|
|
|
3518
3572
|
// src/commands/prs/fixed.ts
|
|
3519
3573
|
function verifySha(sha) {
|
|
3520
3574
|
try {
|
|
3521
|
-
return
|
|
3575
|
+
return execSync19(`git rev-parse --verify ${sha}`, {
|
|
3522
3576
|
encoding: "utf-8"
|
|
3523
3577
|
}).trim();
|
|
3524
3578
|
} catch {
|
|
@@ -3554,7 +3608,7 @@ function isClaudeCode2() {
|
|
|
3554
3608
|
}
|
|
3555
3609
|
|
|
3556
3610
|
// src/commands/prs/fetchThreadIds.ts
|
|
3557
|
-
import { execSync as
|
|
3611
|
+
import { execSync as execSync20 } from "child_process";
|
|
3558
3612
|
import { unlinkSync as unlinkSync6, writeFileSync as writeFileSync15 } from "fs";
|
|
3559
3613
|
import { tmpdir as tmpdir4 } from "os";
|
|
3560
3614
|
import { join as join15 } from "path";
|
|
@@ -3563,7 +3617,7 @@ function fetchThreadIds(org, repo, prNumber) {
|
|
|
3563
3617
|
const queryFile = join15(tmpdir4(), `gh-query-${Date.now()}.graphql`);
|
|
3564
3618
|
writeFileSync15(queryFile, THREAD_QUERY);
|
|
3565
3619
|
try {
|
|
3566
|
-
const result =
|
|
3620
|
+
const result = execSync20(
|
|
3567
3621
|
`gh api graphql -F query=@${queryFile} -F owner="${org}" -F repo="${repo}" -F prNumber=${prNumber}`,
|
|
3568
3622
|
{ encoding: "utf-8" }
|
|
3569
3623
|
);
|
|
@@ -3585,9 +3639,9 @@ function fetchThreadIds(org, repo, prNumber) {
|
|
|
3585
3639
|
}
|
|
3586
3640
|
|
|
3587
3641
|
// src/commands/prs/listComments/fetchReviewComments.ts
|
|
3588
|
-
import { execSync as
|
|
3642
|
+
import { execSync as execSync21 } from "child_process";
|
|
3589
3643
|
function fetchJson(endpoint) {
|
|
3590
|
-
const result =
|
|
3644
|
+
const result = execSync21(`gh api ${endpoint}`, { encoding: "utf-8" });
|
|
3591
3645
|
if (!result.trim()) return [];
|
|
3592
3646
|
return JSON.parse(result);
|
|
3593
3647
|
}
|
|
@@ -3713,7 +3767,7 @@ async function listComments() {
|
|
|
3713
3767
|
}
|
|
3714
3768
|
|
|
3715
3769
|
// src/commands/prs/prs/index.ts
|
|
3716
|
-
import { execSync as
|
|
3770
|
+
import { execSync as execSync22 } from "child_process";
|
|
3717
3771
|
|
|
3718
3772
|
// src/commands/prs/prs/displayPaginated/index.ts
|
|
3719
3773
|
import enquirer5 from "enquirer";
|
|
@@ -3819,7 +3873,7 @@ async function displayPaginated(pullRequests) {
|
|
|
3819
3873
|
async function prs(options2) {
|
|
3820
3874
|
const state = options2.open ? "open" : options2.closed ? "closed" : "all";
|
|
3821
3875
|
try {
|
|
3822
|
-
const result =
|
|
3876
|
+
const result = execSync22(
|
|
3823
3877
|
`gh pr list --state ${state} --json number,title,url,author,createdAt,mergedAt,closedAt,state,changedFiles --limit 100`,
|
|
3824
3878
|
{ encoding: "utf-8" }
|
|
3825
3879
|
);
|
|
@@ -3842,7 +3896,7 @@ async function prs(options2) {
|
|
|
3842
3896
|
}
|
|
3843
3897
|
|
|
3844
3898
|
// src/commands/prs/wontfix.ts
|
|
3845
|
-
import { execSync as
|
|
3899
|
+
import { execSync as execSync23 } from "child_process";
|
|
3846
3900
|
function validateReason(reason) {
|
|
3847
3901
|
const lowerReason = reason.toLowerCase();
|
|
3848
3902
|
if (lowerReason.includes("claude") || lowerReason.includes("opus")) {
|
|
@@ -3859,7 +3913,7 @@ function validateShaReferences(reason) {
|
|
|
3859
3913
|
const invalidShas = [];
|
|
3860
3914
|
for (const sha of shas) {
|
|
3861
3915
|
try {
|
|
3862
|
-
|
|
3916
|
+
execSync23(`git cat-file -t ${sha}`, { stdio: "pipe" });
|
|
3863
3917
|
} catch {
|
|
3864
3918
|
invalidShas.push(sha);
|
|
3865
3919
|
}
|
|
@@ -3963,7 +4017,7 @@ Refactor check failed:
|
|
|
3963
4017
|
}
|
|
3964
4018
|
|
|
3965
4019
|
// src/commands/refactor/check/getViolations/index.ts
|
|
3966
|
-
import { execSync as
|
|
4020
|
+
import { execSync as execSync24 } from "child_process";
|
|
3967
4021
|
import fs15 from "fs";
|
|
3968
4022
|
import { minimatch as minimatch4 } from "minimatch";
|
|
3969
4023
|
|
|
@@ -4013,7 +4067,7 @@ function getGitFiles(options2) {
|
|
|
4013
4067
|
}
|
|
4014
4068
|
const files = /* @__PURE__ */ new Set();
|
|
4015
4069
|
if (options2.staged || options2.modified) {
|
|
4016
|
-
const staged =
|
|
4070
|
+
const staged = execSync24("git diff --cached --name-only", {
|
|
4017
4071
|
encoding: "utf-8"
|
|
4018
4072
|
});
|
|
4019
4073
|
for (const file of staged.trim().split("\n").filter(Boolean)) {
|
|
@@ -4021,7 +4075,7 @@ function getGitFiles(options2) {
|
|
|
4021
4075
|
}
|
|
4022
4076
|
}
|
|
4023
4077
|
if (options2.unstaged || options2.modified) {
|
|
4024
|
-
const unstaged =
|
|
4078
|
+
const unstaged = execSync24("git diff --name-only", { encoding: "utf-8" });
|
|
4025
4079
|
for (const file of unstaged.trim().split("\n").filter(Boolean)) {
|
|
4026
4080
|
files.add(file);
|
|
4027
4081
|
}
|
|
@@ -5299,6 +5353,7 @@ function registerVerify(program2) {
|
|
|
5299
5353
|
verifyCommand.command("list").description("List configured verify commands").action(list);
|
|
5300
5354
|
verifyCommand.command("init").description("Add verify scripts to a project").action(init2);
|
|
5301
5355
|
verifyCommand.command("hardcoded-colors").description("Check for hardcoded hex colors in src/").action(hardcodedColors);
|
|
5356
|
+
verifyCommand.command("no-venv").description("Check that no venv folders exist in the repo").action(noVenv);
|
|
5302
5357
|
}
|
|
5303
5358
|
|
|
5304
5359
|
// src/commands/voice/devices.ts
|
|
@@ -5306,11 +5361,11 @@ import { spawnSync as spawnSync3 } from "child_process";
|
|
|
5306
5361
|
import { join as join24 } from "path";
|
|
5307
5362
|
|
|
5308
5363
|
// src/commands/voice/shared.ts
|
|
5309
|
-
import { homedir as
|
|
5364
|
+
import { homedir as homedir4 } from "os";
|
|
5310
5365
|
import { dirname as dirname17, join as join23 } from "path";
|
|
5311
5366
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
5312
5367
|
var __dirname5 = dirname17(fileURLToPath4(import.meta.url));
|
|
5313
|
-
var VOICE_DIR = join23(
|
|
5368
|
+
var VOICE_DIR = join23(homedir4(), ".assist", "voice");
|
|
5314
5369
|
var voicePaths = {
|
|
5315
5370
|
dir: VOICE_DIR,
|
|
5316
5371
|
pid: join23(VOICE_DIR, "voice.pid"),
|
|
@@ -5372,7 +5427,7 @@ import { mkdirSync as mkdirSync8 } from "fs";
|
|
|
5372
5427
|
import { join as join26 } from "path";
|
|
5373
5428
|
|
|
5374
5429
|
// src/commands/voice/checkLockFile.ts
|
|
5375
|
-
import { execSync as
|
|
5430
|
+
import { execSync as execSync25 } from "child_process";
|
|
5376
5431
|
import { existsSync as existsSync24, mkdirSync as mkdirSync7, readFileSync as readFileSync18, writeFileSync as writeFileSync18 } from "fs";
|
|
5377
5432
|
import { join as join25 } from "path";
|
|
5378
5433
|
function isProcessAlive(pid) {
|
|
@@ -5401,7 +5456,7 @@ function bootstrapVenv() {
|
|
|
5401
5456
|
if (existsSync24(getVenvPython())) return;
|
|
5402
5457
|
console.log("Setting up Python environment...");
|
|
5403
5458
|
const pythonDir = getPythonDir();
|
|
5404
|
-
|
|
5459
|
+
execSync25(`uv sync --project "${pythonDir}" --no-install-project`, {
|
|
5405
5460
|
stdio: "inherit",
|
|
5406
5461
|
env: { ...process.env, UV_PROJECT_ENVIRONMENT: voicePaths.venv }
|
|
5407
5462
|
});
|
|
@@ -5441,26 +5496,8 @@ import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync19 } from "fs";
|
|
|
5441
5496
|
import { join as join27 } from "path";
|
|
5442
5497
|
|
|
5443
5498
|
// src/commands/voice/buildDaemonEnv.ts
|
|
5444
|
-
var ENV_MAP = {
|
|
5445
|
-
VOICE_WAKE_WORDS: (v) => v.wakeWords?.join(","),
|
|
5446
|
-
VOICE_MIC: (v) => v.mic,
|
|
5447
|
-
VOICE_CWD: (v) => v.cwd,
|
|
5448
|
-
VOICE_MODELS_DIR: (v) => v.modelsDir,
|
|
5449
|
-
VOICE_MODEL_VAD: (v) => v.models?.vad,
|
|
5450
|
-
VOICE_MODEL_SMART_TURN: (v) => v.models?.smartTurn,
|
|
5451
|
-
VOICE_MODEL_STT: (v) => v.models?.stt,
|
|
5452
|
-
VOICE_SUBMIT_WORD: (v) => v.submitWord
|
|
5453
|
-
};
|
|
5454
5499
|
function buildDaemonEnv(options2) {
|
|
5455
|
-
const config = loadConfig();
|
|
5456
5500
|
const env = { ...process.env };
|
|
5457
|
-
const voice = config.voice;
|
|
5458
|
-
if (voice) {
|
|
5459
|
-
for (const [key, getter] of Object.entries(ENV_MAP)) {
|
|
5460
|
-
const value = getter(voice);
|
|
5461
|
-
if (value) env[key] = value;
|
|
5462
|
-
}
|
|
5463
|
-
}
|
|
5464
5501
|
env.VOICE_LOG_FILE = voicePaths.log;
|
|
5465
5502
|
if (options2?.debug) env.VOICE_DEBUG = "1";
|
|
5466
5503
|
return env;
|
|
@@ -5583,11 +5620,11 @@ import { randomBytes } from "crypto";
|
|
|
5583
5620
|
import chalk51 from "chalk";
|
|
5584
5621
|
|
|
5585
5622
|
// src/lib/openBrowser.ts
|
|
5586
|
-
import { execSync as
|
|
5623
|
+
import { execSync as execSync26 } from "child_process";
|
|
5587
5624
|
function tryExec(commands) {
|
|
5588
5625
|
for (const cmd of commands) {
|
|
5589
5626
|
try {
|
|
5590
|
-
|
|
5627
|
+
execSync26(cmd);
|
|
5591
5628
|
return true;
|
|
5592
5629
|
} catch {
|
|
5593
5630
|
}
|
|
@@ -5903,8 +5940,12 @@ function onSpawnError(err) {
|
|
|
5903
5940
|
console.error(`Failed to execute command: ${err.message}`);
|
|
5904
5941
|
process.exit(1);
|
|
5905
5942
|
}
|
|
5906
|
-
function spawnCommand2(fullCommand) {
|
|
5907
|
-
const child = spawn5(fullCommand, [], {
|
|
5943
|
+
function spawnCommand2(fullCommand, env) {
|
|
5944
|
+
const child = spawn5(fullCommand, [], {
|
|
5945
|
+
stdio: "inherit",
|
|
5946
|
+
shell: true,
|
|
5947
|
+
env: env ? { ...process.env, ...expandEnv(env) } : void 0
|
|
5948
|
+
});
|
|
5908
5949
|
child.on("close", (code) => process.exit(code ?? 0));
|
|
5909
5950
|
child.on("error", onSpawnError);
|
|
5910
5951
|
}
|
|
@@ -5917,7 +5958,10 @@ function listRunConfigs() {
|
|
|
5917
5958
|
}
|
|
5918
5959
|
function run2(name, args) {
|
|
5919
5960
|
const runConfig = findRunConfig(name);
|
|
5920
|
-
spawnCommand2(
|
|
5961
|
+
spawnCommand2(
|
|
5962
|
+
buildCommand(runConfig.command, runConfig.args ?? [], args),
|
|
5963
|
+
runConfig.env
|
|
5964
|
+
);
|
|
5921
5965
|
}
|
|
5922
5966
|
|
|
5923
5967
|
// src/commands/statusLine.ts
|
|
@@ -6033,7 +6077,7 @@ function syncCommands(claudeDir, targetBase) {
|
|
|
6033
6077
|
}
|
|
6034
6078
|
|
|
6035
6079
|
// src/commands/update.ts
|
|
6036
|
-
import { execSync as
|
|
6080
|
+
import { execSync as execSync27 } from "child_process";
|
|
6037
6081
|
import * as path30 from "path";
|
|
6038
6082
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
6039
6083
|
var __filename3 = fileURLToPath6(import.meta.url);
|
|
@@ -6043,7 +6087,7 @@ function getInstallDir() {
|
|
|
6043
6087
|
}
|
|
6044
6088
|
function isGitRepo(dir) {
|
|
6045
6089
|
try {
|
|
6046
|
-
|
|
6090
|
+
execSync27("git rev-parse --is-inside-work-tree", {
|
|
6047
6091
|
cwd: dir,
|
|
6048
6092
|
stdio: "pipe"
|
|
6049
6093
|
});
|
|
@@ -6054,7 +6098,7 @@ function isGitRepo(dir) {
|
|
|
6054
6098
|
}
|
|
6055
6099
|
function isGlobalNpmInstall(dir) {
|
|
6056
6100
|
try {
|
|
6057
|
-
const globalPrefix =
|
|
6101
|
+
const globalPrefix = execSync27("npm prefix -g", { stdio: "pipe" }).toString().trim();
|
|
6058
6102
|
return path30.resolve(dir).toLowerCase().startsWith(path30.resolve(globalPrefix).toLowerCase());
|
|
6059
6103
|
} catch {
|
|
6060
6104
|
return false;
|
|
@@ -6065,16 +6109,16 @@ async function update() {
|
|
|
6065
6109
|
console.log(`Assist is installed at: ${installDir}`);
|
|
6066
6110
|
if (isGitRepo(installDir)) {
|
|
6067
6111
|
console.log("Detected git repo installation, pulling latest...");
|
|
6068
|
-
|
|
6112
|
+
execSync27("git pull", { cwd: installDir, stdio: "inherit" });
|
|
6069
6113
|
console.log("Building...");
|
|
6070
|
-
|
|
6114
|
+
execSync27("npm run build", { cwd: installDir, stdio: "inherit" });
|
|
6071
6115
|
console.log("Syncing commands...");
|
|
6072
|
-
|
|
6116
|
+
execSync27("assist sync", { stdio: "inherit" });
|
|
6073
6117
|
} else if (isGlobalNpmInstall(installDir)) {
|
|
6074
6118
|
console.log("Detected global npm installation, updating...");
|
|
6075
|
-
|
|
6119
|
+
execSync27("npm i -g @staff0rd/assist@latest", { stdio: "inherit" });
|
|
6076
6120
|
console.log("Syncing commands...");
|
|
6077
|
-
|
|
6121
|
+
execSync27("assist sync", { stdio: "inherit" });
|
|
6078
6122
|
} else {
|
|
6079
6123
|
console.error(
|
|
6080
6124
|
"Could not determine installation method. Expected a git repo or global npm install."
|