claude-phone-local 2.0.6 → 2.0.7
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 +37 -4
- package/cli/lib/docker.js +35 -8
- package/package.json +1 -1
|
@@ -938,6 +938,36 @@ async function setupAPIKeys(config) {
|
|
|
938
938
|
* @returns {Promise<object>} Updated config
|
|
939
939
|
*/
|
|
940
940
|
async function setupSIP(config) {
|
|
941
|
+
// Detect a native 3CX SBC service on this machine (checks the process and
|
|
942
|
+
// port 5060) so drachtio doesn't try to bind the same port and fail
|
|
943
|
+
// silently at startup - the exact bug this setup wizard used to leave
|
|
944
|
+
// standard ("both"/all-in-one) installs exposed to, since this detection
|
|
945
|
+
// previously only ran in the separate Pi-mode setup path.
|
|
946
|
+
const sbcSpinner = ora('Checking for a local 3CX SBC service...').start();
|
|
947
|
+
let sbcDetected = false;
|
|
948
|
+
try {
|
|
949
|
+
sbcDetected = await detect3cxSbc();
|
|
950
|
+
sbcSpinner.succeed(sbcDetected
|
|
951
|
+
? '3CX SBC detected on this PC (port 5060 in use)'
|
|
952
|
+
: 'No local 3CX SBC detected');
|
|
953
|
+
} catch (err) {
|
|
954
|
+
sbcSpinner.warn('3CX SBC detection failed: ' + err.message);
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
const { sbcOnThisPc } = await inquirer.prompt([
|
|
958
|
+
{
|
|
959
|
+
type: 'confirm',
|
|
960
|
+
name: 'sbcOnThisPc',
|
|
961
|
+
message: 'Is the 3CX SBC service running on this same PC?',
|
|
962
|
+
default: sbcDetected || config.sip.registrar === 'auto' || config.sip.registrar === config.server?.externalIp
|
|
963
|
+
}
|
|
964
|
+
]);
|
|
965
|
+
|
|
966
|
+
config.sip.drachtioSipPort = sbcOnThisPc ? 5070 : 5060;
|
|
967
|
+
if (sbcOnThisPc) {
|
|
968
|
+
console.log(chalk.green('✓ Will use port 5070 for drachtio (avoid conflict with the SBC on port 5060)\n'));
|
|
969
|
+
}
|
|
970
|
+
|
|
941
971
|
const answers = await inquirer.prompt([
|
|
942
972
|
{
|
|
943
973
|
type: 'input',
|
|
@@ -954,11 +984,11 @@ async function setupSIP(config) {
|
|
|
954
984
|
return true;
|
|
955
985
|
}
|
|
956
986
|
},
|
|
957
|
-
{
|
|
987
|
+
...(sbcOnThisPc ? [] : [{
|
|
958
988
|
type: 'input',
|
|
959
989
|
name: 'registrar',
|
|
960
990
|
message: '3CX registrar IP (e.g., 192.168.1.100):',
|
|
961
|
-
default: config.sip.registrar,
|
|
991
|
+
default: config.sip.registrar === 'auto' ? '' : config.sip.registrar,
|
|
962
992
|
validate: (input) => {
|
|
963
993
|
if (!input || input.trim() === '') {
|
|
964
994
|
return 'SIP registrar IP is required';
|
|
@@ -968,11 +998,14 @@ async function setupSIP(config) {
|
|
|
968
998
|
}
|
|
969
999
|
return true;
|
|
970
1000
|
}
|
|
971
|
-
}
|
|
1001
|
+
}])
|
|
972
1002
|
]);
|
|
973
1003
|
|
|
974
1004
|
config.sip.domain = answers.domain;
|
|
975
|
-
|
|
1005
|
+
// 'auto' tracks this machine's current LAN IP on every `claude-phone
|
|
1006
|
+
// start`, same as server.externalIp - the SBC and this app running on the
|
|
1007
|
+
// same PC means both need to move together whenever the network changes.
|
|
1008
|
+
config.sip.registrar = sbcOnThisPc ? 'auto' : answers.registrar;
|
|
976
1009
|
|
|
977
1010
|
return config;
|
|
978
1011
|
}
|
package/cli/lib/docker.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { spawn, execSync } from 'child_process';
|
|
2
2
|
import crypto from 'crypto';
|
|
3
3
|
import fs from 'fs';
|
|
4
|
+
import path from 'path';
|
|
4
5
|
import {
|
|
5
6
|
getDockerComposePath,
|
|
6
7
|
getEnvPath,
|
|
7
8
|
getConfigDir
|
|
8
9
|
} from './config.js';
|
|
10
|
+
import { getLocalIP } from './utils.js';
|
|
9
11
|
|
|
10
12
|
/**
|
|
11
13
|
* Detect which docker compose command to use
|
|
@@ -86,18 +88,32 @@ export async function checkDocker() {
|
|
|
86
88
|
* @returns {string} Docker compose YAML content
|
|
87
89
|
*/
|
|
88
90
|
export function generateDockerCompose(config) {
|
|
89
|
-
const
|
|
90
|
-
const drachtioPort = config.deployment?.pi?.drachtioPort || 5070;
|
|
91
|
+
const drachtioPort = config.deployment?.pi?.drachtioPort || config.sip?.drachtioSipPort || 5070;
|
|
91
92
|
const rtpStart = config.server.rtpStart || 30000;
|
|
92
93
|
const rtpEnd = config.server.rtpEnd || 30100;
|
|
93
94
|
|
|
95
|
+
// This file is written to ~/.claude-phone/ (getDockerComposePath()), which
|
|
96
|
+
// has no Dockerfile or source of its own - those live in the installed npm
|
|
97
|
+
// package directory (config.paths.voiceApp's parent). `build: .` would try
|
|
98
|
+
// to build from ~/.claude-phone and fail (or silently reuse a stale cached
|
|
99
|
+
// image tag from an unrelated build, masking the failure) on any machine
|
|
100
|
+
// without that image already present.
|
|
101
|
+
const projectRoot = config.paths?.voiceApp
|
|
102
|
+
? path.dirname(config.paths.voiceApp)
|
|
103
|
+
: '.';
|
|
104
|
+
|
|
94
105
|
// Single image: drachtio + FreeSWITCH + voice-app + STT + TTS under supervisord.
|
|
95
106
|
// They talk over 127.0.0.1, so there is no container DNS to misconfigure.
|
|
96
107
|
// Everything that must outlive a restart is on the ./data bind mount.
|
|
108
|
+
//
|
|
109
|
+
// EXTERNAL_IP is intentionally NOT set under `environment:` here - it comes
|
|
110
|
+
// from `.env` via env_file below. An explicit `environment:` entry would
|
|
111
|
+
// override env_file, so editing .env after a network change (a new LAN IP,
|
|
112
|
+
// a different office) would silently do nothing.
|
|
97
113
|
return `# Generated by \`claude-phone setup\` - edit .env, not this file.
|
|
98
114
|
services:
|
|
99
115
|
claude-phone:
|
|
100
|
-
build: .
|
|
116
|
+
build: "${projectRoot.replace(/\\/g, '/')}"
|
|
101
117
|
image: claude-phone-local:latest
|
|
102
118
|
container_name: claude-phone
|
|
103
119
|
restart: unless-stopped
|
|
@@ -109,7 +125,6 @@ services:
|
|
|
109
125
|
- STT_LOCAL_URL=http://127.0.0.1:9001
|
|
110
126
|
- TTS_LOCAL_URL=http://127.0.0.1:9002
|
|
111
127
|
- AUDIO_BASE_URL=http://127.0.0.1:3000
|
|
112
|
-
- EXTERNAL_IP=${externalIp}
|
|
113
128
|
- RTP_START=${rtpStart}
|
|
114
129
|
- RTP_END=${rtpEnd}
|
|
115
130
|
ports:
|
|
@@ -136,6 +151,15 @@ export function generateEnvFile(config) {
|
|
|
136
151
|
};
|
|
137
152
|
}
|
|
138
153
|
|
|
154
|
+
// 'auto' means re-detect the current LAN IP every time this is written
|
|
155
|
+
// (i.e. every `claude-phone start`), instead of using whatever IP was
|
|
156
|
+
// current the last time setup ran. A saved concrete IP goes stale the
|
|
157
|
+
// moment the machine moves networks (home <-> office, DHCP renewal) and
|
|
158
|
+
// nothing here writes to it again until setup is manually re-run.
|
|
159
|
+
const resolvedExternalIp = config.server.externalIp === 'auto'
|
|
160
|
+
? getLocalIP()
|
|
161
|
+
: config.server.externalIp;
|
|
162
|
+
|
|
139
163
|
// Determine Claude API URL based on deployment mode
|
|
140
164
|
let claudeApiUrl;
|
|
141
165
|
if (config.deployment && config.deployment.mode === 'pi-split' && config.deployment.pi && config.deployment.pi.macIp) {
|
|
@@ -162,14 +186,14 @@ export function generateEnvFile(config) {
|
|
|
162
186
|
'# ====================================',
|
|
163
187
|
'',
|
|
164
188
|
'# Network Configuration',
|
|
165
|
-
`EXTERNAL_IP=${
|
|
189
|
+
`EXTERNAL_IP=${resolvedExternalIp}`,
|
|
166
190
|
'',
|
|
167
191
|
'# Drachtio Configuration',
|
|
168
192
|
'DRACHTIO_HOST=drachtio',
|
|
169
193
|
'DRACHTIO_PORT=9022',
|
|
170
194
|
`DRACHTIO_SECRET=${config.secrets.drachtio}`,
|
|
171
195
|
// SIP port for Contact header (5070 when 3CX SBC is present, 5060 otherwise)
|
|
172
|
-
`DRACHTIO_SIP_PORT=${config.deployment?.pi?.drachtioPort ||
|
|
196
|
+
`DRACHTIO_SIP_PORT=${config.sip?.drachtioSipPort || config.deployment?.pi?.drachtioPort || 5070}`,
|
|
173
197
|
'',
|
|
174
198
|
'# FreeSWITCH Configuration',
|
|
175
199
|
'FREESWITCH_HOST=freeswitch',
|
|
@@ -179,7 +203,10 @@ export function generateEnvFile(config) {
|
|
|
179
203
|
'',
|
|
180
204
|
'# 3CX / SIP Configuration',
|
|
181
205
|
`SIP_DOMAIN=${config.sip.domain}`,
|
|
182
|
-
|
|
206
|
+
// 'auto' means the 3CX SBC runs on this same PC, so its address is
|
|
207
|
+
// whatever this machine's current LAN IP is - re-resolved every start,
|
|
208
|
+
// same reasoning as EXTERNAL_IP above.
|
|
209
|
+
`SIP_REGISTRAR=${config.sip.registrar === 'auto' ? resolvedExternalIp : config.sip.registrar}`,
|
|
183
210
|
'',
|
|
184
211
|
'# Default extension (primary device)',
|
|
185
212
|
`SIP_EXTENSION=${config.devices[0].extension}`,
|
|
@@ -196,7 +223,7 @@ export function generateEnvFile(config) {
|
|
|
196
223
|
`STT_LOCAL_URL=${config.api.local?.sttUrl || 'http://stt-local:9001'}`,
|
|
197
224
|
`TTS_LOCAL_URL=${config.api.local?.ttsUrl || 'http://tts-local:9002'}`,
|
|
198
225
|
`AUDIO_BASE_URL=${config.api.local?.audioBaseUrl || 'http://voice-app:3000'}`,
|
|
199
|
-
`PIPER_VOICE=${config.devices[0].
|
|
226
|
+
`PIPER_VOICE=${config.devices[0].voice || config.api.local?.voice || 'en_US-lessac-medium'}`,
|
|
200
227
|
`WHISPER_MODEL=${config.api.local?.whisperModel || 'small'}`,
|
|
201
228
|
''
|
|
202
229
|
] : [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-phone-local",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.7",
|
|
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": {
|