vora-ai 0.1.34 → 0.1.36
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/.buildstamp +1 -1
- package/dist/build-info.json +3 -3
- package/dist/canvas-host/a2ui/.bundle.hash +1 -1
- package/dist/cli-startup-metadata.json +1 -1
- package/dist/{command-registry-CJzgMEiu.js → command-registry-C-dQzQLt.js} +4 -4
- package/dist/{command-registry-BErLBmAn.js → command-registry-D5bgvc-g.js} +1 -1
- package/dist/completion-cli-BcvhPH1f.js +2 -0
- package/dist/{completion-cli-DOqvgg4j.js → completion-cli-CuDyNItM.js} +2 -2
- package/dist/{doctor-completion-CHyxjRnq.js → doctor-completion-1du2om2X.js} +1 -1
- package/dist/entry.js +2 -2
- package/dist/extensions/telegram/.vora-runtime-deps-stamp.json +1 -1
- package/dist/{gateway-cli-MWd326Fi.js → gateway-cli-D3jVKXtU.js} +1 -1
- package/dist/{help-BWGrT64x.js → help-BX3qk2j9.js} +1 -1
- package/dist/index.js +1 -1
- package/dist/{onboard-Mcuz9Qtv.js → onboard-DwchOLHk.js} +1 -1
- package/dist/{program-C1NP1wtT.js → program-Dah2D_cO.js} +2 -2
- package/dist/{prompt-select-styled-Cnschuoa.js → prompt-select-styled-fmGo-EJl.js} +1 -1
- package/dist/{register.maintenance-Crkq06lG.js → register.maintenance-BHLVtWhx.js} +1 -1
- package/dist/{register.onboard-BLyf9Ec0.js → register.onboard-BQXwigRU.js} +1 -1
- package/dist/{register.setup-DWHzXiXK.js → register.setup-BdrATrzp.js} +1 -1
- package/dist/{register.subclis-Cdt5rvRo.js → register.subclis-CrmcN8zA.js} +2 -2
- package/dist/{register.subclis-_jS15b16.js → register.subclis-Dx_SLWv5.js} +6 -6
- package/dist/{root-help-Tv4Oc4kX.js → root-help-DDj9BENB.js} +2 -2
- package/dist/{run-main-CaN20YYg.js → run-main-C6NIk22l.js} +5 -5
- package/dist/{setup-cXX6csIV.js → setup-CEe7DH30.js} +3 -3
- package/dist/{setup.finalize-DPRk6z4s.js → setup.finalize-tiOrCMkC.js} +4 -4
- package/dist/{subcli-descriptors-Cc423xKz.js → subcli-descriptors-BTTrqeCm.js} +1 -1
- package/dist/{update-cli-DXdoaDuF.js → update-cli-Dmyo-EkW.js} +3 -3
- package/dist/{voice-cli-BL4zwKEh.js → voice-cli-BD1lHjLd.js} +132 -62
- package/package.json +1 -1
- package/scripts/agora-stt-bridge.mjs +166 -10
- package/dist/completion-cli-D9cFhAqm.js +0 -2
package/dist/.buildstamp
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"builtAt":
|
|
1
|
+
{"builtAt":1776448382056,"head":"936286ec3e03bbfaa83f10717f1ccf01e29ff447"}
|
package/dist/build-info.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
e41bbc2c2a8641d063e7675b117011f54b721f5d55b3b94967fee8da0da6a761
|
|
@@ -10,5 +10,5 @@
|
|
|
10
10
|
"signal",
|
|
11
11
|
"imessage"
|
|
12
12
|
],
|
|
13
|
-
"rootHelpText": "\n🌊 VORA 0.1.
|
|
13
|
+
"rootHelpText": "\n🌊 VORA 0.1.36 (936286e) — Listening out for 'Hey Vora'...\n\nUsage: vora [options] [command]\n\nOptions:\n --container <name> Run the CLI inside a running Podman/Docker container\n named <name> (default: env VORA_CONTAINER)\n --dev Dev profile: isolate state under ~/.vora-dev, default\n gateway port 19001, and shift derived ports\n (browser/canvas)\n -h, --help Display help for command\n --log-level <level> Global log level override for file + console\n (silent|fatal|error|warn|info|debug|trace)\n --no-color Disable ANSI colors\n --profile <name> Use a named profile (isolates\n VORA_STATE_DIR/VORA_CONFIG_PATH under ~/.vora-<name>)\n -V, --version output the version number\n\nCommands:\n Hint: commands suffixed with * have subcommands. Run <command> --help for details.\n acp * Agent Control Protocol tools\n agent Run one agent turn via the Gateway\n agents * Manage isolated agents (workspaces, auth, routing)\n approvals * Manage exec approvals (gateway or node host)\n backup * Create and verify local backup archives for Vora state\n channels * Manage connected chat channels (Telegram, Discord, etc.)\n clawbot * Legacy clawbot command aliases\n completion Generate shell completion script\n config * Non-interactive config helpers\n (get/set/unset/file/validate). Default: starts guided\n setup.\n configure Interactive configuration for credentials, channels,\n gateway, and agent defaults\n cron * Manage cron jobs via the Gateway scheduler\n daemon * Gateway service (legacy alias)\n dashboard Open the Control UI with your current token\n devices * Device pairing + token management\n directory * Lookup contact and group IDs (self, peers, groups) for\n supported chat channels\n dns * DNS helpers for wide-area discovery (Tailscale + CoreDNS)\n docs Search the live Vora docs\n doctor Health checks + quick fixes for the gateway and channels\n gateway * Run, inspect, and query the WebSocket Gateway\n health Fetch health from the running gateway\n help Display help for command\n hooks * Manage internal agent hooks\n logs Tail gateway file logs via RPC\n message * Send, read, and manage messages\n models * Discover, scan, and configure models\n node * Run and manage the headless node host service\n nodes * Manage gateway-owned node pairing and node commands\n onboard Interactive onboarding for gateway, workspace, and skills\n pairing * Secure DM pairing (approve inbound requests)\n plugins * Manage Vora plugins and extensions\n qr Generate iOS pairing QR/setup code\n reset Reset local config/state (keeps the CLI installed)\n sandbox * Manage sandbox containers for agent isolation\n secrets * Secrets runtime reload controls\n security * Security tools and local config audits\n sessions * List stored conversation sessions\n setup Initialize local config and agent workspace\n skills * List and inspect available skills\n status Show channel health and recent session recipients\n system * System events, heartbeat, and presence\n tasks * Inspect durable background task state\n tui Open a terminal UI connected to the Gateway\n uninstall Uninstall the gateway service + local data (CLI remains)\n update * Update Vora and inspect update channel status\n voice * Wake-word terminal voice loop (OpenWakeWord trigger + STT\n bridge + Gateway chat + optional ElevenLabs TTS)\n webhooks * Webhook helpers and integrations\n\nExamples:\n vora models --help\n Show detailed help for the models command.\n vora onboard\n Run interactive onboarding for the gateway, workspace, and skills.\n vora configure --section model --section gateway\n Re-open only the model and gateway configuration sections.\n vora gateway --port 27106\n Run the WebSocket Gateway locally.\n vora --dev gateway\n Run a dev Gateway (isolated state/config) on ws://127.0.0.1:19001.\n vora gateway --force\n Kill anything bound to the default gateway port, then start it.\n vora models status --plain\n Show the configured provider and default-model state.\n vora gateway ...\n Gateway control via WebSocket.\n vora message send --channel telegram --target @mychat --message \"Hi\"\n Send through a configured channel and print the result in the terminal.\n\nDocs: https://docs.vora.ai/cli\n\n"
|
|
14
14
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { O as hasHelpOrVersion, T as getPrimaryCommand } from "./logger-DtBbg3AQ.js";
|
|
2
2
|
import { n as removeCommandByName, t as registerLazyCommand } from "./register-lazy-command-C4_WvYdL.js";
|
|
3
3
|
import { t as getCoreCliCommandDescriptors } from "./core-command-descriptors-Dmh3xVCU.js";
|
|
4
|
-
import { i as registerSubCliCommands } from "./register.subclis-
|
|
4
|
+
import { i as registerSubCliCommands } from "./register.subclis-Dx_SLWv5.js";
|
|
5
5
|
//#region src/cli/program/command-registry.ts
|
|
6
6
|
const shouldRegisterCorePrimaryOnly = (argv) => {
|
|
7
7
|
if (hasHelpOrVersion(argv)) return false;
|
|
@@ -15,7 +15,7 @@ const coreEntries = [
|
|
|
15
15
|
hasSubcommands: false
|
|
16
16
|
}],
|
|
17
17
|
register: async ({ program }) => {
|
|
18
|
-
(await import("./register.setup-
|
|
18
|
+
(await import("./register.setup-BdrATrzp.js")).registerSetupCommand(program);
|
|
19
19
|
}
|
|
20
20
|
},
|
|
21
21
|
{
|
|
@@ -25,7 +25,7 @@ const coreEntries = [
|
|
|
25
25
|
hasSubcommands: false
|
|
26
26
|
}],
|
|
27
27
|
register: async ({ program }) => {
|
|
28
|
-
(await import("./register.onboard-
|
|
28
|
+
(await import("./register.onboard-BQXwigRU.js")).registerOnboardCommand(program);
|
|
29
29
|
}
|
|
30
30
|
},
|
|
31
31
|
{
|
|
@@ -82,7 +82,7 @@ const coreEntries = [
|
|
|
82
82
|
}
|
|
83
83
|
],
|
|
84
84
|
register: async ({ program }) => {
|
|
85
|
-
(await import("./register.maintenance-
|
|
85
|
+
(await import("./register.maintenance-BHLVtWhx.js")).registerMaintenanceCommands(program);
|
|
86
86
|
}
|
|
87
87
|
},
|
|
88
88
|
{
|
|
@@ -3,8 +3,8 @@ import { t as formatDocsLink } from "./links-D-QW4VCX.js";
|
|
|
3
3
|
import { r as theme } from "./theme-DEBOahQW.js";
|
|
4
4
|
import { _ as resolveStateDir } from "./paths-5dqsPi5h.js";
|
|
5
5
|
import { m as pathExists } from "./utils-D25qzO4S.js";
|
|
6
|
-
import { n as loadValidatedConfigForPluginRegistration, r as registerSubCliByName, t as getSubCliEntries } from "./register.subclis-
|
|
7
|
-
import { n as registerCoreCliByName, t as getCoreCliCommandNames } from "./command-registry-
|
|
6
|
+
import { n as loadValidatedConfigForPluginRegistration, r as registerSubCliByName, t as getSubCliEntries } from "./register.subclis-Dx_SLWv5.js";
|
|
7
|
+
import { n as registerCoreCliByName, t as getCoreCliCommandNames } from "./command-registry-C-dQzQLt.js";
|
|
8
8
|
import { t as getProgramContext } from "./program-context-C0Ru__k1.js";
|
|
9
9
|
import path from "node:path";
|
|
10
10
|
import os from "node:os";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { t as resolveVoraPackageRoot } from "./vora-root-fqgy2cBi.js";
|
|
2
2
|
import { n as resolveCliName } from "./cli-name-C_IzpPbS.js";
|
|
3
3
|
import { t as note } from "./note-DfcrAbT0.js";
|
|
4
|
-
import { c as usesSlowDynamicCompletion, i as isCompletionInstalled, o as resolveCompletionCachePath, r as installCompletion, s as resolveShellFromEnv, t as completionCacheExists } from "./completion-cli-
|
|
4
|
+
import { c as usesSlowDynamicCompletion, i as isCompletionInstalled, o as resolveCompletionCachePath, r as installCompletion, s as resolveShellFromEnv, t as completionCacheExists } from "./completion-cli-CuDyNItM.js";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { spawnSync } from "node:child_process";
|
|
7
7
|
//#region src/commands/doctor-completion.ts
|
package/dist/entry.js
CHANGED
|
@@ -193,14 +193,14 @@ function tryHandleRootHelpFastPath(argv, deps = {}) {
|
|
|
193
193
|
Promise.resolve().then(() => deps.outputRootHelp?.()).catch(handleError);
|
|
194
194
|
return true;
|
|
195
195
|
}
|
|
196
|
-
import("./root-help-
|
|
196
|
+
import("./root-help-DDj9BENB.js").then(({ outputRootHelp }) => {
|
|
197
197
|
return outputRootHelp();
|
|
198
198
|
}).catch(handleError);
|
|
199
199
|
return true;
|
|
200
200
|
}
|
|
201
201
|
function runMainOrRootHelp(argv) {
|
|
202
202
|
if (tryHandleRootHelpFastPath(argv)) return;
|
|
203
|
-
import("./run-main-
|
|
203
|
+
import("./run-main-C6NIk22l.js").then(({ runCli }) => runCli(argv)).catch((error) => {
|
|
204
204
|
console.error("[vora] Failed to start CLI:", error instanceof Error ? error.stack ?? error.message : error);
|
|
205
205
|
process$1.exitCode = 1;
|
|
206
206
|
});
|
|
@@ -196,7 +196,7 @@ import { s as normalizeUpdateChannel } from "./update-channels-C8-n6_kE.js";
|
|
|
196
196
|
import { n as compareSemverStrings, o as resolveNpmChannelTag, t as checkUpdateStatus } from "./update-check-Cw2Zo7Rn.js";
|
|
197
197
|
import { i as resolveGatewayStartupPluginIds, r as resolveConfiguredDeferredChannelPluginIds } from "./channel-plugin-ids-BgeBHwp9.js";
|
|
198
198
|
import { l as startTaskRegistryMaintenance, n as getInspectableTaskRegistrySummary } from "./task-registry.maintenance-CfvslEdg.js";
|
|
199
|
-
import { t as runSetupWizard } from "./setup-
|
|
199
|
+
import { t as runSetupWizard } from "./setup-CEe7DH30.js";
|
|
200
200
|
import { _ as buildGogWatchStartArgs, g as buildGogWatchServeArgs, i as ensureTailscaleEndpoint, w as resolveGmailHookRuntimeConfig } from "./gmail-setup-utils-CstpQWwK.js";
|
|
201
201
|
import { i as loadAgentIdentity, o as pruneAgentConfig, r as findAgentEntryIndex, t as applyAgentConfig } from "./agents.config-BXIFVvWT.js";
|
|
202
202
|
import { a as resolveApnsAuthConfigFromEnv, c as shouldClearStoredApnsRegistration, d as MediaOffloadError, f as parseMessageWithAttachments, l as resolveApnsRelayConfigFromEnv, n as loadApnsRegistration, o as sendApnsAlert, r as normalizeApnsEnvironment, s as sendApnsBackgroundWake, t as clearApnsRegistrationIfCurrent, u as normalizeRpcAttachmentsToChatAttachments } from "./push-apns-wrSw6X5_.js";
|
|
@@ -6,7 +6,7 @@ import { n as resolveCliName, t as replaceCliName } from "./cli-name-C_IzpPbS.js
|
|
|
6
6
|
import { n as resolveCommitHash } from "./git-commit-DifBr62H.js";
|
|
7
7
|
import { i as hasEmittedCliBanner, r as formatCliBannerLine } from "./banner-C5yJCudk.js";
|
|
8
8
|
import { n as getCoreCliCommandsWithSubcommands } from "./core-command-descriptors-Dmh3xVCU.js";
|
|
9
|
-
import { t as getSubCliCommandsWithSubcommands } from "./subcli-descriptors-
|
|
9
|
+
import { t as getSubCliCommandsWithSubcommands } from "./subcli-descriptors-BTTrqeCm.js";
|
|
10
10
|
import { InvalidArgumentError } from "commander";
|
|
11
11
|
//#region src/cli/log-level-option.ts
|
|
12
12
|
const CLI_LOG_LEVEL_VALUES = ALLOWED_LOG_LEVELS.join("|");
|
package/dist/index.js
CHANGED
|
@@ -28,7 +28,7 @@ let saveSessionStore;
|
|
|
28
28
|
let toWhatsappJid;
|
|
29
29
|
let waitForever;
|
|
30
30
|
async function loadLegacyCliDeps() {
|
|
31
|
-
const [{ installGaxiosFetchCompat }, { runCli }] = await Promise.all([import("./gaxios-fetch-compat-BZRlVrBH.js"), import("./run-main-
|
|
31
|
+
const [{ installGaxiosFetchCompat }, { runCli }] = await Promise.all([import("./gaxios-fetch-compat-BZRlVrBH.js"), import("./run-main-C6NIk22l.js")]);
|
|
32
32
|
return {
|
|
33
33
|
installGaxiosFetchCompat,
|
|
34
34
|
runCli
|
|
@@ -11,7 +11,7 @@ import { c as normalizeGatewayTokenInput, d as randomToken, f as resolveControlU
|
|
|
11
11
|
import { n as logConfigUpdated } from "./logging-BQ-k0nK2.js";
|
|
12
12
|
import { t as WizardCancelledError } from "./prompts-BVJ3zQFl.js";
|
|
13
13
|
import { t as createClackPrompter } from "./clack-prompter-Be8sX0Lg.js";
|
|
14
|
-
import { t as runSetupWizard } from "./setup-
|
|
14
|
+
import { t as runSetupWizard } from "./setup-CEe7DH30.js";
|
|
15
15
|
import { a as resolveManifestProviderOnboardAuthFlags } from "./provider-auth-choices-DcNzlbgs.js";
|
|
16
16
|
import { i as resolveDeprecatedAuthChoiceReplacement, n as isDeprecatedAuthChoice, r as normalizeLegacyOnboardAuthChoice, t as formatDeprecatedNonInteractiveAuthChoiceError } from "./auth-choice-legacy-DGeZIT-G.js";
|
|
17
17
|
import { r as applyLocalSetupWorkspaceConfig } from "./onboard-config-DkkHPbTO.js";
|
|
@@ -7,12 +7,12 @@ import { n as VERSION } from "./version-A4Ns-pm9.js";
|
|
|
7
7
|
import { n as resolveCliName } from "./cli-name-C_IzpPbS.js";
|
|
8
8
|
import { t as emitCliBanner } from "./banner-C5yJCudk.js";
|
|
9
9
|
import { n as resolveCliChannelOptions } from "./channel-options-D_8z6c-h.js";
|
|
10
|
-
import { i as registerProgramCommands } from "./command-registry-
|
|
10
|
+
import { i as registerProgramCommands } from "./command-registry-C-dQzQLt.js";
|
|
11
11
|
import { n as setProgramContext } from "./program-context-C0Ru__k1.js";
|
|
12
12
|
import { t as isCommandJsonOutputMode } from "./json-mode-KmFHsA3R.js";
|
|
13
13
|
import "./ports-CQo1U86K.js";
|
|
14
14
|
import { n as resolvePluginInstallPreactionRequest, t as resolvePluginInstallInvalidConfigPolicy } from "./plugin-install-config-policy-BHANXE7k.js";
|
|
15
|
-
import { t as configureProgramHelp } from "./help-
|
|
15
|
+
import { t as configureProgramHelp } from "./help-BX3qk2j9.js";
|
|
16
16
|
import { Command } from "commander";
|
|
17
17
|
//#region src/cli/program/context.ts
|
|
18
18
|
function createProgramContext() {
|
|
@@ -108,7 +108,7 @@ import { t as ensureSystemdUserLingerInteractive } from "./systemd-linger-B_gzrf
|
|
|
108
108
|
import { t as formatHealthCheckFailure } from "./health-format-TyQPx9cW.js";
|
|
109
109
|
import { a as stripUnknownConfigKeys, i as resolveConfigPathTarget, n as formatConfigPath, r as noteOpencodeProviderOverrides, t as runDoctorConfigPreflight } from "./doctor-config-preflight-B2IpAFkH.js";
|
|
110
110
|
import { a as isMattermostMutableAllowEntry, i as isMSTeamsMutableAllowEntry, n as isGoogleChatMutableAllowEntry, o as isSlackMutableAllowEntry, r as isIrcMutableAllowEntry, s as isZalouserMutableGroupEntry, t as isDiscordMutableAllowEntry } from "./mutable-allowlist-detectors-zVflHd8P.js";
|
|
111
|
-
import { n as doctorShellCompletion } from "./doctor-completion-
|
|
111
|
+
import { n as doctorShellCompletion } from "./doctor-completion-1du2om2X.js";
|
|
112
112
|
import { t as collectChannelStatusIssues } from "./channels-status-issues-CCJ10zgF.js";
|
|
113
113
|
import { t as resolveDefaultChannelAccountContext } from "./channel-account-context-ChxhkByq.js";
|
|
114
114
|
import "./doctor-state-migrations-BlH3irT5.js";
|
|
@@ -16,7 +16,7 @@ import { r as resolveGatewayService } from "./service-BnUY5mkU.js";
|
|
|
16
16
|
import { n as resolveConfiguredSecretInputWithFallback } from "./resolve-configured-secret-input-string-BGHutVyf.js";
|
|
17
17
|
import { n as runCommandWithRuntime } from "./cli-utils-4n6KI2ho.js";
|
|
18
18
|
import { a as removePath, i as listAgentSessionDirs, o as removeStateAndLinkedPaths, r as buildCleanupPlan, s as removeWorkspaceDirs } from "./backup-create-DZood8Ea.js";
|
|
19
|
-
import { n as doctorCommand, t as selectStyled } from "./prompt-select-styled-
|
|
19
|
+
import { n as doctorCommand, t as selectStyled } from "./prompt-select-styled-fmGo-EJl.js";
|
|
20
20
|
import path from "node:path";
|
|
21
21
|
import { cancel, confirm, isCancel, multiselect } from "@clack/prompts";
|
|
22
22
|
//#region src/infra/clipboard.ts
|
|
@@ -4,7 +4,7 @@ import { r as theme } from "./theme-DEBOahQW.js";
|
|
|
4
4
|
import { n as runCommandWithRuntime } from "./cli-utils-4n6KI2ho.js";
|
|
5
5
|
import { a as resolveManifestProviderOnboardAuthFlags } from "./provider-auth-choices-DcNzlbgs.js";
|
|
6
6
|
import { n as formatAuthChoiceChoicesForCli } from "./auth-choice-options-Blz8a0tL.js";
|
|
7
|
-
import { n as CORE_ONBOARD_AUTH_FLAGS, t as setupWizardCommand } from "./onboard-
|
|
7
|
+
import { n as CORE_ONBOARD_AUTH_FLAGS, t as setupWizardCommand } from "./onboard-DwchOLHk.js";
|
|
8
8
|
//#region src/cli/program/register.onboard.ts
|
|
9
9
|
function resolveInstallDaemonFlag(command, opts) {
|
|
10
10
|
if (!command || typeof command !== "object") return;
|
|
@@ -11,7 +11,7 @@ import { s as resolveSessionTranscriptsDir } from "./paths-UyCuvwtc.js";
|
|
|
11
11
|
import { n as safeParseWithSchema } from "./zod-parse-DHX0rEx9.js";
|
|
12
12
|
import { n as runCommandWithRuntime } from "./cli-utils-4n6KI2ho.js";
|
|
13
13
|
import { n as logConfigUpdated, t as formatConfigPath } from "./logging-BQ-k0nK2.js";
|
|
14
|
-
import { t as setupWizardCommand } from "./onboard-
|
|
14
|
+
import { t as setupWizardCommand } from "./onboard-DwchOLHk.js";
|
|
15
15
|
import fs from "node:fs/promises";
|
|
16
16
|
import { z } from "zod";
|
|
17
17
|
import JSON5 from "json5";
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import "./subcli-descriptors-
|
|
2
|
-
import { n as loadValidatedConfigForPluginRegistration, r as registerSubCliByName } from "./register.subclis-
|
|
1
|
+
import "./subcli-descriptors-BTTrqeCm.js";
|
|
2
|
+
import { n as loadValidatedConfigForPluginRegistration, r as registerSubCliByName } from "./register.subclis-Dx_SLWv5.js";
|
|
3
3
|
export { loadValidatedConfigForPluginRegistration, registerSubCliByName };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { O as hasHelpOrVersion, T as getPrimaryCommand } from "./logger-DtBbg3AQ.js";
|
|
2
2
|
import { t as isTruthyEnvValue } from "./env-CLTF3G6u.js";
|
|
3
3
|
import { n as removeCommandByName, t as registerLazyCommand$1 } from "./register-lazy-command-C4_WvYdL.js";
|
|
4
|
-
import { n as getSubCliEntries$1 } from "./subcli-descriptors-
|
|
4
|
+
import { n as getSubCliEntries$1 } from "./subcli-descriptors-BTTrqeCm.js";
|
|
5
5
|
//#region src/cli/program/register.subclis.ts
|
|
6
6
|
const shouldRegisterPrimaryOnly = (argv) => {
|
|
7
7
|
if (isTruthyEnvValue(process.env.VORA_DISABLE_LAZY_SUBCOMMANDS)) return false;
|
|
@@ -30,7 +30,7 @@ const entries = [
|
|
|
30
30
|
description: "Run, inspect, and query the WebSocket Gateway",
|
|
31
31
|
hasSubcommands: true,
|
|
32
32
|
register: async (program) => {
|
|
33
|
-
(await import("./gateway-cli-
|
|
33
|
+
(await import("./gateway-cli-D3jVKXtU.js")).registerGatewayCli(program);
|
|
34
34
|
}
|
|
35
35
|
},
|
|
36
36
|
{
|
|
@@ -115,10 +115,10 @@ const entries = [
|
|
|
115
115
|
},
|
|
116
116
|
{
|
|
117
117
|
name: "voice",
|
|
118
|
-
description: "Wake-word terminal voice loop (OpenWakeWord trigger + STT bridge + Gateway chat + optional
|
|
118
|
+
description: "Wake-word terminal voice loop (OpenWakeWord trigger + STT bridge + Gateway chat + optional ElevenLabs TTS)",
|
|
119
119
|
hasSubcommands: true,
|
|
120
120
|
register: async (program) => {
|
|
121
|
-
(await import("./voice-cli-
|
|
121
|
+
(await import("./voice-cli-BD1lHjLd.js")).registerVoiceCli(program);
|
|
122
122
|
}
|
|
123
123
|
},
|
|
124
124
|
{
|
|
@@ -244,7 +244,7 @@ const entries = [
|
|
|
244
244
|
description: "Update Vora and inspect update channel status",
|
|
245
245
|
hasSubcommands: true,
|
|
246
246
|
register: async (program) => {
|
|
247
|
-
(await import("./update-cli-
|
|
247
|
+
(await import("./update-cli-Dmyo-EkW.js")).registerUpdateCli(program);
|
|
248
248
|
}
|
|
249
249
|
},
|
|
250
250
|
{
|
|
@@ -252,7 +252,7 @@ const entries = [
|
|
|
252
252
|
description: "Generate shell completion script",
|
|
253
253
|
hasSubcommands: false,
|
|
254
254
|
register: async (program) => {
|
|
255
|
-
(await import("./completion-cli-
|
|
255
|
+
(await import("./completion-cli-BcvhPH1f.js")).registerCompletionCli(program);
|
|
256
256
|
}
|
|
257
257
|
}
|
|
258
258
|
];
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { n as VERSION } from "./version-A4Ns-pm9.js";
|
|
2
2
|
import { t as getCoreCliCommandDescriptors } from "./core-command-descriptors-Dmh3xVCU.js";
|
|
3
|
-
import { n as getSubCliEntries } from "./subcli-descriptors-
|
|
4
|
-
import { t as configureProgramHelp } from "./help-
|
|
3
|
+
import { n as getSubCliEntries } from "./subcli-descriptors-BTTrqeCm.js";
|
|
4
|
+
import { t as configureProgramHelp } from "./help-BX3qk2j9.js";
|
|
5
5
|
import { t as getPluginCliCommandDescriptors } from "./cli-CQycN9EN.js";
|
|
6
6
|
import { Command } from "commander";
|
|
7
7
|
//#region src/cli/program/root-help.ts
|
|
@@ -368,13 +368,13 @@ async function runCli(argv = process$1.argv) {
|
|
|
368
368
|
assertSupportedRuntime();
|
|
369
369
|
try {
|
|
370
370
|
if (shouldUseRootHelpFastPath(normalizedArgv)) {
|
|
371
|
-
const { outputRootHelp } = await import("./root-help-
|
|
371
|
+
const { outputRootHelp } = await import("./root-help-DDj9BENB.js");
|
|
372
372
|
await outputRootHelp();
|
|
373
373
|
return;
|
|
374
374
|
}
|
|
375
375
|
if (await tryRouteCli(normalizedArgv)) return;
|
|
376
376
|
enableConsoleCapture();
|
|
377
|
-
const { buildProgram } = await import("./program-
|
|
377
|
+
const { buildProgram } = await import("./program-Dah2D_cO.js");
|
|
378
378
|
const program = buildProgram();
|
|
379
379
|
const { installUnhandledRejectionHandler } = await import("./unhandled-rejections-D0HD8aaS.js");
|
|
380
380
|
installUnhandledRejectionHandler();
|
|
@@ -388,10 +388,10 @@ async function runCli(argv = process$1.argv) {
|
|
|
388
388
|
const { getProgramContext } = await import("./program-context-Bntrf4rW.js");
|
|
389
389
|
const ctx = getProgramContext(program);
|
|
390
390
|
if (ctx) {
|
|
391
|
-
const { registerCoreCliByName } = await import("./command-registry-
|
|
391
|
+
const { registerCoreCliByName } = await import("./command-registry-D5bgvc-g.js");
|
|
392
392
|
await registerCoreCliByName(program, ctx, primary, parseArgv);
|
|
393
393
|
}
|
|
394
|
-
const { registerSubCliByName } = await import("./register.subclis-
|
|
394
|
+
const { registerSubCliByName } = await import("./register.subclis-CrmcN8zA.js");
|
|
395
395
|
await registerSubCliByName(program, primary);
|
|
396
396
|
}
|
|
397
397
|
if (!shouldSkipPluginCommandRegistration({
|
|
@@ -400,7 +400,7 @@ async function runCli(argv = process$1.argv) {
|
|
|
400
400
|
hasBuiltinPrimary: primary !== null && program.commands.some((command) => command.name() === primary)
|
|
401
401
|
})) {
|
|
402
402
|
const { registerPluginCliCommands } = await import("./cli-QwFXIS1_.js");
|
|
403
|
-
const { loadValidatedConfigForPluginRegistration } = await import("./register.subclis-
|
|
403
|
+
const { loadValidatedConfigForPluginRegistration } = await import("./register.subclis-CrmcN8zA.js");
|
|
404
404
|
const config = await loadValidatedConfigForPluginRegistration();
|
|
405
405
|
if (config) {
|
|
406
406
|
await registerPluginCliCommands(program, config, void 0, void 0, {
|
|
@@ -103,10 +103,10 @@ async function setupQuickstartVoiceRuntime(params) {
|
|
|
103
103
|
await params.prompter.note([
|
|
104
104
|
"QuickStart will prepare terminal voice runtime now.",
|
|
105
105
|
"This installs wake-word Python dependencies into ~/.vora/voice-python.",
|
|
106
|
-
"STT/TTS use the VORA backend by default, so local Agora/
|
|
106
|
+
"STT/TTS use the VORA backend by default, so local Agora/ElevenLabs keys are not required."
|
|
107
107
|
].join("\n"), "Voice runtime");
|
|
108
108
|
try {
|
|
109
|
-
const { runVoiceSetup } = await import("./voice-cli-
|
|
109
|
+
const { runVoiceSetup } = await import("./voice-cli-BD1lHjLd.js");
|
|
110
110
|
await runVoiceSetup({}, params.runtime);
|
|
111
111
|
await params.prompter.note([
|
|
112
112
|
"Voice runtime is ready.",
|
|
@@ -464,7 +464,7 @@ async function runSetupWizard(opts, runtime = defaultRuntime, prompter) {
|
|
|
464
464
|
mode
|
|
465
465
|
});
|
|
466
466
|
await writeConfigFile(nextConfig);
|
|
467
|
-
const { finalizeSetupWizard } = await import("./setup.finalize-
|
|
467
|
+
const { finalizeSetupWizard } = await import("./setup.finalize-tiOrCMkC.js");
|
|
468
468
|
const { launchedTui } = await finalizeSetupWizard({
|
|
469
469
|
flow,
|
|
470
470
|
opts,
|
|
@@ -10,12 +10,12 @@ import { f as resolveControlUiLinks, g as waitForGatewayReachable, i as formatCo
|
|
|
10
10
|
import { r as resolveGatewayService, t as describeGatewayServiceRestart } from "./service-BnUY5mkU.js";
|
|
11
11
|
import { i as isSystemdUserServiceAvailable } from "./systemd-9Gclqlhk.js";
|
|
12
12
|
import { t as listConfiguredWebSearchProviders } from "./runtime-B0GlgEXr.js";
|
|
13
|
-
import { r as installCompletion } from "./completion-cli-
|
|
13
|
+
import { r as installCompletion } from "./completion-cli-CuDyNItM.js";
|
|
14
14
|
import { r as healthCommand } from "./health-DJqooRWK.js";
|
|
15
15
|
import { t as ensureControlUiAssetsBuilt } from "./control-ui-assets-DVSZ-yd-.js";
|
|
16
16
|
import { t as resolveSetupSecretInputString } from "./setup.secret-input-DeWdLao-.js";
|
|
17
17
|
import { t as formatHealthCheckFailure } from "./health-format-TyQPx9cW.js";
|
|
18
|
-
import { r as ensureCompletionCacheExists, t as checkShellCompletionStatus } from "./doctor-completion-
|
|
18
|
+
import { r as ensureCompletionCacheExists, t as checkShellCompletionStatus } from "./doctor-completion-1du2om2X.js";
|
|
19
19
|
import { t as runTui } from "./tui-Bay3TR6u.js";
|
|
20
20
|
import path from "node:path";
|
|
21
21
|
import os from "node:os";
|
|
@@ -335,7 +335,7 @@ async function finalizeSetupWizard(options) {
|
|
|
335
335
|
}] : [],
|
|
336
336
|
{
|
|
337
337
|
value: "tui",
|
|
338
|
-
label: "Hatch in TUI
|
|
338
|
+
label: "Hatch in TUI"
|
|
339
339
|
},
|
|
340
340
|
{
|
|
341
341
|
value: "web",
|
|
@@ -351,7 +351,7 @@ async function finalizeSetupWizard(options) {
|
|
|
351
351
|
if (hatchChoice === "voice") {
|
|
352
352
|
restoreTerminalState("pre-setup voice", { resumeStdinIfPaused: true });
|
|
353
353
|
try {
|
|
354
|
-
const { runVoiceLoop } = await import("./voice-cli-
|
|
354
|
+
const { runVoiceLoop } = await import("./voice-cli-BD1lHjLd.js");
|
|
355
355
|
await runVoiceLoop({
|
|
356
356
|
url: links.wsUrl,
|
|
357
357
|
token: settings.authMode === "token" ? settings.gatewayToken : void 0,
|
|
@@ -62,7 +62,7 @@ const SUB_CLI_DESCRIPTORS = [
|
|
|
62
62
|
},
|
|
63
63
|
{
|
|
64
64
|
name: "voice",
|
|
65
|
-
description: "Wake-word terminal voice loop (OpenWakeWord trigger + STT bridge + Gateway chat + optional
|
|
65
|
+
description: "Wake-word terminal voice loop (OpenWakeWord trigger + STT bridge + Gateway chat + optional ElevenLabs TTS)",
|
|
66
66
|
hasSubcommands: true
|
|
67
67
|
},
|
|
68
68
|
{
|
|
@@ -21,14 +21,14 @@ import { a as terminateStaleGatewayPids, i as renderRestartDiagnostics, s as wai
|
|
|
21
21
|
import { r as formatDurationPrecise } from "./format-duration-C7KLrjgc.js";
|
|
22
22
|
import { o as trimLogTail } from "./restart-sentinel-CjIY0IZ8.js";
|
|
23
23
|
import { t as formatHelpExamples } from "./help-format-DC7U6A-Y.js";
|
|
24
|
-
import { r as installCompletion } from "./completion-cli-
|
|
24
|
+
import { r as installCompletion } from "./completion-cli-CuDyNItM.js";
|
|
25
25
|
import { n as renderTable, t as getTerminalTableWidth } from "./table-CYPwb2Cg.js";
|
|
26
26
|
import { c as resolveEffectiveUpdateChannel, i as formatUpdateChannelLabel, l as resolveUpdateChannelDisplay, r as channelToNpmTag, s as normalizeUpdateChannel } from "./update-channels-C8-n6_kE.js";
|
|
27
27
|
import { i as fetchNpmTagVersion, n as compareSemverStrings, o as resolveNpmChannelTag, r as fetchNpmPackageTargetStatus, t as checkUpdateStatus } from "./update-check-Cw2Zo7Rn.js";
|
|
28
28
|
import { a as collectInstalledGlobalPackageErrors, c as detectGlobalInstallManagerForRoot, d as resolveGlobalInstallSpec, f as resolveGlobalPackageRoot, h as readPackageVersion, i as cleanupGlobalRenameDirs, l as globalInstallArgs, m as readPackageName, n as runGatewayUpdate, o as createGlobalInstallEnv, p as normalizePackageTagInput, r as canResolveRegistryVersionForPackageTarget, s as detectGlobalInstallManagerByPresence, u as resolveExpectedInstalledVersionFromSpec } from "./server-startup-matrix-migration-DL743qrx.js";
|
|
29
29
|
import { n as updateNpmInstalledPlugins, t as syncPluginsForUpdateChannel } from "./update-XayfF-ly.js";
|
|
30
|
-
import { n as doctorCommand, t as selectStyled } from "./prompt-select-styled-
|
|
31
|
-
import { r as ensureCompletionCacheExists, t as checkShellCompletionStatus } from "./doctor-completion-
|
|
30
|
+
import { n as doctorCommand, t as selectStyled } from "./prompt-select-styled-fmGo-EJl.js";
|
|
31
|
+
import { r as ensureCompletionCacheExists, t as checkShellCompletionStatus } from "./doctor-completion-1du2om2X.js";
|
|
32
32
|
import { i as resolveUpdateAvailability, n as formatUpdateOneLiner, t as formatUpdateAvailableHint } from "./status.update-209xudNW.js";
|
|
33
33
|
import path from "node:path";
|
|
34
34
|
import { spawn, spawnSync } from "node:child_process";
|
|
@@ -14,8 +14,10 @@ const DEFAULT_WAKE_MODEL_FILE = "hey_vora.onnx";
|
|
|
14
14
|
const DEFAULT_WAKE_THRESHOLD = .5;
|
|
15
15
|
const DEFAULT_WAIT_MS = 4e4;
|
|
16
16
|
const DEFAULT_STT_TIMEOUT_MS = 25e3;
|
|
17
|
-
const DEFAULT_STT_LANGUAGE = "vi-VN";
|
|
18
|
-
const
|
|
17
|
+
const DEFAULT_STT_LANGUAGE = "en-US,vi-VN";
|
|
18
|
+
const DEFAULT_ELEVENLABS_VOICE_ID = "JBFqnCBsd6RMkjVDRZzb";
|
|
19
|
+
const DEFAULT_ELEVENLABS_MODEL_ID = "eleven_multilingual_v2";
|
|
20
|
+
const DEFAULT_ELEVENLABS_OUTPUT_FORMAT = "mp3_44100_128";
|
|
19
21
|
const DEFAULT_VOICE_BACKEND_URL = "https://vora-ai-backend-uemj.onrender.com";
|
|
20
22
|
const DEFAULT_BACKEND_PROBE_TIMEOUT_MS = 6e4;
|
|
21
23
|
const DEFAULT_BACKEND_STT_PROBE_TIMEOUT_MS = 6e4;
|
|
@@ -40,7 +42,8 @@ function buildBundledAgoraBridgeCommand(backendUrl) {
|
|
|
40
42
|
"node",
|
|
41
43
|
quoteShell(scriptPath),
|
|
42
44
|
"--lang {lang}",
|
|
43
|
-
"--timeout-ms {timeout_ms}"
|
|
45
|
+
"--timeout-ms {timeout_ms}",
|
|
46
|
+
"--browser-mode managed"
|
|
44
47
|
];
|
|
45
48
|
if (backendUrl) parts.push("--backend-url", quoteShell(backendUrl));
|
|
46
49
|
return parts.join(" ");
|
|
@@ -93,10 +96,10 @@ function parseSttProvider(raw, agoraCommand, backendUrl) {
|
|
|
93
96
|
if (value === "manual" || value === "agora") return value;
|
|
94
97
|
return agoraCommand || backendUrl ? "agora" : "manual";
|
|
95
98
|
}
|
|
96
|
-
function parseTtsProvider(raw,
|
|
99
|
+
function parseTtsProvider(raw, apiKey, backendUrl) {
|
|
97
100
|
const value = raw?.trim().toLowerCase();
|
|
98
|
-
if (value === "none" || value === "
|
|
99
|
-
return
|
|
101
|
+
if (value === "none" || value === "elevenlabs") return value;
|
|
102
|
+
return apiKey || backendUrl ? "elevenlabs" : "none";
|
|
100
103
|
}
|
|
101
104
|
function detectPythonBinary(explicitPython) {
|
|
102
105
|
const venvPython = resolveVenvPythonBin();
|
|
@@ -213,8 +216,8 @@ function resolveVoiceRuntimeOptions(opts) {
|
|
|
213
216
|
const sttProvider = parseSttProvider(sttProviderRaw, explicitAgoraCommand, backendUrl);
|
|
214
217
|
const bundledAgoraCommand = sttProvider === "agora" ? buildBundledAgoraBridgeCommand(backendUrl) : void 0;
|
|
215
218
|
const agoraSttCommand = explicitAgoraCommand ?? bundledAgoraCommand;
|
|
216
|
-
const
|
|
217
|
-
const ttsProvider = parseTtsProvider(trimToUndefined(opts.ttsProvider) ?? trimToUndefined(process.env.VORA_VOICE_TTS_PROVIDER),
|
|
219
|
+
const elevenLabsApiKey = trimToUndefined(opts.elevenLabsApiKey) ?? trimToUndefined(process.env.VORA_ELEVENLABS_API_KEY) ?? trimToUndefined(process.env.ELEVENLABS_API_KEY);
|
|
220
|
+
const ttsProvider = parseTtsProvider(trimToUndefined(opts.ttsProvider) ?? trimToUndefined(process.env.VORA_VOICE_TTS_PROVIDER), elevenLabsApiKey, backendUrl);
|
|
218
221
|
return {
|
|
219
222
|
gateway: {
|
|
220
223
|
url: trimToUndefined(opts.url),
|
|
@@ -245,8 +248,10 @@ function resolveVoiceRuntimeOptions(opts) {
|
|
|
245
248
|
tts: {
|
|
246
249
|
provider: ttsProvider,
|
|
247
250
|
backendUrl,
|
|
248
|
-
|
|
249
|
-
|
|
251
|
+
elevenLabsApiKey,
|
|
252
|
+
elevenLabsVoiceId: trimToUndefined(opts.elevenLabsVoiceId) ?? trimToUndefined(process.env.VORA_ELEVENLABS_VOICE_ID) ?? trimToUndefined(process.env.ELEVENLABS_VOICE_ID) ?? DEFAULT_ELEVENLABS_VOICE_ID,
|
|
253
|
+
elevenLabsModelId: trimToUndefined(opts.elevenLabsModelId) ?? trimToUndefined(process.env.VORA_ELEVENLABS_MODEL_ID) ?? trimToUndefined(process.env.ELEVENLABS_MODEL_ID) ?? DEFAULT_ELEVENLABS_MODEL_ID,
|
|
254
|
+
elevenLabsOutputFormat: trimToUndefined(opts.elevenLabsOutputFormat) ?? trimToUndefined(process.env.VORA_ELEVENLABS_OUTPUT_FORMAT) ?? trimToUndefined(process.env.ELEVENLABS_OUTPUT_FORMAT) ?? DEFAULT_ELEVENLABS_OUTPUT_FORMAT
|
|
250
255
|
},
|
|
251
256
|
once: Boolean(opts.once)
|
|
252
257
|
};
|
|
@@ -469,26 +474,32 @@ async function runVoiceDoctor(opts) {
|
|
|
469
474
|
ok: true,
|
|
470
475
|
message: "manual (type transcript in terminal)"
|
|
471
476
|
});
|
|
472
|
-
if (resolved.tts.provider === "
|
|
477
|
+
if (resolved.tts.provider === "elevenlabs") {
|
|
473
478
|
if (resolved.tts.backendUrl) {
|
|
474
|
-
const
|
|
479
|
+
const elevenLabsReady = backendProviderStatus(backendHealth, "elevenlabs");
|
|
475
480
|
checks.push({
|
|
476
|
-
key: "
|
|
477
|
-
label: "backend
|
|
478
|
-
ok: backendHealthOk &&
|
|
479
|
-
message:
|
|
481
|
+
key: "backend_elevenlabs",
|
|
482
|
+
label: "backend ElevenLabs",
|
|
483
|
+
ok: backendHealthOk && elevenLabsReady === true,
|
|
484
|
+
message: elevenLabsReady === true ? "configured on backend" : elevenLabsReady === false ? "backend is missing ElevenLabs env" : "backend health does not expose ElevenLabs status"
|
|
480
485
|
});
|
|
481
486
|
} else checks.push({
|
|
482
|
-
key: "
|
|
483
|
-
label: "
|
|
484
|
-
ok: Boolean(resolved.tts.
|
|
485
|
-
message: resolved.tts.
|
|
487
|
+
key: "elevenlabs_api_key",
|
|
488
|
+
label: "ElevenLabs API key",
|
|
489
|
+
ok: Boolean(resolved.tts.elevenLabsApiKey),
|
|
490
|
+
message: resolved.tts.elevenLabsApiKey ? "configured" : "missing VORA_ELEVENLABS_API_KEY/ELEVENLABS_API_KEY"
|
|
486
491
|
});
|
|
487
492
|
checks.push({
|
|
488
|
-
key: "
|
|
489
|
-
label: "
|
|
490
|
-
ok: Boolean(resolved.tts.
|
|
491
|
-
message: resolved.tts.
|
|
493
|
+
key: "elevenlabs_voice_id",
|
|
494
|
+
label: "ElevenLabs voice id",
|
|
495
|
+
ok: Boolean(resolved.tts.elevenLabsVoiceId),
|
|
496
|
+
message: resolved.tts.elevenLabsVoiceId
|
|
497
|
+
});
|
|
498
|
+
checks.push({
|
|
499
|
+
key: "elevenlabs_model_id",
|
|
500
|
+
label: "ElevenLabs model id",
|
|
501
|
+
ok: Boolean(resolved.tts.elevenLabsModelId),
|
|
502
|
+
message: resolved.tts.elevenLabsModelId
|
|
492
503
|
});
|
|
493
504
|
} else checks.push({
|
|
494
505
|
key: "tts_provider",
|
|
@@ -947,37 +958,50 @@ async function waitForAssistantReply(params) {
|
|
|
947
958
|
}
|
|
948
959
|
return null;
|
|
949
960
|
}
|
|
950
|
-
async function
|
|
951
|
-
const
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
961
|
+
async function synthesizeElevenLabsAudio(params) {
|
|
962
|
+
const attempts = [];
|
|
963
|
+
if (params.backendUrl) attempts.push({
|
|
964
|
+
label: "backend",
|
|
965
|
+
run: () => fetch(`${params.backendUrl}/api/tts/elevenlabs`, {
|
|
966
|
+
method: "POST",
|
|
967
|
+
headers: { "Content-Type": "application/json" },
|
|
968
|
+
body: JSON.stringify({
|
|
969
|
+
text: params.text,
|
|
970
|
+
voiceId: params.voiceId,
|
|
971
|
+
modelId: params.modelId,
|
|
972
|
+
outputFormat: params.outputFormat
|
|
973
|
+
})
|
|
957
974
|
})
|
|
958
|
-
})
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
975
|
+
});
|
|
976
|
+
if (params.apiKey) attempts.push({
|
|
977
|
+
label: "direct",
|
|
978
|
+
run: () => fetch(`https://api.elevenlabs.io/v1/text-to-speech/${encodeURIComponent(params.voiceId)}?output_format=${encodeURIComponent(params.outputFormat)}`, {
|
|
979
|
+
method: "POST",
|
|
980
|
+
headers: {
|
|
981
|
+
Accept: "audio/mpeg",
|
|
982
|
+
"Content-Type": "application/json",
|
|
983
|
+
"xi-api-key": params.apiKey ?? ""
|
|
984
|
+
},
|
|
985
|
+
body: JSON.stringify({
|
|
966
986
|
text: params.text,
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
}],
|
|
970
|
-
format: { type: "mp3" }
|
|
987
|
+
model_id: params.modelId
|
|
988
|
+
})
|
|
971
989
|
})
|
|
972
990
|
});
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
991
|
+
let lastError;
|
|
992
|
+
for (const attempt of attempts) {
|
|
993
|
+
const response = await attempt.run();
|
|
994
|
+
if (!response.ok) {
|
|
995
|
+
const detail = await response.text().catch(() => "");
|
|
996
|
+
lastError = /* @__PURE__ */ new Error(`ElevenLabs TTS ${attempt.label} request failed (${response.status}): ${detail.slice(0, 280) || "empty response"}`);
|
|
997
|
+
continue;
|
|
998
|
+
}
|
|
999
|
+
const audioBytes = Buffer.from(await response.arrayBuffer());
|
|
1000
|
+
const filePath = path.join(os.tmpdir(), `vora-elevenlabs-${Date.now()}-${randomUUID().slice(0, 8)}.mp3`);
|
|
1001
|
+
await promises.writeFile(filePath, audioBytes);
|
|
1002
|
+
return filePath;
|
|
976
1003
|
}
|
|
977
|
-
|
|
978
|
-
const filePath = path.join(os.tmpdir(), `vora-hume-${Date.now()}-${randomUUID().slice(0, 8)}.mp3`);
|
|
979
|
-
await promises.writeFile(filePath, audioBytes);
|
|
980
|
-
return filePath;
|
|
1004
|
+
throw lastError ?? /* @__PURE__ */ new Error("ElevenLabs TTS unavailable: missing backend URL or API key");
|
|
981
1005
|
}
|
|
982
1006
|
function hasBinary(command) {
|
|
983
1007
|
return spawnSync(process.platform === "win32" ? "where" : "which", [command], {
|
|
@@ -990,18 +1014,56 @@ async function playAudioFile(filePath) {
|
|
|
990
1014
|
if (process.platform === "linux" && hasBinary("ffplay")) return (await runShellCommand(`ffplay -nodisp -autoexit -loglevel quiet "${filePath.replaceAll("\"", "\\\"")}"`, 12e4)).code === 0;
|
|
991
1015
|
return false;
|
|
992
1016
|
}
|
|
1017
|
+
function quotePowerShellLiteral(value) {
|
|
1018
|
+
return `'${value.replaceAll("'", "''")}'`;
|
|
1019
|
+
}
|
|
1020
|
+
function encodePowerShellCommand(command) {
|
|
1021
|
+
return Buffer.from(command, "utf16le").toString("base64");
|
|
1022
|
+
}
|
|
1023
|
+
async function speakWithSystemVoice(text) {
|
|
1024
|
+
const textPath = path.join(os.tmpdir(), `vora-system-tts-${Date.now()}-${randomUUID().slice(0, 8)}.txt`);
|
|
1025
|
+
await promises.writeFile(textPath, text, "utf8");
|
|
1026
|
+
try {
|
|
1027
|
+
if (process.platform === "darwin" && hasBinary("say")) return (await runShellCommand(`say -f ${quoteShell(textPath)}`, 12e4)).code === 0;
|
|
1028
|
+
if (process.platform === "win32" && hasBinary("powershell")) return (await runShellCommand([
|
|
1029
|
+
"powershell",
|
|
1030
|
+
"-NoProfile",
|
|
1031
|
+
"-ExecutionPolicy",
|
|
1032
|
+
"Bypass",
|
|
1033
|
+
"-EncodedCommand",
|
|
1034
|
+
encodePowerShellCommand([
|
|
1035
|
+
"Add-Type -AssemblyName System.Speech;",
|
|
1036
|
+
"$speaker = New-Object System.Speech.Synthesis.SpeechSynthesizer;",
|
|
1037
|
+
`$speaker.Speak((Get-Content -Raw -LiteralPath ${quotePowerShellLiteral(textPath)}));`
|
|
1038
|
+
].join(" "))
|
|
1039
|
+
].join(" "), 12e4)).code === 0;
|
|
1040
|
+
if (process.platform === "linux" && hasBinary("espeak")) return (await runShellCommand(`espeak -f ${quoteShell(textPath)}`, 12e4)).code === 0;
|
|
1041
|
+
return false;
|
|
1042
|
+
} finally {
|
|
1043
|
+
await promises.rm(textPath, { force: true }).catch(() => void 0);
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
993
1046
|
async function speakReply(resolved, text) {
|
|
994
|
-
if (resolved.tts.provider !== "
|
|
995
|
-
if (!resolved.tts.backendUrl && !resolved.tts.
|
|
996
|
-
defaultRuntime.error("[voice]
|
|
1047
|
+
if (resolved.tts.provider !== "elevenlabs") return;
|
|
1048
|
+
if (!resolved.tts.backendUrl && !resolved.tts.elevenLabsApiKey) {
|
|
1049
|
+
if (!await speakWithSystemVoice(text)) defaultRuntime.error("[voice] ElevenLabs TTS skipped: missing API key");
|
|
1050
|
+
return;
|
|
1051
|
+
}
|
|
1052
|
+
let audioPath;
|
|
1053
|
+
try {
|
|
1054
|
+
audioPath = await synthesizeElevenLabsAudio({
|
|
1055
|
+
text,
|
|
1056
|
+
apiKey: resolved.tts.elevenLabsApiKey,
|
|
1057
|
+
backendUrl: resolved.tts.backendUrl,
|
|
1058
|
+
voiceId: resolved.tts.elevenLabsVoiceId,
|
|
1059
|
+
modelId: resolved.tts.elevenLabsModelId,
|
|
1060
|
+
outputFormat: resolved.tts.elevenLabsOutputFormat
|
|
1061
|
+
});
|
|
1062
|
+
} catch (error) {
|
|
1063
|
+
defaultRuntime.error(`[voice] ElevenLabs TTS failed: ${String(error)}`);
|
|
1064
|
+
if (!await speakWithSystemVoice(text)) defaultRuntime.error("[voice] system TTS fallback unavailable");
|
|
997
1065
|
return;
|
|
998
1066
|
}
|
|
999
|
-
const audioPath = await synthesizeHumeAudio({
|
|
1000
|
-
text,
|
|
1001
|
-
apiKey: resolved.tts.humeApiKey,
|
|
1002
|
-
backendUrl: resolved.tts.backendUrl,
|
|
1003
|
-
voiceId: resolved.tts.humeVoiceId
|
|
1004
|
-
});
|
|
1005
1067
|
try {
|
|
1006
1068
|
if (!await playAudioFile(audioPath)) defaultRuntime.log(`[voice] audio generated: ${audioPath}`);
|
|
1007
1069
|
} finally {
|
|
@@ -1046,7 +1108,7 @@ async function runVoiceLoop(opts) {
|
|
|
1046
1108
|
const wakeDeps = checkWakePythonDependencies(resolved.wake.pythonBin);
|
|
1047
1109
|
if (!wakeDeps.ok) throw new Error(formatWakePythonDependencyError(resolved.wake.pythonBin, wakeDeps.message));
|
|
1048
1110
|
if (resolved.stt.provider === "agora" && !resolved.stt.agoraCommand) throw new Error("Agora STT provider needs a bridge command. Use --agora-stt-command or VORA_AGORA_STT_COMMAND.");
|
|
1049
|
-
if (resolved.tts.provider === "
|
|
1111
|
+
if (resolved.tts.provider === "elevenlabs" && !resolved.tts.backendUrl && !resolved.tts.elevenLabsApiKey) throw new Error("ElevenLabs TTS enabled but no backend/API key is configured. Set --backend-url, --elevenlabs-api-key, VORA_ELEVENLABS_API_KEY, or ELEVENLABS_API_KEY.");
|
|
1050
1112
|
const gatewayClient = await GatewayChatClient.connect({
|
|
1051
1113
|
url: resolved.gateway.url,
|
|
1052
1114
|
token: resolved.gateway.token,
|
|
@@ -1090,6 +1152,7 @@ async function runVoiceLoop(opts) {
|
|
|
1090
1152
|
(async () => {
|
|
1091
1153
|
if (busy || stopRequested) return;
|
|
1092
1154
|
busy = true;
|
|
1155
|
+
wakeWord.stop();
|
|
1093
1156
|
try {
|
|
1094
1157
|
defaultRuntime.log(`[voice] wake trigger model=${event.model} score=${event.score.toFixed(2)} latency=${(Math.max(0, Date.now() / 1e3 - event.sourceTimestampSec) * 1e3).toFixed(0)}ms`);
|
|
1095
1158
|
const transcript = (await transcribeSpeech(resolved)).trim();
|
|
@@ -1108,6 +1171,13 @@ async function runVoiceLoop(opts) {
|
|
|
1108
1171
|
defaultRuntime.error(`[voice] turn failed: ${String(err)}`);
|
|
1109
1172
|
} finally {
|
|
1110
1173
|
busy = false;
|
|
1174
|
+
if (!stopRequested) try {
|
|
1175
|
+
await wakeWord.start();
|
|
1176
|
+
defaultRuntime.log("[voice] wake word armed for next turn");
|
|
1177
|
+
} catch (err) {
|
|
1178
|
+
defaultRuntime.error(`[voice] failed to restart wake word: ${String(err)}`);
|
|
1179
|
+
requestStop();
|
|
1180
|
+
}
|
|
1111
1181
|
}
|
|
1112
1182
|
})();
|
|
1113
1183
|
});
|
|
@@ -1128,10 +1198,10 @@ async function runVoiceLoop(opts) {
|
|
|
1128
1198
|
}
|
|
1129
1199
|
}
|
|
1130
1200
|
function addVoiceOptions(cmd) {
|
|
1131
|
-
return cmd.option("--url <url>", "Gateway WebSocket URL").option("--token <token>", "Gateway token").option("--password <password>", "Gateway password").option("--session <key>", "Session key (default: \"main\")").option("--deliver", "Deliver assistant replies to linked channel routes", false).option("--message <text>", "Send an initial message before listening for wake word").option("--thinking <level>", "Thinking level override").option("--timeout-ms <ms>", "chat.send timeout override (ms)").option("--wait-ms <ms>", "Wait budget for assistant reply after send (ms)").option("--once", "Stop after one successful wake->reply turn", false).option("--wake-dir <path>", "Path to wake_word directory").option("--wake-model <path>", "Wake model path or file name").option("--wake-threshold <0..1>", "Wake word threshold").option("--python <bin>", "Python binary for wake word process").option("--stt-provider <manual|agora>", "STT mode (agora uses external bridge command)").option("--stt-lang <lang>", "STT language hint passed to bridge command").option("--stt-timeout-ms <ms>", "STT timeout (ms)").option("--agora-stt-command <cmd>", "External command for Agora STT bridge (supports {lang}, {timeout_ms}); default uses bundled
|
|
1201
|
+
return cmd.option("--url <url>", "Gateway WebSocket URL").option("--token <token>", "Gateway token").option("--password <password>", "Gateway password").option("--session <key>", "Session key (default: \"main\")").option("--deliver", "Deliver assistant replies to linked channel routes", false).option("--message <text>", "Send an initial message before listening for wake word").option("--thinking <level>", "Thinking level override").option("--timeout-ms <ms>", "chat.send timeout override (ms)").option("--wait-ms <ms>", "Wait budget for assistant reply after send (ms)").option("--once", "Stop after one successful wake->reply turn", false).option("--wake-dir <path>", "Path to wake_word directory").option("--wake-model <path>", "Wake model path or file name").option("--wake-threshold <0..1>", "Wake word threshold").option("--python <bin>", "Python binary for wake word process").option("--stt-provider <manual|agora>", "STT mode (agora uses external bridge command)").option("--stt-lang <lang>", "STT language hint passed to bridge command").option("--stt-timeout-ms <ms>", "STT timeout (ms)").option("--agora-stt-command <cmd>", "External command for Agora STT bridge (supports {lang}, {timeout_ms}); default uses bundled Agora capture bridge").option("--backend-url <url>", `Voice backend URL for provider secrets/tokens (default: ${DEFAULT_VOICE_BACKEND_URL}; use "off" for local env mode)`).option("--tts-provider <none|elevenlabs>", "Voice reply provider").option("--eleven-labs-api-key <key>", "ElevenLabs API key").option("--eleven-labs-voice-id <id>", "ElevenLabs voice ID").option("--eleven-labs-model-id <id>", "ElevenLabs model ID").option("--eleven-labs-output-format <format>", "ElevenLabs output format");
|
|
1132
1202
|
}
|
|
1133
1203
|
function registerVoiceCli(program) {
|
|
1134
|
-
const voice = addVoiceOptions(program.command("voice").description("Wake-word terminal voice loop (OpenWakeWord trigger + STT bridge + Gateway chat + optional
|
|
1204
|
+
const voice = addVoiceOptions(program.command("voice").description("Wake-word terminal voice loop (OpenWakeWord trigger + STT bridge + Gateway chat + optional ElevenLabs TTS)").addHelpText("after", () => `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/voice", "docs.vora.ai/cli/voice")}\n`));
|
|
1135
1205
|
voice.action(async (opts) => {
|
|
1136
1206
|
try {
|
|
1137
1207
|
await runVoiceLoop(opts);
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { spawn } from "node:child_process";
|
|
3
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
4
4
|
import { randomUUID } from "node:crypto";
|
|
5
5
|
import http from "node:http";
|
|
6
6
|
|
|
7
7
|
const DEFAULT_TIMEOUT_MS = 45_000;
|
|
8
8
|
const DEFAULT_API_BASE = "https://api.agora.io";
|
|
9
9
|
const DEFAULT_BACKEND_URL = "https://vora-ai-backend-uemj.onrender.com";
|
|
10
|
-
const DEFAULT_LANGUAGE = "vi-VN";
|
|
10
|
+
const DEFAULT_LANGUAGE = "en-US,vi-VN";
|
|
11
11
|
const DEFAULT_RTC_UID = "1002";
|
|
12
12
|
const DEFAULT_STT_AUDIO_UID = "111";
|
|
13
13
|
const DEFAULT_STT_TEXT_UID = "222";
|
|
@@ -15,16 +15,16 @@ const DEFAULT_STT_BOT_UID = DEFAULT_STT_AUDIO_UID;
|
|
|
15
15
|
|
|
16
16
|
function printUsage() {
|
|
17
17
|
const lines = [
|
|
18
|
-
|
|
18
|
+
"Agora STT bridge (RTC + Agora RTT capture) for `vora voice`",
|
|
19
19
|
"",
|
|
20
20
|
"Usage:",
|
|
21
21
|
" node scripts/agora-stt-bridge.mjs [options]",
|
|
22
22
|
"",
|
|
23
23
|
"Options:",
|
|
24
|
-
|
|
24
|
+
` --lang <code> STT language(s), comma-separated max 2 (default: ${DEFAULT_LANGUAGE})`,
|
|
25
25
|
" --timeout-ms <ms> Timeout waiting for final transcript",
|
|
26
26
|
" --channel <name> RTC channel name",
|
|
27
|
-
" --uid <uid>
|
|
27
|
+
" --uid <uid> Capture RTC UID (default: 1002)",
|
|
28
28
|
" --rtc-token <token> RTC token for browser + STT bot",
|
|
29
29
|
" --stt-audio-uid <uid> STT audio bot UID for Agora RTT service",
|
|
30
30
|
" --stt-text-uid <uid> STT text bot UID for Agora RTT data stream",
|
|
@@ -35,7 +35,9 @@ function printUsage() {
|
|
|
35
35
|
" --customer-key <key> Agora customer key (or VORA_AGORA_CUSTOMER_KEY)",
|
|
36
36
|
" --customer-secret <s> Agora customer secret (or VORA_AGORA_CUSTOMER_SECRET)",
|
|
37
37
|
" --port <port> Fixed local bridge port",
|
|
38
|
-
" --
|
|
38
|
+
" --browser-mode <mode> managed|external|none (default: managed)",
|
|
39
|
+
" --show-browser Show the managed browser window for debugging",
|
|
40
|
+
" --no-open Do not start the managed/external capture page",
|
|
39
41
|
" --help Show this help",
|
|
40
42
|
"",
|
|
41
43
|
"Environment fallbacks:",
|
|
@@ -44,6 +46,7 @@ function printUsage() {
|
|
|
44
46
|
" VORA_AGORA_CHANNEL, VORA_AGORA_UID, VORA_AGORA_RTC_TOKEN",
|
|
45
47
|
" VORA_AGORA_STT_AUDIO_UID, VORA_AGORA_STT_TEXT_UID, VORA_AGORA_STT_BOT_UID",
|
|
46
48
|
" VORA_AGORA_API_BASE, VORA_AGORA_STT_TIMEOUT_MS, VORA_AGORA_STT_LANG",
|
|
49
|
+
" VORA_AGORA_STT_BROWSER_MODE, VORA_AGORA_STT_SHOW_BROWSER",
|
|
47
50
|
];
|
|
48
51
|
process.stderr.write(`${lines.join("\n")}\n`);
|
|
49
52
|
}
|
|
@@ -68,6 +71,10 @@ function parseArgs(argv) {
|
|
|
68
71
|
out.open = true;
|
|
69
72
|
continue;
|
|
70
73
|
}
|
|
74
|
+
if (token === "--show-browser") {
|
|
75
|
+
out["show-browser"] = true;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
71
78
|
const eq = token.indexOf("=");
|
|
72
79
|
if (eq >= 0) {
|
|
73
80
|
const key = token.slice(2, eq);
|
|
@@ -150,6 +157,110 @@ function openUrl(url) {
|
|
|
150
157
|
}
|
|
151
158
|
}
|
|
152
159
|
|
|
160
|
+
function readBoolean(argValue, envName, fallback = false) {
|
|
161
|
+
if (typeof argValue === "boolean") {
|
|
162
|
+
return argValue;
|
|
163
|
+
}
|
|
164
|
+
const raw = typeof argValue === "string" ? argValue : process.env[envName];
|
|
165
|
+
if (!raw) {
|
|
166
|
+
return fallback;
|
|
167
|
+
}
|
|
168
|
+
const normalized = String(raw).trim().toLowerCase();
|
|
169
|
+
if (["1", "true", "yes", "on"].includes(normalized)) {
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
if (["0", "false", "no", "off"].includes(normalized)) {
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
return fallback;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function resolveBrowserMode(args) {
|
|
179
|
+
const raw = readText(args["browser-mode"], "VORA_AGORA_STT_BROWSER_MODE", "managed").toLowerCase();
|
|
180
|
+
if (raw === "managed" || raw === "external" || raw === "none") {
|
|
181
|
+
return raw;
|
|
182
|
+
}
|
|
183
|
+
return "managed";
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function browserExecutableCandidates() {
|
|
187
|
+
if (process.platform === "darwin") {
|
|
188
|
+
return [
|
|
189
|
+
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
190
|
+
"/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
|
|
191
|
+
"/Applications/Chromium.app/Contents/MacOS/Chromium",
|
|
192
|
+
];
|
|
193
|
+
}
|
|
194
|
+
if (process.platform === "win32") {
|
|
195
|
+
const roots = [
|
|
196
|
+
process.env.PROGRAMFILES,
|
|
197
|
+
process.env["PROGRAMFILES(X86)"],
|
|
198
|
+
process.env.LOCALAPPDATA,
|
|
199
|
+
].filter(Boolean);
|
|
200
|
+
return roots.flatMap((root) => [
|
|
201
|
+
`${root}\\Microsoft\\Edge\\Application\\msedge.exe`,
|
|
202
|
+
`${root}\\Google\\Chrome\\Application\\chrome.exe`,
|
|
203
|
+
]);
|
|
204
|
+
}
|
|
205
|
+
return [
|
|
206
|
+
"/usr/bin/google-chrome",
|
|
207
|
+
"/usr/bin/google-chrome-stable",
|
|
208
|
+
"/usr/bin/chromium",
|
|
209
|
+
"/usr/bin/chromium-browser",
|
|
210
|
+
"/snap/bin/chromium",
|
|
211
|
+
];
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async function openManagedBrowser(url, showBrowser) {
|
|
215
|
+
let chromium;
|
|
216
|
+
try {
|
|
217
|
+
({ chromium } = await import("playwright-core"));
|
|
218
|
+
} catch (error) {
|
|
219
|
+
throw new Error(`playwright-core is unavailable: ${String(error)}`);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const executablePath = browserExecutableCandidates().find((candidate) => {
|
|
223
|
+
try {
|
|
224
|
+
return Boolean(candidate) && typeof candidate === "string" && spawnSync(candidate, ["--version"], {
|
|
225
|
+
stdio: "ignore",
|
|
226
|
+
shell: process.platform === "win32",
|
|
227
|
+
}).status === 0;
|
|
228
|
+
} catch {
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
const launchOptions = {
|
|
234
|
+
headless: !showBrowser,
|
|
235
|
+
args: [
|
|
236
|
+
"--use-fake-ui-for-media-stream",
|
|
237
|
+
"--autoplay-policy=no-user-gesture-required",
|
|
238
|
+
"--no-first-run",
|
|
239
|
+
"--disable-background-timer-throttling",
|
|
240
|
+
],
|
|
241
|
+
};
|
|
242
|
+
if (executablePath) {
|
|
243
|
+
launchOptions.executablePath = executablePath;
|
|
244
|
+
} else if (process.platform === "win32") {
|
|
245
|
+
launchOptions.channel = "msedge";
|
|
246
|
+
} else {
|
|
247
|
+
launchOptions.channel = "chrome";
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const browser = await chromium.launch(launchOptions);
|
|
251
|
+
const context = await browser.newContext({
|
|
252
|
+
permissions: ["microphone"],
|
|
253
|
+
viewport: { width: 720, height: 520 },
|
|
254
|
+
});
|
|
255
|
+
const page = await context.newPage();
|
|
256
|
+
await page.goto(url, { waitUntil: "domcontentloaded" });
|
|
257
|
+
return {
|
|
258
|
+
close: async () => {
|
|
259
|
+
await browser.close().catch(() => undefined);
|
|
260
|
+
},
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
153
264
|
function jsonResponse(res, statusCode, payload) {
|
|
154
265
|
const body = JSON.stringify(payload);
|
|
155
266
|
res.writeHead(statusCode, {
|
|
@@ -437,7 +548,12 @@ function buildBrowserHtml(browserConfig) {
|
|
|
437
548
|
if (words.length === 0) {
|
|
438
549
|
return;
|
|
439
550
|
}
|
|
440
|
-
const text = words
|
|
551
|
+
const text = words
|
|
552
|
+
.map((word) => String(word?.text || "").trim())
|
|
553
|
+
.filter(Boolean)
|
|
554
|
+
.join(" ")
|
|
555
|
+
.replace(/\\s+([,.!?;:])/g, "$1")
|
|
556
|
+
.trim();
|
|
441
557
|
if (!text) {
|
|
442
558
|
return;
|
|
443
559
|
}
|
|
@@ -553,6 +669,15 @@ function buildBrowserHtml(browserConfig) {
|
|
|
553
669
|
</html>`;
|
|
554
670
|
}
|
|
555
671
|
|
|
672
|
+
function parseLanguages(lang) {
|
|
673
|
+
const languages = String(lang || DEFAULT_LANGUAGE)
|
|
674
|
+
.split(",")
|
|
675
|
+
.map((entry) => entry.trim())
|
|
676
|
+
.filter(Boolean)
|
|
677
|
+
.slice(0, 2);
|
|
678
|
+
return languages.length > 0 ? languages : [DEFAULT_LANGUAGE.split(",")[0]];
|
|
679
|
+
}
|
|
680
|
+
|
|
556
681
|
async function startAgoraStt(config, lang) {
|
|
557
682
|
if (config.backendUrl) {
|
|
558
683
|
const payload = await backendJsonRequest(config.backendUrl, "/api/agora/stt/start", {
|
|
@@ -573,7 +698,7 @@ async function startAgoraStt(config, lang) {
|
|
|
573
698
|
const auth = Buffer.from(`${config.customerKey}:${config.customerSecret}`).toString("base64");
|
|
574
699
|
const startPayload = {
|
|
575
700
|
name: `vora-voice-${Date.now()}`,
|
|
576
|
-
languages:
|
|
701
|
+
languages: parseLanguages(lang),
|
|
577
702
|
maxIdleTime: Math.max(10, Math.min(300, Math.ceil(config.timeoutMs / 1000))),
|
|
578
703
|
rtcConfig: {
|
|
579
704
|
channelName: config.channel,
|
|
@@ -677,6 +802,8 @@ async function main() {
|
|
|
677
802
|
lang: readText(args.lang, "VORA_AGORA_STT_LANG", DEFAULT_LANGUAGE),
|
|
678
803
|
timeoutMs: readPositiveInt(args["timeout-ms"], "VORA_AGORA_STT_TIMEOUT_MS", DEFAULT_TIMEOUT_MS),
|
|
679
804
|
open: args.open !== false,
|
|
805
|
+
browserMode: args.open === false ? "none" : resolveBrowserMode(args),
|
|
806
|
+
showBrowser: readBoolean(args["show-browser"], "VORA_AGORA_STT_SHOW_BROWSER", false),
|
|
680
807
|
port: readPositiveInt(args.port, "VORA_AGORA_STT_PORT", 0),
|
|
681
808
|
};
|
|
682
809
|
|
|
@@ -703,6 +830,16 @@ async function main() {
|
|
|
703
830
|
let server;
|
|
704
831
|
let done = false;
|
|
705
832
|
let currentAgentId = "";
|
|
833
|
+
let browserController = null;
|
|
834
|
+
|
|
835
|
+
const closeBrowser = async () => {
|
|
836
|
+
if (!browserController) {
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
const controller = browserController;
|
|
840
|
+
browserController = null;
|
|
841
|
+
await controller.close().catch(() => undefined);
|
|
842
|
+
};
|
|
706
843
|
|
|
707
844
|
const fail = async (message) => {
|
|
708
845
|
if (done) {
|
|
@@ -710,6 +847,7 @@ async function main() {
|
|
|
710
847
|
}
|
|
711
848
|
done = true;
|
|
712
849
|
await stopAgoraStt(resolvedConfig, currentAgentId).catch(() => undefined);
|
|
850
|
+
await closeBrowser();
|
|
713
851
|
if (server) {
|
|
714
852
|
await closeServer(server);
|
|
715
853
|
}
|
|
@@ -723,6 +861,7 @@ async function main() {
|
|
|
723
861
|
}
|
|
724
862
|
done = true;
|
|
725
863
|
await stopAgoraStt(resolvedConfig, currentAgentId).catch(() => undefined);
|
|
864
|
+
await closeBrowser();
|
|
726
865
|
if (server) {
|
|
727
866
|
await closeServer(server);
|
|
728
867
|
}
|
|
@@ -819,10 +958,27 @@ async function main() {
|
|
|
819
958
|
const localUrl = `http://127.0.0.1:${address.port}/`;
|
|
820
959
|
const source = resolvedConfig.backendUrl ? `backend=${safeBase(resolvedConfig.backendUrl)}` : "direct-agora";
|
|
821
960
|
process.stderr.write(`[agora-stt-bridge] channel=${resolvedConfig.channel} uid=${resolvedConfig.uid} ${source}\n`);
|
|
822
|
-
process.stderr.write(`[agora-stt-bridge]
|
|
961
|
+
process.stderr.write(`[agora-stt-bridge] local capture URL:\n${localUrl}\n`);
|
|
823
962
|
process.stderr.write("[agora-stt-bridge] waiting for one final transcript...\n");
|
|
824
|
-
if (config.
|
|
963
|
+
if (config.browserMode === "managed") {
|
|
964
|
+
try {
|
|
965
|
+
browserController = await openManagedBrowser(localUrl, config.showBrowser);
|
|
966
|
+
process.stderr.write(
|
|
967
|
+
`[agora-stt-bridge] managed browser capture started${config.showBrowser ? "" : " (hidden)"}\n`,
|
|
968
|
+
);
|
|
969
|
+
} catch (error) {
|
|
970
|
+
await fail(
|
|
971
|
+
[
|
|
972
|
+
`managed browser unavailable: ${String(error)}`,
|
|
973
|
+
"Install Google Chrome/Microsoft Edge, or run with --browser-mode external for manual debugging.",
|
|
974
|
+
].join("\n"),
|
|
975
|
+
);
|
|
976
|
+
return;
|
|
977
|
+
}
|
|
978
|
+
} else if (config.browserMode === "external") {
|
|
825
979
|
openUrl(localUrl);
|
|
980
|
+
} else {
|
|
981
|
+
process.stderr.write("[agora-stt-bridge] browser auto-open disabled; open the local capture URL manually.\n");
|
|
826
982
|
}
|
|
827
983
|
|
|
828
984
|
const timeout = setTimeout(() => {
|