@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/README.md +12 -0
- package/claude/commands/voice-logs.md +5 -0
- package/claude/commands/voice-setup.md +5 -0
- package/claude/commands/voice-start.md +5 -0
- package/claude/commands/voice-status.md +5 -0
- package/claude/commands/voice-stop.md +5 -0
- package/claude/settings.json +11 -0
- package/dist/commands/voice/python/audio_capture.py +49 -0
- package/dist/commands/voice/python/dispatch.py +14 -0
- package/dist/commands/voice/python/keyboard.py +73 -0
- package/dist/commands/voice/python/list_devices.py +20 -0
- package/dist/commands/voice/python/logger.py +38 -0
- package/dist/commands/voice/python/pyproject.toml +34 -0
- package/dist/commands/voice/python/setup_models.py +91 -0
- package/dist/commands/voice/python/smart_turn.py +63 -0
- package/dist/commands/voice/python/stt.py +51 -0
- package/dist/commands/voice/python/uv.lock +5947 -0
- package/dist/commands/voice/python/vad.py +50 -0
- package/dist/commands/voice/python/voice_daemon.py +362 -0
- package/dist/commands/voice/python/wake_word.py +26 -0
- package/dist/index.js +398 -96
- package/package.json +2 -2
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.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(
|
|
820
|
-
return !
|
|
831
|
+
function needsSetup(status2) {
|
|
832
|
+
return !status2.hasScript || !status2.hasPackage || status2.isOutdated;
|
|
821
833
|
}
|
|
822
|
-
function getStatusLabel(
|
|
823
|
-
if (
|
|
824
|
-
if (!
|
|
825
|
-
if (!
|
|
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(
|
|
865
|
-
if (
|
|
876
|
+
function getBuildDescription(setup2) {
|
|
877
|
+
if (setup2.hasVite && setup2.hasTypescript)
|
|
866
878
|
return "TypeScript + Vite build verification";
|
|
867
|
-
if (
|
|
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,
|
|
926
|
-
return typeof desc === "function" ? desc(
|
|
937
|
+
function resolveDescription(desc, setup2) {
|
|
938
|
+
return typeof desc === "function" ? desc(setup2) : desc;
|
|
927
939
|
}
|
|
928
|
-
function toVerifyOption(def,
|
|
940
|
+
function toVerifyOption(def, setup2) {
|
|
929
941
|
return {
|
|
930
|
-
name: `${def.label}${getStatusLabel(
|
|
942
|
+
name: `${def.label}${getStatusLabel(setup2[def.toolKey])}`,
|
|
931
943
|
value: def.value,
|
|
932
|
-
description: resolveDescription(def.description,
|
|
944
|
+
description: resolveDescription(def.description, setup2)
|
|
933
945
|
};
|
|
934
946
|
}
|
|
935
|
-
function getAvailableOptions(
|
|
947
|
+
function getAvailableOptions(setup2) {
|
|
936
948
|
return options.filter(
|
|
937
|
-
(def) => needsSetup(
|
|
938
|
-
).map((def) => toVerifyOption(def,
|
|
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
|
|
984
|
-
const selected = await promptForScripts(getAvailableOptions(
|
|
995
|
+
const setup2 = detectExistingSetup(pkg);
|
|
996
|
+
const selected = await promptForScripts(getAvailableOptions(setup2));
|
|
985
997
|
if (!selected) return;
|
|
986
998
|
const handlers = getSetupHandlers(
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
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(
|
|
1083
|
-
if (
|
|
1084
|
-
if (
|
|
1085
|
-
if (
|
|
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(
|
|
1100
|
+
function getAvailableOptions2(setup2) {
|
|
1089
1101
|
const options2 = [];
|
|
1090
|
-
const launchDescription = getLaunchDescription(
|
|
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 (!
|
|
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,
|
|
1119
|
+
function applySelections(selected, setup2) {
|
|
1108
1120
|
removeVscodeFromGitignore();
|
|
1109
1121
|
ensureVscodeFolder();
|
|
1110
|
-
const launchType =
|
|
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
|
|
1127
|
-
const options2 = getAvailableOptions2(
|
|
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,
|
|
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
|
|
1441
|
-
console.log(` ${
|
|
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,
|
|
1990
|
+
function setStatus(id, status2) {
|
|
1979
1991
|
const result = loadAndFindItem(id);
|
|
1980
1992
|
if (!result) return void 0;
|
|
1981
|
-
result.item.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(
|
|
2130
|
-
switch (
|
|
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,
|
|
2231
|
-
res.writeHead(
|
|
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,
|
|
3698
|
-
return `${chalk44.cyan(`#${pr.number}`)} ${pr.title} ${chalk44.dim(`(${pr.author.login},`)} ${
|
|
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
|
|
3715
|
-
return pullRequests.slice(
|
|
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,
|
|
4930
|
-
let i =
|
|
4941
|
+
function scanTextLines(lines, start3) {
|
|
4942
|
+
let i = start3;
|
|
4931
4943
|
while (i < lines.length && isTextLine(lines[i])) i++;
|
|
4932
|
-
return { texts: lines.slice(
|
|
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
|
|
5556
|
+
import { execSync as execSync25 } from "child_process";
|
|
5260
5557
|
function tryExec(commands) {
|
|
5261
5558
|
for (const cmd of commands) {
|
|
5262
5559
|
try {
|
|
5263
|
-
|
|
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,
|
|
5305
|
-
res.writeHead(
|
|
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
|
|
5761
|
+
import { spawn as spawn5 } from "child_process";
|
|
5465
5762
|
|
|
5466
5763
|
// src/commands/run/add.ts
|
|
5467
|
-
import { mkdirSync as
|
|
5468
|
-
import { join as
|
|
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 =
|
|
5523
|
-
|
|
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 =
|
|
5531
|
-
|
|
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 =
|
|
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
|
|
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
|
-
|
|
5664
|
-
|
|
5665
|
-
|
|
5666
|
-
|
|
5667
|
-
|
|
5668
|
-
|
|
5669
|
-
|
|
5670
|
-
|
|
5671
|
-
|
|
5672
|
-
|
|
5673
|
-
|
|
5674
|
-
|
|
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 =
|
|
5684
|
-
var
|
|
5685
|
-
async function sync() {
|
|
5686
|
-
const claudeDir = path29.join(
|
|
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
|
|
6006
|
+
import { execSync as execSync26 } from "child_process";
|
|
5706
6007
|
import * as path30 from "path";
|
|
5707
|
-
import { fileURLToPath as
|
|
5708
|
-
var __filename3 =
|
|
5709
|
-
var
|
|
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(
|
|
6012
|
+
return path30.resolve(__dirname7, "..");
|
|
5712
6013
|
}
|
|
5713
6014
|
function isGitRepo(dir) {
|
|
5714
6015
|
try {
|
|
5715
|
-
|
|
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 =
|
|
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
|
-
|
|
6038
|
+
execSync26("git pull", { cwd: installDir, stdio: "inherit" });
|
|
5738
6039
|
console.log("Building...");
|
|
5739
|
-
|
|
6040
|
+
execSync26("npm run build", { cwd: installDir, stdio: "inherit" });
|
|
5740
6041
|
console.log("Syncing commands...");
|
|
5741
|
-
|
|
6042
|
+
execSync26("assist sync", { stdio: "inherit" });
|
|
5742
6043
|
} else if (isGlobalNpmInstall(installDir)) {
|
|
5743
6044
|
console.log("Detected global npm installation, updating...");
|
|
5744
|
-
|
|
6045
|
+
execSync26("npm i -g @staff0rd/assist@latest", { stdio: "inherit" });
|
|
5745
6046
|
console.log("Syncing commands...");
|
|
5746
|
-
|
|
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();
|