@settinghead/voxlert 0.3.6 → 0.3.7
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 +2 -4
- package/package.json +1 -1
- package/src/cli.js +7 -0
- package/src/commands/pack-helpers.js +1 -2
- package/src/commands/setup.js +4 -2
- package/src/setup.js +127 -26
- package/src/tts-test.js +2 -3
package/README.md
CHANGED
|
@@ -57,7 +57,7 @@ You will also want:
|
|
|
57
57
|
|
|
58
58
|
The setup wizard auto-detects running TTS backends. If none are running yet, setup still completes, but you will only get text notifications and fallback phrases until you start one and rerun setup.
|
|
59
59
|
|
|
60
|
-
> **Can't run local TTS?** Both backends require a GPU or Apple Silicon.
|
|
60
|
+
> **Can't run local TTS?** Both backends require a GPU or Apple Silicon. Voxlert still works without TTS — you'll get text notifications and fallback phrases. Need help? [Post in Setup help & troubleshooting](https://github.com/settinghead/voxlert/discussions/6).
|
|
61
61
|
|
|
62
62
|
### 2. Install and run setup
|
|
63
63
|
|
|
@@ -366,9 +366,7 @@ See [Creating Voice Packs](docs/creating-voice-packs.md) for building your own c
|
|
|
366
366
|
|
|
367
367
|
- **Protoss Advisor** voice pack inspired by [openclaw/protoss-voice](https://playbooks.com/skills/openclaw/skills/protoss-voice)
|
|
368
368
|
|
|
369
|
-
##
|
|
370
|
-
|
|
371
|
-
If the local TTS setup is a blocker, [vote or comment in this Discussion](https://github.com/settinghead/voxlert/discussions/5). A hosted API (no local Python or model required) is on the roadmap if demand is there.
|
|
369
|
+
## Need help?
|
|
372
370
|
|
|
373
371
|
Having trouble with setup? Post in the [Setup help & troubleshooting Discussion](https://github.com/settinghead/voxlert/discussions/6).
|
|
374
372
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@settinghead/voxlert",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.7",
|
|
4
4
|
"description": "LLM-generated voice notifications for Claude Code, Cursor, OpenAI Codex, and OpenClaw, spoken by game characters like the StarCraft Adjutant, Kerrigan, C&C EVA, SHODAN, and more.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
package/src/cli.js
CHANGED
|
@@ -23,6 +23,13 @@ function createHelpText() {
|
|
|
23
23
|
|
|
24
24
|
async function maybeRunSetup(command) {
|
|
25
25
|
if (command.skipSetupWizard || existsSync(STATE_DIR)) return false;
|
|
26
|
+
const args = process.argv.slice(2);
|
|
27
|
+
const nonInteractive = args.includes("--yes") || args.includes("-y");
|
|
28
|
+
if (nonInteractive) {
|
|
29
|
+
const { runSetup } = await import("./setup.js");
|
|
30
|
+
await runSetup({ nonInteractive: true });
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
26
33
|
console.log("Welcome to Voxlert! First time here?\n");
|
|
27
34
|
const select = (await import("@inquirer/select")).default;
|
|
28
35
|
const action = await select({
|
|
@@ -48,8 +48,7 @@ export async function testPipeline(text, pack) {
|
|
|
48
48
|
console.log("TTS failed — no audio was produced.");
|
|
49
49
|
console.log("Make sure your TTS server is running (see: voxlert setup).");
|
|
50
50
|
console.log("");
|
|
51
|
-
console.log("
|
|
52
|
-
console.log(" https://settinghead.github.io/pipevox-signup");
|
|
51
|
+
console.log("Need help? https://github.com/settinghead/voxlert/discussions/6");
|
|
53
52
|
} else {
|
|
54
53
|
console.log("Done.");
|
|
55
54
|
}
|
package/src/commands/setup.js
CHANGED
|
@@ -3,11 +3,13 @@ export const setupCommand = {
|
|
|
3
3
|
aliases: [],
|
|
4
4
|
help: [
|
|
5
5
|
" voxlert setup Interactive setup wizard (LLM, voice, TTS, hooks)",
|
|
6
|
+
" voxlert setup --yes Accept all defaults non-interactively",
|
|
6
7
|
],
|
|
7
8
|
skipSetupWizard: true,
|
|
8
9
|
skipUpgradeCheck: false,
|
|
9
|
-
async run() {
|
|
10
|
+
async run({ args }) {
|
|
11
|
+
const nonInteractive = args.includes("--yes") || args.includes("-y");
|
|
10
12
|
const { runSetup } = await import("../setup.js");
|
|
11
|
-
await runSetup();
|
|
13
|
+
await runSetup({ nonInteractive });
|
|
12
14
|
},
|
|
13
15
|
};
|
package/src/setup.js
CHANGED
|
@@ -179,28 +179,36 @@ function ensurePacks() {
|
|
|
179
179
|
}
|
|
180
180
|
}
|
|
181
181
|
|
|
182
|
-
export async function runSetup() {
|
|
182
|
+
export async function runSetup({ nonInteractive = false } = {}) {
|
|
183
183
|
// Ensure config exists
|
|
184
184
|
ensureConfig();
|
|
185
185
|
ensurePacks();
|
|
186
186
|
mkdirSync(CACHE_DIR, { recursive: true });
|
|
187
187
|
|
|
188
|
-
const
|
|
188
|
+
const rawConfig = loadConfig();
|
|
189
189
|
|
|
190
|
-
//
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
190
|
+
// Auto-persist: any property write saves to disk immediately
|
|
191
|
+
const config = new Proxy(rawConfig, {
|
|
192
|
+
set(target, prop, value) {
|
|
193
|
+
target[prop] = value;
|
|
194
|
+
try { saveConfig(target); } catch { /* ignore */ }
|
|
195
|
+
return true;
|
|
196
|
+
},
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
process.on("SIGINT", () => {
|
|
200
|
+
console.log("");
|
|
201
|
+
printWarning("Setup interrupted — progress saved. Run 'voxlert setup' to resume.");
|
|
202
|
+
console.log("");
|
|
203
|
+
process.exit(130);
|
|
204
|
+
});
|
|
202
205
|
|
|
203
206
|
try {
|
|
207
|
+
|
|
208
|
+
if (nonInteractive) {
|
|
209
|
+
return await runNonInteractiveSetup(config);
|
|
210
|
+
}
|
|
211
|
+
|
|
204
212
|
const currentBackend = config.llm_backend || "openrouter";
|
|
205
213
|
const currentProvider = getProvider(currentBackend);
|
|
206
214
|
const currentModel = config.llm_model || currentProvider?.defaultModel || "default";
|
|
@@ -242,6 +250,7 @@ export async function runSetup() {
|
|
|
242
250
|
|
|
243
251
|
if (chosenProvider !== "skip") {
|
|
244
252
|
config.llm_backend = chosenProvider;
|
|
253
|
+
|
|
245
254
|
const provider = getProvider(chosenProvider);
|
|
246
255
|
|
|
247
256
|
// --- Step 2: API Key ---
|
|
@@ -288,26 +297,30 @@ export async function runSetup() {
|
|
|
288
297
|
|
|
289
298
|
if (apiKey) {
|
|
290
299
|
config.llm_api_key = apiKey;
|
|
291
|
-
// Clear legacy field if using the new unified field
|
|
292
300
|
if (chosenProvider === "openrouter") {
|
|
293
301
|
config.openrouter_api_key = apiKey;
|
|
294
302
|
}
|
|
303
|
+
|
|
295
304
|
} else {
|
|
296
305
|
config.llm_api_key = null;
|
|
297
306
|
config.openrouter_api_key = null;
|
|
307
|
+
|
|
298
308
|
}
|
|
299
309
|
} else {
|
|
300
310
|
config.llm_api_key = null;
|
|
301
311
|
config.openrouter_api_key = null;
|
|
312
|
+
|
|
302
313
|
}
|
|
303
314
|
|
|
304
315
|
// Set default model for chosen provider
|
|
305
316
|
if (!config.llm_model && !config.openrouter_model) {
|
|
306
317
|
config.llm_model = provider.defaultModel;
|
|
318
|
+
|
|
307
319
|
}
|
|
308
320
|
} else {
|
|
309
321
|
config.llm_api_key = null;
|
|
310
322
|
config.openrouter_api_key = null;
|
|
323
|
+
|
|
311
324
|
console.log("");
|
|
312
325
|
printWarning("Using fallback phrases from the voice pack.");
|
|
313
326
|
console.log("");
|
|
@@ -384,6 +397,7 @@ export async function runSetup() {
|
|
|
384
397
|
default: active || "random",
|
|
385
398
|
});
|
|
386
399
|
config.active_pack = chosenPack;
|
|
400
|
+
|
|
387
401
|
} else {
|
|
388
402
|
printWarning("No voice packs found. Using default.");
|
|
389
403
|
console.log("");
|
|
@@ -487,10 +501,106 @@ export async function runSetup() {
|
|
|
487
501
|
printWarning("No platforms selected. Run 'voxlert setup' again to install hooks later.");
|
|
488
502
|
}
|
|
489
503
|
|
|
490
|
-
// ---
|
|
504
|
+
// --- Summary ---
|
|
505
|
+
printSetupSummary(config, "skip", []);
|
|
506
|
+
|
|
507
|
+
} catch (err) {
|
|
508
|
+
// Inquirer throws on Ctrl+C (ExitPromptError); progress already persisted
|
|
509
|
+
if (err && (err.name === "ExitPromptError" || err.message === "Prompt was canceled")) {
|
|
510
|
+
|
|
511
|
+
console.log("");
|
|
512
|
+
printWarning("Setup interrupted — progress saved. Run 'voxlert setup' to resume.");
|
|
513
|
+
console.log("");
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
throw err;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* Non-interactive setup: accept all defaults, skip prompts.
|
|
522
|
+
* Useful for CI, Docker, and automated testing.
|
|
523
|
+
*/
|
|
524
|
+
async function runNonInteractiveSetup(config) {
|
|
525
|
+
console.log("Running non-interactive setup (--yes)...\n");
|
|
526
|
+
|
|
527
|
+
// Step 1–2: LLM — skip (fallback phrases only)
|
|
528
|
+
printStep(1, "LLM Provider");
|
|
529
|
+
printStatus("LLM", "Skipped (fallback phrases only)");
|
|
530
|
+
config.llm_api_key = null;
|
|
531
|
+
config.openrouter_api_key = null;
|
|
532
|
+
console.log("");
|
|
533
|
+
|
|
534
|
+
// Step 3: Download default voice packs
|
|
535
|
+
printStep(3, "Download voice packs");
|
|
536
|
+
mkdirSync(PACKS_DIR, { recursive: true });
|
|
537
|
+
|
|
538
|
+
const existingPackIds = new Set();
|
|
539
|
+
try {
|
|
540
|
+
for (const entry of readdirSync(PACKS_DIR, { withFileTypes: true })) {
|
|
541
|
+
if (entry.isDirectory() && existsSync(join(PACKS_DIR, entry.name, "pack.json"))) {
|
|
542
|
+
existingPackIds.add(entry.name);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
} catch {
|
|
546
|
+
// PACKS_DIR may not exist yet
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
const baseUrl = getPackRegistryBaseUrl();
|
|
550
|
+
for (const packId of DEFAULT_DOWNLOAD_PACK_IDS) {
|
|
551
|
+
if (existingPackIds.has(packId)) continue;
|
|
552
|
+
const pack = PACK_REGISTRY.find((p) => p.id === packId);
|
|
553
|
+
const label = pack ? pack.name : packId;
|
|
554
|
+
process.stdout.write(` Downloading ${label}... `);
|
|
555
|
+
try {
|
|
556
|
+
await downloadPack(packId, baseUrl);
|
|
557
|
+
console.log("done.");
|
|
558
|
+
} catch (err) {
|
|
559
|
+
console.log(`failed (${err.message}).`);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
console.log("");
|
|
563
|
+
|
|
564
|
+
// Step 4: Voice — random
|
|
565
|
+
printStep(4, "Voice Pack");
|
|
566
|
+
config.active_pack = "random";
|
|
567
|
+
printStatus("Voice", "random");
|
|
568
|
+
console.log("");
|
|
569
|
+
|
|
570
|
+
// Step 5: TTS — detect and pick best available, skip verification
|
|
571
|
+
printStep(5, "TTS Server");
|
|
572
|
+
|
|
573
|
+
process.stdout.write(" Checking Chatterbox... ");
|
|
574
|
+
const chatterboxUp = await probeTtsBackend(config, "chatterbox");
|
|
575
|
+
console.log(chatterboxUp ? "detected!" : "not running");
|
|
576
|
+
|
|
577
|
+
process.stdout.write(" Checking Qwen TTS... ");
|
|
578
|
+
const qwenUp = await probeTtsBackend(config, "qwen");
|
|
579
|
+
console.log(qwenUp ? "detected!" : "not running");
|
|
580
|
+
|
|
581
|
+
if (qwenUp) {
|
|
582
|
+
config.tts_backend = "qwen";
|
|
583
|
+
} else if (chatterboxUp) {
|
|
584
|
+
config.tts_backend = "chatterbox";
|
|
585
|
+
} else {
|
|
586
|
+
config.tts_backend = config.tts_backend || "qwen";
|
|
587
|
+
}
|
|
588
|
+
printStatus("TTS", config.tts_backend + (qwenUp || chatterboxUp ? "" : " (not running — text notifications only)"));
|
|
589
|
+
console.log("");
|
|
590
|
+
|
|
591
|
+
// Step 6: Hooks — skip
|
|
592
|
+
printStep(6, "Hooks");
|
|
593
|
+
printStatus("Hooks", "Skipped (run 'voxlert setup' to install hooks later)");
|
|
594
|
+
console.log("");
|
|
595
|
+
|
|
596
|
+
// Save config
|
|
491
597
|
saveConfig(config);
|
|
492
598
|
|
|
493
|
-
//
|
|
599
|
+
// Summary
|
|
600
|
+
printSetupSummary(config, "skip", []);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
function printSetupSummary(config, chosenProvider, selectedPlatforms) {
|
|
494
604
|
console.log("");
|
|
495
605
|
console.log(highlight("=== Setup Complete ==="));
|
|
496
606
|
console.log("");
|
|
@@ -516,13 +626,4 @@ export async function runSetup() {
|
|
|
516
626
|
}
|
|
517
627
|
printStatus("Reconfigure", "voxlert setup");
|
|
518
628
|
console.log("");
|
|
519
|
-
|
|
520
|
-
} catch (err) {
|
|
521
|
-
// Inquirer throws on Ctrl+C (ExitPromptError); save partial progress
|
|
522
|
-
if (err && (err.name === "ExitPromptError" || err.message === "Prompt was canceled")) {
|
|
523
|
-
savePartial();
|
|
524
|
-
return;
|
|
525
|
-
}
|
|
526
|
-
throw err;
|
|
527
|
-
}
|
|
528
629
|
}
|
package/src/tts-test.js
CHANGED
|
@@ -222,8 +222,7 @@ export async function chooseTtsBackend(config, { qwenUp, chatterboxUp }) {
|
|
|
222
222
|
: "";
|
|
223
223
|
|
|
224
224
|
if (!qwenUp && !chatterboxUp) {
|
|
225
|
-
printStatus("Note", "Local TTS needs a GPU or Apple Silicon.
|
|
226
|
-
printStatus("Hosted option", "https://settinghead.github.io/pipevox-signup");
|
|
225
|
+
printStatus("Note", "Local TTS needs a GPU or Apple Silicon. Setup still works — you'll get text notifications until TTS is running.");
|
|
227
226
|
console.log("");
|
|
228
227
|
}
|
|
229
228
|
|
|
@@ -302,7 +301,7 @@ export async function verifyTtsSetup(config, backend) {
|
|
|
302
301
|
}
|
|
303
302
|
|
|
304
303
|
printWarning("Still not working? Local TTS requires specific hardware (Apple Silicon or NVIDIA GPU).");
|
|
305
|
-
printStatus("
|
|
304
|
+
printStatus("Setup help", "https://github.com/settinghead/voxlert/discussions/6");
|
|
306
305
|
printStatus(`${label} docs`, docsUrl);
|
|
307
306
|
console.log("");
|
|
308
307
|
}
|