auralwise_cli 1.0.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/bin/auralwise.js +35 -0
- package/lib/client.js +106 -0
- package/lib/commands/delete.js +34 -0
- package/lib/commands/events.js +58 -0
- package/lib/commands/result.js +31 -0
- package/lib/commands/task.js +26 -0
- package/lib/commands/tasks.js +45 -0
- package/lib/commands/transcribe.js +124 -0
- package/lib/i18n.js +124 -0
- package/lib/utils.js +130 -0
- package/package.json +46 -0
package/bin/auralwise.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import { setLocale, t } from '../lib/i18n.js';
|
|
5
|
+
import { registerTranscribe } from '../lib/commands/transcribe.js';
|
|
6
|
+
import { registerTasks } from '../lib/commands/tasks.js';
|
|
7
|
+
import { registerTask } from '../lib/commands/task.js';
|
|
8
|
+
import { registerResult } from '../lib/commands/result.js';
|
|
9
|
+
import { registerDelete } from '../lib/commands/delete.js';
|
|
10
|
+
import { registerEvents } from '../lib/commands/events.js';
|
|
11
|
+
|
|
12
|
+
const program = new Command();
|
|
13
|
+
|
|
14
|
+
// Parse --locale early before command registration so descriptions use correct language
|
|
15
|
+
const localeIdx = process.argv.indexOf('--locale');
|
|
16
|
+
if (localeIdx !== -1 && process.argv[localeIdx + 1]) {
|
|
17
|
+
setLocale(process.argv[localeIdx + 1]);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
program
|
|
21
|
+
.name('auralwise')
|
|
22
|
+
.description(t('descMain'))
|
|
23
|
+
.version('1.0.0')
|
|
24
|
+
.option('--api-key <key>', 'API key (or set AURALWISE_API_KEY env)')
|
|
25
|
+
.option('--base-url <url>', 'API base URL', 'https://auralwise.cn/api/v1')
|
|
26
|
+
.option('--locale <locale>', 'UI language: en or zh (default: en)');
|
|
27
|
+
|
|
28
|
+
registerTranscribe(program);
|
|
29
|
+
registerTasks(program);
|
|
30
|
+
registerTask(program);
|
|
31
|
+
registerResult(program);
|
|
32
|
+
registerDelete(program);
|
|
33
|
+
registerEvents(program);
|
|
34
|
+
|
|
35
|
+
program.parse();
|
package/lib/client.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
const DEFAULT_BASE_URL = 'https://auralwise.cn/api/v1';
|
|
2
|
+
|
|
3
|
+
export class AuralWiseClient {
|
|
4
|
+
constructor({ apiKey, baseUrl } = {}) {
|
|
5
|
+
this.apiKey = apiKey;
|
|
6
|
+
this.baseUrl = (baseUrl || DEFAULT_BASE_URL).replace(/\/+$/, '');
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
async _request(method, path, { body, query, headers: extraHeaders, timeout } = {}) {
|
|
10
|
+
let url = `${this.baseUrl}${path}`;
|
|
11
|
+
if (query) {
|
|
12
|
+
const params = new URLSearchParams();
|
|
13
|
+
for (const [k, v] of Object.entries(query)) {
|
|
14
|
+
if (v != null) params.set(k, String(v));
|
|
15
|
+
}
|
|
16
|
+
const qs = params.toString();
|
|
17
|
+
if (qs) url += `?${qs}`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const headers = { 'X-API-Key': this.apiKey, ...extraHeaders };
|
|
21
|
+
const fetchOpts = { method, headers };
|
|
22
|
+
|
|
23
|
+
if (body !== undefined) {
|
|
24
|
+
if (typeof body === 'string' || body instanceof Buffer || body instanceof ArrayBuffer) {
|
|
25
|
+
fetchOpts.body = body;
|
|
26
|
+
} else {
|
|
27
|
+
headers['Content-Type'] = 'application/json';
|
|
28
|
+
fetchOpts.body = JSON.stringify(body);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (timeout) {
|
|
33
|
+
fetchOpts.signal = AbortSignal.timeout(timeout);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const resp = await fetch(url, fetchOpts);
|
|
37
|
+
if (!resp.ok) {
|
|
38
|
+
let errMsg;
|
|
39
|
+
try {
|
|
40
|
+
const errBody = await resp.json();
|
|
41
|
+
errMsg = errBody.error || errBody.message || JSON.stringify(errBody);
|
|
42
|
+
} catch {
|
|
43
|
+
errMsg = await resp.text();
|
|
44
|
+
}
|
|
45
|
+
throw new Error(`API error ${resp.status}: ${errMsg}`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (resp.status === 204) return null;
|
|
49
|
+
|
|
50
|
+
const contentType = resp.headers.get('content-type') || '';
|
|
51
|
+
if (contentType.includes('application/json')) {
|
|
52
|
+
return resp.json();
|
|
53
|
+
}
|
|
54
|
+
return resp.text();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async createTask({ audioUrl, audioBase64, audioFilename, options, batchMode, priority, callbackUrl, callbackSecret }) {
|
|
58
|
+
const body = {};
|
|
59
|
+
if (audioUrl) {
|
|
60
|
+
body.audio_url = audioUrl;
|
|
61
|
+
} else if (audioBase64) {
|
|
62
|
+
body.audio_base64 = audioBase64;
|
|
63
|
+
}
|
|
64
|
+
if (audioFilename) body.audio_filename = audioFilename;
|
|
65
|
+
if (options) body.options = options;
|
|
66
|
+
if (batchMode) body.batch_mode = true;
|
|
67
|
+
if (priority != null) body.priority = priority;
|
|
68
|
+
if (callbackUrl) body.callback_url = callbackUrl;
|
|
69
|
+
if (callbackSecret) body.callback_secret = callbackSecret;
|
|
70
|
+
|
|
71
|
+
return this._request('POST', '/tasks', { body, timeout: 300000 });
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async listTasks({ page, pageSize, status } = {}) {
|
|
75
|
+
return this._request('GET', '/tasks', {
|
|
76
|
+
query: { page, page_size: pageSize, status },
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async getTask(taskId) {
|
|
81
|
+
return this._request('GET', `/tasks/${taskId}`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async getResult(taskId) {
|
|
85
|
+
return this._request('GET', `/tasks/${taskId}/result`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async deleteTask(taskId) {
|
|
89
|
+
return this._request('DELETE', `/tasks/${taskId}`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async getAudioEventClasses() {
|
|
93
|
+
return this._request('GET', '/audio-event-classes');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async pollTask(taskId, { interval = 5000, onStatus } = {}) {
|
|
97
|
+
while (true) {
|
|
98
|
+
const task = await this.getTask(taskId);
|
|
99
|
+
if (onStatus) onStatus(task);
|
|
100
|
+
if (task.status === 'done' || task.status === 'failed' || task.status === 'abandoned') {
|
|
101
|
+
return task;
|
|
102
|
+
}
|
|
103
|
+
await new Promise(r => setTimeout(r, interval));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { t } from '../i18n.js';
|
|
3
|
+
import { createClient } from '../utils.js';
|
|
4
|
+
|
|
5
|
+
export function registerDelete(program) {
|
|
6
|
+
program
|
|
7
|
+
.command('delete')
|
|
8
|
+
.description(t('descDelete'))
|
|
9
|
+
.argument('<id>', t('argTaskId'))
|
|
10
|
+
.option('--force', 'Skip confirmation')
|
|
11
|
+
.action(async (id, opts) => {
|
|
12
|
+
try {
|
|
13
|
+
if (!opts.force) {
|
|
14
|
+
const readline = await import('node:readline');
|
|
15
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
16
|
+
const answer = await new Promise(resolve => {
|
|
17
|
+
rl.question(t('confirmDelete', { id: id.substring(0, 8) }) + ' (y/N) ', resolve);
|
|
18
|
+
});
|
|
19
|
+
rl.close();
|
|
20
|
+
if (answer.toLowerCase() !== 'y') {
|
|
21
|
+
console.log(t('deleteCancelled'));
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const client = createClient(program);
|
|
27
|
+
await client.deleteTask(id);
|
|
28
|
+
console.log(chalk.green(t('taskDeleted', { id: id.substring(0, 8) })));
|
|
29
|
+
} catch (err) {
|
|
30
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { t, getLocale } from '../i18n.js';
|
|
3
|
+
import { createClient } from '../utils.js';
|
|
4
|
+
|
|
5
|
+
export function registerEvents(program) {
|
|
6
|
+
program
|
|
7
|
+
.command('events')
|
|
8
|
+
.description(t('descEvents'))
|
|
9
|
+
.option('--category <cat>', 'Filter by category')
|
|
10
|
+
.option('--search <keyword>', 'Search event name')
|
|
11
|
+
.option('--json', 'Output as JSON')
|
|
12
|
+
.action(async (opts) => {
|
|
13
|
+
try {
|
|
14
|
+
const client = createClient(program);
|
|
15
|
+
let classes = await client.getAudioEventClasses();
|
|
16
|
+
|
|
17
|
+
if (opts.category) {
|
|
18
|
+
const cat = opts.category.toLowerCase();
|
|
19
|
+
classes = classes.filter(c =>
|
|
20
|
+
(c.category || '').toLowerCase().includes(cat) ||
|
|
21
|
+
(c.category_zh || '').toLowerCase().includes(cat)
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (opts.search) {
|
|
26
|
+
const kw = opts.search.toLowerCase();
|
|
27
|
+
classes = classes.filter(c =>
|
|
28
|
+
(c.display_name || '').toLowerCase().includes(kw) ||
|
|
29
|
+
(c.zh_name || '').toLowerCase().includes(kw)
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (opts.json) {
|
|
34
|
+
console.log(JSON.stringify(classes, null, 2));
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (classes.length === 0) {
|
|
39
|
+
console.log(t('noEventsFound'));
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const isZh = getLocale() === 'zh';
|
|
44
|
+
console.log(chalk.bold(`${t('totalClasses')}: ${classes.length}`));
|
|
45
|
+
console.log();
|
|
46
|
+
console.log(chalk.bold('Index Name Category'));
|
|
47
|
+
console.log('─'.repeat(80));
|
|
48
|
+
for (const c of classes) {
|
|
49
|
+
const name = isZh ? (c.zh_name || c.display_name) : c.display_name;
|
|
50
|
+
const cat = isZh ? (c.category_zh || c.category) : c.category;
|
|
51
|
+
console.log(`${String(c.index).padStart(5)} ${name.padEnd(40)} ${cat}`);
|
|
52
|
+
}
|
|
53
|
+
} catch (err) {
|
|
54
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { writeFile } from 'node:fs/promises';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { t } from '../i18n.js';
|
|
4
|
+
import { createClient, printResult } from '../utils.js';
|
|
5
|
+
|
|
6
|
+
export function registerResult(program) {
|
|
7
|
+
program
|
|
8
|
+
.command('result')
|
|
9
|
+
.description(t('descResult'))
|
|
10
|
+
.argument('<id>', t('argTaskId'))
|
|
11
|
+
.option('--json', 'Output as JSON')
|
|
12
|
+
.option('--output <file>', 'Save result to file')
|
|
13
|
+
.action(async (id, opts) => {
|
|
14
|
+
try {
|
|
15
|
+
const client = createClient(program);
|
|
16
|
+
const result = await client.getResult(id);
|
|
17
|
+
|
|
18
|
+
if (opts.output) {
|
|
19
|
+
await writeFile(opts.output, JSON.stringify(result, null, 2));
|
|
20
|
+
console.log(chalk.green(t('resultSaved', { file: opts.output })));
|
|
21
|
+
} else if (opts.json) {
|
|
22
|
+
console.log(JSON.stringify(result, null, 2));
|
|
23
|
+
} else {
|
|
24
|
+
printResult(result);
|
|
25
|
+
}
|
|
26
|
+
} catch (err) {
|
|
27
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { t } from '../i18n.js';
|
|
3
|
+
import { createClient, printTaskDetail } from '../utils.js';
|
|
4
|
+
|
|
5
|
+
export function registerTask(program) {
|
|
6
|
+
program
|
|
7
|
+
.command('task')
|
|
8
|
+
.description(t('descTask'))
|
|
9
|
+
.argument('<id>', t('argTaskId'))
|
|
10
|
+
.option('--json', 'Output as JSON')
|
|
11
|
+
.action(async (id, opts) => {
|
|
12
|
+
try {
|
|
13
|
+
const client = createClient(program);
|
|
14
|
+
const task = await client.getTask(id);
|
|
15
|
+
|
|
16
|
+
if (opts.json) {
|
|
17
|
+
console.log(JSON.stringify(task, null, 2));
|
|
18
|
+
} else {
|
|
19
|
+
printTaskDetail(task);
|
|
20
|
+
}
|
|
21
|
+
} catch (err) {
|
|
22
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { t } from '../i18n.js';
|
|
3
|
+
import { createClient, formatTaskRow } from '../utils.js';
|
|
4
|
+
|
|
5
|
+
export function registerTasks(program) {
|
|
6
|
+
program
|
|
7
|
+
.command('tasks')
|
|
8
|
+
.description(t('descTasks'))
|
|
9
|
+
.option('--status <status>', 'Filter by status (pending/processing/done/failed/abandoned)')
|
|
10
|
+
.option('--page <n>', 'Page number', parseInt, 1)
|
|
11
|
+
.option('--page-size <n>', 'Items per page', parseInt, 20)
|
|
12
|
+
.option('--json', 'Output as JSON')
|
|
13
|
+
.action(async (opts) => {
|
|
14
|
+
try {
|
|
15
|
+
const client = createClient(program);
|
|
16
|
+
const data = await client.listTasks({
|
|
17
|
+
page: opts.page,
|
|
18
|
+
pageSize: opts.pageSize,
|
|
19
|
+
status: opts.status,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
if (opts.json) {
|
|
23
|
+
console.log(JSON.stringify(data, null, 2));
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const tasks = data.tasks || [];
|
|
28
|
+
if (tasks.length === 0) {
|
|
29
|
+
console.log(t('noTasks'));
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
console.log(chalk.bold('ID Status Filename Created'));
|
|
34
|
+
console.log('─'.repeat(100));
|
|
35
|
+
for (const task of tasks) {
|
|
36
|
+
console.log(formatTaskRow(task));
|
|
37
|
+
}
|
|
38
|
+
console.log('─'.repeat(100));
|
|
39
|
+
console.log(`${t('total')}: ${data.total || tasks.length} ${t('page')}: ${data.page || 1}/${Math.ceil((data.total || tasks.length) / (data.page_size || 20))}`);
|
|
40
|
+
} catch (err) {
|
|
41
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { basename } from 'node:path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import ora from 'ora';
|
|
5
|
+
import { t } from '../i18n.js';
|
|
6
|
+
import { createClient, printResult, formatStatus } from '../utils.js';
|
|
7
|
+
|
|
8
|
+
export function registerTranscribe(program) {
|
|
9
|
+
program
|
|
10
|
+
.command('transcribe')
|
|
11
|
+
.description(t('descTranscribe'))
|
|
12
|
+
.argument('<source>', t('argSource'))
|
|
13
|
+
.option('--base64', 'Force base64 mode for local file upload')
|
|
14
|
+
.option('--language <lang>', 'ASR language (zh/en/ja/... or null for auto)')
|
|
15
|
+
.option('--no-asr', 'Disable ASR')
|
|
16
|
+
.option('--no-diarize', 'Disable speaker diarization')
|
|
17
|
+
.option('--no-events', 'Disable audio event detection')
|
|
18
|
+
.option('--optimize-zh', 'Enable Chinese optimization mode')
|
|
19
|
+
.option('--beam-size <n>', 'Beam search width', parseInt)
|
|
20
|
+
.option('--temperature <n>', 'Decoding temperature', parseFloat)
|
|
21
|
+
.option('--hotwords <words>', 'Hotwords (comma-separated)')
|
|
22
|
+
.option('--initial-prompt <text>', 'Initial prompt text')
|
|
23
|
+
.option('--vad-threshold <n>', 'VAD threshold (0-1)', parseFloat)
|
|
24
|
+
.option('--vad-min-speech <ms>', 'Min speech duration in ms', parseInt)
|
|
25
|
+
.option('--vad-min-silence <ms>', 'Min silence duration in ms', parseInt)
|
|
26
|
+
.option('--no-speech-threshold <n>', 'No-speech probability threshold', parseFloat)
|
|
27
|
+
.option('--num-speakers <n>', 'Fixed number of speakers', parseInt)
|
|
28
|
+
.option('--min-speakers <n>', 'Minimum speakers for auto-detection', parseInt)
|
|
29
|
+
.option('--max-speakers <n>', 'Maximum speakers for auto-detection', parseInt)
|
|
30
|
+
.option('--events-threshold <n>', 'Audio event confidence threshold', parseFloat)
|
|
31
|
+
.option('--events-classes <classes>', 'Specific event classes (comma-separated)')
|
|
32
|
+
.option('--batch', 'Enable batch mode (lower priority)')
|
|
33
|
+
.option('--callback-url <url>', 'Webhook callback URL')
|
|
34
|
+
.option('--callback-secret <secret>', 'Webhook HMAC secret')
|
|
35
|
+
.option('--no-wait', 'Submit and return immediately without polling')
|
|
36
|
+
.option('--poll-interval <seconds>', 'Polling interval in seconds', parseFloat, 5)
|
|
37
|
+
.option('--json', 'Output as JSON')
|
|
38
|
+
.option('--output <file>', 'Save result to file')
|
|
39
|
+
.action(async (source, opts) => {
|
|
40
|
+
try {
|
|
41
|
+
const client = createClient(program);
|
|
42
|
+
const isUrl = /^https?:\/\//i.test(source);
|
|
43
|
+
|
|
44
|
+
// Build options
|
|
45
|
+
const options = {};
|
|
46
|
+
if (opts.asr === false) options.enable_asr = false;
|
|
47
|
+
if (opts.diarize === false) options.enable_diarize = false;
|
|
48
|
+
if (opts.events === false) options.enable_audio_events = false;
|
|
49
|
+
if (opts.optimizeZh) options.optimize_zh = true;
|
|
50
|
+
if (opts.language) options.asr_language = opts.language;
|
|
51
|
+
if (opts.beamSize != null) options.asr_beam_size = opts.beamSize;
|
|
52
|
+
if (opts.temperature != null) options.asr_temperature = opts.temperature;
|
|
53
|
+
if (opts.hotwords) options.hotwords = opts.hotwords.split(',').map(s => s.trim());
|
|
54
|
+
if (opts.initialPrompt) options.initial_prompt = opts.initialPrompt;
|
|
55
|
+
if (opts.vadThreshold != null) options.vad_threshold = opts.vadThreshold;
|
|
56
|
+
if (opts.vadMinSpeech != null) options.vad_min_speech_ms = opts.vadMinSpeech;
|
|
57
|
+
if (opts.vadMinSilence != null) options.vad_min_silence_ms = opts.vadMinSilence;
|
|
58
|
+
if (opts.noSpeechThreshold != null) options.no_speech_threshold = opts.noSpeechThreshold;
|
|
59
|
+
if (opts.numSpeakers != null) options.num_speakers = opts.numSpeakers;
|
|
60
|
+
if (opts.minSpeakers != null) options.min_speakers = opts.minSpeakers;
|
|
61
|
+
if (opts.maxSpeakers != null) options.max_speakers = opts.maxSpeakers;
|
|
62
|
+
if (opts.eventsThreshold != null) options.audio_events_threshold = opts.eventsThreshold;
|
|
63
|
+
if (opts.eventsClasses) options.audio_events_classes = opts.eventsClasses.split(',').map(s => s.trim());
|
|
64
|
+
|
|
65
|
+
const taskParams = {
|
|
66
|
+
options: Object.keys(options).length > 0 ? options : undefined,
|
|
67
|
+
batchMode: opts.batch || false,
|
|
68
|
+
callbackUrl: opts.callbackUrl,
|
|
69
|
+
callbackSecret: opts.callbackSecret,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
if (isUrl) {
|
|
73
|
+
taskParams.audioUrl = source;
|
|
74
|
+
} else {
|
|
75
|
+
const fileData = await readFile(source);
|
|
76
|
+
taskParams.audioBase64 = fileData.toString('base64');
|
|
77
|
+
taskParams.audioFilename = basename(source);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const spinner = ora(t('submittingTask')).start();
|
|
81
|
+
const task = await client.createTask(taskParams);
|
|
82
|
+
spinner.succeed(`${t('taskCreated')}: ${task.id}`);
|
|
83
|
+
|
|
84
|
+
if (opts.wait === false) {
|
|
85
|
+
if (opts.json) {
|
|
86
|
+
console.log(JSON.stringify(task, null, 2));
|
|
87
|
+
} else {
|
|
88
|
+
console.log(chalk.bold(`${t('status')}:`), formatStatus(task.status));
|
|
89
|
+
}
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const pollSpinner = ora(t('waitingTask')).start();
|
|
94
|
+
const completed = await client.pollTask(task.id, {
|
|
95
|
+
interval: (opts.pollInterval || 5) * 1000,
|
|
96
|
+
onStatus: (tsk) => {
|
|
97
|
+
const stage = tsk.current_stage ? ` (${tsk.current_stage})` : '';
|
|
98
|
+
const progress = tsk.progress != null ? ` ${tsk.progress}%` : '';
|
|
99
|
+
pollSpinner.text = `${tsk.status}${stage}${progress}`;
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
if (completed.status === 'done') {
|
|
104
|
+
pollSpinner.succeed(t('taskCompleted'));
|
|
105
|
+
const result = await client.getResult(task.id);
|
|
106
|
+
|
|
107
|
+
if (opts.output) {
|
|
108
|
+
await writeFile(opts.output, JSON.stringify(result, null, 2));
|
|
109
|
+
console.log(chalk.green(t('resultSaved', { file: opts.output })));
|
|
110
|
+
} else if (opts.json) {
|
|
111
|
+
console.log(JSON.stringify(result, null, 2));
|
|
112
|
+
} else {
|
|
113
|
+
printResult(result);
|
|
114
|
+
}
|
|
115
|
+
} else {
|
|
116
|
+
pollSpinner.fail(t('taskFailed', { status: completed.status, error: completed.error_message || 'unknown' }));
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}
|
|
119
|
+
} catch (err) {
|
|
120
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
}
|
package/lib/i18n.js
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
const messages = {
|
|
2
|
+
en: {
|
|
3
|
+
// General
|
|
4
|
+
apiKeyRequired: 'Error: API key required. Use --api-key or set AURALWISE_API_KEY environment variable.',
|
|
5
|
+
// Transcribe
|
|
6
|
+
submittingTask: 'Submitting task...',
|
|
7
|
+
taskCreated: 'Task created',
|
|
8
|
+
waitingTask: 'Waiting for task to complete...',
|
|
9
|
+
taskCompleted: 'Task completed!',
|
|
10
|
+
taskFailed: 'Task {status}: {error}',
|
|
11
|
+
resultSaved: 'Result saved to {file}',
|
|
12
|
+
// Result display
|
|
13
|
+
audioDuration: 'Audio Duration',
|
|
14
|
+
language: 'Language',
|
|
15
|
+
speakers: 'Speakers',
|
|
16
|
+
transcription: 'Transcription',
|
|
17
|
+
audioEvents: 'Audio Events',
|
|
18
|
+
speakerEmbeddings: 'Speaker Embeddings',
|
|
19
|
+
segments: 'segments',
|
|
20
|
+
dimVector: '-dim vector',
|
|
21
|
+
// Task detail
|
|
22
|
+
taskId: 'Task ID',
|
|
23
|
+
status: 'Status',
|
|
24
|
+
source: 'Source',
|
|
25
|
+
filename: 'Filename',
|
|
26
|
+
size: 'Size',
|
|
27
|
+
created: 'Created',
|
|
28
|
+
started: 'Started',
|
|
29
|
+
finished: 'Finished',
|
|
30
|
+
stage: 'Stage',
|
|
31
|
+
progress: 'Progress',
|
|
32
|
+
error: 'Error',
|
|
33
|
+
options: 'Options',
|
|
34
|
+
// Tasks list
|
|
35
|
+
noTasks: 'No tasks found.',
|
|
36
|
+
total: 'Total',
|
|
37
|
+
page: 'Page',
|
|
38
|
+
// Delete
|
|
39
|
+
taskDeleted: 'Task {id} deleted.',
|
|
40
|
+
confirmDelete: 'Are you sure you want to delete task {id}?',
|
|
41
|
+
deleteCancelled: 'Delete cancelled.',
|
|
42
|
+
// Events
|
|
43
|
+
totalClasses: 'Total event classes',
|
|
44
|
+
category: 'Category',
|
|
45
|
+
noEventsFound: 'No matching events found.',
|
|
46
|
+
// Descriptions
|
|
47
|
+
descMain: 'CLI for AuralWise audio intelligence API',
|
|
48
|
+
descTranscribe: 'Submit an audio transcription task (URL or local file)',
|
|
49
|
+
descTasks: 'List tasks',
|
|
50
|
+
descTask: 'Get task details',
|
|
51
|
+
descResult: 'Get task result',
|
|
52
|
+
descDelete: 'Delete a task',
|
|
53
|
+
descEvents: 'List audio event classes (521 classes)',
|
|
54
|
+
argSource: 'Audio URL or local file path',
|
|
55
|
+
argTaskId: 'Task ID',
|
|
56
|
+
},
|
|
57
|
+
zh: {
|
|
58
|
+
apiKeyRequired: '错误:需要 API Key。请使用 --api-key 参数或设置 AURALWISE_API_KEY 环境变量。',
|
|
59
|
+
submittingTask: '提交任务中...',
|
|
60
|
+
taskCreated: '任务已创建',
|
|
61
|
+
waitingTask: '等待任务完成...',
|
|
62
|
+
taskCompleted: '任务完成!',
|
|
63
|
+
taskFailed: '任务{status}:{error}',
|
|
64
|
+
resultSaved: '结果已保存到 {file}',
|
|
65
|
+
audioDuration: '音频时长',
|
|
66
|
+
language: '语言',
|
|
67
|
+
speakers: '说话人数',
|
|
68
|
+
transcription: '转写结果',
|
|
69
|
+
audioEvents: '声音事件',
|
|
70
|
+
speakerEmbeddings: '声纹向量',
|
|
71
|
+
segments: '段',
|
|
72
|
+
dimVector: '维向量',
|
|
73
|
+
taskId: '任务 ID',
|
|
74
|
+
status: '状态',
|
|
75
|
+
source: '来源',
|
|
76
|
+
filename: '文件名',
|
|
77
|
+
size: '大小',
|
|
78
|
+
created: '创建时间',
|
|
79
|
+
started: '开始时间',
|
|
80
|
+
finished: '完成时间',
|
|
81
|
+
stage: '阶段',
|
|
82
|
+
progress: '进度',
|
|
83
|
+
error: '错误',
|
|
84
|
+
options: '选项',
|
|
85
|
+
noTasks: '没有找到任务。',
|
|
86
|
+
total: '总计',
|
|
87
|
+
page: '页',
|
|
88
|
+
taskDeleted: '任务 {id} 已删除。',
|
|
89
|
+
confirmDelete: '确定要删除任务 {id} 吗?',
|
|
90
|
+
deleteCancelled: '已取消删除。',
|
|
91
|
+
totalClasses: '事件类别总数',
|
|
92
|
+
category: '类别',
|
|
93
|
+
noEventsFound: '未找到匹配的事件。',
|
|
94
|
+
descMain: 'AuralWise 语音智能 API 命令行工具',
|
|
95
|
+
descTranscribe: '提交音频转写任务(URL 或本地文件)',
|
|
96
|
+
descTasks: '列出任务',
|
|
97
|
+
descTask: '查看任务详情',
|
|
98
|
+
descResult: '获取任务结果',
|
|
99
|
+
descDelete: '删除任务',
|
|
100
|
+
descEvents: '列出声音事件类别(521 类)',
|
|
101
|
+
argSource: '音频 URL 或本地文件路径',
|
|
102
|
+
argTaskId: '任务 ID',
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
let currentLocale = 'en';
|
|
107
|
+
|
|
108
|
+
export function setLocale(locale) {
|
|
109
|
+
currentLocale = (locale === 'zh' || locale === 'cn') ? 'zh' : 'en';
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function getLocale() {
|
|
113
|
+
return currentLocale;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function t(key, params) {
|
|
117
|
+
let msg = messages[currentLocale]?.[key] || messages.en[key] || key;
|
|
118
|
+
if (params) {
|
|
119
|
+
for (const [k, v] of Object.entries(params)) {
|
|
120
|
+
msg = msg.replace(`{${k}}`, v);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return msg;
|
|
124
|
+
}
|
package/lib/utils.js
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { t } from './i18n.js';
|
|
3
|
+
import { AuralWiseClient } from './client.js';
|
|
4
|
+
|
|
5
|
+
export function formatTime(seconds) {
|
|
6
|
+
const h = Math.floor(seconds / 3600);
|
|
7
|
+
const m = Math.floor((seconds % 3600) / 60);
|
|
8
|
+
const s = (seconds % 60).toFixed(1);
|
|
9
|
+
if (h > 0) return `${h}:${String(m).padStart(2, '0')}:${s.padStart(4, '0')}`;
|
|
10
|
+
return `${m}:${s.padStart(4, '0')}`;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function formatDuration(seconds) {
|
|
14
|
+
if (seconds < 60) return `${seconds.toFixed(1)}s`;
|
|
15
|
+
if (seconds < 3600) return `${(seconds / 60).toFixed(1)}min`;
|
|
16
|
+
return `${(seconds / 3600).toFixed(1)}h`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function formatFileSize(bytes) {
|
|
20
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
21
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
22
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function formatStatus(status) {
|
|
26
|
+
const map = {
|
|
27
|
+
pending: chalk.yellow('pending'),
|
|
28
|
+
preprocessing: chalk.cyan('preprocessing'),
|
|
29
|
+
processing: chalk.blue('processing'),
|
|
30
|
+
proofreading: chalk.magenta('proofreading'),
|
|
31
|
+
done: chalk.green('done'),
|
|
32
|
+
failed: chalk.red('failed'),
|
|
33
|
+
abandoned: chalk.gray('abandoned'),
|
|
34
|
+
};
|
|
35
|
+
return map[status] || status;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function formatTaskRow(task) {
|
|
39
|
+
const id = task.id.substring(0, 8);
|
|
40
|
+
const status = formatStatus(task.status);
|
|
41
|
+
const filename = (task.audio_filename || '-').substring(0, 38);
|
|
42
|
+
const created = new Date(task.created_at).toLocaleString();
|
|
43
|
+
return `${id} ${status.padEnd(20)} ${filename.padEnd(40)} ${created}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function printResult(result) {
|
|
47
|
+
if (result.audio_duration != null) {
|
|
48
|
+
console.log(chalk.bold(`${t('audioDuration')}:`), formatDuration(result.audio_duration));
|
|
49
|
+
}
|
|
50
|
+
if (result.language) {
|
|
51
|
+
const prob = result.language_probability != null ? ` (${(result.language_probability * 100).toFixed(0)}%)` : '';
|
|
52
|
+
console.log(chalk.bold(`${t('language')}:`), result.language + prob);
|
|
53
|
+
}
|
|
54
|
+
if (result.num_speakers != null) {
|
|
55
|
+
console.log(chalk.bold(`${t('speakers')}:`), result.num_speakers);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (result.segments && result.segments.length > 0) {
|
|
59
|
+
console.log();
|
|
60
|
+
console.log(chalk.bold.underline(t('transcription')));
|
|
61
|
+
console.log();
|
|
62
|
+
for (const seg of result.segments) {
|
|
63
|
+
const time = `[${formatTime(seg.start)} - ${formatTime(seg.end)}]`;
|
|
64
|
+
const speaker = seg.speaker ? chalk.cyan(`${seg.speaker}: `) : '';
|
|
65
|
+
console.log(`${chalk.gray(time)} ${speaker}${seg.text}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (result.audio_events && result.audio_events.length > 0) {
|
|
70
|
+
console.log();
|
|
71
|
+
console.log(chalk.bold.underline(t('audioEvents')));
|
|
72
|
+
console.log();
|
|
73
|
+
for (const ev of result.audio_events) {
|
|
74
|
+
const time = `[${formatTime(ev.start)} - ${formatTime(ev.end)}]`;
|
|
75
|
+
const conf = `${(ev.confidence * 100).toFixed(0)}%`;
|
|
76
|
+
console.log(`${chalk.gray(time)} ${ev.class} ${chalk.dim(`(${conf})`)}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (result.speaker_embeddings && result.speaker_embeddings.length > 0) {
|
|
81
|
+
console.log();
|
|
82
|
+
console.log(chalk.bold.underline(t('speakerEmbeddings')));
|
|
83
|
+
console.log();
|
|
84
|
+
for (const spk of result.speaker_embeddings) {
|
|
85
|
+
console.log(` ${chalk.cyan(spk.speaker_id)}: ${spk.segment_count} ${t('segments')}, ${spk.embedding.length}${t('dimVector')}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function printTaskDetail(task) {
|
|
91
|
+
console.log(chalk.bold(`${t('taskId')}:`), task.id);
|
|
92
|
+
console.log(chalk.bold(`${t('status')}:`), formatStatus(task.status));
|
|
93
|
+
console.log(chalk.bold(`${t('source')}:`), task.audio_source_type || '-');
|
|
94
|
+
console.log(chalk.bold(`${t('filename')}:`), task.audio_filename || '-');
|
|
95
|
+
if (task.audio_size) console.log(chalk.bold(`${t('size')}:`), formatFileSize(task.audio_size));
|
|
96
|
+
console.log(chalk.bold(`${t('created')}:`), new Date(task.created_at).toLocaleString());
|
|
97
|
+
if (task.started_at) console.log(chalk.bold(`${t('started')}:`), new Date(task.started_at).toLocaleString());
|
|
98
|
+
if (task.finished_at) console.log(chalk.bold(`${t('finished')}:`), new Date(task.finished_at).toLocaleString());
|
|
99
|
+
if (task.current_stage) console.log(chalk.bold(`${t('stage')}:`), task.current_stage);
|
|
100
|
+
if (task.progress != null) console.log(chalk.bold(`${t('progress')}:`), `${task.progress}%`);
|
|
101
|
+
if (task.error_message) console.log(chalk.bold.red(`${t('error')}:`), task.error_message);
|
|
102
|
+
|
|
103
|
+
if (task.options) {
|
|
104
|
+
console.log(chalk.bold(`${t('options')}:`));
|
|
105
|
+
const opts = task.options;
|
|
106
|
+
const lines = [];
|
|
107
|
+
if (opts.enable_asr != null) lines.push(` ASR: ${opts.enable_asr}`);
|
|
108
|
+
if (opts.enable_diarize != null) lines.push(` Diarize: ${opts.enable_diarize}`);
|
|
109
|
+
if (opts.enable_audio_events != null) lines.push(` Audio Events: ${opts.enable_audio_events}`);
|
|
110
|
+
if (opts.asr_language) lines.push(` Language: ${opts.asr_language}`);
|
|
111
|
+
if (opts.optimize_zh) lines.push(` Optimize ZH: ${opts.optimize_zh}`);
|
|
112
|
+
if (lines.length > 0) console.log(lines.join('\n'));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function resolveApiKey(cmdOpts) {
|
|
117
|
+
const key = cmdOpts?.apiKey || process.env.AURALWISE_API_KEY;
|
|
118
|
+
if (!key) {
|
|
119
|
+
console.error(chalk.red(t('apiKeyRequired')));
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
return key;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function createClient(program) {
|
|
126
|
+
const opts = program.opts();
|
|
127
|
+
const apiKey = resolveApiKey(opts);
|
|
128
|
+
const baseUrl = opts.baseUrl;
|
|
129
|
+
return new AuralWiseClient({ apiKey, baseUrl });
|
|
130
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "auralwise_cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "CLI for AuralWise audio intelligence API - transcription, speaker diarization, audio event detection",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"auralwise": "./bin/auralwise.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin/",
|
|
11
|
+
"lib/"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"test": "vitest run",
|
|
15
|
+
"test:watch": "vitest"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"asr",
|
|
19
|
+
"speech-to-text",
|
|
20
|
+
"transcription",
|
|
21
|
+
"speaker-diarization",
|
|
22
|
+
"audio-events",
|
|
23
|
+
"auralwise",
|
|
24
|
+
"cli",
|
|
25
|
+
"audio",
|
|
26
|
+
"voice"
|
|
27
|
+
],
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "https://github.com/wavpub/auralwise_cli"
|
|
32
|
+
},
|
|
33
|
+
"homepage": "https://auralwise.cn",
|
|
34
|
+
"author": "WavPub",
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=18.0.0"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"chalk": "^5.3.0",
|
|
40
|
+
"commander": "^12.1.0",
|
|
41
|
+
"ora": "^8.1.0"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"vitest": "^2.1.0"
|
|
45
|
+
}
|
|
46
|
+
}
|