@laitszkin/apollo-toolkit 4.1.4 → 5.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/CHANGELOG.md +37 -0
- package/bin/apollo-toolkit.ts +4 -0
- package/dist/bin/apollo-toolkit.js +4 -0
- package/package.json +4 -2
- package/packages/cli/dist/help-text-builder.d.ts +23 -0
- package/packages/cli/dist/help-text-builder.js +166 -0
- package/packages/cli/dist/index.d.ts +6 -17
- package/packages/cli/dist/index.js +52 -246
- package/packages/cli/dist/installer.d.ts +1 -0
- package/packages/cli/dist/installer.js +20 -7
- package/packages/cli/dist/parsers/install-parser.d.ts +15 -0
- package/packages/cli/dist/parsers/install-parser.js +87 -0
- package/packages/cli/dist/parsers/parser-utils.d.ts +9 -0
- package/packages/cli/dist/parsers/parser-utils.js +16 -0
- package/packages/cli/dist/parsers/tool-parser.d.ts +16 -0
- package/packages/cli/dist/parsers/tool-parser.js +58 -0
- package/packages/cli/dist/parsers/types.d.ts +50 -0
- package/packages/cli/dist/parsers/types.js +1 -0
- package/packages/cli/dist/parsers/uninstall-parser.d.ts +15 -0
- package/packages/cli/dist/parsers/uninstall-parser.js +67 -0
- package/packages/cli/dist/tool-registration.d.ts +2 -0
- package/packages/cli/dist/tool-registration.js +2 -0
- package/packages/cli/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/cli/dist/types.d.ts +3 -1
- package/packages/cli/dist/updater.js +11 -5
- package/packages/cli/help-text-builder.ts +180 -0
- package/packages/cli/index.ts +59 -251
- package/packages/cli/installer.ts +19 -7
- package/packages/cli/package.json +6 -3
- package/packages/cli/parsers/install-parser.ts +94 -0
- package/packages/cli/parsers/parser-utils.ts +17 -0
- package/packages/cli/parsers/tool-parser.ts +65 -0
- package/packages/cli/parsers/types.ts +56 -0
- package/packages/cli/parsers/uninstall-parser.ts +75 -0
- package/packages/cli/tool-registration.ts +3 -0
- package/packages/cli/types.ts +6 -1
- package/packages/cli/updater.ts +11 -5
- package/packages/tool-registry/dist/registry.js +3 -4
- package/packages/tool-registry/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/tool-registry/dist/types.d.ts +2 -9
- package/packages/tool-registry/package.json +3 -3
- package/packages/tool-registry/registry.ts +3 -4
- package/packages/tool-registry/tsconfig.json +6 -2
- package/packages/tool-registry/types.ts +3 -9
- package/packages/tool-utils/app-error.ts +97 -0
- package/packages/tool-utils/dist/app-error.d.ts +49 -0
- package/packages/tool-utils/dist/app-error.js +80 -0
- package/packages/tool-utils/dist/index.d.ts +5 -0
- package/packages/tool-utils/dist/index.js +3 -0
- package/packages/tool-utils/dist/platform-adapter.d.ts +48 -0
- package/packages/tool-utils/dist/platform-adapter.js +73 -0
- package/packages/tool-utils/dist/schema.d.ts +68 -0
- package/packages/tool-utils/dist/schema.js +67 -0
- package/packages/tool-utils/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/tool-utils/index.ts +12 -0
- package/packages/tool-utils/package.json +3 -3
- package/packages/tool-utils/platform-adapter.ts +112 -0
- package/packages/tool-utils/schema.ts +122 -0
- package/packages/tools/architecture/dist/index.d.ts +13 -0
- package/packages/tools/architecture/dist/index.js +55 -57
- package/packages/tools/architecture/dist/index.test.js +17 -4
- package/packages/tools/architecture/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/tools/architecture/index.test.ts +27 -14
- package/packages/tools/architecture/index.ts +85 -88
- package/packages/tools/architecture/package.json +3 -3
- package/packages/tools/codegraph/dist/index.js +12 -22
- package/packages/tools/codegraph/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/tools/codegraph/index.ts +13 -22
- package/packages/tools/codegraph/package.json +3 -3
- package/packages/tools/create-review-report/dist/index.d.ts +1 -2
- package/packages/tools/create-review-report/dist/index.js +46 -77
- package/packages/tools/create-review-report/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/tools/create-review-report/index.ts +52 -81
- package/packages/tools/create-review-report/package.json +3 -3
- package/packages/tools/create-specs/dist/index.d.ts +1 -2
- package/packages/tools/create-specs/dist/index.js +70 -123
- package/packages/tools/create-specs/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/tools/create-specs/index.ts +82 -128
- package/packages/tools/create-specs/package.json +3 -3
- package/packages/tools/docs-to-voice/dist/index.d.ts +1 -2
- package/packages/tools/docs-to-voice/dist/index.js +116 -219
- package/packages/tools/docs-to-voice/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/tools/docs-to-voice/index.ts +265 -385
- package/packages/tools/docs-to-voice/package.json +3 -3
- package/packages/tools/enforce-video-aspect-ratio/dist/index.d.ts +1 -2
- package/packages/tools/enforce-video-aspect-ratio/dist/index.js +77 -154
- package/packages/tools/enforce-video-aspect-ratio/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/tools/enforce-video-aspect-ratio/index.ts +87 -172
- package/packages/tools/enforce-video-aspect-ratio/package.json +3 -3
- package/packages/tools/eval/dist/index.js +7 -0
- package/packages/tools/eval/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/tools/eval/index.ts +8 -0
- package/packages/tools/eval/package.json +3 -3
- package/packages/tools/extract-conversations/dist/index.d.ts +1 -2
- package/packages/tools/extract-conversations/dist/index.js +31 -29
- package/packages/tools/extract-conversations/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/tools/extract-conversations/index.ts +37 -30
- package/packages/tools/extract-conversations/package.json +3 -3
- package/packages/tools/extract-pdf-text/dist/index.d.ts +1 -2
- package/packages/tools/extract-pdf-text/dist/index.js +44 -65
- package/packages/tools/extract-pdf-text/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/tools/extract-pdf-text/index.ts +55 -74
- package/packages/tools/extract-pdf-text/package.json +3 -3
- package/packages/tools/filter-logs/dist/index.js +60 -84
- package/packages/tools/filter-logs/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/tools/filter-logs/index.ts +67 -97
- package/packages/tools/filter-logs/package.json +3 -3
- package/packages/tools/find-github-issues/dist/index.d.ts +10 -0
- package/packages/tools/find-github-issues/dist/index.js +34 -5
- package/packages/tools/find-github-issues/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/tools/find-github-issues/index.ts +37 -5
- package/packages/tools/find-github-issues/package.json +3 -3
- package/packages/tools/generate-storyboard-images/dist/index.d.ts +1 -2
- package/packages/tools/generate-storyboard-images/dist/index.js +98 -173
- package/packages/tools/generate-storyboard-images/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/tools/generate-storyboard-images/index.ts +100 -188
- package/packages/tools/generate-storyboard-images/package.json +3 -3
- package/packages/tools/open-github-issue/dist/index.d.ts +13 -0
- package/packages/tools/open-github-issue/dist/index.js +67 -68
- package/packages/tools/open-github-issue/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/tools/open-github-issue/index.ts +71 -72
- package/packages/tools/open-github-issue/package.json +3 -3
- package/packages/tools/read-github-issue/dist/index.d.ts +16 -1
- package/packages/tools/read-github-issue/dist/index.js +32 -40
- package/packages/tools/read-github-issue/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/tools/read-github-issue/index.ts +32 -45
- package/packages/tools/read-github-issue/package.json +3 -3
- package/packages/tools/render-error-book/dist/index.d.ts +1 -2
- package/packages/tools/render-error-book/dist/index.js +74 -95
- package/packages/tools/render-error-book/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/tools/render-error-book/index.ts +88 -103
- package/packages/tools/render-error-book/package.json +3 -3
- package/packages/tools/render-katex/dist/index.d.ts +1 -2
- package/packages/tools/render-katex/dist/index.js +70 -157
- package/packages/tools/render-katex/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/tools/render-katex/index.ts +138 -222
- package/packages/tools/render-katex/package.json +3 -3
- package/packages/tools/review-threads/dist/index.d.ts +12 -0
- package/packages/tools/review-threads/dist/index.js +83 -86
- package/packages/tools/review-threads/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/tools/review-threads/index.ts +90 -84
- package/packages/tools/review-threads/package.json +3 -3
- package/packages/tools/search-logs/dist/index.js +100 -136
- package/packages/tools/search-logs/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/tools/search-logs/index.ts +113 -145
- package/packages/tools/search-logs/package.json +3 -3
- package/packages/tools/sync-memory-index/dist/index.js +34 -28
- package/packages/tools/sync-memory-index/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/tools/sync-memory-index/index.ts +37 -28
- package/packages/tools/sync-memory-index/package.json +3 -3
- package/packages/tools/validate-openai-agent-config/dist/index.js +13 -7
- package/packages/tools/validate-openai-agent-config/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/tools/validate-openai-agent-config/index.ts +13 -7
- package/packages/tools/validate-openai-agent-config/package.json +3 -3
- package/packages/tools/validate-skill-frontmatter/dist/index.js +12 -6
- package/packages/tools/validate-skill-frontmatter/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/tools/validate-skill-frontmatter/index.ts +12 -6
- package/packages/tools/validate-skill-frontmatter/package.json +3 -3
- package/packages/tui/dist/index.d.ts +2 -1
- package/packages/tui/dist/index.js +1 -0
- package/packages/tui/dist/stdio-adapter.d.ts +36 -0
- package/packages/tui/dist/stdio-adapter.js +69 -0
- package/packages/tui/dist/terminal.js +3 -1
- package/packages/tui/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/tui/dist/types.d.ts +17 -0
- package/packages/tui/index.ts +2 -1
- package/packages/tui/package.json +6 -5
- package/packages/tui/stdio-adapter.ts +85 -0
- package/packages/tui/terminal.ts +3 -1
- package/packages/tui/tsconfig.json +5 -2
- package/packages/tui/types.ts +19 -0
- package/resources/project-architecture/assets/architecture.css +2 -1
- package/resources/project-architecture/atlas/atlas.history.log +1 -0
- package/resources/project-architecture/atlas/atlas.history.undo.json +13 -2
- package/resources/project-architecture/atlas/atlas.history.undo.stack.json +610 -0
- package/resources/project-architecture/atlas/atlas.index.yaml +81 -5
- package/resources/project-architecture/atlas/features/cli-dispatch.yaml +43 -0
- package/resources/project-architecture/atlas/features/terminal-ui.yaml +29 -0
- package/resources/project-architecture/atlas/features/tool-registry.yaml +22 -0
- package/resources/project-architecture/atlas/features/tool-utils.yaml +22 -0
- package/resources/project-architecture/features/cli-dispatch/arg-parser.html +40 -0
- package/resources/project-architecture/features/cli-dispatch/help-builder.html +40 -0
- package/resources/project-architecture/features/cli-dispatch/index.html +64 -0
- package/resources/project-architecture/features/cli-dispatch/installer-core.html +40 -0
- package/resources/project-architecture/features/cli-dispatch/tool-discovery.html +40 -0
- package/resources/project-architecture/features/cli-dispatch/update-checker.html +40 -0
- package/resources/project-architecture/features/terminal-ui/banner-display.html +40 -0
- package/resources/project-architecture/features/terminal-ui/index.html +50 -0
- package/resources/project-architecture/features/terminal-ui/interactive-prompts.html +40 -0
- package/resources/project-architecture/features/terminal-ui/terminal-detection.html +40 -0
- package/resources/project-architecture/features/tool-registry/formatter.html +40 -0
- package/resources/project-architecture/features/tool-registry/index.html +43 -0
- package/resources/project-architecture/features/tool-registry/registry-core.html +40 -0
- package/resources/project-architecture/features/tool-utils/index.html +43 -0
- package/resources/project-architecture/features/tool-utils/log-utils.html +40 -0
- package/resources/project-architecture/features/tool-utils/skill-discovery.html +40 -0
- package/resources/project-architecture/index.html +365 -121
- package/scripts/rewrite-imports.mjs +2 -2
- package/scripts/test.sh +144 -8
- package/skills/design/SKILL.md +57 -64
- package/skills/design/assets/templates/DESIGN.md +12 -0
- package/skills/design/references/code-smells.md +94 -0
- package/skills/design/references/module-boundary-adjustment.md +126 -0
- package/skills/design/references/module-internal-restructuring.md +132 -0
- package/skills/design/references/module-internal-simplification.md +164 -0
|
@@ -3,112 +3,19 @@ import fs from 'node:fs';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import https from 'node:https';
|
|
5
5
|
import http from 'node:http';
|
|
6
|
+
import { SystemError, UserInputError, createToolRunner } from '../../../tool-utils/dist/index.js';
|
|
6
7
|
const DEFAULT_API_ENDPOINT = 'https://dashscope-intl.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation';
|
|
7
8
|
const DEFAULT_API_MODEL = 'qwen3-tts';
|
|
8
9
|
const DEFAULT_API_VOICE = 'Cherry';
|
|
9
|
-
function
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
inputFile: null,
|
|
13
|
-
projectDir: '.',
|
|
14
|
-
projectName: null,
|
|
15
|
-
outputName: null,
|
|
16
|
-
mode: 'say',
|
|
17
|
-
voice: null,
|
|
18
|
-
rate: null,
|
|
19
|
-
speechRate: null,
|
|
20
|
-
apiEndpoint: DEFAULT_API_ENDPOINT,
|
|
21
|
-
apiModel: DEFAULT_API_MODEL,
|
|
22
|
-
apiVoice: DEFAULT_API_VOICE,
|
|
23
|
-
apiKey: null,
|
|
24
|
-
maxChars: null,
|
|
25
|
-
noAutoProsody: false,
|
|
26
|
-
force: false,
|
|
27
|
-
help: false,
|
|
28
|
-
};
|
|
29
|
-
for (let i = 0; i < args.length; i++) {
|
|
30
|
-
const arg = args[i];
|
|
31
|
-
if (arg === '--help' || arg === '-h') {
|
|
32
|
-
parsed.help = true;
|
|
33
|
-
continue;
|
|
34
|
-
}
|
|
35
|
-
if (arg.startsWith('--')) {
|
|
36
|
-
const eqIndex = arg.indexOf('=');
|
|
37
|
-
let key;
|
|
38
|
-
let value;
|
|
39
|
-
if (eqIndex !== -1) {
|
|
40
|
-
key = arg.slice(2, eqIndex);
|
|
41
|
-
value = arg.slice(eqIndex + 1);
|
|
42
|
-
}
|
|
43
|
-
else {
|
|
44
|
-
key = arg.slice(2);
|
|
45
|
-
value = args[++i] || '';
|
|
46
|
-
}
|
|
47
|
-
switch (key) {
|
|
48
|
-
case 'text':
|
|
49
|
-
parsed.inputText = value;
|
|
50
|
-
break;
|
|
51
|
-
case 'input':
|
|
52
|
-
case 'input-file':
|
|
53
|
-
parsed.inputFile = value;
|
|
54
|
-
break;
|
|
55
|
-
case 'project-dir':
|
|
56
|
-
parsed.projectDir = value;
|
|
57
|
-
break;
|
|
58
|
-
case 'project-name':
|
|
59
|
-
parsed.projectName = value;
|
|
60
|
-
break;
|
|
61
|
-
case 'output-name':
|
|
62
|
-
parsed.outputName = value;
|
|
63
|
-
break;
|
|
64
|
-
case 'engine':
|
|
65
|
-
case 'mode':
|
|
66
|
-
parsed.mode = value.toLowerCase();
|
|
67
|
-
break;
|
|
68
|
-
case 'voice':
|
|
69
|
-
parsed.voice = value;
|
|
70
|
-
break;
|
|
71
|
-
case 'rate':
|
|
72
|
-
parsed.rate = value;
|
|
73
|
-
break;
|
|
74
|
-
case 'speech-rate':
|
|
75
|
-
parsed.speechRate = value;
|
|
76
|
-
break;
|
|
77
|
-
case 'api-endpoint':
|
|
78
|
-
parsed.apiEndpoint = value;
|
|
79
|
-
break;
|
|
80
|
-
case 'api-model':
|
|
81
|
-
parsed.apiModel = value;
|
|
82
|
-
break;
|
|
83
|
-
case 'api-voice':
|
|
84
|
-
parsed.apiVoice = value;
|
|
85
|
-
break;
|
|
86
|
-
case 'api-key':
|
|
87
|
-
parsed.apiKey = value;
|
|
88
|
-
break;
|
|
89
|
-
case 'max-chars':
|
|
90
|
-
parsed.maxChars = value;
|
|
91
|
-
break;
|
|
92
|
-
case 'no-auto-prosody':
|
|
93
|
-
parsed.noAutoProsody = true;
|
|
94
|
-
break;
|
|
95
|
-
case 'force':
|
|
96
|
-
parsed.force = true;
|
|
97
|
-
break;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
return parsed;
|
|
102
|
-
}
|
|
103
|
-
function readInputText(opts) {
|
|
104
|
-
if (opts.inputFile) {
|
|
105
|
-
const inputPath = path.resolve(opts.inputFile);
|
|
10
|
+
function readInputText(inputFile, inputText) {
|
|
11
|
+
if (inputFile) {
|
|
12
|
+
const inputPath = path.resolve(inputFile);
|
|
106
13
|
if (!fs.existsSync(inputPath)) {
|
|
107
|
-
throw new
|
|
14
|
+
throw new UserInputError(`Input file not found: ${inputPath}`);
|
|
108
15
|
}
|
|
109
16
|
return fs.readFileSync(inputPath, 'utf-8');
|
|
110
17
|
}
|
|
111
|
-
return
|
|
18
|
+
return inputText || '';
|
|
112
19
|
}
|
|
113
20
|
function splitSentences(rawText) {
|
|
114
21
|
const endings = new Set(['。', '!', '?', '!', '?', ';', ';']);
|
|
@@ -299,7 +206,7 @@ function applySpeechRateToAudio(outputPath, speechRate) {
|
|
|
299
206
|
catch (err) {
|
|
300
207
|
if (fs.existsSync(tmpPath))
|
|
301
208
|
fs.unlinkSync(tmpPath);
|
|
302
|
-
throw new
|
|
209
|
+
throw new SystemError(`ffmpeg failed while applying --speech-rate: ${err instanceof Error ? err.message : 'unknown error'}`, undefined, { cause: err });
|
|
303
210
|
}
|
|
304
211
|
}
|
|
305
212
|
function splitTextForTts(text, maxChars) {
|
|
@@ -344,7 +251,7 @@ function splitTextForTts(text, maxChars) {
|
|
|
344
251
|
}
|
|
345
252
|
function concatAudioFiles(partPaths, outputPath) {
|
|
346
253
|
if (partPaths.length === 0) {
|
|
347
|
-
throw new
|
|
254
|
+
throw new SystemError('No chunk audio generated for concatenation.');
|
|
348
255
|
}
|
|
349
256
|
if (partPaths.length === 1) {
|
|
350
257
|
fs.copyFileSync(partPaths[0], outputPath);
|
|
@@ -359,7 +266,7 @@ function concatAudioFiles(partPaths, outputPath) {
|
|
|
359
266
|
execSync(`ffmpeg -hide_banner -loglevel error -y -f concat -safe 0 -i "${listFile}" -c:a copy "${outputPath}"`, { stdio: 'ignore', timeout: 120000 });
|
|
360
267
|
}
|
|
361
268
|
catch (err) {
|
|
362
|
-
throw new
|
|
269
|
+
throw new SystemError(`ffmpeg concat failed: ${err instanceof Error ? err.message : 'unknown error'}`, undefined, { cause: err });
|
|
363
270
|
}
|
|
364
271
|
finally {
|
|
365
272
|
try {
|
|
@@ -415,104 +322,104 @@ function requestAlibabaCloudTTS(endpoint, apiKey, model, voice, text) {
|
|
|
415
322
|
const audioData = audio.data || '';
|
|
416
323
|
const audioFormat = audio.format || audio.mime_type || '';
|
|
417
324
|
if (!audioUrl && !audioData) {
|
|
418
|
-
reject(new
|
|
325
|
+
reject(new SystemError('API response does not contain output.audio.url or output.audio.data'));
|
|
419
326
|
return;
|
|
420
327
|
}
|
|
421
328
|
resolve({ audioUrl, audioData, audioFormat });
|
|
422
329
|
}
|
|
423
330
|
catch {
|
|
424
|
-
reject(new
|
|
331
|
+
reject(new SystemError('API response is not valid JSON.'));
|
|
425
332
|
}
|
|
426
333
|
});
|
|
427
334
|
res.on('error', reject);
|
|
428
335
|
});
|
|
429
336
|
req.on('error', reject);
|
|
430
|
-
req.on('timeout', () => { req.destroy(); reject(new
|
|
337
|
+
req.on('timeout', () => { req.destroy(); reject(new SystemError('API request timed out')); });
|
|
431
338
|
req.write(payload);
|
|
432
339
|
req.end();
|
|
433
340
|
});
|
|
434
341
|
}
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
342
|
+
const schema = {
|
|
343
|
+
options: {
|
|
344
|
+
'text': { type: 'string' },
|
|
345
|
+
'input': { type: 'string' },
|
|
346
|
+
'input-file': { type: 'string' },
|
|
347
|
+
'project-dir': { type: 'string', default: '.' },
|
|
348
|
+
'project-name': { type: 'string' },
|
|
349
|
+
'output-name': { type: 'string' },
|
|
350
|
+
'engine': { type: 'string' },
|
|
351
|
+
'mode': { type: 'string', default: 'say' },
|
|
352
|
+
'voice': { type: 'string' },
|
|
353
|
+
'rate': { type: 'string' },
|
|
354
|
+
'speech-rate': { type: 'string' },
|
|
355
|
+
'api-endpoint': { type: 'string', default: DEFAULT_API_ENDPOINT },
|
|
356
|
+
'api-model': { type: 'string', default: DEFAULT_API_MODEL },
|
|
357
|
+
'api-voice': { type: 'string', default: DEFAULT_API_VOICE },
|
|
358
|
+
'api-key': { type: 'string' },
|
|
359
|
+
'max-chars': { type: 'string' },
|
|
360
|
+
'no-auto-prosody': { type: 'boolean', default: false },
|
|
361
|
+
'force': { type: 'boolean', default: false },
|
|
362
|
+
},
|
|
363
|
+
allowPositionals: true,
|
|
364
|
+
usage: 'apltk docs-to-voice [options]',
|
|
365
|
+
description: 'Convert text into audio and sentence timelines.',
|
|
366
|
+
handler: async (values, _positionals, context) => {
|
|
367
|
+
const stdout = context.stdout ?? process.stdout;
|
|
368
|
+
const stderr = context.stderr ?? process.stderr;
|
|
369
|
+
// Resolve args (previously handled by parseCliArgs)
|
|
370
|
+
const inputText = values['text'] ?? null;
|
|
371
|
+
const inputFile = values['input'] || values['input-file'] || null;
|
|
372
|
+
const projectDir = values['project-dir'] || '.';
|
|
373
|
+
const projectName = values['project-name'] ?? null;
|
|
374
|
+
const outputName = values['output-name'] ?? null;
|
|
375
|
+
const mode = (values['mode'] || values['engine'] || 'say').toLowerCase();
|
|
376
|
+
const voice = values['voice'] ?? null;
|
|
377
|
+
const rate = values['rate'] ?? null;
|
|
378
|
+
const speechRate = values['speech-rate'] ?? null;
|
|
379
|
+
const apiEndpoint = values['api-endpoint'] || DEFAULT_API_ENDPOINT;
|
|
380
|
+
const apiModel = values['api-model'] || DEFAULT_API_MODEL;
|
|
381
|
+
const apiVoice = values['api-voice'] || DEFAULT_API_VOICE;
|
|
382
|
+
const apiKey = values['api-key'] ?? null;
|
|
383
|
+
const maxChars = values['max-chars'] ?? null;
|
|
384
|
+
const noAutoProsody = !!values['no-auto-prosody'];
|
|
385
|
+
const force = !!values['force'];
|
|
386
|
+
if (mode !== 'say' && mode !== 'api') {
|
|
387
|
+
throw new UserInputError('--mode must be one of: say, api');
|
|
468
388
|
}
|
|
469
|
-
const sourceText = readInputText(
|
|
389
|
+
const sourceText = readInputText(inputFile, inputText);
|
|
470
390
|
if (!sourceText.trim()) {
|
|
471
|
-
|
|
472
|
-
return 1;
|
|
391
|
+
throw new UserInputError('No text content found for conversion.');
|
|
473
392
|
}
|
|
474
393
|
// Resolve output directory
|
|
475
|
-
const
|
|
476
|
-
const
|
|
477
|
-
if (!
|
|
478
|
-
|
|
479
|
-
return 1;
|
|
394
|
+
const resolvedProjectDir = path.resolve(projectDir);
|
|
395
|
+
const resolvedProjectName = projectName || path.basename(resolvedProjectDir);
|
|
396
|
+
if (!resolvedProjectName) {
|
|
397
|
+
throw new UserInputError('Unable to determine project name.');
|
|
480
398
|
}
|
|
481
|
-
const outputDir = path.join(
|
|
399
|
+
const outputDir = path.join(resolvedProjectDir, 'audio', resolvedProjectName);
|
|
482
400
|
fs.mkdirSync(outputDir, { recursive: true });
|
|
483
401
|
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
|
484
|
-
const
|
|
485
|
-
const hasExtension =
|
|
486
|
-
if (
|
|
487
|
-
|
|
488
|
-
const textChunks = splitTextForTts(sourceText, opts.maxChars ? parseInt(opts.maxChars, 10) || null : null);
|
|
489
|
-
if (textChunks.length === 0) {
|
|
490
|
-
stderr.write('Error: No text content found for conversion.\n');
|
|
491
|
-
return 1;
|
|
492
|
-
}
|
|
493
|
-
// Check if `say` is available
|
|
494
|
-
try {
|
|
495
|
-
execSync('which say', { stdio: 'ignore' });
|
|
496
|
-
}
|
|
497
|
-
catch {
|
|
498
|
-
stderr.write("Error: macOS 'say' command not found.\n");
|
|
499
|
-
return 1;
|
|
500
|
-
}
|
|
501
|
-
const finalOutputName = hasExtension ? outputName : `${outputName}.aiff`;
|
|
402
|
+
const outName = outputName || `voice-${timestamp}`;
|
|
403
|
+
const hasExtension = outName.includes('.');
|
|
404
|
+
if (mode === 'say') {
|
|
405
|
+
const finalOutputName = hasExtension ? outName : `${outName}.aiff`;
|
|
502
406
|
const outputPath = path.join(outputDir, finalOutputName);
|
|
503
|
-
if (fs.existsSync(outputPath) && !
|
|
504
|
-
|
|
505
|
-
return 1;
|
|
407
|
+
if (fs.existsSync(outputPath) && !force) {
|
|
408
|
+
throw new UserInputError(`Output already exists: ${outputPath}. Use --force to overwrite.`);
|
|
506
409
|
}
|
|
507
410
|
// Build prosody-enhanced text
|
|
508
|
-
const
|
|
411
|
+
const textChunks = splitTextForTts(sourceText, maxChars ? parseInt(maxChars, 10) || null : null);
|
|
412
|
+
if (textChunks.length === 0) {
|
|
413
|
+
throw new UserInputError('No text content found for conversion.');
|
|
414
|
+
}
|
|
415
|
+
const chunks = noAutoProsody ? textChunks : textChunks.map(buildAutoProsodyText);
|
|
509
416
|
if (chunks.length === 1) {
|
|
510
417
|
// Single say command
|
|
511
418
|
const sayArgs = ['-o', outputPath];
|
|
512
|
-
if (
|
|
513
|
-
sayArgs.push('-v',
|
|
514
|
-
if (
|
|
515
|
-
sayArgs.push('-r',
|
|
419
|
+
if (voice)
|
|
420
|
+
sayArgs.push('-v', voice);
|
|
421
|
+
if (rate)
|
|
422
|
+
sayArgs.push('-r', rate);
|
|
516
423
|
const tmpFile = path.join(fs.mkdtempSync('docs-to-voice-'), 'input.txt');
|
|
517
424
|
fs.mkdirSync(path.dirname(tmpFile), { recursive: true });
|
|
518
425
|
fs.writeFileSync(tmpFile, chunks[0], 'utf-8');
|
|
@@ -525,7 +432,7 @@ Options:
|
|
|
525
432
|
}
|
|
526
433
|
catch (err) {
|
|
527
434
|
const msg = err instanceof Error ? err.message : 'unknown error';
|
|
528
|
-
throw new
|
|
435
|
+
throw new SystemError(`say mode failed: ${msg}`);
|
|
529
436
|
}
|
|
530
437
|
finally {
|
|
531
438
|
try {
|
|
@@ -543,15 +450,15 @@ Options:
|
|
|
543
450
|
try {
|
|
544
451
|
for (let i = 0; i < chunks.length; i++) {
|
|
545
452
|
const partPath = path.join(tempDir, `part-${String(i + 1).padStart(4, '0')}${partExt}`);
|
|
546
|
-
const
|
|
547
|
-
if (
|
|
548
|
-
|
|
549
|
-
if (
|
|
550
|
-
|
|
551
|
-
const
|
|
552
|
-
fs.writeFileSync(
|
|
553
|
-
|
|
554
|
-
execSync(`say ${
|
|
453
|
+
const sArgs = ['-o', partPath];
|
|
454
|
+
if (voice)
|
|
455
|
+
sArgs.push('-v', voice);
|
|
456
|
+
if (rate)
|
|
457
|
+
sArgs.push('-r', rate);
|
|
458
|
+
const tFile = path.join(tempDir, `chunk-${i}.txt`);
|
|
459
|
+
fs.writeFileSync(tFile, chunks[i], 'utf-8');
|
|
460
|
+
sArgs.push('-f', tFile);
|
|
461
|
+
execSync(`say ${sArgs.map((a) => (a.includes(' ') ? `"${a}"` : a)).join(' ')}`, { stdio: 'ignore', timeout: 300000 });
|
|
555
462
|
partPaths.push(partPath);
|
|
556
463
|
}
|
|
557
464
|
concatAudioFiles(partPaths, outputPath);
|
|
@@ -564,10 +471,10 @@ Options:
|
|
|
564
471
|
}
|
|
565
472
|
}
|
|
566
473
|
// Apply speech rate if requested
|
|
567
|
-
if (
|
|
568
|
-
const
|
|
569
|
-
if (
|
|
570
|
-
applySpeechRateToAudio(outputPath,
|
|
474
|
+
if (speechRate) {
|
|
475
|
+
const rateVal = parseFloat(speechRate);
|
|
476
|
+
if (rateVal > 0)
|
|
477
|
+
applySpeechRateToAudio(outputPath, rateVal);
|
|
571
478
|
}
|
|
572
479
|
// Write timeline files
|
|
573
480
|
writeTimelineFiles(sourceText, outputPath, null);
|
|
@@ -575,23 +482,20 @@ Options:
|
|
|
575
482
|
}
|
|
576
483
|
else {
|
|
577
484
|
// API mode
|
|
578
|
-
const apiKey = opts.apiKey;
|
|
579
485
|
if (!apiKey) {
|
|
580
|
-
|
|
581
|
-
return 1;
|
|
486
|
+
throw new UserInputError('--api-key is required for api mode.');
|
|
582
487
|
}
|
|
583
488
|
const sentences = splitSentences(sourceText);
|
|
584
489
|
if (sentences.length === 0) {
|
|
585
|
-
|
|
586
|
-
return 1;
|
|
490
|
+
throw new UserInputError('No text content found for conversion.');
|
|
587
491
|
}
|
|
588
|
-
const
|
|
492
|
+
const maxCharsNum = maxChars ? parseInt(maxChars, 10) || null : null;
|
|
589
493
|
const requestItems = [];
|
|
590
494
|
for (let si = 0; si < sentences.length; si++) {
|
|
591
495
|
const sentence = sentences[si];
|
|
592
|
-
if (
|
|
593
|
-
for (let i = 0; i < sentence.length; i +=
|
|
594
|
-
requestItems.push({ sentenceIndex: si, text: sentence.slice(i, i +
|
|
496
|
+
if (maxCharsNum && sentence.length > maxCharsNum) {
|
|
497
|
+
for (let i = 0; i < sentence.length; i += maxCharsNum) {
|
|
498
|
+
requestItems.push({ sentenceIndex: si, text: sentence.slice(i, i + maxCharsNum) });
|
|
595
499
|
}
|
|
596
500
|
}
|
|
597
501
|
else {
|
|
@@ -599,8 +503,7 @@ Options:
|
|
|
599
503
|
}
|
|
600
504
|
}
|
|
601
505
|
if (requestItems.length === 0) {
|
|
602
|
-
|
|
603
|
-
return 1;
|
|
506
|
+
throw new UserInputError('No text content found for conversion.');
|
|
604
507
|
}
|
|
605
508
|
const tempDir = fs.mkdtempSync('docs-to-voice-api-');
|
|
606
509
|
const partPaths = [];
|
|
@@ -610,7 +513,7 @@ Options:
|
|
|
610
513
|
try {
|
|
611
514
|
for (let i = 0; i < requestItems.length; i++) {
|
|
612
515
|
const item = requestItems[i];
|
|
613
|
-
const apiResult = await requestAlibabaCloudTTS(
|
|
516
|
+
const apiResult = await requestAlibabaCloudTTS(apiEndpoint, apiKey, apiModel, apiVoice, item.text);
|
|
614
517
|
const currentExt = apiResult.audioFormat || 'wav';
|
|
615
518
|
if (!partExt)
|
|
616
519
|
partExt = currentExt;
|
|
@@ -622,10 +525,10 @@ Options:
|
|
|
622
525
|
fs.writeFileSync(partPath, Buffer.from(apiResult.audioData, 'base64'));
|
|
623
526
|
}
|
|
624
527
|
else {
|
|
625
|
-
throw new
|
|
528
|
+
throw new SystemError('No audio data in API response.');
|
|
626
529
|
}
|
|
627
530
|
if (!fs.existsSync(partPath) || fs.statSync(partPath).size === 0) {
|
|
628
|
-
throw new
|
|
531
|
+
throw new SystemError(`Failed to generate audio chunk ${i + 1}.`);
|
|
629
532
|
}
|
|
630
533
|
partPaths.push(partPath);
|
|
631
534
|
const partDuration = readDurationSeconds(partPath);
|
|
@@ -637,12 +540,11 @@ Options:
|
|
|
637
540
|
}
|
|
638
541
|
}
|
|
639
542
|
const finalOutputName = hasExtension
|
|
640
|
-
?
|
|
641
|
-
: `${
|
|
543
|
+
? outName
|
|
544
|
+
: `${outName}.${partExt || 'wav'}`;
|
|
642
545
|
const outputPath = path.join(outputDir, finalOutputName);
|
|
643
|
-
if (fs.existsSync(outputPath) && !
|
|
644
|
-
|
|
645
|
-
return 1;
|
|
546
|
+
if (fs.existsSync(outputPath) && !force) {
|
|
547
|
+
throw new UserInputError(`Output already exists: ${outputPath}. Use --force to overwrite.`);
|
|
646
548
|
}
|
|
647
549
|
concatAudioFiles(partPaths, outputPath);
|
|
648
550
|
// Build timeline durations
|
|
@@ -670,12 +572,12 @@ Options:
|
|
|
670
572
|
}
|
|
671
573
|
}
|
|
672
574
|
// Apply speech rate if requested
|
|
673
|
-
if (
|
|
674
|
-
const
|
|
675
|
-
if (
|
|
676
|
-
applySpeechRateToAudio(outputPath,
|
|
575
|
+
if (speechRate) {
|
|
576
|
+
const rateVal = parseFloat(speechRate);
|
|
577
|
+
if (rateVal > 0) {
|
|
578
|
+
applySpeechRateToAudio(outputPath, rateVal);
|
|
677
579
|
if (timelineDurations) {
|
|
678
|
-
timelineDurations = timelineDurations.map((d) => d /
|
|
580
|
+
timelineDurations = timelineDurations.map((d) => d / rateVal);
|
|
679
581
|
}
|
|
680
582
|
}
|
|
681
583
|
}
|
|
@@ -690,16 +592,11 @@ Options:
|
|
|
690
592
|
}
|
|
691
593
|
}
|
|
692
594
|
return 0;
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
const msg = err instanceof Error ? err.message : 'Unknown error';
|
|
696
|
-
stderr.write(`Error: ${msg}\n`);
|
|
697
|
-
return 1;
|
|
698
|
-
}
|
|
699
|
-
}
|
|
595
|
+
},
|
|
596
|
+
};
|
|
700
597
|
export const tool = {
|
|
701
598
|
name: 'docs-to-voice',
|
|
702
599
|
category: 'media',
|
|
703
600
|
description: 'Convert text into audio and sentence timelines.',
|
|
704
|
-
handler:
|
|
601
|
+
handler: createToolRunner(schema),
|
|
705
602
|
};
|