claude-phone-local 2.0.2 → 2.0.4
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/cli/lib/commands/setup.js +74 -37
- package/cli/lib/prereqs/checks/disk.js +29 -1
- package/package.json +1 -1
|
@@ -1071,18 +1071,6 @@ async function setupDevice(config) {
|
|
|
1071
1071
|
return true;
|
|
1072
1072
|
}
|
|
1073
1073
|
},
|
|
1074
|
-
{
|
|
1075
|
-
type: 'input',
|
|
1076
|
-
name: 'voiceId',
|
|
1077
|
-
message: 'ElevenLabs voice ID:',
|
|
1078
|
-
default: existingDevice?.voiceId || config.api.elevenlabs.defaultVoiceId || '',
|
|
1079
|
-
validate: (input) => {
|
|
1080
|
-
if (!input || input.trim() === '') {
|
|
1081
|
-
return 'Voice ID is required';
|
|
1082
|
-
}
|
|
1083
|
-
return true;
|
|
1084
|
-
}
|
|
1085
|
-
},
|
|
1086
1074
|
{
|
|
1087
1075
|
type: 'input',
|
|
1088
1076
|
name: 'prompt',
|
|
@@ -1097,39 +1085,88 @@ async function setupDevice(config) {
|
|
|
1097
1085
|
}
|
|
1098
1086
|
]);
|
|
1099
1087
|
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1088
|
+
const device = {
|
|
1089
|
+
name: answers.name,
|
|
1090
|
+
extension: answers.extension,
|
|
1091
|
+
authId: answers.authId,
|
|
1092
|
+
password: answers.password,
|
|
1093
|
+
prompt: answers.prompt
|
|
1094
|
+
};
|
|
1103
1095
|
|
|
1104
|
-
if (
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
const {
|
|
1096
|
+
if (config.api.mode === 'local') {
|
|
1097
|
+
// Local mode: ask for a Piper voice name, not an ElevenLabs voice ID.
|
|
1098
|
+
// Falls back to the call-wide default voice picked in setupAPIKeys().
|
|
1099
|
+
const { voice } = await inquirer.prompt([
|
|
1108
1100
|
{
|
|
1109
|
-
type: '
|
|
1110
|
-
name: '
|
|
1111
|
-
message: '
|
|
1112
|
-
default:
|
|
1101
|
+
type: 'input',
|
|
1102
|
+
name: 'voice',
|
|
1103
|
+
message: 'Piper voice for this device (default: ' + (config.api.local?.voice || 'en_US-lessac-medium') + '):',
|
|
1104
|
+
default: existingDevice?.voice || config.api.local?.voice || 'en_US-lessac-medium'
|
|
1113
1105
|
}
|
|
1114
1106
|
]);
|
|
1115
1107
|
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1108
|
+
device.voice = voice;
|
|
1109
|
+
|
|
1110
|
+
const voicesDir = path.join(config.paths.ttsLocal, 'voices');
|
|
1111
|
+
const dlSpinner = ora(`Checking Piper voice "${voice}"...`).start();
|
|
1112
|
+
try {
|
|
1113
|
+
const result = await ensureVoiceModel(voice, voicesDir);
|
|
1114
|
+
if (result.downloaded) {
|
|
1115
|
+
dlSpinner.succeed(chalk.green(`Downloaded voice "${voice}"`));
|
|
1116
|
+
} else if (result.skipped) {
|
|
1117
|
+
dlSpinner.succeed(chalk.green(`Voice "${voice}" already present`));
|
|
1118
|
+
} else {
|
|
1119
|
+
dlSpinner.warn(chalk.yellow(result.error));
|
|
1120
|
+
}
|
|
1121
|
+
} catch (error) {
|
|
1122
|
+
dlSpinner.warn(chalk.yellow(`Could not auto-download voice: ${error.message}`));
|
|
1123
|
+
console.log(chalk.gray(
|
|
1124
|
+
` Known auto-downloadable voices: ${Object.keys(KNOWN_VOICES).join(', ')}\n` +
|
|
1125
|
+
` For others, place the .onnx + .onnx.json pair in ${voicesDir}/\n`
|
|
1126
|
+
));
|
|
1120
1127
|
}
|
|
1121
1128
|
} else {
|
|
1122
|
-
|
|
1123
|
-
|
|
1129
|
+
// Cloud mode: original ElevenLabs voice ID flow.
|
|
1130
|
+
const { voiceId } = await inquirer.prompt([
|
|
1131
|
+
{
|
|
1132
|
+
type: 'input',
|
|
1133
|
+
name: 'voiceId',
|
|
1134
|
+
message: 'ElevenLabs voice ID:',
|
|
1135
|
+
default: existingDevice?.voiceId || config.api.elevenlabs.defaultVoiceId || '',
|
|
1136
|
+
validate: (input) => {
|
|
1137
|
+
if (!input || input.trim() === '') {
|
|
1138
|
+
return 'Voice ID is required';
|
|
1139
|
+
}
|
|
1140
|
+
return true;
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
]);
|
|
1124
1144
|
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1145
|
+
const voiceSpinner = ora('Validating ElevenLabs voice ID...').start();
|
|
1146
|
+
const voiceValidation = await validateVoiceId(config.api.elevenlabs.apiKey, voiceId);
|
|
1147
|
+
|
|
1148
|
+
if (!voiceValidation.valid) {
|
|
1149
|
+
voiceSpinner.fail(`Voice ID validation failed: ${voiceValidation.error}`);
|
|
1150
|
+
console.log(chalk.yellow('\n⚠️ You can continue setup, but the voice ID may not work.'));
|
|
1151
|
+
const { continueAnyway } = await inquirer.prompt([
|
|
1152
|
+
{
|
|
1153
|
+
type: 'confirm',
|
|
1154
|
+
name: 'continueAnyway',
|
|
1155
|
+
message: 'Continue anyway?',
|
|
1156
|
+
default: false
|
|
1157
|
+
}
|
|
1158
|
+
]);
|
|
1159
|
+
|
|
1160
|
+
if (!continueAnyway) {
|
|
1161
|
+
console.log(chalk.gray('\nReturning to device setup...'));
|
|
1162
|
+
return setupDevice(config);
|
|
1163
|
+
}
|
|
1164
|
+
} else {
|
|
1165
|
+
voiceSpinner.succeed(`Voice ID validated: ${voiceValidation.name}`);
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
device.voiceId = voiceId;
|
|
1169
|
+
}
|
|
1133
1170
|
|
|
1134
1171
|
// Replace first device or add new
|
|
1135
1172
|
if (config.devices.length > 0) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { execSync } from 'child_process';
|
|
1
|
+
import { execSync, execFileSync } from 'child_process';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Check disk space availability
|
|
@@ -80,6 +80,34 @@ function getDiskSpace(platform) {
|
|
|
80
80
|
const availableKB = parseInt(parts[3], 10);
|
|
81
81
|
return availableKB * 1024; // Convert KB to bytes
|
|
82
82
|
}
|
|
83
|
+
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (platform.os === 'win32') {
|
|
88
|
+
// Check the drive the CLI's config directory (~/.claude-phone) lives on,
|
|
89
|
+
// since that's where the ./data volume actually gets written - not
|
|
90
|
+
// necessarily the same drive as the OS install.
|
|
91
|
+
const driveLetter = (process.env.USERPROFILE || process.env.HOMEDRIVE || 'C:')
|
|
92
|
+
.slice(0, 2)
|
|
93
|
+
.toUpperCase();
|
|
94
|
+
|
|
95
|
+
// wmic is deprecated but still present on every supported Windows
|
|
96
|
+
// release; avoids a PowerShell startup cost on every prereq check.
|
|
97
|
+
// execFileSync (argument array, no shell) - driveLetter is untrusted
|
|
98
|
+
// env-derived input, so it must not be interpolated into a shell string.
|
|
99
|
+
const output = execFileSync(
|
|
100
|
+
'wmic',
|
|
101
|
+
['logicaldisk', 'where', `DeviceID='${driveLetter}'`, 'get', 'FreeSpace', '/value'],
|
|
102
|
+
{ encoding: 'utf-8', stdio: 'pipe' }
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
const match = output.match(/FreeSpace=(\d+)/);
|
|
106
|
+
if (match) {
|
|
107
|
+
return parseInt(match[1], 10);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return null;
|
|
83
111
|
}
|
|
84
112
|
|
|
85
113
|
return null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-phone-local",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.4",
|
|
4
4
|
"description": "Local/offline fork of NetworkChuck's claude-phone: talk to Claude Code over 3CX/SIP with faster-whisper STT + Piper TTS in one Docker container.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|