Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions src/commands/speech/synthesize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { CLIError } from '../../errors/base';
import { ExitCode } from '../../errors/codes';
import { request, requestJson } from '../../client/http';
import { speechEndpoint } from '../../client/endpoints';
import { parseSSE } from '../../client/stream';
import { detectOutputFormat, formatOutput } from '../../output/formatter';
import { saveAudioOutput } from '../../output/audio';
import { readTextFromPathOrStdin } from '../../utils/fs';
Expand Down Expand Up @@ -98,14 +99,14 @@ export default defineCommand({

if (flags.stream) {
const res = await request(config, { url, method: 'POST', body, stream: true });
const reader = res.body?.getReader();
if (!reader) throw new CLIError('No response body', ExitCode.GENERAL);
while (true) {
const { done, value } = await reader.read();
if (done) break;
process.stdout.write(value);
for await (const event of parseSSE(res)) {
if (!event.data || event.data === '[DONE]') break;
const parsed = JSON.parse(event.data);
const audioHex = parsed?.data?.audio;
if (audioHex) {
process.stdout.write(Buffer.from(audioHex, 'hex'));
}
}
reader.releaseLock();
return;
}

Expand Down
6 changes: 6 additions & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ process.on('SIGINT', () => {
process.exit(130);
});

// Handle stdout EPIPE gracefully (e.g., piped to `mpv` that exits early)
process.stdout.on('error', (e) => {
if (e.code === 'EPIPE') process.exit(0);
else throw e;
});

// Commands that manage their own auth or need no key
const NO_AUTH_SETUP = [
['auth', 'login'],
Expand Down