@eightstate/escli 0.5.0
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/CONVENTIONS.md +59 -0
- package/LICENSE +21 -0
- package/README.md +106 -0
- package/RELEASE-NOTES-0.5.0.md +34 -0
- package/dist/base-command.js +166 -0
- package/dist/commands/audio/get.js +39 -0
- package/dist/commands/audio/index.js +18 -0
- package/dist/commands/audio/list.js +39 -0
- package/dist/commands/audio/status.js +34 -0
- package/dist/commands/audio/transcribe.js +99 -0
- package/dist/commands/auth/index.js +18 -0
- package/dist/commands/auth/login.js +38 -0
- package/dist/commands/auth/logout.js +27 -0
- package/dist/commands/auth/profiles.js +31 -0
- package/dist/commands/auth/status.js +27 -0
- package/dist/commands/auth/switch.js +24 -0
- package/dist/commands/docs/fetch.js +37 -0
- package/dist/commands/docs/get.js +47 -0
- package/dist/commands/docs/index.js +18 -0
- package/dist/commands/docs/search.js +55 -0
- package/dist/commands/fetch.js +55 -0
- package/dist/commands/image/edit.js +59 -0
- package/dist/commands/image/generate.js +67 -0
- package/dist/commands/image/index.js +18 -0
- package/dist/commands/models.js +27 -0
- package/dist/commands/research.js +92 -0
- package/dist/commands/search.js +54 -0
- package/dist/commands/social.js +69 -0
- package/dist/commands/usage.js +51 -0
- package/dist/commands/version.js +22 -0
- package/dist/entry.js +120 -0
- package/dist/io/io.js +322 -0
- package/dist/lib/build-flags.js +2 -0
- package/dist/lib/command-metadata.js +8 -0
- package/dist/lib/envelope.js +28 -0
- package/dist/lib/escli-error.js +20 -0
- package/dist/lib/global-flags.js +29 -0
- package/dist/lib/globals.js +2 -0
- package/dist/lib/manifest.js +67 -0
- package/dist/lib/oclif-manifest-check.js +11 -0
- package/dist/lib/registry.js +228 -0
- package/dist/services/audio.js +454 -0
- package/dist/services/auth.js +329 -0
- package/dist/services/credentials.js +137 -0
- package/dist/services/docs.js +303 -0
- package/dist/services/fetch.js +197 -0
- package/dist/services/image.js +297 -0
- package/dist/services/models.js +131 -0
- package/dist/services/research.js +504 -0
- package/dist/services/search.js +195 -0
- package/dist/services/social.js +224 -0
- package/dist/services/usage.js +165 -0
- package/oclif.manifest.json +3377 -0
- package/package.json +57 -0
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
import { chmod, mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { ErrorCode } from '@eightstate/contracts/errors';
|
|
5
|
+
import { ExitCodes } from '@eightstate/contracts/exit-codes';
|
|
6
|
+
import { EscliError } from '../lib/escli-error.js';
|
|
7
|
+
import { callWithRetry, vendKeyForService } from './credentials.js';
|
|
8
|
+
const ASSEMBLYAI_BASE_URL = 'https://api.assemblyai.com';
|
|
9
|
+
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
10
|
+
const DEFAULT_UPLOAD_TIMEOUT_MS = 300_000;
|
|
11
|
+
const DEFAULT_POLL_INTERVAL_MS = 3_000;
|
|
12
|
+
const MAX_POLL_ATTEMPTS = 1_200;
|
|
13
|
+
export async function transcribeAudio(options) {
|
|
14
|
+
const started = Date.now();
|
|
15
|
+
const { audioUrl, credential } = isRemoteUrl(options.source)
|
|
16
|
+
? { audioUrl: options.source, credential: await resolveAssemblyAiCredential() }
|
|
17
|
+
: await uploadLocalAudio(options.source);
|
|
18
|
+
const request = await requestJson({
|
|
19
|
+
method: 'POST',
|
|
20
|
+
path: '/v2/transcript',
|
|
21
|
+
apiKey: credential.apiKey,
|
|
22
|
+
baseUrl: credential.baseUrl,
|
|
23
|
+
jsonBody: buildTranscriptBody(options, audioUrl),
|
|
24
|
+
});
|
|
25
|
+
const transcriptId = stringValue(request.id);
|
|
26
|
+
if (!transcriptId)
|
|
27
|
+
throw new EscliError('invalid AssemblyAI transcript response: missing id', { code: ErrorCode.GateInvalidResponse, details: request });
|
|
28
|
+
await rememberTranscriptCredential(transcriptId, credential);
|
|
29
|
+
const transcript = await pollTranscript(transcriptId, credential);
|
|
30
|
+
if (transcript.status === 'error') {
|
|
31
|
+
throw new EscliError(stringValue(transcript.error) ?? 'transcription failed', {
|
|
32
|
+
code: ErrorCode.AudioTranscriptionFailed,
|
|
33
|
+
details: { id: transcriptId, error: transcript.error },
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
if (options.format === 'text' && !options.output)
|
|
37
|
+
return summarizeTranscript(transcript, roundElapsed((Date.now() - started) / 1000));
|
|
38
|
+
const output = await formatTranscript(transcript, options.format, credential);
|
|
39
|
+
if (options.output) {
|
|
40
|
+
await writeOutputFile(options.output, output);
|
|
41
|
+
return { path: options.output, format: options.format, id: transcriptId };
|
|
42
|
+
}
|
|
43
|
+
if (options.format === 'json')
|
|
44
|
+
return transcript;
|
|
45
|
+
return { output, format: options.format, id: transcriptId };
|
|
46
|
+
}
|
|
47
|
+
export async function getAudioStatus(transcriptId) {
|
|
48
|
+
const { transcript } = await fetchTranscriptWithCredential(transcriptId);
|
|
49
|
+
return { id: transcriptId, status: stringValue(transcript.status) ?? 'unknown', error: transcript.error ?? null };
|
|
50
|
+
}
|
|
51
|
+
export async function getAudioTranscript(transcriptId, format, outputPath) {
|
|
52
|
+
const { transcript, credential } = await fetchTranscriptWithCredential(transcriptId);
|
|
53
|
+
if (transcript.status !== 'completed') {
|
|
54
|
+
throw new EscliError(stringValue(transcript.error) ?? `transcript not ready: ${stringValue(transcript.status) ?? 'unknown'}`, {
|
|
55
|
+
code: transcript.status === 'error' ? ErrorCode.AudioTranscriptionFailed : ErrorCode.AudioTranscriptNotFound,
|
|
56
|
+
details: { id: transcriptId, status: transcript.status, error: transcript.error },
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
const output = await formatTranscript(transcript, format, credential);
|
|
60
|
+
if (outputPath) {
|
|
61
|
+
await writeOutputFile(outputPath, output);
|
|
62
|
+
return { path: outputPath, format };
|
|
63
|
+
}
|
|
64
|
+
if (format === 'json')
|
|
65
|
+
return transcript;
|
|
66
|
+
return { output, format };
|
|
67
|
+
}
|
|
68
|
+
export async function listAudioTranscripts() {
|
|
69
|
+
const credential = await resolveAssemblyAiCredential();
|
|
70
|
+
const data = await requestJson({ method: 'GET', path: '/v2/transcript?limit=20', apiKey: credential.apiKey, baseUrl: credential.baseUrl });
|
|
71
|
+
const transcripts = arrayValue(data.transcripts)
|
|
72
|
+
.map((item) => recordValue(item))
|
|
73
|
+
.filter((item) => Boolean(item))
|
|
74
|
+
.map((item) => ({ ...item, id: String(item.id ?? '') }));
|
|
75
|
+
return { transcripts, count: transcripts.length };
|
|
76
|
+
}
|
|
77
|
+
export async function formatTranscript(transcript, format, credential) {
|
|
78
|
+
if (format === 'json')
|
|
79
|
+
return JSON.stringify(transcript, null, 2);
|
|
80
|
+
if (format === 'srt' || format === 'vtt') {
|
|
81
|
+
const id = stringValue(transcript.id);
|
|
82
|
+
if (!id)
|
|
83
|
+
throw new EscliError('invalid AssemblyAI transcript response: missing id', { code: ErrorCode.GateInvalidResponse, details: transcript });
|
|
84
|
+
const resolved = credential ?? await resolveAssemblyAiCredential();
|
|
85
|
+
return requestText({ method: 'GET', path: `/v2/transcript/${encodeURIComponent(id)}/${format}`, apiKey: resolved.apiKey, baseUrl: resolved.baseUrl, expectText: true });
|
|
86
|
+
}
|
|
87
|
+
return formatTranscriptText(transcript);
|
|
88
|
+
}
|
|
89
|
+
export function formatTranscriptText(data) {
|
|
90
|
+
const lines = [];
|
|
91
|
+
const utterances = Array.isArray(data.utterances) ? data.utterances : [];
|
|
92
|
+
if (utterances.length > 0) {
|
|
93
|
+
for (const utterance of utterances) {
|
|
94
|
+
const speaker = stringValue(utterance.speaker) ?? '?';
|
|
95
|
+
const text = stringValue(utterance.text) ?? '';
|
|
96
|
+
lines.push(`Speaker ${speaker}: ${text}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
else if (stringValue(data.text)) {
|
|
100
|
+
lines.push(stringValue(data.text) ?? '');
|
|
101
|
+
}
|
|
102
|
+
const chapters = arrayValue(data.chapters).map(recordValue).filter((item) => Boolean(item));
|
|
103
|
+
if (chapters.length > 0) {
|
|
104
|
+
lines.push('\n--- Chapters ---');
|
|
105
|
+
for (const chapter of chapters) {
|
|
106
|
+
lines.push(`\n## ${stringValue(chapter.headline) ?? ''}`);
|
|
107
|
+
lines.push(stringValue(chapter.summary) ?? '');
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (stringValue(data.summary))
|
|
111
|
+
lines.push(`\n--- Summary ---\n${stringValue(data.summary) ?? ''}`);
|
|
112
|
+
const sentiments = arrayValue(data.sentiment_analysis_results).map(recordValue).filter((item) => Boolean(item));
|
|
113
|
+
if (sentiments.length > 0) {
|
|
114
|
+
lines.push('\n--- Sentiment ---');
|
|
115
|
+
for (const sentiment of sentiments.slice(0, 20))
|
|
116
|
+
lines.push(` [${stringValue(sentiment.sentiment) ?? ''}] ${(stringValue(sentiment.text) ?? '').slice(0, 80)}`);
|
|
117
|
+
}
|
|
118
|
+
const entities = arrayValue(data.entities).map(recordValue).filter((item) => Boolean(item));
|
|
119
|
+
if (entities.length > 0) {
|
|
120
|
+
lines.push('\n--- Entities ---');
|
|
121
|
+
for (const entity of entities.slice(0, 20))
|
|
122
|
+
lines.push(` ${stringValue(entity.entity_type) ?? ''}: ${stringValue(entity.text) ?? ''}`);
|
|
123
|
+
}
|
|
124
|
+
return lines.join('\n');
|
|
125
|
+
}
|
|
126
|
+
function buildTranscriptBody(options, audioUrl) {
|
|
127
|
+
const body = {
|
|
128
|
+
audio_url: audioUrl,
|
|
129
|
+
speech_models: ['universal-3-pro', 'universal-2'],
|
|
130
|
+
};
|
|
131
|
+
if (options.language)
|
|
132
|
+
body.language_code = options.language;
|
|
133
|
+
else
|
|
134
|
+
body.language_detection = true;
|
|
135
|
+
if (options.speakers)
|
|
136
|
+
body.speaker_labels = true;
|
|
137
|
+
if (options.speakersExpected !== undefined) {
|
|
138
|
+
body.speaker_labels = true;
|
|
139
|
+
body.speakers_expected = options.speakersExpected;
|
|
140
|
+
}
|
|
141
|
+
if (options.speakerNames) {
|
|
142
|
+
body.speaker_labels = true;
|
|
143
|
+
body.speech_understanding = {
|
|
144
|
+
request: {
|
|
145
|
+
speaker_identification: {
|
|
146
|
+
speaker_type: 'name',
|
|
147
|
+
known_values: splitCommaList(options.speakerNames),
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
if (options.sentiment)
|
|
153
|
+
body.sentiment_analysis = true;
|
|
154
|
+
if (options.chapters)
|
|
155
|
+
body.auto_chapters = true;
|
|
156
|
+
if (options.entities)
|
|
157
|
+
body.entity_detection = true;
|
|
158
|
+
if (options.summarize) {
|
|
159
|
+
body.summarization = true;
|
|
160
|
+
body.summary_model = 'informative';
|
|
161
|
+
body.summary_type = 'bullets';
|
|
162
|
+
}
|
|
163
|
+
if (options.highlights)
|
|
164
|
+
body.auto_highlights = true;
|
|
165
|
+
if (options.topics)
|
|
166
|
+
body.iab_categories = true;
|
|
167
|
+
if (options.contentSafety)
|
|
168
|
+
body.content_safety = true;
|
|
169
|
+
if (options.dualChannel)
|
|
170
|
+
body.dual_channel = true;
|
|
171
|
+
if (options.multichannel)
|
|
172
|
+
body.multichannel = true;
|
|
173
|
+
if (options.wordBoost) {
|
|
174
|
+
body.word_boost = splitCommaList(options.wordBoost);
|
|
175
|
+
body.boost_param = 'high';
|
|
176
|
+
}
|
|
177
|
+
if (options.disfluencies)
|
|
178
|
+
body.disfluencies = true;
|
|
179
|
+
if (options.filterProfanity)
|
|
180
|
+
body.filter_profanity = true;
|
|
181
|
+
if (options.redactPii) {
|
|
182
|
+
body.redact_pii = true;
|
|
183
|
+
body.redact_pii_policies = ['email_address', 'phone_number', 'person_name', 'location', 'date_of_birth', 'credit_card_number'];
|
|
184
|
+
}
|
|
185
|
+
return body;
|
|
186
|
+
}
|
|
187
|
+
async function uploadLocalAudio(path) {
|
|
188
|
+
const content = await readLocalFile(path);
|
|
189
|
+
const explicitKey = process.env.ASSEMBLYAI_API_KEY;
|
|
190
|
+
if (explicitKey)
|
|
191
|
+
return { audioUrl: await uploadWithKey(content, explicitKey), credential: { apiKey: explicitKey } };
|
|
192
|
+
try {
|
|
193
|
+
const response = await callWithRetry('assemblyai', async (key) => ({ audioUrl: await uploadWithKey(content, key.apiKey, key.baseUrl), credential: fromVendedKey(key) }));
|
|
194
|
+
if (response)
|
|
195
|
+
return response;
|
|
196
|
+
}
|
|
197
|
+
catch (error) {
|
|
198
|
+
if (!(error instanceof Error && error.name === 'ServiceCallError'))
|
|
199
|
+
throw error;
|
|
200
|
+
throw new EscliError(error.message, { code: ErrorCode.AudioUploadFailed, details: error.message });
|
|
201
|
+
}
|
|
202
|
+
throw missingCredentialError();
|
|
203
|
+
}
|
|
204
|
+
async function uploadWithKey(content, apiKey, baseUrl) {
|
|
205
|
+
const response = await requestJson({ method: 'POST', path: '/v2/upload', apiKey, baseUrl, content, timeoutMs: DEFAULT_UPLOAD_TIMEOUT_MS });
|
|
206
|
+
const uploadUrl = stringValue(response.upload_url);
|
|
207
|
+
if (!uploadUrl)
|
|
208
|
+
throw new EscliError('invalid AssemblyAI upload response: missing upload_url', { code: ErrorCode.GateInvalidResponse, details: response });
|
|
209
|
+
return uploadUrl;
|
|
210
|
+
}
|
|
211
|
+
async function readLocalFile(path) {
|
|
212
|
+
try {
|
|
213
|
+
return await readFile(path);
|
|
214
|
+
}
|
|
215
|
+
catch (error) {
|
|
216
|
+
const nodeError = error;
|
|
217
|
+
if (nodeError.code === 'ENOENT')
|
|
218
|
+
throw new EscliError(`file not found: ${path}`, { code: ErrorCode.FileNotFound, details: { path } });
|
|
219
|
+
throw new EscliError(`failed to read file: ${path}`, { code: ErrorCode.FileReadFailed, details: error instanceof Error ? error.message : error });
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
async function resolveAssemblyAiCredential() {
|
|
223
|
+
if (process.env.ASSEMBLYAI_API_KEY)
|
|
224
|
+
return { apiKey: process.env.ASSEMBLYAI_API_KEY };
|
|
225
|
+
const key = await vendKeyForService('assemblyai');
|
|
226
|
+
if (key?.apiKey)
|
|
227
|
+
return fromVendedKey(key);
|
|
228
|
+
throw missingCredentialError();
|
|
229
|
+
}
|
|
230
|
+
async function fetchTranscriptWithCredential(transcriptId) {
|
|
231
|
+
if (process.env.ASSEMBLYAI_API_KEY) {
|
|
232
|
+
const credential = { apiKey: process.env.ASSEMBLYAI_API_KEY };
|
|
233
|
+
return { transcript: await requestTranscript(transcriptId, credential), credential };
|
|
234
|
+
}
|
|
235
|
+
const remembered = await readRememberedTranscriptCredential(transcriptId);
|
|
236
|
+
if (remembered)
|
|
237
|
+
return { transcript: await requestTranscript(transcriptId, remembered), credential: remembered };
|
|
238
|
+
let notFound;
|
|
239
|
+
for (let attempt = 0; attempt < 100; attempt += 1) {
|
|
240
|
+
const key = await vendKeyForService('assemblyai');
|
|
241
|
+
if (!key)
|
|
242
|
+
break;
|
|
243
|
+
const credential = fromVendedKey(key);
|
|
244
|
+
try {
|
|
245
|
+
return { transcript: await requestTranscript(transcriptId, credential), credential };
|
|
246
|
+
}
|
|
247
|
+
catch (error) {
|
|
248
|
+
if (error instanceof EscliError && error.code === ErrorCode.AudioTranscriptNotFound) {
|
|
249
|
+
notFound = error;
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
throw error;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
if (notFound)
|
|
256
|
+
throw notFound;
|
|
257
|
+
throw missingCredentialError();
|
|
258
|
+
}
|
|
259
|
+
function requestTranscript(transcriptId, credential) {
|
|
260
|
+
return requestJson({ method: 'GET', path: `/v2/transcript/${encodeURIComponent(transcriptId)}`, apiKey: credential.apiKey, baseUrl: credential.baseUrl });
|
|
261
|
+
}
|
|
262
|
+
async function rememberTranscriptCredential(transcriptId, credential) {
|
|
263
|
+
if (process.env.ASSEMBLYAI_API_KEY)
|
|
264
|
+
return;
|
|
265
|
+
const entries = await readRememberedTranscriptCredentials();
|
|
266
|
+
entries[transcriptId] = credential;
|
|
267
|
+
await mkdir(cacheDir(), { recursive: true });
|
|
268
|
+
await writeFile(transcriptCredentialCachePath(), `${JSON.stringify(entries, null, 2)}\n`);
|
|
269
|
+
await chmod(transcriptCredentialCachePath(), 0o600);
|
|
270
|
+
}
|
|
271
|
+
async function readRememberedTranscriptCredential(transcriptId) {
|
|
272
|
+
return readRememberedTranscriptCredentials().then((entries) => entries[transcriptId]);
|
|
273
|
+
}
|
|
274
|
+
async function readRememberedTranscriptCredentials() {
|
|
275
|
+
try {
|
|
276
|
+
const parsed = JSON.parse(await readFile(transcriptCredentialCachePath(), 'utf8'));
|
|
277
|
+
return recordValue(parsed) ?? {};
|
|
278
|
+
}
|
|
279
|
+
catch {
|
|
280
|
+
return {};
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
function transcriptCredentialCachePath() {
|
|
284
|
+
return join(cacheDir(), 'audio-transcripts.json');
|
|
285
|
+
}
|
|
286
|
+
function cacheDir() {
|
|
287
|
+
return process.env.ESCLI_CACHE_DIR ?? join(homedir(), '.escli', 'cache');
|
|
288
|
+
}
|
|
289
|
+
function fromVendedKey(key) {
|
|
290
|
+
return { apiKey: key.apiKey, baseUrl: key.baseUrl };
|
|
291
|
+
}
|
|
292
|
+
function missingCredentialError() {
|
|
293
|
+
return new EscliError('no AssemblyAI API key. Set ASSEMBLYAI_API_KEY or add one via the dashboard.', {
|
|
294
|
+
code: ErrorCode.AuthRequired,
|
|
295
|
+
exitCode: ExitCodes.Auth,
|
|
296
|
+
remediation: { hint: 'Set ASSEMBLYAI_API_KEY or authenticate with a profile that can vend assemblyai keys.' },
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
async function pollTranscript(transcriptId, credential) {
|
|
300
|
+
for (let attempt = 0; attempt < MAX_POLL_ATTEMPTS; attempt += 1) {
|
|
301
|
+
const data = await requestJson({ method: 'GET', path: `/v2/transcript/${encodeURIComponent(transcriptId)}`, apiKey: credential.apiKey, baseUrl: credential.baseUrl });
|
|
302
|
+
if (data.status === 'completed' || data.status === 'error')
|
|
303
|
+
return data;
|
|
304
|
+
await sleep(pollIntervalMs());
|
|
305
|
+
}
|
|
306
|
+
throw new EscliError('transcription polling timed out', { code: ErrorCode.NetworkTimeout, details: { id: transcriptId } });
|
|
307
|
+
}
|
|
308
|
+
async function requestJson(options) {
|
|
309
|
+
const text = await requestText(options);
|
|
310
|
+
try {
|
|
311
|
+
return JSON.parse(text);
|
|
312
|
+
}
|
|
313
|
+
catch (error) {
|
|
314
|
+
throw new EscliError(`invalid JSON response: ${error instanceof Error ? error.message : String(error)}`, {
|
|
315
|
+
code: ErrorCode.GateInvalidResponse,
|
|
316
|
+
details: text,
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
async function requestText(options) {
|
|
321
|
+
const controller = new AbortController();
|
|
322
|
+
const timeout = setTimeout(() => controller.abort(), options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
323
|
+
try {
|
|
324
|
+
const response = await fetch(`${assemblyAiBaseUrl(options.baseUrl)}${options.path}`, {
|
|
325
|
+
method: options.method,
|
|
326
|
+
headers: requestHeaders(options.apiKey, Boolean(options.jsonBody)),
|
|
327
|
+
body: options.jsonBody ? JSON.stringify(options.jsonBody) : options.content,
|
|
328
|
+
redirect: 'follow',
|
|
329
|
+
signal: controller.signal,
|
|
330
|
+
});
|
|
331
|
+
const text = await response.text();
|
|
332
|
+
if (!response.ok)
|
|
333
|
+
throw httpStatusError(response.status, formatHttpError(response.status, text), text);
|
|
334
|
+
return text;
|
|
335
|
+
}
|
|
336
|
+
catch (error) {
|
|
337
|
+
if (isHttpStatusError(error))
|
|
338
|
+
throw mapHttpStatusError(error);
|
|
339
|
+
throw mapNetworkError(error);
|
|
340
|
+
}
|
|
341
|
+
finally {
|
|
342
|
+
clearTimeout(timeout);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
function requestHeaders(apiKey, hasJsonBody) {
|
|
346
|
+
const headers = { Authorization: apiKey };
|
|
347
|
+
if (hasJsonBody)
|
|
348
|
+
headers['Content-Type'] = 'application/json';
|
|
349
|
+
return headers;
|
|
350
|
+
}
|
|
351
|
+
function httpStatusError(status, message, details) {
|
|
352
|
+
const error = new Error(message);
|
|
353
|
+
error.status = status;
|
|
354
|
+
error.details = details;
|
|
355
|
+
return error;
|
|
356
|
+
}
|
|
357
|
+
function isHttpStatusError(error) {
|
|
358
|
+
return error instanceof Error && typeof error.status === 'number';
|
|
359
|
+
}
|
|
360
|
+
function mapHttpStatusError(error) {
|
|
361
|
+
const status = error.status;
|
|
362
|
+
if (status === 401)
|
|
363
|
+
return new EscliError(error.message, { code: ErrorCode.ApiUnauthorized, details: error.details });
|
|
364
|
+
if (status === 403)
|
|
365
|
+
return new EscliError(error.message, { code: ErrorCode.ApiForbidden, details: error.details });
|
|
366
|
+
if (status === 404)
|
|
367
|
+
return new EscliError(error.message, { code: ErrorCode.AudioTranscriptNotFound, details: error.details });
|
|
368
|
+
if (status === 429)
|
|
369
|
+
return new EscliError(error.message, { code: ErrorCode.ApiRateLimited, details: error.details });
|
|
370
|
+
if (status >= 500)
|
|
371
|
+
return new EscliError(error.message, { code: ErrorCode.ServiceUnavailable, details: error.details });
|
|
372
|
+
return new EscliError(error.message, { code: ErrorCode.ApiError, details: error.details });
|
|
373
|
+
}
|
|
374
|
+
function mapNetworkError(error) {
|
|
375
|
+
const isAbort = error instanceof Error && error.name === 'AbortError';
|
|
376
|
+
return new EscliError(isAbort ? 'network error: request timed out' : `network error: ${error instanceof Error ? error.message : String(error)}`, {
|
|
377
|
+
code: isAbort ? ErrorCode.NetworkTimeout : ErrorCode.NetworkError,
|
|
378
|
+
details: error instanceof Error ? error.message : error,
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
async function writeOutputFile(path, content) {
|
|
382
|
+
const { writeFile } = await import('node:fs/promises');
|
|
383
|
+
try {
|
|
384
|
+
await writeFile(path, content);
|
|
385
|
+
}
|
|
386
|
+
catch (error) {
|
|
387
|
+
throw new EscliError(`failed to write file: ${path}`, { code: ErrorCode.FileWriteFailed, details: error instanceof Error ? error.message : error });
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
function summarizeTranscript(transcript, elapsedSeconds) {
|
|
391
|
+
const utterances = Array.isArray(transcript.utterances) ? transcript.utterances : [];
|
|
392
|
+
return {
|
|
393
|
+
id: transcript.id,
|
|
394
|
+
elapsed_seconds: elapsedSeconds,
|
|
395
|
+
text: stringValue(transcript.text) ?? '',
|
|
396
|
+
speakers: new Set(utterances.map((utterance) => stringValue(utterance.speaker) ?? '')).size,
|
|
397
|
+
words: Array.isArray(transcript.words) ? transcript.words : [],
|
|
398
|
+
utterances,
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
function formatHttpError(status, body) {
|
|
402
|
+
const parsed = parseMaybeJson(body);
|
|
403
|
+
const message = extractErrorMessage(parsed) ?? (typeof parsed === 'string' ? parsed : undefined);
|
|
404
|
+
return message ? `API error (${status}): ${message}` : `API error (${status})`;
|
|
405
|
+
}
|
|
406
|
+
function parseMaybeJson(value) {
|
|
407
|
+
try {
|
|
408
|
+
return JSON.parse(value);
|
|
409
|
+
}
|
|
410
|
+
catch {
|
|
411
|
+
return value;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
function extractErrorMessage(body) {
|
|
415
|
+
const root = recordValue(body);
|
|
416
|
+
return stringValue(root?.error) ?? stringValue(root?.message) ?? stringValue(recordValue(root?.error)?.message);
|
|
417
|
+
}
|
|
418
|
+
function assemblyAiBaseUrl(baseUrl) {
|
|
419
|
+
if (baseUrl)
|
|
420
|
+
return baseUrl.replace(/\/+$/u, '');
|
|
421
|
+
if ((typeof __ESCLI_TEST__ === 'undefined' || __ESCLI_TEST__) && process.env.ESCLI_TEST_ASSEMBLYAI_URL)
|
|
422
|
+
return process.env.ESCLI_TEST_ASSEMBLYAI_URL;
|
|
423
|
+
return ASSEMBLYAI_BASE_URL;
|
|
424
|
+
}
|
|
425
|
+
function pollIntervalMs() {
|
|
426
|
+
if (typeof __ESCLI_TEST__ === 'undefined' || __ESCLI_TEST__) {
|
|
427
|
+
const value = Number(process.env.ESCLI_AUDIO_POLL_INTERVAL_MS);
|
|
428
|
+
if (Number.isFinite(value) && value >= 0)
|
|
429
|
+
return Math.trunc(value);
|
|
430
|
+
}
|
|
431
|
+
return DEFAULT_POLL_INTERVAL_MS;
|
|
432
|
+
}
|
|
433
|
+
function isRemoteUrl(value) {
|
|
434
|
+
return value.startsWith('http://') || value.startsWith('https://');
|
|
435
|
+
}
|
|
436
|
+
function splitCommaList(value) {
|
|
437
|
+
return value.split(',').map((item) => item.trim()).filter(Boolean);
|
|
438
|
+
}
|
|
439
|
+
function sleep(ms) {
|
|
440
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
441
|
+
}
|
|
442
|
+
function roundElapsed(seconds) {
|
|
443
|
+
return Math.round(seconds * 10) / 10;
|
|
444
|
+
}
|
|
445
|
+
function arrayValue(value) {
|
|
446
|
+
return Array.isArray(value) ? value : [];
|
|
447
|
+
}
|
|
448
|
+
function recordValue(value) {
|
|
449
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : undefined;
|
|
450
|
+
}
|
|
451
|
+
function stringValue(value) {
|
|
452
|
+
return typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
453
|
+
}
|
|
454
|
+
//# sourceMappingURL=audio.js.map
|