@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 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. If that's a blocker, [request early access to Pipevox](https://settinghead.github.io/pipevox-signup) a hosted option that needs no local TTS setup.
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
- ## Considering a hosted version?
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.6",
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("Can't run local TTS? Request hosted access:");
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
  }
@@ -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 config = loadConfig();
188
+ const rawConfig = loadConfig();
189
189
 
190
- // Save partial progress on Ctrl+C so completed steps are preserved
191
- const savePartial = () => {
192
- try {
193
- saveConfig(config);
194
- console.log("");
195
- printWarning("Setup interrupted — progress saved. Run 'voxlert setup' to resume.");
196
- console.log("");
197
- } catch {
198
- // ignore write errors during exit
199
- }
200
- };
201
- process.on("SIGINT", () => { savePartial(); process.exit(130); });
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
- // --- Save config ---
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
- // --- Summary ---
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. If that's a blocker:");
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("Hosted option", "https://settinghead.github.io/pipevox-signup — no local TTS needed");
304
+ printStatus("Setup help", "https://github.com/settinghead/voxlert/discussions/6");
306
305
  printStatus(`${label} docs`, docsUrl);
307
306
  console.log("");
308
307
  }