@staff0rd/assist 0.79.0 → 0.80.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/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.79.0",
9
+ version: "0.80.0",
10
10
  type: "module",
11
11
  main: "dist/index.js",
12
12
  bin: {
@@ -26,7 +26,7 @@ var package_default = {
26
26
  "verify:build": "tsup",
27
27
  "verify:types": "tsc --noEmit",
28
28
  "verify:knip": "knip --no-progress --treat-config-hints-as-errors",
29
- "verify:duplicate-code": "jscpd --format 'typescript,tsx' --exitCode 1 --ignore '**/*.test.*' -r consoleFull src",
29
+ "verify:duplicate-code": "jscpd --format 'typescript,tsx,python' --exitCode 1 --ignore '**/*.test.*' -r consoleFull src",
30
30
  "verify:maintainability": "assist complexity maintainability ./src --threshold 70",
31
31
  "verify:custom-lint": "assist lint",
32
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\\""'
@@ -130,7 +130,19 @@ var assistConfigSchema = z.strictObject({
130
130
  tokenExpiresAt: z.number().optional()
131
131
  }).optional(),
132
132
  run: z.array(runConfigSchema).optional(),
133
- transcript: transcriptConfigSchema.optional()
133
+ transcript: transcriptConfigSchema.optional(),
134
+ voice: z.strictObject({
135
+ wakeWords: z.array(z.string()).default(["claude"]),
136
+ mic: z.string().optional(),
137
+ cwd: z.string().optional(),
138
+ modelsDir: z.string().optional(),
139
+ lockDir: z.string().optional(),
140
+ models: z.strictObject({
141
+ vad: z.string().optional(),
142
+ smartTurn: z.string().optional(),
143
+ stt: z.string().optional()
144
+ }).optional()
145
+ }).optional()
134
146
  });
135
147
 
136
148
  // src/shared/loadConfig.ts
@@ -816,13 +828,13 @@ async function setupTest(packageJsonPath) {
816
828
  }
817
829
 
818
830
  // src/commands/verify/init/detectExistingSetup/needsSetup.ts
819
- function needsSetup(status) {
820
- return !status.hasScript || !status.hasPackage || status.isOutdated;
831
+ function needsSetup(status2) {
832
+ return !status2.hasScript || !status2.hasPackage || status2.isOutdated;
821
833
  }
822
- function getStatusLabel(status) {
823
- if (status.isOutdated) return " (outdated)";
824
- if (!status.hasScript) return "";
825
- if (!status.hasPackage) return " (package missing)";
834
+ function getStatusLabel(status2) {
835
+ if (status2.isOutdated) return " (outdated)";
836
+ if (!status2.hasScript) return "";
837
+ if (!status2.hasPackage) return " (package missing)";
826
838
  return "";
827
839
  }
828
840
 
@@ -861,10 +873,10 @@ function detectExistingSetup(pkg) {
861
873
  }
862
874
 
863
875
  // src/commands/verify/init/getAvailableOptions/options.ts
864
- function getBuildDescription(setup) {
865
- if (setup.hasVite && setup.hasTypescript)
876
+ function getBuildDescription(setup2) {
877
+ if (setup2.hasVite && setup2.hasTypescript)
866
878
  return "TypeScript + Vite build verification";
867
- if (setup.hasVite) return "Vite build verification";
879
+ if (setup2.hasVite) return "Vite build verification";
868
880
  return "Build verification";
869
881
  }
870
882
  var options = [
@@ -922,20 +934,20 @@ var options = [
922
934
  ];
923
935
 
924
936
  // src/commands/verify/init/getAvailableOptions/index.ts
925
- function resolveDescription(desc, setup) {
926
- return typeof desc === "function" ? desc(setup) : desc;
937
+ function resolveDescription(desc, setup2) {
938
+ return typeof desc === "function" ? desc(setup2) : desc;
927
939
  }
928
- function toVerifyOption(def, setup) {
940
+ function toVerifyOption(def, setup2) {
929
941
  return {
930
- name: `${def.label}${getStatusLabel(setup[def.toolKey])}`,
942
+ name: `${def.label}${getStatusLabel(setup2[def.toolKey])}`,
931
943
  value: def.value,
932
- description: resolveDescription(def.description, setup)
944
+ description: resolveDescription(def.description, setup2)
933
945
  };
934
946
  }
935
- function getAvailableOptions(setup) {
947
+ function getAvailableOptions(setup2) {
936
948
  return options.filter(
937
- (def) => needsSetup(setup[def.toolKey]) && (def.extraCondition?.(setup) ?? true)
938
- ).map((def) => toVerifyOption(def, setup));
949
+ (def) => needsSetup(setup2[def.toolKey]) && (def.extraCondition?.(setup2) ?? true)
950
+ ).map((def) => toVerifyOption(def, setup2));
939
951
  }
940
952
 
941
953
  // src/commands/verify/init/index.ts
@@ -980,13 +992,13 @@ async function promptForScripts(availableOptions) {
980
992
  }
981
993
  async function init2() {
982
994
  const { packageJsonPath, pkg } = requirePackageJson();
983
- const setup = detectExistingSetup(pkg);
984
- const selected = await promptForScripts(getAvailableOptions(setup));
995
+ const setup2 = detectExistingSetup(pkg);
996
+ const selected = await promptForScripts(getAvailableOptions(setup2));
985
997
  if (!selected) return;
986
998
  const handlers = getSetupHandlers(
987
- setup.hasVite,
988
- setup.hasTypescript,
989
- setup.hasOpenColor
999
+ setup2.hasVite,
1000
+ setup2.hasTypescript,
1001
+ setup2.hasOpenColor
990
1002
  );
991
1003
  await runSelectedSetups(selected, packageJsonPath, handlers);
992
1004
  }
@@ -1079,22 +1091,22 @@ function detectVscodeSetup(pkg) {
1079
1091
  }
1080
1092
 
1081
1093
  // src/commands/vscode/init/getAvailableOptions.ts
1082
- function getLaunchDescription(setup) {
1083
- if (setup.hasLaunchJson) return void 0;
1084
- if (setup.hasVite) return "Debug configuration for Vite dev server";
1085
- if (setup.hasTsup) return "Debug configuration for Node.js CLI";
1094
+ function getLaunchDescription(setup2) {
1095
+ if (setup2.hasLaunchJson) return void 0;
1096
+ if (setup2.hasVite) return "Debug configuration for Vite dev server";
1097
+ if (setup2.hasTsup) return "Debug configuration for Node.js CLI";
1086
1098
  return void 0;
1087
1099
  }
1088
- function getAvailableOptions2(setup) {
1100
+ function getAvailableOptions2(setup2) {
1089
1101
  const options2 = [];
1090
- const launchDescription = getLaunchDescription(setup);
1102
+ const launchDescription = getLaunchDescription(setup2);
1091
1103
  if (launchDescription)
1092
1104
  options2.push({
1093
1105
  name: "launch",
1094
1106
  value: "launch",
1095
1107
  description: launchDescription
1096
1108
  });
1097
- if (!setup.hasSettingsJson)
1109
+ if (!setup2.hasSettingsJson)
1098
1110
  options2.push({
1099
1111
  name: "settings",
1100
1112
  value: "settings",
@@ -1104,10 +1116,10 @@ function getAvailableOptions2(setup) {
1104
1116
  }
1105
1117
 
1106
1118
  // src/commands/vscode/init/index.ts
1107
- function applySelections(selected, setup) {
1119
+ function applySelections(selected, setup2) {
1108
1120
  removeVscodeFromGitignore();
1109
1121
  ensureVscodeFolder();
1110
- const launchType = setup.hasVite ? "vite" : "tsup";
1122
+ const launchType = setup2.hasVite ? "vite" : "tsup";
1111
1123
  const handlers = {
1112
1124
  launch: () => createLaunchJson(launchType),
1113
1125
  settings: () => {
@@ -1123,8 +1135,8 @@ async function promptForOptions(options2) {
1123
1135
  }
1124
1136
  async function init3() {
1125
1137
  const { pkg } = requirePackageJson();
1126
- const setup = detectVscodeSetup(pkg);
1127
- const options2 = getAvailableOptions2(setup);
1138
+ const setup2 = detectVscodeSetup(pkg);
1139
+ const options2 = getAvailableOptions2(setup2);
1128
1140
  if (options2.length === 0) {
1129
1141
  console.log(chalk17.green("VS Code configuration already exists!"));
1130
1142
  return;
@@ -1134,7 +1146,7 @@ async function init3() {
1134
1146
  console.log(chalk17.yellow("No configurations selected"));
1135
1147
  return;
1136
1148
  }
1137
- applySelections(selected, setup);
1149
+ applySelections(selected, setup2);
1138
1150
  console.log(
1139
1151
  chalk17.green(`
1140
1152
  Added ${selected.length} VS Code configuration(s)`)
@@ -1437,8 +1449,8 @@ function printTaskStatuses(tasks) {
1437
1449
  for (const task of tasks) {
1438
1450
  if (task.endTime !== void 0) {
1439
1451
  const duration = formatDuration(task.endTime - task.startTime);
1440
- const status = task.code === 0 ? "\u2713" : "\u2717";
1441
- console.log(` ${status} ${task.script}: ${duration}`);
1452
+ const status2 = task.code === 0 ? "\u2713" : "\u2717";
1453
+ console.log(` ${status2} ${task.script}: ${duration}`);
1442
1454
  } else {
1443
1455
  const elapsed = formatDuration(Date.now() - task.startTime);
1444
1456
  console.log(` \u22EF ${task.script}: running (${elapsed})`);
@@ -1975,10 +1987,10 @@ function loadAndFindItem(id) {
1975
1987
  }
1976
1988
  return { items, item };
1977
1989
  }
1978
- function setStatus(id, status) {
1990
+ function setStatus(id, status2) {
1979
1991
  const result = loadAndFindItem(id);
1980
1992
  if (!result) return void 0;
1981
- result.item.status = status;
1993
+ result.item.status = status2;
1982
1994
  saveBacklog(result.items);
1983
1995
  return result.item.name;
1984
1996
  }
@@ -2126,8 +2138,8 @@ async function init6() {
2126
2138
  // src/commands/backlog/list/index.ts
2127
2139
  import { existsSync as existsSync14 } from "fs";
2128
2140
  import chalk27 from "chalk";
2129
- function statusIcon(status) {
2130
- switch (status) {
2141
+ function statusIcon(status2) {
2142
+ switch (status2) {
2131
2143
  case "todo":
2132
2144
  return chalk27.dim("[ ]");
2133
2145
  case "in-progress":
@@ -2227,8 +2239,8 @@ function getHtml() {
2227
2239
  }
2228
2240
 
2229
2241
  // src/commands/backlog/web/respondJson.ts
2230
- function respondJson(res, status, data) {
2231
- res.writeHead(status, { "Content-Type": "application/json" });
2242
+ function respondJson(res, status2, data) {
2243
+ res.writeHead(status2, { "Content-Type": "application/json" });
2232
2244
  res.end(JSON.stringify(data));
2233
2245
  }
2234
2246
  function readBody(req) {
@@ -3694,8 +3706,8 @@ function getStatus(pr) {
3694
3706
  function formatDate(dateStr) {
3695
3707
  return new Date(dateStr).toISOString().split("T")[0];
3696
3708
  }
3697
- function formatPrHeader(pr, status) {
3698
- return `${chalk44.cyan(`#${pr.number}`)} ${pr.title} ${chalk44.dim(`(${pr.author.login},`)} ${status.label} ${chalk44.dim(`${formatDate(status.date)})`)}`;
3709
+ function formatPrHeader(pr, status2) {
3710
+ return `${chalk44.cyan(`#${pr.number}`)} ${pr.title} ${chalk44.dim(`(${pr.author.login},`)} ${status2.label} ${chalk44.dim(`${formatDate(status2.date)})`)}`;
3699
3711
  }
3700
3712
  function logPrDetails(pr) {
3701
3713
  console.log(
@@ -3711,8 +3723,8 @@ function printPr(pr) {
3711
3723
  // src/commands/prs/prs/displayPaginated/index.ts
3712
3724
  var PAGE_SIZE = 10;
3713
3725
  function getPageSlice(pullRequests, page) {
3714
- const start2 = page * PAGE_SIZE;
3715
- return pullRequests.slice(start2, start2 + PAGE_SIZE);
3726
+ const start3 = page * PAGE_SIZE;
3727
+ return pullRequests.slice(start3, start3 + PAGE_SIZE);
3716
3728
  }
3717
3729
  function pageHeader(page, totalPages, total) {
3718
3730
  return `
@@ -4926,10 +4938,10 @@ function extractSpeaker(fullText) {
4926
4938
  function isTextLine(line) {
4927
4939
  return !!line.trim() && !line.includes("-->");
4928
4940
  }
4929
- function scanTextLines(lines, start2) {
4930
- let i = start2;
4941
+ function scanTextLines(lines, start3) {
4942
+ let i = start3;
4931
4943
  while (i < lines.length && isTextLine(lines[i])) i++;
4932
- return { texts: lines.slice(start2, i).map((l) => l.trim()), end: i };
4944
+ return { texts: lines.slice(start3, i).map((l) => l.trim()), end: i };
4933
4945
  }
4934
4946
  function collectTextLines(lines, startIndex) {
4935
4947
  const { texts, end } = scanTextLines(lines, startIndex);
@@ -5251,16 +5263,301 @@ function registerVerify(program2) {
5251
5263
  verifyCommand.command("hardcoded-colors").description("Check for hardcoded hex colors in src/").action(hardcodedColors);
5252
5264
  }
5253
5265
 
5266
+ // src/commands/voice/devices.ts
5267
+ import { spawnSync as spawnSync3 } from "child_process";
5268
+ import { join as join24 } from "path";
5269
+
5270
+ // src/commands/voice/shared.ts
5271
+ import { homedir as homedir3 } from "os";
5272
+ import { dirname as dirname17, join as join23 } from "path";
5273
+ import { fileURLToPath as fileURLToPath4 } from "url";
5274
+ var __dirname5 = dirname17(fileURLToPath4(import.meta.url));
5275
+ var VOICE_DIR = join23(homedir3(), ".assist", "voice");
5276
+ var voicePaths = {
5277
+ dir: VOICE_DIR,
5278
+ pid: join23(VOICE_DIR, "voice.pid"),
5279
+ log: join23(VOICE_DIR, "voice.log"),
5280
+ venv: join23(VOICE_DIR, ".venv"),
5281
+ lock: join23(VOICE_DIR, "voice.lock")
5282
+ };
5283
+ function getPythonDir() {
5284
+ return join23(__dirname5, "commands", "voice", "python");
5285
+ }
5286
+ function getVenvPython() {
5287
+ return process.platform === "win32" ? join23(voicePaths.venv, "Scripts", "python.exe") : join23(voicePaths.venv, "bin", "python");
5288
+ }
5289
+ function getLockDir() {
5290
+ const config = loadConfig();
5291
+ return config.voice?.lockDir ?? VOICE_DIR;
5292
+ }
5293
+ function getLockFile() {
5294
+ return join23(getLockDir(), "voice.lock");
5295
+ }
5296
+
5297
+ // src/commands/voice/devices.ts
5298
+ function devices() {
5299
+ const script = join24(getPythonDir(), "list_devices.py");
5300
+ spawnSync3(getVenvPython(), [script], { stdio: "inherit" });
5301
+ }
5302
+
5303
+ // src/commands/voice/logs.ts
5304
+ import { existsSync as existsSync23, readFileSync as readFileSync17 } from "fs";
5305
+ function logs(options2) {
5306
+ if (!existsSync23(voicePaths.log)) {
5307
+ console.log("No voice log file found");
5308
+ return;
5309
+ }
5310
+ const count = Number.parseInt(options2.lines ?? "20", 10);
5311
+ const content = readFileSync17(voicePaths.log, "utf-8").trim();
5312
+ if (!content) {
5313
+ console.log("Voice log is empty");
5314
+ return;
5315
+ }
5316
+ const lines = content.split("\n").slice(-count);
5317
+ for (const line of lines) {
5318
+ try {
5319
+ const event = JSON.parse(line);
5320
+ const time = event.timestamp?.slice(11, 19) ?? "";
5321
+ const level = (event.level ?? "info").toUpperCase().padEnd(5);
5322
+ const msg = event.message ?? "";
5323
+ const extra = event.data ? ` ${JSON.stringify(event.data)}` : "";
5324
+ console.log(`${time} ${level} [${event.event}] ${msg}${extra}`);
5325
+ } catch {
5326
+ console.log(line);
5327
+ }
5328
+ }
5329
+ }
5330
+
5331
+ // src/commands/voice/setup.ts
5332
+ import { execSync as execSync23, spawnSync as spawnSync4 } from "child_process";
5333
+ import { existsSync as existsSync24, mkdirSync as mkdirSync7 } from "fs";
5334
+ import { join as join25 } from "path";
5335
+ function setup() {
5336
+ mkdirSync7(voicePaths.dir, { recursive: true });
5337
+ if (!existsSync24(getVenvPython())) {
5338
+ console.log("Creating Python virtual environment...");
5339
+ execSync23(`uv venv "${voicePaths.venv}"`, { stdio: "inherit" });
5340
+ console.log("Installing dependencies...");
5341
+ const pythonDir = getPythonDir();
5342
+ execSync23(
5343
+ `uv pip install --python "${getVenvPython()}" -e "${pythonDir}[dev]"`,
5344
+ { stdio: "inherit" }
5345
+ );
5346
+ }
5347
+ console.log("\nDownloading models...\n");
5348
+ const script = join25(getPythonDir(), "setup_models.py");
5349
+ const result = spawnSync4(getVenvPython(), [script], {
5350
+ stdio: "inherit",
5351
+ env: { ...process.env, VOICE_LOG_FILE: voicePaths.log }
5352
+ });
5353
+ if (result.status !== 0) {
5354
+ console.error("Model setup failed");
5355
+ process.exit(1);
5356
+ }
5357
+ }
5358
+
5359
+ // src/commands/voice/start.ts
5360
+ import { spawn as spawn4 } from "child_process";
5361
+ import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync19 } from "fs";
5362
+ import { join as join27 } from "path";
5363
+
5364
+ // src/commands/voice/buildDaemonEnv.ts
5365
+ var ENV_MAP = {
5366
+ VOICE_WAKE_WORDS: (v) => v.wakeWords?.join(","),
5367
+ VOICE_MIC: (v) => v.mic,
5368
+ VOICE_CWD: (v) => v.cwd,
5369
+ VOICE_MODELS_DIR: (v) => v.modelsDir,
5370
+ VOICE_MODEL_VAD: (v) => v.models?.vad,
5371
+ VOICE_MODEL_SMART_TURN: (v) => v.models?.smartTurn,
5372
+ VOICE_MODEL_STT: (v) => v.models?.stt
5373
+ };
5374
+ function buildDaemonEnv(options2) {
5375
+ const config = loadConfig();
5376
+ const env = { ...process.env };
5377
+ const voice = config.voice;
5378
+ if (voice) {
5379
+ for (const [key, getter] of Object.entries(ENV_MAP)) {
5380
+ const value = getter(voice);
5381
+ if (value) env[key] = value;
5382
+ }
5383
+ }
5384
+ env.VOICE_LOG_FILE = voicePaths.log;
5385
+ if (options2?.debug) env.VOICE_DEBUG = "1";
5386
+ return env;
5387
+ }
5388
+
5389
+ // src/commands/voice/checkLockFile.ts
5390
+ import { execSync as execSync24 } from "child_process";
5391
+ import { existsSync as existsSync25, mkdirSync as mkdirSync8, readFileSync as readFileSync18, writeFileSync as writeFileSync18 } from "fs";
5392
+ import { join as join26 } from "path";
5393
+ function isProcessAlive(pid) {
5394
+ try {
5395
+ process.kill(pid, 0);
5396
+ return true;
5397
+ } catch {
5398
+ return false;
5399
+ }
5400
+ }
5401
+ function checkLockFile() {
5402
+ const lockFile = getLockFile();
5403
+ if (!existsSync25(lockFile)) return;
5404
+ try {
5405
+ const lock = JSON.parse(readFileSync18(lockFile, "utf-8"));
5406
+ if (lock.pid && isProcessAlive(lock.pid)) {
5407
+ console.error(
5408
+ `Voice daemon already running (PID ${lock.pid}, env: ${lock.env}). Stop it first with: assist voice stop`
5409
+ );
5410
+ process.exit(1);
5411
+ }
5412
+ } catch {
5413
+ }
5414
+ }
5415
+ function bootstrapVenv() {
5416
+ if (existsSync25(getVenvPython())) return;
5417
+ console.log("Creating Python virtual environment...");
5418
+ execSync24(`uv venv "${voicePaths.venv}"`, { stdio: "inherit" });
5419
+ console.log("Installing dependencies...");
5420
+ const pythonDir = getPythonDir();
5421
+ execSync24(
5422
+ `uv pip install --python "${getVenvPython()}" -e "${pythonDir}[dev]"`,
5423
+ { stdio: "inherit" }
5424
+ );
5425
+ }
5426
+ function writeLockFile(pid) {
5427
+ const lockFile = getLockFile();
5428
+ mkdirSync8(join26(lockFile, ".."), { recursive: true });
5429
+ writeFileSync18(
5430
+ lockFile,
5431
+ JSON.stringify({
5432
+ pid,
5433
+ env: process.platform === "win32" ? "win" : "wsl",
5434
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
5435
+ })
5436
+ );
5437
+ }
5438
+
5439
+ // src/commands/voice/start.ts
5440
+ function spawnForeground(python, script, env) {
5441
+ console.log("Starting voice daemon in foreground...");
5442
+ const child = spawn4(python, [script], { stdio: "inherit", env });
5443
+ child.on("exit", (code) => process.exit(code ?? 0));
5444
+ }
5445
+ function spawnBackground(python, script, env) {
5446
+ const child = spawn4(python, [script], {
5447
+ detached: true,
5448
+ stdio: "ignore",
5449
+ env
5450
+ });
5451
+ child.unref();
5452
+ const pid = child.pid;
5453
+ if (!pid) {
5454
+ console.error("Failed to start voice daemon");
5455
+ process.exit(1);
5456
+ }
5457
+ writeFileSync19(voicePaths.pid, String(pid));
5458
+ writeLockFile(pid);
5459
+ console.log(`Voice daemon started (PID ${pid})`);
5460
+ }
5461
+ function start2(options2) {
5462
+ mkdirSync9(voicePaths.dir, { recursive: true });
5463
+ checkLockFile();
5464
+ bootstrapVenv();
5465
+ const debug = options2.debug || options2.foreground;
5466
+ const env = buildDaemonEnv({ debug });
5467
+ const script = join27(getPythonDir(), "voice_daemon.py");
5468
+ const python = getVenvPython();
5469
+ if (options2.foreground) {
5470
+ spawnForeground(python, script, env);
5471
+ } else {
5472
+ spawnBackground(python, script, env);
5473
+ }
5474
+ }
5475
+
5476
+ // src/commands/voice/status.ts
5477
+ import { existsSync as existsSync26, readFileSync as readFileSync19 } from "fs";
5478
+ function isProcessAlive2(pid) {
5479
+ try {
5480
+ process.kill(pid, 0);
5481
+ return true;
5482
+ } catch {
5483
+ return false;
5484
+ }
5485
+ }
5486
+ function readRecentLogs(count) {
5487
+ if (!existsSync26(voicePaths.log)) return [];
5488
+ const lines = readFileSync19(voicePaths.log, "utf-8").trim().split("\n");
5489
+ return lines.slice(-count);
5490
+ }
5491
+ function status() {
5492
+ if (!existsSync26(voicePaths.pid)) {
5493
+ console.log("Voice daemon: not running (no PID file)");
5494
+ return;
5495
+ }
5496
+ const pid = Number.parseInt(readFileSync19(voicePaths.pid, "utf-8").trim(), 10);
5497
+ const alive = isProcessAlive2(pid);
5498
+ console.log(`Voice daemon: ${alive ? "running" : "dead"} (PID ${pid})`);
5499
+ const recent = readRecentLogs(5);
5500
+ if (recent.length > 0) {
5501
+ console.log("\nRecent events:");
5502
+ for (const line of recent) {
5503
+ try {
5504
+ const event = JSON.parse(line);
5505
+ const time = event.timestamp?.slice(11, 19) ?? "";
5506
+ console.log(` ${time} [${event.event}] ${event.message ?? ""}`);
5507
+ } catch {
5508
+ console.log(` ${line}`);
5509
+ }
5510
+ }
5511
+ }
5512
+ }
5513
+
5514
+ // src/commands/voice/stop.ts
5515
+ import { existsSync as existsSync27, readFileSync as readFileSync20, unlinkSync as unlinkSync7 } from "fs";
5516
+ function stop() {
5517
+ if (!existsSync27(voicePaths.pid)) {
5518
+ console.log("Voice daemon is not running (no PID file)");
5519
+ return;
5520
+ }
5521
+ const pid = Number.parseInt(readFileSync20(voicePaths.pid, "utf-8").trim(), 10);
5522
+ try {
5523
+ process.kill(pid, "SIGTERM");
5524
+ console.log(`Sent SIGTERM to voice daemon (PID ${pid})`);
5525
+ } catch {
5526
+ console.log(`Voice daemon (PID ${pid}) is not running`);
5527
+ }
5528
+ try {
5529
+ unlinkSync7(voicePaths.pid);
5530
+ } catch {
5531
+ }
5532
+ try {
5533
+ const lockFile = getLockFile();
5534
+ if (existsSync27(lockFile)) unlinkSync7(lockFile);
5535
+ } catch {
5536
+ }
5537
+ console.log("Voice daemon stopped");
5538
+ }
5539
+
5540
+ // src/commands/registerVoice.ts
5541
+ function registerVoice(program2) {
5542
+ const voiceCommand = program2.command("voice").description("Voice interaction daemon for hands-free Claude commands");
5543
+ voiceCommand.command("start").description("Start the voice daemon").option("--foreground", "Run in foreground for debugging").option("--debug", "Enable debug output (live VAD meter, verbose logging)").action((options2) => start2(options2));
5544
+ voiceCommand.command("setup").description("Download required voice models (VAD, STT)").action(setup);
5545
+ voiceCommand.command("stop").description("Stop the voice daemon").action(stop);
5546
+ voiceCommand.command("status").description("Check voice daemon status").action(status);
5547
+ voiceCommand.command("devices").description("List available audio input devices").action(devices);
5548
+ voiceCommand.command("logs").description("Tail voice daemon logs").option("-n, --lines <count>", "Number of lines to show", "20").action((options2) => logs(options2));
5549
+ }
5550
+
5254
5551
  // src/commands/roam/auth.ts
5255
5552
  import { randomBytes } from "crypto";
5256
5553
  import chalk51 from "chalk";
5257
5554
 
5258
5555
  // src/lib/openBrowser.ts
5259
- import { execSync as execSync23 } from "child_process";
5556
+ import { execSync as execSync25 } from "child_process";
5260
5557
  function tryExec(commands) {
5261
5558
  for (const cmd of commands) {
5262
5559
  try {
5263
- execSync23(cmd);
5560
+ execSync25(cmd);
5264
5561
  return true;
5265
5562
  } catch {
5266
5563
  }
@@ -5301,8 +5598,8 @@ ${url}`);
5301
5598
 
5302
5599
  // src/commands/roam/waitForCallback.ts
5303
5600
  import { createServer as createServer2 } from "http";
5304
- function respondHtml(res, status, title) {
5305
- res.writeHead(status, { "Content-Type": "text/html" });
5601
+ function respondHtml(res, status2, title) {
5602
+ res.writeHead(status2, { "Content-Type": "text/html" });
5306
5603
  res.end(
5307
5604
  `<html><body><h1>${title}</h1><p>You can close this tab.</p></body></html>`
5308
5605
  );
@@ -5461,11 +5758,11 @@ function registerRoam(program2) {
5461
5758
  }
5462
5759
 
5463
5760
  // src/commands/run/index.ts
5464
- import { spawn as spawn4 } from "child_process";
5761
+ import { spawn as spawn5 } from "child_process";
5465
5762
 
5466
5763
  // src/commands/run/add.ts
5467
- import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync18 } from "fs";
5468
- import { join as join23 } from "path";
5764
+ import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync20 } from "fs";
5765
+ import { join as join28 } from "path";
5469
5766
  function findAddIndex() {
5470
5767
  const addIndex = process.argv.indexOf("add");
5471
5768
  if (addIndex === -1 || addIndex + 2 >= process.argv.length) return -1;
@@ -5519,16 +5816,16 @@ function saveNewRunConfig(name, command, args) {
5519
5816
  saveConfig(config);
5520
5817
  }
5521
5818
  function createCommandFile(name) {
5522
- const dir = join23(".claude", "commands");
5523
- mkdirSync7(dir, { recursive: true });
5819
+ const dir = join28(".claude", "commands");
5820
+ mkdirSync10(dir, { recursive: true });
5524
5821
  const content = `---
5525
5822
  description: Run ${name}
5526
5823
  ---
5527
5824
 
5528
5825
  Run \`assist run ${name} $ARGUMENTS 2>&1\`.
5529
5826
  `;
5530
- const filePath = join23(dir, `${name}.md`);
5531
- writeFileSync18(filePath, content);
5827
+ const filePath = join28(dir, `${name}.md`);
5828
+ writeFileSync20(filePath, content);
5532
5829
  console.log(`Created command file: ${filePath}`);
5533
5830
  }
5534
5831
  function add2() {
@@ -5577,7 +5874,7 @@ function onSpawnError(err) {
5577
5874
  process.exit(1);
5578
5875
  }
5579
5876
  function spawnCommand2(fullCommand) {
5580
- const child = spawn4(fullCommand, [], { stdio: "inherit", shell: true });
5877
+ const child = spawn5(fullCommand, [], { stdio: "inherit", shell: true });
5581
5878
  child.on("close", (code) => process.exit(code ?? 0));
5582
5879
  child.on("error", onSpawnError);
5583
5880
  }
@@ -5615,7 +5912,7 @@ async function statusLine() {
5615
5912
  import * as fs23 from "fs";
5616
5913
  import * as os from "os";
5617
5914
  import * as path29 from "path";
5618
- import { fileURLToPath as fileURLToPath4 } from "url";
5915
+ import { fileURLToPath as fileURLToPath5 } from "url";
5619
5916
 
5620
5917
  // src/commands/sync/syncClaudeMd.ts
5621
5918
  import * as fs21 from "fs";
@@ -5651,7 +5948,7 @@ async function syncClaudeMd(claudeDir, targetBase) {
5651
5948
  import * as fs22 from "fs";
5652
5949
  import * as path28 from "path";
5653
5950
  import chalk53 from "chalk";
5654
- async function syncSettings(claudeDir, targetBase) {
5951
+ async function syncSettings(claudeDir, targetBase, options2) {
5655
5952
  const source = path28.join(claudeDir, "settings.json");
5656
5953
  const target = path28.join(targetBase, "settings.json");
5657
5954
  const sourceContent = fs22.readFileSync(source, "utf-8");
@@ -5660,18 +5957,22 @@ async function syncSettings(claudeDir, targetBase) {
5660
5957
  const targetContent = fs22.readFileSync(target, "utf-8");
5661
5958
  const normalizedTarget = JSON.stringify(JSON.parse(targetContent), null, 2);
5662
5959
  if (normalizedSource !== normalizedTarget) {
5663
- console.log(
5664
- chalk53.yellow("\n\u26A0\uFE0F Warning: settings.json differs from existing file")
5665
- );
5666
- console.log();
5667
- printDiff(targetContent, sourceContent);
5668
- const confirm = await promptConfirm(
5669
- chalk53.red("Overwrite existing settings.json?"),
5670
- false
5671
- );
5672
- if (!confirm) {
5673
- console.log("Skipped settings.json");
5674
- return;
5960
+ if (!options2?.yes) {
5961
+ console.log(
5962
+ chalk53.yellow(
5963
+ "\n\u26A0\uFE0F Warning: settings.json differs from existing file"
5964
+ )
5965
+ );
5966
+ console.log();
5967
+ printDiff(targetContent, sourceContent);
5968
+ const confirm = await promptConfirm(
5969
+ chalk53.red("Overwrite existing settings.json?"),
5970
+ false
5971
+ );
5972
+ if (!confirm) {
5973
+ console.log("Skipped settings.json");
5974
+ return;
5975
+ }
5675
5976
  }
5676
5977
  }
5677
5978
  }
@@ -5680,13 +5981,13 @@ async function syncSettings(claudeDir, targetBase) {
5680
5981
  }
5681
5982
 
5682
5983
  // src/commands/sync.ts
5683
- var __filename2 = fileURLToPath4(import.meta.url);
5684
- var __dirname5 = path29.dirname(__filename2);
5685
- async function sync() {
5686
- const claudeDir = path29.join(__dirname5, "..", "claude");
5984
+ var __filename2 = fileURLToPath5(import.meta.url);
5985
+ var __dirname6 = path29.dirname(__filename2);
5986
+ async function sync(options2) {
5987
+ const claudeDir = path29.join(__dirname6, "..", "claude");
5687
5988
  const targetBase = path29.join(os.homedir(), ".claude");
5688
5989
  syncCommands(claudeDir, targetBase);
5689
- await syncSettings(claudeDir, targetBase);
5990
+ await syncSettings(claudeDir, targetBase, { yes: options2?.yes });
5690
5991
  await syncClaudeMd(claudeDir, targetBase);
5691
5992
  }
5692
5993
  function syncCommands(claudeDir, targetBase) {
@@ -5702,17 +6003,17 @@ function syncCommands(claudeDir, targetBase) {
5702
6003
  }
5703
6004
 
5704
6005
  // src/commands/update.ts
5705
- import { execSync as execSync24 } from "child_process";
6006
+ import { execSync as execSync26 } from "child_process";
5706
6007
  import * as path30 from "path";
5707
- import { fileURLToPath as fileURLToPath5 } from "url";
5708
- var __filename3 = fileURLToPath5(import.meta.url);
5709
- var __dirname6 = path30.dirname(__filename3);
6008
+ import { fileURLToPath as fileURLToPath6 } from "url";
6009
+ var __filename3 = fileURLToPath6(import.meta.url);
6010
+ var __dirname7 = path30.dirname(__filename3);
5710
6011
  function getInstallDir() {
5711
- return path30.resolve(__dirname6, "..");
6012
+ return path30.resolve(__dirname7, "..");
5712
6013
  }
5713
6014
  function isGitRepo(dir) {
5714
6015
  try {
5715
- execSync24("git rev-parse --is-inside-work-tree", {
6016
+ execSync26("git rev-parse --is-inside-work-tree", {
5716
6017
  cwd: dir,
5717
6018
  stdio: "pipe"
5718
6019
  });
@@ -5723,7 +6024,7 @@ function isGitRepo(dir) {
5723
6024
  }
5724
6025
  function isGlobalNpmInstall(dir) {
5725
6026
  try {
5726
- const globalPrefix = execSync24("npm prefix -g", { stdio: "pipe" }).toString().trim();
6027
+ const globalPrefix = execSync26("npm prefix -g", { stdio: "pipe" }).toString().trim();
5727
6028
  return path30.resolve(dir).toLowerCase().startsWith(path30.resolve(globalPrefix).toLowerCase());
5728
6029
  } catch {
5729
6030
  return false;
@@ -5734,16 +6035,16 @@ async function update() {
5734
6035
  console.log(`Assist is installed at: ${installDir}`);
5735
6036
  if (isGitRepo(installDir)) {
5736
6037
  console.log("Detected git repo installation, pulling latest...");
5737
- execSync24("git pull", { cwd: installDir, stdio: "inherit" });
6038
+ execSync26("git pull", { cwd: installDir, stdio: "inherit" });
5738
6039
  console.log("Building...");
5739
- execSync24("npm run build", { cwd: installDir, stdio: "inherit" });
6040
+ execSync26("npm run build", { cwd: installDir, stdio: "inherit" });
5740
6041
  console.log("Syncing commands...");
5741
- execSync24("assist sync", { stdio: "inherit" });
6042
+ execSync26("assist sync", { stdio: "inherit" });
5742
6043
  } else if (isGlobalNpmInstall(installDir)) {
5743
6044
  console.log("Detected global npm installation, updating...");
5744
- execSync24("npm i -g @staff0rd/assist@latest", { stdio: "inherit" });
6045
+ execSync26("npm i -g @staff0rd/assist@latest", { stdio: "inherit" });
5745
6046
  console.log("Syncing commands...");
5746
- execSync24("assist sync", { stdio: "inherit" });
6047
+ execSync26("assist sync", { stdio: "inherit" });
5747
6048
  } else {
5748
6049
  console.error(
5749
6050
  "Could not determine installation method. Expected a git repo or global npm install."
@@ -5755,7 +6056,7 @@ async function update() {
5755
6056
  // src/index.ts
5756
6057
  var program = new Command();
5757
6058
  program.name("assist").description("CLI application").version(package_default.version);
5758
- program.command("sync").description("Copy command files to ~/.claude/commands").action(sync);
6059
+ program.command("sync").description("Copy command files to ~/.claude/commands").option("-y, --yes", "Overwrite settings.json without prompting").action((options2) => sync(options2));
5759
6060
  program.command("init").description("Initialize VS Code and verify configurations").action(init4);
5760
6061
  program.command("commit <message>").description("Create a git commit with validation").action(commit);
5761
6062
  var configCommand = program.command("config").description("View and modify assist.yml configuration");
@@ -5786,4 +6087,5 @@ registerDevlog(program);
5786
6087
  registerDeploy(program);
5787
6088
  registerComplexity(program);
5788
6089
  registerTranscript(program);
6090
+ registerVoice(program);
5789
6091
  program.parse();