Package not found. Please check the package name and try again.

@coeiro-operator/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/README.md ADDED
@@ -0,0 +1,81 @@
1
+ # @coeiro-operator/cli
2
+
3
+ COEIROINKと連携するCLIツール集
4
+
5
+ ## インストール
6
+
7
+ ```bash
8
+ npm install -g @coeiro-operator/cli
9
+ ```
10
+
11
+ ## コマンド
12
+
13
+ ### say-coeiroink
14
+
15
+ COEIROINK音声合成コマンド(macOS sayコマンド互換)
16
+
17
+ ```bash
18
+ # 基本使用
19
+ say-coeiroink "こんにちは"
20
+
21
+ # 音声一覧表示
22
+ say-coeiroink -v "?"
23
+
24
+ # 話速調整(WPM)
25
+ say-coeiroink -r 150 "ゆっくり話します"
26
+
27
+ # ファイル出力
28
+ say-coeiroink -o output.wav "保存テスト"
29
+
30
+ # スタイル指定
31
+ say-coeiroink --style "セクシー" "別のスタイルで話します"
32
+ ```
33
+
34
+ ### operator-manager
35
+
36
+ ターミナルセッションのオペレータ管理
37
+
38
+ ```bash
39
+ # オペレータ割り当て(ランダム)
40
+ operator-manager assign
41
+
42
+ # 特定キャラクターを指定
43
+ operator-manager assign tsukuyomi
44
+
45
+ # 現在の状態確認
46
+ operator-manager status
47
+
48
+ # オペレータ解放
49
+ operator-manager release
50
+
51
+ # 利用可能なオペレータ一覧
52
+ operator-manager available
53
+ ```
54
+
55
+ ### dictionary-register
56
+
57
+ COEIROINK用語辞書登録
58
+
59
+ ```bash
60
+ # 単語登録
61
+ dictionary-register "COEIROINK" "コエイロインク" 0 7
62
+
63
+ # パラメータ
64
+ # - 単語(表記)
65
+ # - 読み方(カタカナ)
66
+ # - アクセント位置
67
+ # - モーラ数
68
+ ```
69
+
70
+ ## 動作要件
71
+
72
+ - Node.js 18以上
73
+ - COEIROINK本体が起動済み(http://localhost:50032)
74
+
75
+ ## 詳細
76
+
77
+ 完全なドキュメントは [GitHub](https://github.com/otolab/coeiro-operator) を参照してください。
78
+
79
+ ## ライセンス
80
+
81
+ MIT
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node --no-deprecation
2
+ /**
3
+ * COEIROINK ユーザー辞書登録 CLIツール
4
+ *
5
+ * 使用方法:
6
+ * dictionary-register # デフォルト技術用語を登録
7
+ * dictionary-register --preset all # 全プリセット登録
8
+ * dictionary-register --file dict.json # カスタム辞書ファイル
9
+ * dictionary-register --word KARTE --yomi カルテ --accent 1 --moras 3
10
+ */
11
+ export {};
12
+ //# sourceMappingURL=dictionary-register.d.ts.map
@@ -0,0 +1,212 @@
1
+ #!/usr/bin/env node --no-deprecation
2
+ /**
3
+ * COEIROINK ユーザー辞書登録 CLIツール
4
+ *
5
+ * 使用方法:
6
+ * dictionary-register # デフォルト技術用語を登録
7
+ * dictionary-register --preset all # 全プリセット登録
8
+ * dictionary-register --file dict.json # カスタム辞書ファイル
9
+ * dictionary-register --word KARTE --yomi カルテ --accent 1 --moras 3
10
+ */
11
+ import { Command } from 'commander';
12
+ import fs from 'fs';
13
+ import { DictionaryService, DEFAULT_TECHNICAL_WORDS, CHARACTER_NAME_WORDS } from '@coeiro-operator/core';
14
+ const program = new Command();
15
+ program
16
+ .name('dictionary-register')
17
+ .description('COEIROINKのユーザー辞書に単語を登録')
18
+ .version('1.0.0')
19
+ .option('-p, --preset <type>', 'プリセット辞書 (technical|characters|all)', 'technical')
20
+ .option('-f, --file <path>', 'カスタム辞書JSONファイル')
21
+ .option('-w, --word <word>', '登録する単語')
22
+ .option('-y, --yomi <yomi>', '読み方(カタカナ)')
23
+ .option('-a, --accent <number>', 'アクセント位置', parseInt)
24
+ .option('-m, --moras <number>', 'モーラ数', parseInt)
25
+ .option('--host <host>', 'COEIROINKサーバーホスト', 'localhost')
26
+ .option('--port <port>', 'COEIROINKサーバーポート', '50032')
27
+ .option('--list', 'デフォルト辞書の内容を表示')
28
+ .option('--export <path>', 'デフォルト辞書をJSONファイルにエクスポート')
29
+ .option('--test <word>', '指定した単語の韻律解析をテスト')
30
+ .option('--persist', '辞書データを永続化する(デフォルト: true)', true)
31
+ .option('--no-persist', '辞書データを永続化しない')
32
+ .parse(process.argv);
33
+ const options = program.opts();
34
+ /**
35
+ * 韻律解析テスト
36
+ */
37
+ async function testProsody(word, host, port) {
38
+ try {
39
+ const response = await fetch(`http://${host}:${port}/v1/estimate_prosody`, {
40
+ method: 'POST',
41
+ headers: {
42
+ 'Content-Type': 'application/json',
43
+ },
44
+ body: JSON.stringify({ text: word }),
45
+ });
46
+ if (response.ok) {
47
+ const result = await response.json();
48
+ console.log(`\n📝 「${word}」の韻律解析結果:`);
49
+ console.log('─'.repeat(60));
50
+ if (result.detail && result.detail[0]) {
51
+ console.log('モーラ解析:');
52
+ result.detail[0].forEach((mora, index) => {
53
+ console.log(` ${index + 1}. ${mora.hira} (${mora.phoneme}) - アクセント: ${mora.accent}`);
54
+ });
55
+ console.log('\n音素記号:');
56
+ console.log(' ' + result.plain.join(' '));
57
+ }
58
+ else {
59
+ console.log('詳細情報なし');
60
+ }
61
+ console.log('─'.repeat(60));
62
+ }
63
+ else {
64
+ console.error('❌ 韻律解析に失敗しました:', response.statusText);
65
+ }
66
+ }
67
+ catch (error) {
68
+ console.error('❌ エラー:', error.message);
69
+ }
70
+ }
71
+ /**
72
+ * メイン処理
73
+ */
74
+ async function main() {
75
+ // リスト表示
76
+ if (options.list) {
77
+ console.log('📚 デフォルト辞書:');
78
+ console.log('\n技術用語:');
79
+ console.log(JSON.stringify(DEFAULT_TECHNICAL_WORDS, null, 2));
80
+ console.log('\nキャラクター名:');
81
+ console.log(JSON.stringify(CHARACTER_NAME_WORDS, null, 2));
82
+ process.exit(0);
83
+ }
84
+ // エクスポート
85
+ if (options.export) {
86
+ const exportData = [...DEFAULT_TECHNICAL_WORDS, ...CHARACTER_NAME_WORDS];
87
+ fs.writeFileSync(options.export, JSON.stringify(exportData, null, 2));
88
+ console.log(`✅ デフォルト辞書をエクスポートしました: ${options.export}`);
89
+ process.exit(0);
90
+ }
91
+ // 韻律解析テスト
92
+ if (options.test) {
93
+ await testProsody(options.test, options.host, options.port);
94
+ process.exit(0);
95
+ }
96
+ // DictionaryServiceのインスタンス作成
97
+ const service = new DictionaryService({
98
+ host: options.host,
99
+ port: options.port,
100
+ });
101
+ // 初期化と接続確認
102
+ await service.initialize();
103
+ const isConnected = await service.checkConnection();
104
+ if (!isConnected) {
105
+ console.error('❌ COEIROINKサーバーに接続できません');
106
+ console.error(` 接続先: http://${options.host}:${options.port}`);
107
+ console.error(' サーバーが起動していることを確認してください');
108
+ process.exit(1);
109
+ }
110
+ // 登録する単語を決定
111
+ let wordsToRegister = [];
112
+ // カスタムファイル
113
+ if (options.file) {
114
+ try {
115
+ const content = fs.readFileSync(options.file, 'utf8');
116
+ wordsToRegister = JSON.parse(content);
117
+ console.log(`📁 カスタム辞書ファイルを読み込みました: ${options.file}`);
118
+ }
119
+ catch (error) {
120
+ console.error(`❌ ファイル読み込みエラー: ${error.message}`);
121
+ process.exit(1);
122
+ }
123
+ }
124
+ // 単一単語の登録
125
+ else if (options.word) {
126
+ if (!options.yomi || options.accent === undefined || !options.moras) {
127
+ console.error('❌ 単語登録には --yomi, --accent, --moras が必要です');
128
+ process.exit(1);
129
+ }
130
+ wordsToRegister = [
131
+ {
132
+ word: options.word,
133
+ yomi: options.yomi,
134
+ accent: options.accent,
135
+ numMoras: options.moras,
136
+ },
137
+ ];
138
+ }
139
+ // プリセット辞書
140
+ else {
141
+ switch (options.preset) {
142
+ case 'technical':
143
+ wordsToRegister = DEFAULT_TECHNICAL_WORDS;
144
+ break;
145
+ case 'characters':
146
+ wordsToRegister = CHARACTER_NAME_WORDS;
147
+ break;
148
+ case 'all':
149
+ wordsToRegister = [...DEFAULT_TECHNICAL_WORDS, ...CHARACTER_NAME_WORDS];
150
+ break;
151
+ default:
152
+ console.error(`❌ 無効なプリセット: ${options.preset}`);
153
+ process.exit(1);
154
+ }
155
+ }
156
+ if (wordsToRegister.length === 0) {
157
+ console.error('⚠️ 登録する単語がありません');
158
+ process.exit(1);
159
+ }
160
+ // 辞書登録実行
161
+ console.log(`📝 ${wordsToRegister.length}個の単語を登録中...`);
162
+ // 単一単語の場合はaddWord、複数の場合は直接登録
163
+ let success = false;
164
+ if (options.word && wordsToRegister.length === 1) {
165
+ // 単一単語はDictionaryServiceのaddWordを使用(永続化含む)
166
+ success = await service.addWord(wordsToRegister[0]);
167
+ if (success) {
168
+ console.log(`✅ 単語を登録しました\n`);
169
+ if (options.persist) {
170
+ console.log('💾 辞書データを永続化しました(次回起動時に自動登録されます)\n');
171
+ }
172
+ }
173
+ }
174
+ else {
175
+ // プリセットなど複数単語の場合は初期化で既に登録済み
176
+ // ここでは表示のみ
177
+ success = true;
178
+ console.log(`✅ ${wordsToRegister.length}個の単語を登録しました\n`);
179
+ }
180
+ if (success) {
181
+ // 登録内容を表示
182
+ console.log('登録された単語:');
183
+ console.log('─'.repeat(60));
184
+ console.log('単語\t\t読み方\t\tアクセント\tモーラ数');
185
+ console.log('─'.repeat(60));
186
+ for (const word of wordsToRegister) {
187
+ const paddedWord = word.word.padEnd(16);
188
+ const paddedYomi = word.yomi.padEnd(16);
189
+ console.log(`${paddedWord}${paddedYomi}${word.accent}\t\t${word.numMoras}`);
190
+ }
191
+ console.log('─'.repeat(60));
192
+ console.log('\n⚠️ 注意事項:');
193
+ console.log('• 登録した辞書はCOEIROINK再起動時にリセットされます');
194
+ console.log('• 全角で登録された単語は半角入力にも適用されます');
195
+ if (options.persist) {
196
+ console.log('• 永続化した辞書は次回起動時に自動的に登録されます');
197
+ }
198
+ else {
199
+ console.log('• 永続的な登録が必要な場合は --persist オプションを使用してください');
200
+ }
201
+ }
202
+ else {
203
+ console.error(`❌ 辞書登録に失敗しました`);
204
+ process.exit(1);
205
+ }
206
+ }
207
+ // エラーハンドリング
208
+ main().catch(error => {
209
+ console.error('❌ 予期しないエラー:', error.message);
210
+ process.exit(1);
211
+ });
212
+ //# sourceMappingURL=dictionary-register.js.map
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env node --no-deprecation
2
+ /**
3
+ * src/operator/cli.ts: オペレータ管理CLI
4
+ * operator-managerスクリプトのJavaScript版
5
+ */
6
+ declare class OperatorManagerCLI {
7
+ private manager;
8
+ private terminalBackground;
9
+ private configManager;
10
+ constructor();
11
+ private parseAssignArgs;
12
+ private executeAssignment;
13
+ showUsage(): Promise<void>;
14
+ run(args: string[]): Promise<void>;
15
+ handleAssign(args: string[]): Promise<void>;
16
+ handleRelease(): Promise<void>;
17
+ handleStatus(): Promise<void>;
18
+ handleAvailable(): Promise<void>;
19
+ handleClear(): Promise<void>;
20
+ }
21
+ export default OperatorManagerCLI;
22
+ //# sourceMappingURL=operator-manager.d.ts.map
@@ -0,0 +1,139 @@
1
+ #!/usr/bin/env node --no-deprecation
2
+ /**
3
+ * src/operator/cli.ts: オペレータ管理CLI
4
+ * operator-managerスクリプトのJavaScript版
5
+ */
6
+ import { OperatorManager, ConfigManager, TerminalBackground, getConfigDir } from '@coeiro-operator/core';
7
+ class OperatorManagerCLI {
8
+ manager;
9
+ terminalBackground = null;
10
+ configManager = null;
11
+ constructor() {
12
+ this.manager = new OperatorManager();
13
+ }
14
+ parseAssignArgs(args) {
15
+ let characterId = null;
16
+ let style = null;
17
+ for (let i = 0; i < args.length; i++) {
18
+ const arg = args[i];
19
+ if (arg.startsWith('--style=')) {
20
+ style = arg.substring(8);
21
+ }
22
+ else if (!arg.startsWith('--')) {
23
+ characterId = arg;
24
+ }
25
+ }
26
+ return { characterId, style };
27
+ }
28
+ async executeAssignment(characterId, style) {
29
+ if (characterId) {
30
+ const result = await this.manager.assignSpecificOperator(characterId, style);
31
+ console.log(`オペレータ決定: ${result.characterName} (${result.characterId})`);
32
+ if (result.currentStyle) {
33
+ console.log(`スタイル: ${result.currentStyle.styleName} - ${result.currentStyle.personality}`);
34
+ }
35
+ // 背景画像を切り替え
36
+ if (this.terminalBackground && await this.terminalBackground.isEnabled()) {
37
+ await this.terminalBackground.switchCharacter(result.characterId);
38
+ }
39
+ }
40
+ else {
41
+ const currentStatus = await this.manager.showCurrentOperator();
42
+ if (currentStatus.characterId) {
43
+ console.log(currentStatus.message);
44
+ }
45
+ else {
46
+ const result = await this.manager.assignRandomOperator(style);
47
+ console.log(`オペレータ決定: ${result.characterName} (${result.characterId})`);
48
+ if (result.currentStyle) {
49
+ console.log(`スタイル: ${result.currentStyle.styleName} - ${result.currentStyle.personality}`);
50
+ }
51
+ // 背景画像を切り替え
52
+ if (this.terminalBackground && await this.terminalBackground.isEnabled()) {
53
+ await this.terminalBackground.switchCharacter(result.characterId);
54
+ }
55
+ }
56
+ }
57
+ }
58
+ async showUsage() {
59
+ console.log(`使用法: operator-manager {assign|release|status|available|clear}
60
+ assign [オペレータID] [--style=スタイル名] - オペレータを割り当て(IDを指定しない場合はランダム)
61
+ release - 現在のオペレータを返却
62
+ status - 現在のオペレータを表示
63
+ available - 利用可能なオペレータを表示
64
+ clear - 全てのオペレータ利用状況をクリア`);
65
+ }
66
+ async run(args) {
67
+ await this.manager.initialize();
68
+ // ConfigManagerとTerminalBackgroundを初期化
69
+ const configDir = await getConfigDir();
70
+ this.configManager = new ConfigManager(configDir);
71
+ this.terminalBackground = new TerminalBackground(this.configManager);
72
+ const command = args[0];
73
+ try {
74
+ switch (command) {
75
+ case 'assign':
76
+ await this.handleAssign(args.slice(1));
77
+ break;
78
+ case 'release':
79
+ await this.handleRelease();
80
+ break;
81
+ case 'status':
82
+ await this.handleStatus();
83
+ break;
84
+ case 'available':
85
+ await this.handleAvailable();
86
+ break;
87
+ case 'clear':
88
+ await this.handleClear();
89
+ break;
90
+ default:
91
+ await this.showUsage();
92
+ process.exit(1);
93
+ }
94
+ }
95
+ catch (error) {
96
+ console.error(`エラー: ${error.message}`);
97
+ process.exit(1);
98
+ }
99
+ }
100
+ async handleAssign(args) {
101
+ const { characterId, style } = this.parseAssignArgs(args);
102
+ await this.executeAssignment(characterId, style);
103
+ }
104
+ async handleRelease() {
105
+ const result = await this.manager.releaseOperator();
106
+ if (result.wasAssigned) {
107
+ console.log(`オペレータ返却: ${result.characterName}`);
108
+ }
109
+ else {
110
+ console.log('オペレータは割り当てられていません');
111
+ }
112
+ // 背景画像をクリア(オペレータの有無に関わらず実行)
113
+ if (this.terminalBackground && await this.terminalBackground.isEnabled()) {
114
+ await this.terminalBackground.clearBackground();
115
+ }
116
+ }
117
+ async handleStatus() {
118
+ const result = await this.manager.showCurrentOperator();
119
+ console.log(result.message);
120
+ }
121
+ async handleAvailable() {
122
+ const result = await this.manager.getAvailableOperators();
123
+ console.log(`利用可能なオペレータ: ${result.available.join(', ')}`);
124
+ if (result.busy.length > 0) {
125
+ console.log(`仕事中のオペレータ: ${result.busy.join(', ')}`);
126
+ }
127
+ }
128
+ async handleClear() {
129
+ await this.manager.clearAllOperators();
130
+ console.log('全てのオペレータ利用状況をクリアしました');
131
+ }
132
+ }
133
+ // メイン実行
134
+ if (import.meta.url === `file://${process.argv[1]}`) {
135
+ const cli = new OperatorManagerCLI();
136
+ await cli.run(process.argv.slice(2));
137
+ }
138
+ export default OperatorManagerCLI;
139
+ //# sourceMappingURL=operator-manager.js.map
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env node --no-deprecation
2
+ /**
3
+ * src/say/cli.ts: say-coeiroinkコマンドラインインターフェース
4
+ * macOS sayコマンド互換のCLIツール
5
+ */
6
+ import { SayCoeiroink } from '@coeiro-operator/audio';
7
+ import type { Config } from '@coeiro-operator/audio';
8
+ declare class SayCoeiroinkCLI {
9
+ private sayCoeiroink;
10
+ private config;
11
+ constructor(sayCoeiroink: SayCoeiroink, config: Config);
12
+ showUsage(): Promise<void>;
13
+ private parseArguments;
14
+ private getInputText;
15
+ run(args: string[]): Promise<void>;
16
+ }
17
+ export default SayCoeiroinkCLI;
18
+ //# sourceMappingURL=say-coeiroink.d.ts.map
@@ -0,0 +1,250 @@
1
+ #!/usr/bin/env node --no-deprecation
2
+ /**
3
+ * src/say/cli.ts: say-coeiroinkコマンドラインインターフェース
4
+ * macOS sayコマンド互換のCLIツール
5
+ */
6
+ import { readFile, access } from 'fs/promises';
7
+ import { constants } from 'fs';
8
+ import { SayCoeiroink } from '@coeiro-operator/audio';
9
+ import { ConfigManager, getConfigDir } from '@coeiro-operator/core';
10
+ import { LoggerPresets } from '@coeiro-operator/common';
11
+ import { BUFFER_SIZES } from '@coeiro-operator/audio';
12
+ class SayCoeiroinkCLI {
13
+ sayCoeiroink;
14
+ config;
15
+ constructor(sayCoeiroink, config) {
16
+ this.sayCoeiroink = sayCoeiroink;
17
+ this.config = config;
18
+ }
19
+ async showUsage() {
20
+ const defaultRate = this.config?.operator?.rate || 200;
21
+ console.log(`Usage: say-coeiroink [-v voice] [-r rate] [-o outfile] [-f file | text] [--style style] [--chunk-mode mode] [--buffer-size size]
22
+
23
+ 低レイテンシストリーミング音声合成・再生(macOS sayコマンド互換)
24
+
25
+ Options:
26
+ -v voice Specify voice (voice ID or name, use '?' to list available voices)
27
+ -r rate Speech rate in words per minute (default: ${defaultRate})
28
+ -o outfile Write audio to file instead of playing (WAV format)
29
+ -f file Read text from file (use '-' for stdin)
30
+ --style style Specify voice style (e.g., 'のーまる', 'セクシー')
31
+ --chunk-mode mode Text splitting mode: punctuation|none|small|medium|large (default: punctuation)
32
+ --buffer-size size Audio buffer size in bytes: ${BUFFER_SIZES.MIN}-${BUFFER_SIZES.MAX} (default: ${BUFFER_SIZES.DEFAULT})
33
+ -h Show this help
34
+
35
+ Chunk Modes:
36
+ none No text splitting (best for long text, natural speech)
37
+ small 30 chars (low latency, interactive use)
38
+ medium 50 chars (balanced)
39
+ large 100 chars (stability focused)
40
+ punctuation Sentence-based splitting (default, natural Japanese)
41
+
42
+ Buffer Sizes:
43
+ 256 Lowest latency, higher CPU usage
44
+ 512 Low latency, moderate CPU usage
45
+ 1024 Balanced
46
+ 2048 Higher stability (default)
47
+ 4096+ Maximum stability, background use
48
+
49
+ Features:
50
+ - ネイティブ音声出力(speakerライブラリ)
51
+ - カスタマイズ可能な分割制御
52
+ - バッファサイズ制御
53
+ - 真のストリーミング再生
54
+ - macOS sayコマンド互換
55
+
56
+ Examples:
57
+ say-coeiroink "短いテキスト"
58
+ say-coeiroink -v "?" # 音声リスト表示
59
+ say-coeiroink -o output.wav "ファイル保存"
60
+ say-coeiroink --chunk-mode none "長文を分割せずにスムーズに読み上げ"
61
+ say-coeiroink --chunk-mode small --buffer-size 256 "低レイテンシ再生"
62
+ say-coeiroink --buffer-size 2048 "高品質・安定再生"
63
+ echo "テキスト" | say-coeiroink -f -`);
64
+ }
65
+ async parseArguments(args) {
66
+ const options = {
67
+ voice: process.env.COEIROINK_VOICE || '',
68
+ rate: this.config?.operator?.rate || 200,
69
+ inputFile: '',
70
+ outputFile: '',
71
+ text: '',
72
+ chunkMode: 'punctuation',
73
+ bufferSize: BUFFER_SIZES.DEFAULT,
74
+ };
75
+ // args が undefined や null の場合はデフォルト値を返す
76
+ if (!args || !Array.isArray(args)) {
77
+ return options;
78
+ }
79
+ for (let i = 0; i < args.length; i++) {
80
+ const arg = args[i];
81
+ switch (arg) {
82
+ case '-h':
83
+ case '--help':
84
+ await this.showUsage();
85
+ throw new Error('HELP_REQUESTED');
86
+ case '-v':
87
+ if (args[i + 1] === '?') {
88
+ await this.sayCoeiroink.listVoices();
89
+ throw new Error('VOICE_LIST_REQUESTED');
90
+ }
91
+ options.voice = args[i + 1];
92
+ i++;
93
+ break;
94
+ case '-r':
95
+ case '--rate':
96
+ options.rate = parseInt(args[i + 1]);
97
+ i++;
98
+ break;
99
+ case '-o':
100
+ case '--output-file':
101
+ options.outputFile = args[i + 1];
102
+ i++;
103
+ break;
104
+ case '-f':
105
+ case '--input-file':
106
+ options.inputFile = args[i + 1];
107
+ i++;
108
+ break;
109
+ case '--style':
110
+ options.style = args[i + 1];
111
+ i++;
112
+ break;
113
+ case '--chunk-mode': {
114
+ const chunkMode = args[i + 1];
115
+ if (!['none', 'small', 'medium', 'large', 'punctuation'].includes(chunkMode)) {
116
+ throw new Error(`Invalid chunk mode: ${chunkMode}. Must be one of: none, small, medium, large, punctuation`);
117
+ }
118
+ options.chunkMode = chunkMode;
119
+ i++;
120
+ break;
121
+ }
122
+ case '--buffer-size': {
123
+ const bufferSize = parseInt(args[i + 1]);
124
+ if (isNaN(bufferSize) || bufferSize < BUFFER_SIZES.MIN || bufferSize > BUFFER_SIZES.MAX) {
125
+ throw new Error(`Invalid buffer size: ${args[i + 1]}. Must be a number between ${BUFFER_SIZES.MIN} and ${BUFFER_SIZES.MAX}`);
126
+ }
127
+ options.bufferSize = bufferSize;
128
+ i++;
129
+ break;
130
+ }
131
+ default:
132
+ if (arg.startsWith('-')) {
133
+ throw new Error(`Unknown option ${arg}`);
134
+ }
135
+ else {
136
+ options.text = options.text ? `${options.text} ${arg}` : arg;
137
+ }
138
+ break;
139
+ }
140
+ }
141
+ return options;
142
+ }
143
+ async getInputText(options) {
144
+ let text = options.text;
145
+ if (options.inputFile) {
146
+ if (options.inputFile === '-') {
147
+ const chunks = [];
148
+ for await (const chunk of process.stdin) {
149
+ chunks.push(chunk);
150
+ }
151
+ text = Buffer.concat(chunks).toString('utf8').trim();
152
+ }
153
+ else {
154
+ try {
155
+ await access(options.inputFile, constants.F_OK);
156
+ text = (await readFile(options.inputFile, 'utf8')).trim();
157
+ }
158
+ catch {
159
+ throw new Error(`File '${options.inputFile}' not found`);
160
+ }
161
+ }
162
+ }
163
+ else if (!text) {
164
+ const chunks = [];
165
+ for await (const chunk of process.stdin) {
166
+ chunks.push(chunk);
167
+ }
168
+ text = Buffer.concat(chunks).toString('utf8').trim();
169
+ }
170
+ if (!text) {
171
+ throw new Error('No text to speak');
172
+ }
173
+ return text;
174
+ }
175
+ async run(args) {
176
+ const options = await this.parseArguments(args);
177
+ const text = await this.getInputText(options);
178
+ // ファイル出力の場合はウォームアップ不要
179
+ if (!options.outputFile) {
180
+ // オーディオドライバーのウォームアップ
181
+ await this.sayCoeiroink.warmup();
182
+ }
183
+ // 音声合成タスクをキューに追加
184
+ this.sayCoeiroink.synthesize(text, {
185
+ voice: options.voice || null,
186
+ rate: options.rate,
187
+ outputFile: options.outputFile || null,
188
+ style: options.style || undefined,
189
+ chunkMode: options.chunkMode,
190
+ bufferSize: options.bufferSize,
191
+ });
192
+ if (options.outputFile) {
193
+ console.error(`Audio saved to: ${options.outputFile}`);
194
+ }
195
+ // すべてのタスクの完了を待つ
196
+ await this.sayCoeiroink.waitCompletion();
197
+ }
198
+ }
199
+ // プロセス終了ハンドリング(テスト環境では無効化)
200
+ if (process.env.NODE_ENV !== 'test') {
201
+ process.on('uncaughtException', error => {
202
+ console.error('Uncaught Exception:', error.message);
203
+ process.exit(1);
204
+ });
205
+ process.on('unhandledRejection', (reason) => {
206
+ console.error('Unhandled Rejection:', reason);
207
+ process.exit(1);
208
+ });
209
+ }
210
+ // メイン実行関数
211
+ async function main() {
212
+ // デバッグモード判定
213
+ const isDebugMode = process.argv.includes('--debug');
214
+ // CLIモードでは通常ログレベル(info)を使用、デバッグモードではdebugレベル
215
+ if (isDebugMode) {
216
+ LoggerPresets.debug();
217
+ }
218
+ else {
219
+ LoggerPresets.cli();
220
+ }
221
+ const configDir = await getConfigDir();
222
+ const configManager = new ConfigManager(configDir);
223
+ await configManager.buildDynamicConfig();
224
+ const sayCoeiroink = new SayCoeiroink(configManager);
225
+ await sayCoeiroink.initialize();
226
+ await sayCoeiroink.buildDynamicConfig();
227
+ const config = await configManager.getFullConfig();
228
+ const cli = new SayCoeiroinkCLI(sayCoeiroink, config);
229
+ await cli.run(process.argv.slice(2));
230
+ }
231
+ // メイン実行(テスト環境では実行しない)
232
+ if (process.env.NODE_ENV !== 'test') {
233
+ main()
234
+ .then(() => {
235
+ process.exit(0);
236
+ })
237
+ .catch(error => {
238
+ // 特別なエラーメッセージは正常終了扱い
239
+ if (error.message === 'HELP_REQUESTED' ||
240
+ error.message === 'VOICE_LIST_REQUESTED') {
241
+ process.exit(0);
242
+ }
243
+ else {
244
+ console.error(`Error: ${error.message}`);
245
+ process.exit(1);
246
+ }
247
+ });
248
+ }
249
+ export default SayCoeiroinkCLI;
250
+ //# sourceMappingURL=say-coeiroink.js.map
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "@coeiro-operator/cli",
3
+ "version": "1.0.0",
4
+ "description": "CLI tools for COEIRO Operator",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "os": [
9
+ "darwin",
10
+ "linux",
11
+ "win32"
12
+ ],
13
+ "cpu": [
14
+ "x64",
15
+ "arm64",
16
+ "ia32"
17
+ ],
18
+ "bin": {
19
+ "operator-manager": "./dist/operator-manager.js",
20
+ "say-coeiroink": "./dist/say-coeiroink.js",
21
+ "dictionary-register": "./dist/dictionary-register.js"
22
+ },
23
+ "scripts": {
24
+ "build": "tsc --build",
25
+ "test": "vitest",
26
+ "test:watch": "vitest --watch",
27
+ "type-check": "tsc --noEmit",
28
+ "lint": "eslint src --ext .ts,.js",
29
+ "format": "prettier --write \"src/**/*.{ts,js,json,md}\""
30
+ },
31
+ "dependencies": {
32
+ "@coeiro-operator/audio": "^1.0.0",
33
+ "@coeiro-operator/common": "^1.0.0",
34
+ "@coeiro-operator/core": "^1.0.0",
35
+ "commander": "^14.0.1"
36
+ },
37
+ "devDependencies": {
38
+ "@types/commander": "^2.12.5",
39
+ "@types/node": "^22.18.3",
40
+ "typescript": "^5.7.3",
41
+ "vitest": "^3.2.4"
42
+ },
43
+ "keywords": [
44
+ "cli",
45
+ "tools",
46
+ "coeiroink"
47
+ ],
48
+ "license": "MIT",
49
+ "repository": {
50
+ "type": "git",
51
+ "url": "git+https://github.com/otolab/coeiro-operator.git",
52
+ "directory": "packages/cli"
53
+ },
54
+ "homepage": "https://github.com/otolab/coeiro-operator#readme",
55
+ "bugs": {
56
+ "url": "https://github.com/otolab/coeiro-operator/issues"
57
+ },
58
+ "publishConfig": {
59
+ "access": "public"
60
+ }
61
+ }