@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.
@@ -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 _print_vad_bar(
40
- prob: float, threshold: float, state: str, chunk: np.ndarray
41
- ) -> None:
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
- self._submit_word = os.environ.get("VOICE_SUBMIT_WORD", "").strip().lower()
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._submit_word:
71
- log("daemon_init", f"Submit word: '{self._submit_word}'")
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 = self._hide_submit_word(text.strip())
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
- 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
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
- partial = self._hide_submit_word(command)
121
- if partial and partial != self._typed_text:
122
- self._update_typed_text(partial)
176
+ if command != self._typed_text:
177
+ self._update_typed_text(command)
123
178
 
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
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, should_submit: bool, stripped: str) -> None:
257
+ def _dispatch_result(self, text: str) -> None:
216
258
  """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)")
259
+ should_submit = self._should_submit()
260
+ if should_submit:
261
+ log("dispatch_enter", text)
232
262
  if DEBUG:
233
- print(" Submit word only [Enter]", file=sys.stderr)
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
- 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)
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
- 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)
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
- should_submit, stripped = self._strip_submit_word(self._typed_text)
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
- 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()
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
- _print_vad_bar(prob, self._vad.threshold, self._state, chunk)
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.83.1",
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(["computer"]),
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().optional(),
135
+ modelsDir: z.string().default(DEFAULT_MODELS_DIR),
133
136
  lockDir: z.string().optional(),
134
- submitWord: z.string().optional(),
137
+ submitWindows: z.array(z.string()).optional(),
135
138
  models: z.strictObject({
136
139
  vad: z.string().optional(),
137
- smartTurn: z.string().optional(),
138
- stt: z.string().optional()
139
- }).optional()
140
- }).optional()
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 execSync9 } from "child_process";
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 execSync6 } from "child_process";
1521
+ import { execSync as execSync7 } from "child_process";
1482
1522
  function getChangedFiles() {
1483
- const output = execSync6("git diff --name-only HEAD", {
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 execSync7 } from "child_process";
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
- execSync7("git init", { stdio: "inherit" });
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 execSync8 } from "child_process";
1646
+ import { execSync as execSync9 } from "child_process";
1593
1647
  function initPackageJson(name) {
1594
1648
  console.log("Initializing package.json...");
1595
- execSync8("npm init -y", { stdio: "inherit" });
1649
+ execSync9("npm init -y", { stdio: "inherit" });
1596
1650
  console.log("Configuring package.json...");
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"', {
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
- execSync9("npm install commander", { stdio: "inherit" });
1667
- execSync9("npm install -D tsup typescript @types/node", {
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 execSync11 } from "child_process";
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 execSync10 } from "child_process";
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
- execSync10("netlify sites:create --disable-linking", { stdio: "inherit" });
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
- execSync10("npm install -g netlify-cli", { stdio: "inherit" });
1802
+ execSync11("npm install -g netlify-cli", { stdio: "inherit" });
1749
1803
  console.log();
1750
- execSync10("netlify sites:create --disable-linking", { stdio: "inherit" });
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
- execSync11("npm create vite@latest . -- --template react-ts", {
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 execSync13 } from "child_process";
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 execSync12 } from "child_process";
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 homedir2 } from "os";
3009
+ import { homedir as homedir3 } from "os";
2956
3010
  import { join as join11 } from "path";
2957
- var DEVLOG_DIR = join11(homedir2(), "git/blog/src/content/devlog");
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 = execSync12(`git show --name-only --format="" ${hash}`, {
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 = execSync13(
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 execSync14 } from "child_process";
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 = execSync14(`git show ${hash}:package.json`, {
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 = execSync14(
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 execSync15 } from "child_process";
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 = execSync15(
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 execSync16 } from "child_process";
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
- execSync16("gh repo view --json owner,name", { encoding: "utf-8" })
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
- execSync16("gh pr view --json number", { encoding: "utf-8" })
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
- execSync16("gh pr view --json id", { encoding: "utf-8" })
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 execSync18 } from "child_process";
3480
+ import { execSync as execSync19 } from "child_process";
3427
3481
 
3428
3482
  // src/commands/prs/resolveCommentWithReply.ts
3429
- import { execSync as execSync17 } from "child_process";
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
- execSync17(
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
- execSync17(
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 execSync18(`git rev-parse --verify ${sha}`, {
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 execSync19 } from "child_process";
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 = execSync19(
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 execSync20 } from "child_process";
3642
+ import { execSync as execSync21 } from "child_process";
3589
3643
  function fetchJson(endpoint) {
3590
- const result = execSync20(`gh api ${endpoint}`, { encoding: "utf-8" });
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 execSync21 } from "child_process";
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 = execSync21(
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 execSync22 } from "child_process";
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
- execSync22(`git cat-file -t ${sha}`, { stdio: "pipe" });
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 execSync23 } from "child_process";
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 = execSync23("git diff --cached --name-only", {
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 = execSync23("git diff --name-only", { encoding: "utf-8" });
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 homedir3 } from "os";
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(homedir3(), ".assist", "voice");
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 execSync24 } from "child_process";
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
- execSync24(`uv sync --project "${pythonDir}" --no-install-project`, {
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 execSync25 } from "child_process";
5623
+ import { execSync as execSync26 } from "child_process";
5587
5624
  function tryExec(commands) {
5588
5625
  for (const cmd of commands) {
5589
5626
  try {
5590
- execSync25(cmd);
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, [], { stdio: "inherit", shell: true });
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(buildCommand(runConfig.command, runConfig.args ?? [], args));
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 execSync26 } from "child_process";
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
- execSync26("git rev-parse --is-inside-work-tree", {
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 = execSync26("npm prefix -g", { stdio: "pipe" }).toString().trim();
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
- execSync26("git pull", { cwd: installDir, stdio: "inherit" });
6112
+ execSync27("git pull", { cwd: installDir, stdio: "inherit" });
6069
6113
  console.log("Building...");
6070
- execSync26("npm run build", { cwd: installDir, stdio: "inherit" });
6114
+ execSync27("npm run build", { cwd: installDir, stdio: "inherit" });
6071
6115
  console.log("Syncing commands...");
6072
- execSync26("assist sync", { stdio: "inherit" });
6116
+ execSync27("assist sync", { stdio: "inherit" });
6073
6117
  } else if (isGlobalNpmInstall(installDir)) {
6074
6118
  console.log("Detected global npm installation, updating...");
6075
- execSync26("npm i -g @staff0rd/assist@latest", { stdio: "inherit" });
6119
+ execSync27("npm i -g @staff0rd/assist@latest", { stdio: "inherit" });
6076
6120
  console.log("Syncing commands...");
6077
- execSync26("assist sync", { stdio: "inherit" });
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."
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@staff0rd/assist",
3
- "version": "0.83.1",
3
+ "version": "0.85.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "bin": {