neuro-cli 4.1.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/LICENSE +21 -0
- package/README.md +510 -0
- package/dist/advisor/advisor.d.ts +50 -0
- package/dist/advisor/advisor.js +178 -0
- package/dist/agents/base.d.ts +62 -0
- package/dist/agents/base.js +215 -0
- package/dist/agents/orchestrator.d.ts +46 -0
- package/dist/agents/orchestrator.js +192 -0
- package/dist/agents/team.d.ts +51 -0
- package/dist/agents/team.js +210 -0
- package/dist/api/models.d.ts +23 -0
- package/dist/api/models.js +514 -0
- package/dist/api/ollama.d.ts +153 -0
- package/dist/api/ollama.js +751 -0
- package/dist/api/openrouter.d.ts +55 -0
- package/dist/api/openrouter.js +223 -0
- package/dist/commands/commands.d.ts +43 -0
- package/dist/commands/commands.js +308 -0
- package/dist/config/config.d.ts +8 -0
- package/dist/config/config.js +311 -0
- package/dist/context/compaction.d.ts +54 -0
- package/dist/context/compaction.js +251 -0
- package/dist/context/custom-agents.d.ts +107 -0
- package/dist/context/custom-agents.js +397 -0
- package/dist/context/custom-tools.d.ts +120 -0
- package/dist/context/custom-tools.js +564 -0
- package/dist/context/git-checkpoint.d.ts +52 -0
- package/dist/context/git-checkpoint.js +240 -0
- package/dist/context/neuro-md.d.ts +48 -0
- package/dist/context/neuro-md.js +202 -0
- package/dist/context/neuroignore.d.ts +102 -0
- package/dist/context/neuroignore.js +441 -0
- package/dist/context/repo-map.d.ts +38 -0
- package/dist/context/repo-map.js +220 -0
- package/dist/context/skill-standard.d.ts +262 -0
- package/dist/context/skill-standard.js +1156 -0
- package/dist/context/skill-system.d.ts +75 -0
- package/dist/context/skill-system.js +578 -0
- package/dist/context/tree-sitter.d.ts +99 -0
- package/dist/context/tree-sitter.js +1956 -0
- package/dist/core/acp.d.ts +325 -0
- package/dist/core/acp.js +1498 -0
- package/dist/core/api-server.d.ts +143 -0
- package/dist/core/api-server.js +550 -0
- package/dist/core/approval.d.ts +81 -0
- package/dist/core/approval.js +432 -0
- package/dist/core/auto-compact.d.ts +127 -0
- package/dist/core/auto-compact.js +436 -0
- package/dist/core/auto-mode.d.ts +232 -0
- package/dist/core/auto-mode.js +831 -0
- package/dist/core/background-session.d.ts +166 -0
- package/dist/core/background-session.js +696 -0
- package/dist/core/cicd.d.ts +257 -0
- package/dist/core/cicd.js +1443 -0
- package/dist/core/cloud-sync.d.ts +156 -0
- package/dist/core/cloud-sync.js +582 -0
- package/dist/core/code-review.d.ts +132 -0
- package/dist/core/code-review.js +1191 -0
- package/dist/core/completion.d.ts +49 -0
- package/dist/core/completion.js +384 -0
- package/dist/core/context.d.ts +38 -0
- package/dist/core/context.js +144 -0
- package/dist/core/diff-preview.d.ts +35 -0
- package/dist/core/diff-preview.js +173 -0
- package/dist/core/doom-loop.d.ts +51 -0
- package/dist/core/doom-loop.js +179 -0
- package/dist/core/engine.d.ts +183 -0
- package/dist/core/engine.js +942 -0
- package/dist/core/extended-thinking.d.ts +103 -0
- package/dist/core/extended-thinking.js +269 -0
- package/dist/core/fallback.d.ts +54 -0
- package/dist/core/fallback.js +104 -0
- package/dist/core/git-worktree.d.ts +139 -0
- package/dist/core/git-worktree.js +614 -0
- package/dist/core/headless.d.ts +30 -0
- package/dist/core/headless.js +101 -0
- package/dist/core/i18n.d.ts +83 -0
- package/dist/core/i18n.js +739 -0
- package/dist/core/linting.d.ts +129 -0
- package/dist/core/linting.js +699 -0
- package/dist/core/model-router.d.ts +109 -0
- package/dist/core/model-router.js +581 -0
- package/dist/core/multi-model.d.ts +243 -0
- package/dist/core/multi-model.js +1099 -0
- package/dist/core/multi-session.d.ts +144 -0
- package/dist/core/multi-session.js +442 -0
- package/dist/core/multimodal.d.ts +125 -0
- package/dist/core/multimodal.js +286 -0
- package/dist/core/observability.d.ts +93 -0
- package/dist/core/observability.js +737 -0
- package/dist/core/os-sandbox.d.ts +122 -0
- package/dist/core/os-sandbox.js +1193 -0
- package/dist/core/outcome-grading.d.ts +228 -0
- package/dist/core/outcome-grading.js +1123 -0
- package/dist/core/output-styles.d.ts +57 -0
- package/dist/core/output-styles.js +382 -0
- package/dist/core/parallel-agents.d.ts +183 -0
- package/dist/core/parallel-agents.js +563 -0
- package/dist/core/plugin-bundle.d.ts +236 -0
- package/dist/core/plugin-bundle.js +887 -0
- package/dist/core/plugin-sdk.d.ts +139 -0
- package/dist/core/plugin-sdk.js +273 -0
- package/dist/core/prompt-cache.d.ts +163 -0
- package/dist/core/prompt-cache.js +599 -0
- package/dist/core/sandbox.d.ts +127 -0
- package/dist/core/sandbox.js +369 -0
- package/dist/core/scheduled-tasks.d.ts +151 -0
- package/dist/core/scheduled-tasks.js +502 -0
- package/dist/core/security-scanner.d.ts +160 -0
- package/dist/core/security-scanner.js +1494 -0
- package/dist/core/session.d.ts +83 -0
- package/dist/core/session.js +269 -0
- package/dist/core/shell-completion.d.ts +51 -0
- package/dist/core/shell-completion.js +674 -0
- package/dist/core/smart-monitor.d.ts +146 -0
- package/dist/core/smart-monitor.js +1199 -0
- package/dist/core/spec-driven.d.ts +233 -0
- package/dist/core/spec-driven.js +1485 -0
- package/dist/core/spending-warnings.d.ts +123 -0
- package/dist/core/spending-warnings.js +456 -0
- package/dist/core/sub-agent.d.ts +298 -0
- package/dist/core/sub-agent.js +1023 -0
- package/dist/core/telemetry.d.ts +157 -0
- package/dist/core/telemetry.js +412 -0
- package/dist/core/terminal-ux.d.ts +134 -0
- package/dist/core/terminal-ux.js +649 -0
- package/dist/core/testing.d.ts +146 -0
- package/dist/core/testing.js +1200 -0
- package/dist/core/types.d.ts +439 -0
- package/dist/core/types.js +6 -0
- package/dist/core/undo-redo.d.ts +112 -0
- package/dist/core/undo-redo.js +290 -0
- package/dist/core/updater.d.ts +159 -0
- package/dist/core/updater.js +608 -0
- package/dist/core/vim-mode.d.ts +151 -0
- package/dist/core/vim-mode.js +771 -0
- package/dist/core/voice.d.ts +137 -0
- package/dist/core/voice.js +538 -0
- package/dist/core/web-dashboard.d.ts +109 -0
- package/dist/core/web-dashboard.js +484 -0
- package/dist/hooks/hooks.d.ts +74 -0
- package/dist/hooks/hooks.js +160 -0
- package/dist/hooks/llm-evaluator.d.ts +165 -0
- package/dist/hooks/llm-evaluator.js +560 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1186 -0
- package/dist/lsp/lsp-manager.d.ts +63 -0
- package/dist/lsp/lsp-manager.js +351 -0
- package/dist/mcp/client.d.ts +133 -0
- package/dist/mcp/client.js +684 -0
- package/dist/mcp/mcp-apps.d.ts +70 -0
- package/dist/mcp/mcp-apps.js +1007 -0
- package/dist/tools/bash.d.ts +5 -0
- package/dist/tools/bash.js +195 -0
- package/dist/tools/browser.d.ts +92 -0
- package/dist/tools/browser.js +1570 -0
- package/dist/tools/extended.d.ts +6 -0
- package/dist/tools/extended.js +191 -0
- package/dist/tools/file.d.ts +10 -0
- package/dist/tools/file.js +382 -0
- package/dist/tools/github.d.ts +389 -0
- package/dist/tools/github.js +759 -0
- package/dist/tools/index.d.ts +9 -0
- package/dist/tools/index.js +40 -0
- package/dist/tools/memory.d.ts +6 -0
- package/dist/tools/memory.js +197 -0
- package/dist/tools/registry.d.ts +29 -0
- package/dist/tools/registry.js +64 -0
- package/dist/tools/web.d.ts +6 -0
- package/dist/tools/web.js +150 -0
- package/dist/ui/renderer.d.ts +97 -0
- package/dist/ui/renderer.js +279 -0
- package/dist/ui/theme.d.ts +27 -0
- package/dist/ui/theme.js +106 -0
- package/package.json +83 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
export interface VoiceConfig {
|
|
2
|
+
/** Whether voice I/O is enabled */
|
|
3
|
+
enabled: boolean;
|
|
4
|
+
/** TTS engine to use */
|
|
5
|
+
ttsEngine: 'system' | 'espeak' | 'say' | 'auto';
|
|
6
|
+
/** STT engine to use */
|
|
7
|
+
sttEngine: 'system' | 'whisper' | 'auto';
|
|
8
|
+
/** TTS voice name */
|
|
9
|
+
voiceName: string;
|
|
10
|
+
/** TTS speech rate (words per minute or 0.5-2.0) */
|
|
11
|
+
speechRate: number;
|
|
12
|
+
/** TTS volume (0-100 or 0.0-1.0) */
|
|
13
|
+
volume: number;
|
|
14
|
+
/** TTS pitch adjustment */
|
|
15
|
+
pitch: number;
|
|
16
|
+
/** Language for TTS/STT */
|
|
17
|
+
language: string;
|
|
18
|
+
/** Whether to auto-speak assistant responses */
|
|
19
|
+
autoSpeak: boolean;
|
|
20
|
+
/** Whether to listen for voice commands continuously */
|
|
21
|
+
continuousListening: boolean;
|
|
22
|
+
/** Voice command prefix (e.g. "hey neuro") */
|
|
23
|
+
wakeWord: string;
|
|
24
|
+
/** STT silence timeout in ms */
|
|
25
|
+
silenceTimeout: number;
|
|
26
|
+
}
|
|
27
|
+
export interface VoiceCommand {
|
|
28
|
+
command: string;
|
|
29
|
+
pattern: RegExp;
|
|
30
|
+
handler: (match: RegExpMatchArray) => void;
|
|
31
|
+
description: string;
|
|
32
|
+
}
|
|
33
|
+
export interface VoiceRecognitionResult {
|
|
34
|
+
text: string;
|
|
35
|
+
confidence: number;
|
|
36
|
+
isCommand: boolean;
|
|
37
|
+
commandName?: string;
|
|
38
|
+
}
|
|
39
|
+
export declare class VoiceIO {
|
|
40
|
+
private config;
|
|
41
|
+
private activeTTSProcess;
|
|
42
|
+
private isSpeaking;
|
|
43
|
+
private isListening;
|
|
44
|
+
private voiceCommands;
|
|
45
|
+
private ttsAvailable;
|
|
46
|
+
private sttAvailable;
|
|
47
|
+
private speechQueue;
|
|
48
|
+
private onTranscript;
|
|
49
|
+
constructor(config?: Partial<VoiceConfig>);
|
|
50
|
+
/**
|
|
51
|
+
* Check if voice I/O is enabled
|
|
52
|
+
*/
|
|
53
|
+
isEnabled(): boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Enable voice I/O
|
|
56
|
+
*/
|
|
57
|
+
enable(): void;
|
|
58
|
+
/**
|
|
59
|
+
* Disable voice I/O
|
|
60
|
+
*/
|
|
61
|
+
disable(): void;
|
|
62
|
+
/**
|
|
63
|
+
* Toggle voice I/O
|
|
64
|
+
*/
|
|
65
|
+
toggle(): boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Speak text using TTS
|
|
68
|
+
*/
|
|
69
|
+
speak(text: string): Promise<boolean>;
|
|
70
|
+
/**
|
|
71
|
+
* Speak text asynchronously (queue it)
|
|
72
|
+
*/
|
|
73
|
+
speakAsync(text: string): void;
|
|
74
|
+
/**
|
|
75
|
+
* Stop current TTS
|
|
76
|
+
*/
|
|
77
|
+
stopSpeaking(): void;
|
|
78
|
+
/**
|
|
79
|
+
* Check if currently speaking
|
|
80
|
+
*/
|
|
81
|
+
getIsSpeaking(): boolean;
|
|
82
|
+
/**
|
|
83
|
+
* Start listening for voice input
|
|
84
|
+
*/
|
|
85
|
+
startListening(callback: (text: string) => void): Promise<boolean>;
|
|
86
|
+
/**
|
|
87
|
+
* Stop listening for voice input
|
|
88
|
+
*/
|
|
89
|
+
stopListening(): void;
|
|
90
|
+
/**
|
|
91
|
+
* Check if currently listening
|
|
92
|
+
*/
|
|
93
|
+
getIsListening(): boolean;
|
|
94
|
+
/**
|
|
95
|
+
* Process a voice transcript for commands
|
|
96
|
+
*/
|
|
97
|
+
processVoiceInput(text: string): VoiceRecognitionResult;
|
|
98
|
+
/**
|
|
99
|
+
* Register a voice command
|
|
100
|
+
*/
|
|
101
|
+
registerCommand(command: VoiceCommand): void;
|
|
102
|
+
/**
|
|
103
|
+
* List registered voice commands
|
|
104
|
+
*/
|
|
105
|
+
listCommands(): VoiceCommand[];
|
|
106
|
+
/**
|
|
107
|
+
* Set TTS engine
|
|
108
|
+
*/
|
|
109
|
+
setTTSEngine(engine: VoiceConfig['ttsEngine']): void;
|
|
110
|
+
/**
|
|
111
|
+
* Set STT engine
|
|
112
|
+
*/
|
|
113
|
+
setSTTEngine(engine: VoiceConfig['sttEngine']): void;
|
|
114
|
+
/**
|
|
115
|
+
* Set voice parameters
|
|
116
|
+
*/
|
|
117
|
+
setVoice(params: Partial<Pick<VoiceConfig, 'voiceName' | 'speechRate' | 'volume' | 'pitch' | 'language'>>): void;
|
|
118
|
+
/**
|
|
119
|
+
* List available TTS voices
|
|
120
|
+
*/
|
|
121
|
+
listVoices(): string[];
|
|
122
|
+
/**
|
|
123
|
+
* Get config
|
|
124
|
+
*/
|
|
125
|
+
getConfig(): VoiceConfig;
|
|
126
|
+
/**
|
|
127
|
+
* Print voice status
|
|
128
|
+
*/
|
|
129
|
+
printStatus(): void;
|
|
130
|
+
private checkTTSAvailability;
|
|
131
|
+
private checkSTTAvailability;
|
|
132
|
+
private cleanTextForTTS;
|
|
133
|
+
private registerDefaultCommands;
|
|
134
|
+
private saveConfig;
|
|
135
|
+
private loadConfig;
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=voice.d.ts.map
|
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// NeuroCLI - Voice I/O System
|
|
3
|
+
// Text-to-speech output, speech-to-text input,
|
|
4
|
+
// voice command recognition, /voice on/off
|
|
5
|
+
// Configurable voice settings
|
|
6
|
+
// ============================================================
|
|
7
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
|
|
8
|
+
import { join } from 'path';
|
|
9
|
+
import { homedir } from 'os';
|
|
10
|
+
import { execSync, spawn } from 'child_process';
|
|
11
|
+
import chalk from 'chalk';
|
|
12
|
+
// -----------------------------------------------------------
|
|
13
|
+
// Default config
|
|
14
|
+
// -----------------------------------------------------------
|
|
15
|
+
const VOICE_CONFIG_PATH = join(homedir(), '.neuro', 'voice-config.json');
|
|
16
|
+
function detectTTSEngine() {
|
|
17
|
+
// macOS has 'say' built in
|
|
18
|
+
if (process.platform === 'darwin')
|
|
19
|
+
return 'say';
|
|
20
|
+
// Check for espeak on Linux
|
|
21
|
+
try {
|
|
22
|
+
execSync('which espeak', { stdio: 'ignore' });
|
|
23
|
+
return 'espeak';
|
|
24
|
+
}
|
|
25
|
+
catch { /* not found */ }
|
|
26
|
+
try {
|
|
27
|
+
execSync('which espeak-ng', { stdio: 'ignore' });
|
|
28
|
+
return 'espeak';
|
|
29
|
+
}
|
|
30
|
+
catch { /* not found */ }
|
|
31
|
+
return 'system';
|
|
32
|
+
}
|
|
33
|
+
function detectSTTEngine() {
|
|
34
|
+
try {
|
|
35
|
+
execSync('which whisper', { stdio: 'ignore' });
|
|
36
|
+
return 'whisper';
|
|
37
|
+
}
|
|
38
|
+
catch { /* not found */ }
|
|
39
|
+
return 'system';
|
|
40
|
+
}
|
|
41
|
+
function defaultConfig() {
|
|
42
|
+
return {
|
|
43
|
+
enabled: false,
|
|
44
|
+
ttsEngine: 'auto',
|
|
45
|
+
sttEngine: 'auto',
|
|
46
|
+
voiceName: '',
|
|
47
|
+
speechRate: 1.0,
|
|
48
|
+
volume: 0.8,
|
|
49
|
+
pitch: 1.0,
|
|
50
|
+
language: 'en',
|
|
51
|
+
autoSpeak: false,
|
|
52
|
+
continuousListening: false,
|
|
53
|
+
wakeWord: 'hey neuro',
|
|
54
|
+
silenceTimeout: 3000,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
// -----------------------------------------------------------
|
|
58
|
+
// VoiceIO
|
|
59
|
+
// -----------------------------------------------------------
|
|
60
|
+
export class VoiceIO {
|
|
61
|
+
config;
|
|
62
|
+
activeTTSProcess = null;
|
|
63
|
+
isSpeaking = false;
|
|
64
|
+
isListening = false;
|
|
65
|
+
voiceCommands = [];
|
|
66
|
+
ttsAvailable = false;
|
|
67
|
+
sttAvailable = false;
|
|
68
|
+
speechQueue = [];
|
|
69
|
+
onTranscript = null;
|
|
70
|
+
constructor(config) {
|
|
71
|
+
this.config = { ...defaultConfig(), ...config };
|
|
72
|
+
this.loadConfig();
|
|
73
|
+
// Auto-detect engines
|
|
74
|
+
if (this.config.ttsEngine === 'auto') {
|
|
75
|
+
this.config.ttsEngine = detectTTSEngine();
|
|
76
|
+
}
|
|
77
|
+
if (this.config.sttEngine === 'auto') {
|
|
78
|
+
this.config.sttEngine = detectSTTEngine();
|
|
79
|
+
}
|
|
80
|
+
// Check availability
|
|
81
|
+
this.checkTTSAvailability();
|
|
82
|
+
this.checkSTTAvailability();
|
|
83
|
+
// Register default voice commands
|
|
84
|
+
this.registerDefaultCommands();
|
|
85
|
+
}
|
|
86
|
+
// ----------------------------------------------------------
|
|
87
|
+
// Public API
|
|
88
|
+
// ----------------------------------------------------------
|
|
89
|
+
/**
|
|
90
|
+
* Check if voice I/O is enabled
|
|
91
|
+
*/
|
|
92
|
+
isEnabled() {
|
|
93
|
+
return this.config.enabled;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Enable voice I/O
|
|
97
|
+
*/
|
|
98
|
+
enable() {
|
|
99
|
+
if (!this.ttsAvailable && !this.sttAvailable) {
|
|
100
|
+
console.log(chalk.yellow('No TTS/STT engines found. Install espeak or use macOS for TTS, or whisper for STT.'));
|
|
101
|
+
console.log(chalk.gray('Voice I/O enabled in limited mode (will use system APIs if available).'));
|
|
102
|
+
}
|
|
103
|
+
this.config.enabled = true;
|
|
104
|
+
this.saveConfig();
|
|
105
|
+
console.log(chalk.green('Voice I/O enabled.'));
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Disable voice I/O
|
|
109
|
+
*/
|
|
110
|
+
disable() {
|
|
111
|
+
this.stopSpeaking();
|
|
112
|
+
this.stopListening();
|
|
113
|
+
this.config.enabled = false;
|
|
114
|
+
this.saveConfig();
|
|
115
|
+
console.log(chalk.gray('Voice I/O disabled.'));
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Toggle voice I/O
|
|
119
|
+
*/
|
|
120
|
+
toggle() {
|
|
121
|
+
if (this.config.enabled)
|
|
122
|
+
this.disable();
|
|
123
|
+
else
|
|
124
|
+
this.enable();
|
|
125
|
+
return this.config.enabled;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Speak text using TTS
|
|
129
|
+
*/
|
|
130
|
+
async speak(text) {
|
|
131
|
+
if (!this.config.enabled || !this.ttsAvailable)
|
|
132
|
+
return false;
|
|
133
|
+
// Clean text for TTS (remove markdown, code blocks, etc.)
|
|
134
|
+
const cleanText = this.cleanTextForTTS(text);
|
|
135
|
+
if (!cleanText.trim())
|
|
136
|
+
return false;
|
|
137
|
+
return new Promise((resolve) => {
|
|
138
|
+
try {
|
|
139
|
+
let command;
|
|
140
|
+
let args;
|
|
141
|
+
switch (this.config.ttsEngine) {
|
|
142
|
+
case 'say': // macOS
|
|
143
|
+
command = 'say';
|
|
144
|
+
args = [];
|
|
145
|
+
if (this.config.voiceName)
|
|
146
|
+
args.push('-v', this.config.voiceName);
|
|
147
|
+
if (this.config.speechRate !== 1.0)
|
|
148
|
+
args.push('-r', String(Math.round(this.config.speechRate * 200)));
|
|
149
|
+
args.push(cleanText);
|
|
150
|
+
break;
|
|
151
|
+
case 'espeak':
|
|
152
|
+
command = 'espeak';
|
|
153
|
+
args = [];
|
|
154
|
+
if (this.config.voiceName)
|
|
155
|
+
args.push('-v', this.config.voiceName);
|
|
156
|
+
if (this.config.speechRate > 0)
|
|
157
|
+
args.push('-s', String(Math.round(this.config.speechRate * 175)));
|
|
158
|
+
if (this.config.pitch !== 1.0)
|
|
159
|
+
args.push('-p', String(Math.round(this.config.pitch * 50)));
|
|
160
|
+
if (this.config.volume < 1.0)
|
|
161
|
+
args.push('-a', String(Math.round(this.config.volume * 100)));
|
|
162
|
+
args.push(cleanText);
|
|
163
|
+
break;
|
|
164
|
+
default:
|
|
165
|
+
// Try system default
|
|
166
|
+
if (process.platform === 'darwin') {
|
|
167
|
+
command = 'say';
|
|
168
|
+
args = [cleanText];
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
resolve(false);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
this.isSpeaking = true;
|
|
176
|
+
const proc = spawn(command, args, { stdio: 'ignore' });
|
|
177
|
+
this.activeTTSProcess = proc;
|
|
178
|
+
proc.on('close', () => {
|
|
179
|
+
this.isSpeaking = false;
|
|
180
|
+
this.activeTTSProcess = null;
|
|
181
|
+
// Process queue
|
|
182
|
+
if (this.speechQueue.length > 0) {
|
|
183
|
+
const next = this.speechQueue.shift();
|
|
184
|
+
this.speak(next).then(resolve);
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
resolve(true);
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
proc.on('error', () => {
|
|
191
|
+
this.isSpeaking = false;
|
|
192
|
+
this.activeTTSProcess = null;
|
|
193
|
+
resolve(false);
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
resolve(false);
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Speak text asynchronously (queue it)
|
|
203
|
+
*/
|
|
204
|
+
speakAsync(text) {
|
|
205
|
+
if (!this.config.enabled)
|
|
206
|
+
return;
|
|
207
|
+
if (this.isSpeaking) {
|
|
208
|
+
this.speechQueue.push(text);
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
this.speak(text).catch(() => { });
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Stop current TTS
|
|
216
|
+
*/
|
|
217
|
+
stopSpeaking() {
|
|
218
|
+
if (this.activeTTSProcess) {
|
|
219
|
+
try {
|
|
220
|
+
this.activeTTSProcess.kill();
|
|
221
|
+
}
|
|
222
|
+
catch { /* ignore */ }
|
|
223
|
+
this.activeTTSProcess = null;
|
|
224
|
+
}
|
|
225
|
+
this.isSpeaking = false;
|
|
226
|
+
this.speechQueue = [];
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Check if currently speaking
|
|
230
|
+
*/
|
|
231
|
+
getIsSpeaking() {
|
|
232
|
+
return this.isSpeaking;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Start listening for voice input
|
|
236
|
+
*/
|
|
237
|
+
async startListening(callback) {
|
|
238
|
+
if (!this.config.enabled || !this.sttAvailable)
|
|
239
|
+
return false;
|
|
240
|
+
this.onTranscript = callback;
|
|
241
|
+
this.isListening = true;
|
|
242
|
+
console.log(chalk.cyan('Listening... (speak now)'));
|
|
243
|
+
try {
|
|
244
|
+
switch (this.config.sttEngine) {
|
|
245
|
+
case 'whisper': {
|
|
246
|
+
// Use whisper CLI to record and transcribe
|
|
247
|
+
const proc = spawn('whisper', [
|
|
248
|
+
'--model', 'base',
|
|
249
|
+
'--language', this.config.language,
|
|
250
|
+
'--output_format', 'txt',
|
|
251
|
+
'--output_dir', '-',
|
|
252
|
+
], { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
253
|
+
let transcript = '';
|
|
254
|
+
proc.stdout.on('data', (data) => {
|
|
255
|
+
transcript += data.toString();
|
|
256
|
+
});
|
|
257
|
+
proc.on('close', () => {
|
|
258
|
+
this.isListening = false;
|
|
259
|
+
if (transcript.trim() && this.onTranscript) {
|
|
260
|
+
this.onTranscript(transcript.trim());
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
return true;
|
|
264
|
+
}
|
|
265
|
+
default: {
|
|
266
|
+
// System API fallback - use arecord + whisper or similar
|
|
267
|
+
console.log(chalk.yellow('System STT not fully supported. Consider installing whisper CLI.'));
|
|
268
|
+
this.isListening = false;
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
catch {
|
|
274
|
+
this.isListening = false;
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Stop listening for voice input
|
|
280
|
+
*/
|
|
281
|
+
stopListening() {
|
|
282
|
+
this.isListening = false;
|
|
283
|
+
this.onTranscript = null;
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Check if currently listening
|
|
287
|
+
*/
|
|
288
|
+
getIsListening() {
|
|
289
|
+
return this.isListening;
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Process a voice transcript for commands
|
|
293
|
+
*/
|
|
294
|
+
processVoiceInput(text) {
|
|
295
|
+
const lower = text.toLowerCase().trim();
|
|
296
|
+
// Check for wake word
|
|
297
|
+
if (this.config.wakeWord && !lower.startsWith(this.config.wakeWord.toLowerCase())) {
|
|
298
|
+
return {
|
|
299
|
+
text,
|
|
300
|
+
confidence: 1.0,
|
|
301
|
+
isCommand: false,
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
// Remove wake word
|
|
305
|
+
const commandText = this.config.wakeWord
|
|
306
|
+
? lower.replace(this.config.wakeWord.toLowerCase(), '').trim()
|
|
307
|
+
: lower;
|
|
308
|
+
// Check voice commands
|
|
309
|
+
for (const cmd of this.voiceCommands) {
|
|
310
|
+
const match = commandText.match(cmd.pattern);
|
|
311
|
+
if (match) {
|
|
312
|
+
cmd.handler(match);
|
|
313
|
+
return {
|
|
314
|
+
text,
|
|
315
|
+
confidence: 1.0,
|
|
316
|
+
isCommand: true,
|
|
317
|
+
commandName: cmd.command,
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
return {
|
|
322
|
+
text,
|
|
323
|
+
confidence: 1.0,
|
|
324
|
+
isCommand: false,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Register a voice command
|
|
329
|
+
*/
|
|
330
|
+
registerCommand(command) {
|
|
331
|
+
this.voiceCommands.push(command);
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* List registered voice commands
|
|
335
|
+
*/
|
|
336
|
+
listCommands() {
|
|
337
|
+
return [...this.voiceCommands];
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Set TTS engine
|
|
341
|
+
*/
|
|
342
|
+
setTTSEngine(engine) {
|
|
343
|
+
this.config.ttsEngine = engine;
|
|
344
|
+
this.checkTTSAvailability();
|
|
345
|
+
this.saveConfig();
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Set STT engine
|
|
349
|
+
*/
|
|
350
|
+
setSTTEngine(engine) {
|
|
351
|
+
this.config.sttEngine = engine;
|
|
352
|
+
this.checkSTTAvailability();
|
|
353
|
+
this.saveConfig();
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Set voice parameters
|
|
357
|
+
*/
|
|
358
|
+
setVoice(params) {
|
|
359
|
+
if (params.voiceName !== undefined)
|
|
360
|
+
this.config.voiceName = params.voiceName;
|
|
361
|
+
if (params.speechRate !== undefined)
|
|
362
|
+
this.config.speechRate = params.speechRate;
|
|
363
|
+
if (params.volume !== undefined)
|
|
364
|
+
this.config.volume = params.volume;
|
|
365
|
+
if (params.pitch !== undefined)
|
|
366
|
+
this.config.pitch = params.pitch;
|
|
367
|
+
if (params.language !== undefined)
|
|
368
|
+
this.config.language = params.language;
|
|
369
|
+
this.saveConfig();
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* List available TTS voices
|
|
373
|
+
*/
|
|
374
|
+
listVoices() {
|
|
375
|
+
try {
|
|
376
|
+
switch (this.config.ttsEngine) {
|
|
377
|
+
case 'say': {
|
|
378
|
+
const output = execSync('say -v "?"', { encoding: 'utf-8' });
|
|
379
|
+
return output.split('\n').filter(l => l.trim()).map(l => l.split(/\s+/)[0]).filter(Boolean);
|
|
380
|
+
}
|
|
381
|
+
case 'espeak': {
|
|
382
|
+
const output = execSync('espeak --voices', { encoding: 'utf-8' });
|
|
383
|
+
return output.split('\n').slice(1).filter(l => l.trim()).map(l => {
|
|
384
|
+
const parts = l.trim().split(/\s+/);
|
|
385
|
+
return parts.length >= 4 ? parts[3] : '';
|
|
386
|
+
}).filter(Boolean);
|
|
387
|
+
}
|
|
388
|
+
default:
|
|
389
|
+
return [];
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
catch {
|
|
393
|
+
return [];
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Get config
|
|
398
|
+
*/
|
|
399
|
+
getConfig() {
|
|
400
|
+
return { ...this.config };
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Print voice status
|
|
404
|
+
*/
|
|
405
|
+
printStatus() {
|
|
406
|
+
console.log('');
|
|
407
|
+
console.log(chalk.bold('--- NeuroCLI Voice I/O ---'));
|
|
408
|
+
console.log(` Enabled: ${this.config.enabled ? chalk.green('yes') : chalk.gray('no')}`);
|
|
409
|
+
console.log(` TTS Engine: ${chalk.cyan(this.config.ttsEngine)} ${this.ttsAvailable ? chalk.green('(available)') : chalk.red('(unavailable)')}`);
|
|
410
|
+
console.log(` STT Engine: ${chalk.cyan(this.config.sttEngine)} ${this.sttAvailable ? chalk.green('(available)') : chalk.red('(unavailable)')}`);
|
|
411
|
+
console.log(` Voice: ${this.config.voiceName || chalk.gray('(default)')}`);
|
|
412
|
+
console.log(` Rate: ${this.config.speechRate}`);
|
|
413
|
+
console.log(` Language: ${this.config.language}`);
|
|
414
|
+
console.log(` Auto-speak: ${this.config.autoSpeak ? chalk.green('on') : chalk.gray('off')}`);
|
|
415
|
+
console.log(` Wake word: ${this.config.wakeWord || chalk.gray('(none)')}`);
|
|
416
|
+
console.log(` Voice commands: ${this.voiceCommands.length}`);
|
|
417
|
+
if (this.isSpeaking)
|
|
418
|
+
console.log(` ${chalk.yellow('Currently speaking...')}`);
|
|
419
|
+
if (this.isListening)
|
|
420
|
+
console.log(` ${chalk.cyan('Currently listening...')}`);
|
|
421
|
+
console.log('');
|
|
422
|
+
}
|
|
423
|
+
// ----------------------------------------------------------
|
|
424
|
+
// Private helpers
|
|
425
|
+
// ----------------------------------------------------------
|
|
426
|
+
checkTTSAvailability() {
|
|
427
|
+
try {
|
|
428
|
+
switch (this.config.ttsEngine) {
|
|
429
|
+
case 'say':
|
|
430
|
+
execSync('which say', { stdio: 'ignore' });
|
|
431
|
+
this.ttsAvailable = true;
|
|
432
|
+
break;
|
|
433
|
+
case 'espeak':
|
|
434
|
+
try {
|
|
435
|
+
execSync('which espeak', { stdio: 'ignore' });
|
|
436
|
+
}
|
|
437
|
+
catch {
|
|
438
|
+
execSync('which espeak-ng', { stdio: 'ignore' });
|
|
439
|
+
}
|
|
440
|
+
this.ttsAvailable = true;
|
|
441
|
+
break;
|
|
442
|
+
default:
|
|
443
|
+
this.ttsAvailable = process.platform === 'darwin';
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
catch {
|
|
447
|
+
this.ttsAvailable = false;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
checkSTTAvailability() {
|
|
451
|
+
try {
|
|
452
|
+
if (this.config.sttEngine === 'whisper') {
|
|
453
|
+
execSync('which whisper', { stdio: 'ignore' });
|
|
454
|
+
this.sttAvailable = true;
|
|
455
|
+
}
|
|
456
|
+
else {
|
|
457
|
+
this.sttAvailable = false;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
catch {
|
|
461
|
+
this.sttAvailable = false;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
cleanTextForTTS(text) {
|
|
465
|
+
return text
|
|
466
|
+
// Remove code blocks
|
|
467
|
+
.replace(/```[\s\S]*?```/g, ' code block ')
|
|
468
|
+
// Remove inline code
|
|
469
|
+
.replace(/`[^`]+`/g, ' code ')
|
|
470
|
+
// Remove markdown formatting
|
|
471
|
+
.replace(/\*\*([^*]+)\*\*/g, '$1')
|
|
472
|
+
.replace(/\*([^*]+)\*/g, '$1')
|
|
473
|
+
.replace(/__([^_]+)__/g, '$1')
|
|
474
|
+
.replace(/_([^_]+)_/g, '$1')
|
|
475
|
+
.replace(/##?\s+/g, '')
|
|
476
|
+
// Remove URLs
|
|
477
|
+
.replace(/https?:\/\/\S+/g, ' link ')
|
|
478
|
+
// Remove ANSI escape codes
|
|
479
|
+
.replace(/\x1b\[[0-9;]*m/g, '')
|
|
480
|
+
// Collapse whitespace
|
|
481
|
+
.replace(/\s+/g, ' ')
|
|
482
|
+
.trim();
|
|
483
|
+
}
|
|
484
|
+
registerDefaultCommands() {
|
|
485
|
+
this.voiceCommands = [
|
|
486
|
+
{
|
|
487
|
+
command: 'exit',
|
|
488
|
+
pattern: /^(exit|quit|bye)/,
|
|
489
|
+
handler: () => console.log(chalk.yellow('Use /exit or Ctrl+C to quit.')),
|
|
490
|
+
description: 'Exit NeuroCLI',
|
|
491
|
+
},
|
|
492
|
+
{
|
|
493
|
+
command: 'model',
|
|
494
|
+
pattern: /^(switch model|change model|use model)\s+(.+)/,
|
|
495
|
+
handler: (match) => console.log(chalk.gray(`Model switch requested: ${match[2]}`)),
|
|
496
|
+
description: 'Switch model',
|
|
497
|
+
},
|
|
498
|
+
{
|
|
499
|
+
command: 'clear',
|
|
500
|
+
pattern: /^(clear screen|clear)/,
|
|
501
|
+
handler: () => console.clear(),
|
|
502
|
+
description: 'Clear terminal',
|
|
503
|
+
},
|
|
504
|
+
{
|
|
505
|
+
command: 'help',
|
|
506
|
+
pattern: /^(help|commands)/,
|
|
507
|
+
handler: () => console.log(chalk.gray('Voice commands: exit, model <name>, clear, help, stop')),
|
|
508
|
+
description: 'Show help',
|
|
509
|
+
},
|
|
510
|
+
{
|
|
511
|
+
command: 'stop',
|
|
512
|
+
pattern: /^(stop|shut up|quiet|silence)/,
|
|
513
|
+
handler: () => this.stopSpeaking(),
|
|
514
|
+
description: 'Stop speaking',
|
|
515
|
+
},
|
|
516
|
+
];
|
|
517
|
+
}
|
|
518
|
+
saveConfig() {
|
|
519
|
+
try {
|
|
520
|
+
const dir = join(VOICE_CONFIG_PATH, '..');
|
|
521
|
+
if (!existsSync(dir))
|
|
522
|
+
mkdirSync(dir, { recursive: true });
|
|
523
|
+
writeFileSync(VOICE_CONFIG_PATH, JSON.stringify(this.config, null, 2), 'utf-8');
|
|
524
|
+
}
|
|
525
|
+
catch { /* Silently fail */ }
|
|
526
|
+
}
|
|
527
|
+
loadConfig() {
|
|
528
|
+
try {
|
|
529
|
+
if (existsSync(VOICE_CONFIG_PATH)) {
|
|
530
|
+
const raw = readFileSync(VOICE_CONFIG_PATH, 'utf-8');
|
|
531
|
+
const saved = JSON.parse(raw);
|
|
532
|
+
this.config = { ...this.config, ...saved };
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
catch { /* Silently fail */ }
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
//# sourceMappingURL=voice.js.map
|