@juspay/neurolink
Version:
The pipe layer of an AI nervous system: one interface connecting provider neurons to your application, across three inference types — generate, stream and decide. `decide` returns typed, calibrated judgements from a non-generative model (~400ms, ~$0.00002
102 lines • 4.51 kB
JavaScript
/**
* `neurolink voices` — list the TTS voices a provider offers.
*
* `--tts-voice` on `generate` takes a provider-specific identifier
* (`en-US-Neural2-C`, `alloy`, `en-US-JennyNeural`), and nothing in the CLI
* previously printed what those identifiers are. This is the discovery half
* of that flag.
*
* @module cli/commands/voices
*/
import chalk from "chalk";
import { TTSProcessor } from "../../utils/ttsProcessor.js";
import { logger } from "../../utils/logger.js";
export class VoicesCommandFactory {
static createVoicesCommand() {
return {
command: "voices",
describe: "List the text-to-speech voices a provider offers",
builder: (yargs) => yargs
.option("provider", {
type: "string",
demandOption: true,
description: "TTS provider to query (e.g. google-ai, openai-tts, elevenlabs)",
})
.option("language", {
type: "string",
description: "Only show voices for this language (e.g. en-US)",
})
.option("json", {
type: "boolean",
default: false,
description: "Emit the voice list as JSON",
})
.example("$0 voices --provider openai-tts", "List every OpenAI TTS voice")
.example("$0 voices --provider google-ai --language en-US", "List Google Cloud TTS voices for US English")
.example("$0 voices --provider elevenlabs --json", "Emit the voice list as JSON for scripting"),
handler: async (argv) => {
await VoicesCommandFactory.execute(argv);
},
};
}
/**
* Registration is credential-gated — `registerDefaultTTSHandlers()` skips
* any handler whose API key is absent — so an unconfigured provider is
* indistinguishable from a misspelled one at the registry. Naming what IS
* registered turns both into the same actionable message.
*/
static async execute(argv) {
const { ProviderRegistry } = await import("../../factories/providerRegistry.js");
await ProviderRegistry.registerAllProviders();
let voices;
try {
voices = await TTSProcessor.getVoices(argv.provider, {
...(argv.language !== undefined ? { languageCode: argv.language } : {}),
});
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
logger.always(chalk.red(`Could not list voices: ${message}`));
process.exitCode = 1;
return;
}
const sorted = [...voices].sort((a, b) =>
// Codepoint order, not localeCompare: collation depends on the Node ICU
// build, so the same list would sort differently on two machines.
a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
if (argv.json === true) {
logger.always(JSON.stringify(sorted, null, 2));
return;
}
if (sorted.length === 0) {
logger.always(chalk.yellow(argv.language !== undefined
? `No voices for language "${argv.language}" on provider "${argv.provider}".`
: `Provider "${argv.provider}" reported no voices.`));
return;
}
logger.always(VoicesCommandFactory.formatTable(sorted));
logger.always(chalk.gray(`\n${sorted.length} voice${sorted.length === 1 ? "" : "s"} for ${argv.provider}` +
(argv.language !== undefined ? ` (language: ${argv.language})` : "")));
}
static formatTable(voices) {
const headers = ["ID", "NAME", "LANGUAGE", "GENDER", "TYPE"];
const rows = voices.map((voice) => [
voice.id,
voice.name,
voice.languageCode,
voice.gender ?? "-",
voice.type ?? "-",
]);
const widths = headers.map((header, column) => rows.reduce((widest, row) => Math.max(widest, (row[column] ?? "").length), header.length));
const line = (cells, paint) => paint(cells
.map((cell, column) => cell.padEnd(widths[column] ?? 0))
.join(" ")
.trimEnd());
return [
line(headers, chalk.bold),
chalk.gray(widths.map((width) => "-".repeat(width)).join(" ")),
...rows.map((row) => line(row, (s) => s)),
].join("\n");
}
}
//# sourceMappingURL=voices.js.map