claude-phone-local 2.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.
Files changed (96) hide show
  1. package/.env.example +108 -0
  2. package/Dockerfile +84 -0
  3. package/LICENSE +22 -0
  4. package/README.md +231 -0
  5. package/claude-api-server/package-lock.json +832 -0
  6. package/claude-api-server/package.json +20 -0
  7. package/claude-api-server/server.js +540 -0
  8. package/claude-api-server/structured.js +208 -0
  9. package/cli/README.md +231 -0
  10. package/cli/bin/check-publish-safety.js +53 -0
  11. package/cli/bin/claude-phone.js +23 -0
  12. package/cli/bin/cli-main.js +284 -0
  13. package/cli/bin/postinstall.js +20 -0
  14. package/cli/lib/commands/api-server.js +77 -0
  15. package/cli/lib/commands/backup.js +69 -0
  16. package/cli/lib/commands/config/path.js +30 -0
  17. package/cli/lib/commands/config/reset.js +73 -0
  18. package/cli/lib/commands/config/show.js +86 -0
  19. package/cli/lib/commands/device/add.js +143 -0
  20. package/cli/lib/commands/device/list.js +55 -0
  21. package/cli/lib/commands/device/remove.js +83 -0
  22. package/cli/lib/commands/doctor.js +422 -0
  23. package/cli/lib/commands/logs.js +186 -0
  24. package/cli/lib/commands/restore.js +172 -0
  25. package/cli/lib/commands/setup.js +1246 -0
  26. package/cli/lib/commands/start.js +346 -0
  27. package/cli/lib/commands/status.js +139 -0
  28. package/cli/lib/commands/stop.js +101 -0
  29. package/cli/lib/commands/uninstall.js +183 -0
  30. package/cli/lib/commands/update.js +205 -0
  31. package/cli/lib/config.js +113 -0
  32. package/cli/lib/docker.js +384 -0
  33. package/cli/lib/mcp-register.js +108 -0
  34. package/cli/lib/network.js +109 -0
  35. package/cli/lib/platform.js +85 -0
  36. package/cli/lib/port-check.js +135 -0
  37. package/cli/lib/prereqs/checks/compose.js +147 -0
  38. package/cli/lib/prereqs/checks/disk.js +89 -0
  39. package/cli/lib/prereqs/checks/docker.js +155 -0
  40. package/cli/lib/prereqs/checks/network.js +78 -0
  41. package/cli/lib/prereqs/checks/node.js +95 -0
  42. package/cli/lib/prereqs/installers/docker-desktop.js +248 -0
  43. package/cli/lib/prereqs/installers/docker.js +229 -0
  44. package/cli/lib/prereqs/installers/node.js +254 -0
  45. package/cli/lib/prereqs/platform.js +175 -0
  46. package/cli/lib/prereqs/utils/execute.js +208 -0
  47. package/cli/lib/prereqs/utils/rollback.js +223 -0
  48. package/cli/lib/prereqs/utils/sudo.js +177 -0
  49. package/cli/lib/prereqs.js +228 -0
  50. package/cli/lib/prerequisites.js +115 -0
  51. package/cli/lib/process-manager.js +176 -0
  52. package/cli/lib/utils.js +78 -0
  53. package/cli/lib/validators.js +219 -0
  54. package/cli/lib/voice-downloader.js +79 -0
  55. package/docker/entrypoint.sh +110 -0
  56. package/docker/supervisord.conf +78 -0
  57. package/docker-compose.yml +40 -0
  58. package/docs/CLAUDE-CODE-SKILL.md +45 -0
  59. package/docs/LANGUAGES.md +113 -0
  60. package/docs/MCP-SERVER.md +111 -0
  61. package/docs/TROUBLESHOOTING.md +288 -0
  62. package/mcp-server/index.js +159 -0
  63. package/mcp-server/package-lock.json +1201 -0
  64. package/mcp-server/package.json +9 -0
  65. package/package.json +73 -0
  66. package/stt-local/Dockerfile +12 -0
  67. package/stt-local/server.py +45 -0
  68. package/tts-local/Dockerfile +9 -0
  69. package/tts-local/server.py +83 -0
  70. package/voice-app/API-QUERY-CONTRACT.md +238 -0
  71. package/voice-app/DEPLOYMENT.md +176 -0
  72. package/voice-app/Dockerfile +20 -0
  73. package/voice-app/README-OUTBOUND.md +316 -0
  74. package/voice-app/config/devices.json.example +18 -0
  75. package/voice-app/index.js +273 -0
  76. package/voice-app/lib/audio-fork.js +465 -0
  77. package/voice-app/lib/claude-bridge.js +113 -0
  78. package/voice-app/lib/connection-retry.js +58 -0
  79. package/voice-app/lib/conversation-loop.js +490 -0
  80. package/voice-app/lib/device-registry.js +171 -0
  81. package/voice-app/lib/http-server.js +242 -0
  82. package/voice-app/lib/logger.js +35 -0
  83. package/voice-app/lib/multi-registrar.js +133 -0
  84. package/voice-app/lib/outbound-handler.js +299 -0
  85. package/voice-app/lib/outbound-routes.js +417 -0
  86. package/voice-app/lib/outbound-session.js +324 -0
  87. package/voice-app/lib/query-routes.js +484 -0
  88. package/voice-app/lib/registrar.js +144 -0
  89. package/voice-app/lib/sip-handler.js +490 -0
  90. package/voice-app/lib/tts-service.js +205 -0
  91. package/voice-app/lib/whisper-client.js +140 -0
  92. package/voice-app/package.json +38 -0
  93. package/voice-app/static/gotit-beep.wav +0 -0
  94. package/voice-app/static/hold-music.wav +0 -0
  95. package/voice-app/static/ready-beep.wav +0 -0
  96. package/voice-app/test/freeswitch-retry.test.js +100 -0
@@ -0,0 +1,248 @@
1
+ import chalk from 'chalk';
2
+ import inquirer from 'inquirer';
3
+ import ora from 'ora';
4
+ import { execSync } from 'child_process';
5
+ import fs from 'fs';
6
+ import { runWithLogging } from '../utils/execute.js';
7
+
8
+ /**
9
+ * Install or launch Docker Desktop on macOS
10
+ * @param {object} _platform - Platform info from detectPlatform()
11
+ * @returns {Promise<{success: boolean, cancelled?: boolean}>}
12
+ */
13
+ export async function installDockerDesktop(_platform) {
14
+ console.log(chalk.bold.cyan('\n📦 Docker Desktop\n'));
15
+
16
+ // Check if Docker Desktop is installed
17
+ const installed = isDockerDesktopInstalled();
18
+
19
+ if (installed) {
20
+ // Docker Desktop is installed but not running
21
+ return await launchDockerDesktop();
22
+ } else {
23
+ // Docker Desktop not installed - offer to install
24
+ return await installDockerDesktopNew();
25
+ }
26
+ }
27
+
28
+ /**
29
+ * Check if Docker Desktop is installed
30
+ * @returns {boolean} True if installed
31
+ */
32
+ function isDockerDesktopInstalled() {
33
+ return fs.existsSync('/Applications/Docker.app');
34
+ }
35
+
36
+ /**
37
+ * Launch existing Docker Desktop installation
38
+ * @returns {Promise<{success: boolean, cancelled?: boolean}>}
39
+ */
40
+ async function launchDockerDesktop() {
41
+ console.log(chalk.yellow('Docker Desktop is installed but not running.\n'));
42
+
43
+ const { launch } = await inquirer.prompt([
44
+ {
45
+ type: 'confirm',
46
+ name: 'launch',
47
+ message: 'Launch Docker Desktop?',
48
+ default: true
49
+ }
50
+ ]);
51
+
52
+ if (!launch) {
53
+ console.log(chalk.gray('\nPlease launch Docker Desktop manually and run "claude-phone setup" again.\n'));
54
+ return { success: false, cancelled: true };
55
+ }
56
+
57
+ // Launch Docker Desktop
58
+ try {
59
+ console.log(chalk.cyan('\nLaunching Docker Desktop...'));
60
+ execSync('open -a Docker', { stdio: 'pipe' });
61
+
62
+ // Wait for Docker daemon to start
63
+ return await waitForDockerDaemon();
64
+ } catch (error) {
65
+ console.error(chalk.red(`\n❌ Failed to launch Docker Desktop: ${error.message}`));
66
+ return { success: false };
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Install Docker Desktop via Homebrew or manual download
72
+ * @returns {Promise<{success: boolean, cancelled?: boolean}>}
73
+ */
74
+ async function installDockerDesktopNew() {
75
+ console.log(chalk.yellow('Docker Desktop is not installed.\n'));
76
+
77
+ // Check if Homebrew is available
78
+ const hasHomebrew = checkHomebrew();
79
+
80
+ if (hasHomebrew) {
81
+ // Offer Homebrew installation
82
+ const { method } = await inquirer.prompt([
83
+ {
84
+ type: 'list',
85
+ name: 'method',
86
+ message: 'How would you like to install Docker Desktop?',
87
+ choices: [
88
+ { name: 'Install via Homebrew (recommended)', value: 'brew' },
89
+ { name: 'Manual download from docker.com', value: 'manual' },
90
+ { name: 'Cancel', value: 'cancel' }
91
+ ]
92
+ }
93
+ ]);
94
+
95
+ if (method === 'cancel') {
96
+ return { success: false, cancelled: true };
97
+ }
98
+
99
+ if (method === 'brew') {
100
+ return await installViaHomebrew();
101
+ } else {
102
+ return await installManually();
103
+ }
104
+ } else {
105
+ // No Homebrew - only manual option
106
+ return await installManually();
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Install Docker Desktop via Homebrew
112
+ * @returns {Promise<{success: boolean}>}
113
+ */
114
+ async function installViaHomebrew() {
115
+ try {
116
+ console.log(chalk.cyan('\nInstalling Docker Desktop via Homebrew...'));
117
+ console.log(chalk.gray('This may take several minutes...\n'));
118
+
119
+ const result = await runWithLogging('brew install --cask docker');
120
+
121
+ if (!result.success) {
122
+ console.error(chalk.red('\n❌ Homebrew installation failed'));
123
+ return { success: false };
124
+ }
125
+
126
+ console.log(chalk.green('\n✓ Docker Desktop installed'));
127
+
128
+ // Launch Docker Desktop
129
+ const { launch } = await inquirer.prompt([
130
+ {
131
+ type: 'confirm',
132
+ name: 'launch',
133
+ message: 'Launch Docker Desktop now?',
134
+ default: true
135
+ }
136
+ ]);
137
+
138
+ if (!launch) {
139
+ console.log(chalk.gray('\nPlease launch Docker Desktop manually and run "claude-phone setup" again.\n'));
140
+ return { success: false, cancelled: true };
141
+ }
142
+
143
+ execSync('open -a Docker', { stdio: 'pipe' });
144
+
145
+ return await waitForDockerDaemon();
146
+ } catch (error) {
147
+ console.error(chalk.red(`\n❌ Installation failed: ${error.message}`));
148
+ return { success: false };
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Guide user through manual Docker Desktop installation
154
+ * @returns {Promise<{success: boolean, cancelled?: boolean}>}
155
+ */
156
+ async function installManually() {
157
+ console.log(chalk.cyan('\n📥 Manual Installation\n'));
158
+ console.log(chalk.gray('1. Download Docker Desktop from:'));
159
+ console.log(chalk.cyan(' https://www.docker.com/products/docker-desktop/\n'));
160
+ console.log(chalk.gray('2. Open the .dmg file and drag Docker to Applications'));
161
+ console.log(chalk.gray('3. Launch Docker from Applications\n'));
162
+
163
+ const { ready } = await inquirer.prompt([
164
+ {
165
+ type: 'confirm',
166
+ name: 'ready',
167
+ message: 'Press Enter when Docker Desktop is installed and running',
168
+ default: true
169
+ }
170
+ ]);
171
+
172
+ if (!ready) {
173
+ return { success: false, cancelled: true };
174
+ }
175
+
176
+ // Wait for Docker daemon
177
+ return await waitForDockerDaemon();
178
+ }
179
+
180
+ /**
181
+ * Wait for Docker daemon to become available
182
+ * @param {number} timeout - Timeout in milliseconds (default: 5 minutes)
183
+ * @returns {Promise<{success: boolean}>}
184
+ */
185
+ async function waitForDockerDaemon(timeout = 300000) {
186
+ const spinner = ora('Waiting for Docker daemon...').start();
187
+ const startTime = Date.now();
188
+ const checkInterval = 5000; // Check every 5 seconds
189
+
190
+ while (Date.now() - startTime < timeout) {
191
+ // Check if Docker is running
192
+ const running = await isDockerRunning();
193
+
194
+ if (running) {
195
+ spinner.succeed('Docker daemon ready!');
196
+ return { success: true };
197
+ }
198
+
199
+ // Update spinner with elapsed time
200
+ const elapsed = Math.floor((Date.now() - startTime) / 1000);
201
+ spinner.text = `Waiting for Docker daemon... (${elapsed}s)`;
202
+
203
+ // Wait before next check
204
+ await sleep(checkInterval);
205
+ }
206
+
207
+ // Timeout
208
+ spinner.fail('Timeout waiting for Docker daemon');
209
+ console.log(chalk.yellow('\n⚠️ Docker Desktop is taking longer than expected to start.'));
210
+ console.log(chalk.gray('Please check Docker Desktop and run "claude-phone setup" again.\n'));
211
+
212
+ return { success: false };
213
+ }
214
+
215
+ /**
216
+ * Check if Docker daemon is running
217
+ * @returns {Promise<boolean>} True if running
218
+ */
219
+ async function isDockerRunning() {
220
+ try {
221
+ execSync('docker ps', { stdio: 'pipe' });
222
+ return true;
223
+ } catch (error) {
224
+ return false;
225
+ }
226
+ }
227
+
228
+ /**
229
+ * Check if Homebrew is installed
230
+ * @returns {boolean} True if Homebrew is available
231
+ */
232
+ function checkHomebrew() {
233
+ try {
234
+ execSync('brew --version', { stdio: 'pipe' });
235
+ return true;
236
+ } catch (error) {
237
+ return false;
238
+ }
239
+ }
240
+
241
+ /**
242
+ * Sleep for specified milliseconds
243
+ * @param {number} ms - Milliseconds to sleep
244
+ * @returns {Promise<void>}
245
+ */
246
+ function sleep(ms) {
247
+ return new Promise(resolve => setTimeout(resolve, ms));
248
+ }
@@ -0,0 +1,229 @@
1
+ import chalk from 'chalk';
2
+ import inquirer from 'inquirer';
3
+ import { execSync } from 'child_process';
4
+ import { withSudo } from '../utils/sudo.js';
5
+ import { checkDocker } from '../checks/docker.js';
6
+
7
+ /**
8
+ * Install Docker on Linux
9
+ * @param {object} platform - Platform info from detectPlatform()
10
+ * @returns {Promise<{success: boolean, cancelled?: boolean}>}
11
+ */
12
+ export async function installDocker(platform) {
13
+ console.log(chalk.bold.cyan('\n📦 Docker Installation\n'));
14
+
15
+ // Confirm installation
16
+ const { confirmed } = await inquirer.prompt([
17
+ {
18
+ type: 'confirm',
19
+ name: 'confirmed',
20
+ message: 'Install Docker automatically?',
21
+ default: false
22
+ }
23
+ ]);
24
+
25
+ if (!confirmed) {
26
+ showDockerManualInstructions(platform);
27
+ return { success: false, cancelled: true };
28
+ }
29
+
30
+ // Route to platform-specific installer
31
+ let result;
32
+
33
+ switch (platform.packageManager) {
34
+ case 'apt':
35
+ result = await installDockerApt();
36
+ break;
37
+ case 'dnf':
38
+ case 'yum':
39
+ result = await installDockerDnf(platform.packageManager);
40
+ break;
41
+ case 'pacman':
42
+ result = await installDockerPacman();
43
+ break;
44
+ default:
45
+ console.error(chalk.red(`\n❌ Unsupported package manager: ${platform.packageManager}`));
46
+ showDockerManualInstructions(platform);
47
+ return { success: false };
48
+ }
49
+
50
+ if (!result.success) {
51
+ return result;
52
+ }
53
+
54
+ // Add user to docker group
55
+ await addUserToDockerGroup();
56
+
57
+ // Start Docker daemon
58
+ await startDockerDaemon();
59
+
60
+ // Verify installation
61
+ console.log(chalk.cyan('\n✓ Verifying Docker installation...'));
62
+ const check = await checkDocker(platform);
63
+
64
+ if (check.passed) {
65
+ console.log(chalk.green(`✓ Docker v${check.version} installed successfully\n`));
66
+ console.log(chalk.yellow('⚠️ You may need to log out and back in for group changes to take effect.'));
67
+ return { success: true };
68
+ } else {
69
+ console.error(chalk.red('\n❌ Docker installation verification failed'));
70
+ return { success: false };
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Install Docker on Ubuntu/Debian
76
+ * @returns {Promise<{success: boolean}>}
77
+ */
78
+ async function installDockerApt() {
79
+ console.log(chalk.cyan('\nInstalling Docker via apt...'));
80
+
81
+ const commands = [
82
+ // Install prerequisites
83
+ 'apt-get update',
84
+ 'apt-get install -y ca-certificates curl gnupg',
85
+
86
+ // Add Docker GPG key
87
+ 'install -m 0755 -d /etc/apt/keyrings',
88
+ 'curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc',
89
+ 'chmod a+r /etc/apt/keyrings/docker.asc',
90
+
91
+ // Add Docker repository
92
+ 'echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null',
93
+
94
+ // Install Docker
95
+ 'apt-get update',
96
+ 'apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin'
97
+ ];
98
+
99
+ return await withSudo(commands);
100
+ }
101
+
102
+ /**
103
+ * Install Docker on RHEL/Fedora
104
+ * @param {string} packageManager - 'dnf' or 'yum'
105
+ * @returns {Promise<{success: boolean}>}
106
+ */
107
+ async function installDockerDnf(packageManager) {
108
+ console.log(chalk.cyan(`\nInstalling Docker via ${packageManager}...`));
109
+
110
+ const commands = [
111
+ // Install prerequisites
112
+ `${packageManager} install -y ${packageManager}-plugins-core`,
113
+
114
+ // Add Docker repository
115
+ `${packageManager} config-manager --add-repo https://download.docker.com/linux/fedora/docker-ce.repo`,
116
+
117
+ // Install Docker
118
+ `${packageManager} install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin`
119
+ ];
120
+
121
+ return await withSudo(commands);
122
+ }
123
+
124
+ /**
125
+ * Install Docker on Arch Linux
126
+ * @returns {Promise<{success: boolean}>}
127
+ */
128
+ async function installDockerPacman() {
129
+ console.log(chalk.cyan('\nInstalling Docker via pacman...'));
130
+
131
+ const commands = [
132
+ 'pacman -Sy --noconfirm docker docker-compose'
133
+ ];
134
+
135
+ return await withSudo(commands);
136
+ }
137
+
138
+ /**
139
+ * Add current user to docker group
140
+ * @returns {Promise<void>}
141
+ */
142
+ async function addUserToDockerGroup() {
143
+ try {
144
+ const username = execSync('whoami', { encoding: 'utf-8' }).trim();
145
+
146
+ console.log(chalk.cyan(`\nAdding user '${username}' to docker group...`));
147
+
148
+ const commands = [
149
+ 'groupadd -f docker',
150
+ `usermod -aG docker ${username}`
151
+ ];
152
+
153
+ await withSudo(commands, { skipConfirm: true });
154
+ } catch (error) {
155
+ console.log(chalk.yellow('⚠️ Could not add user to docker group'));
156
+ console.log(chalk.gray('You may need to run: sudo usermod -aG docker $USER'));
157
+ }
158
+ }
159
+
160
+ /**
161
+ * Start Docker daemon
162
+ * @returns {Promise<void>}
163
+ */
164
+ async function startDockerDaemon() {
165
+ try {
166
+ console.log(chalk.cyan('\nStarting Docker daemon...'));
167
+
168
+ // Try systemctl first
169
+ try {
170
+ execSync('systemctl --version', { stdio: 'pipe' });
171
+
172
+ const commands = [
173
+ 'systemctl enable docker',
174
+ 'systemctl start docker'
175
+ ];
176
+
177
+ await withSudo(commands, { skipConfirm: true });
178
+ } catch (e) {
179
+ // Fall back to service command
180
+ const commands = ['service docker start'];
181
+ await withSudo(commands, { skipConfirm: true });
182
+ }
183
+
184
+ console.log(chalk.green('✓ Docker daemon started'));
185
+ } catch (error) {
186
+ console.log(chalk.yellow('⚠️ Could not start Docker daemon automatically'));
187
+ console.log(chalk.gray('Try running: sudo systemctl start docker'));
188
+ }
189
+ }
190
+
191
+ /**
192
+ * Show manual installation instructions for Docker
193
+ * @param {object} platform - Platform info
194
+ * @returns {void}
195
+ */
196
+ function showDockerManualInstructions(platform) {
197
+ console.log(chalk.yellow('\n📋 Manual Docker Installation\n'));
198
+
199
+ switch (platform.packageManager) {
200
+ case 'apt':
201
+ console.log(chalk.gray('For Ubuntu/Debian:\n'));
202
+ console.log(chalk.cyan(' # Add Docker repository'));
203
+ console.log(chalk.cyan(' curl -fsSL https://get.docker.com -o get-docker.sh'));
204
+ console.log(chalk.cyan(' sudo sh get-docker.sh\n'));
205
+ console.log(chalk.cyan(' # Add user to docker group'));
206
+ console.log(chalk.cyan(' sudo usermod -aG docker $USER\n'));
207
+ break;
208
+
209
+ case 'dnf':
210
+ case 'yum':
211
+ console.log(chalk.gray('For RHEL/Fedora:\n'));
212
+ console.log(chalk.cyan(' sudo dnf install docker-ce docker-ce-cli containerd.io'));
213
+ console.log(chalk.cyan(' sudo systemctl start docker'));
214
+ console.log(chalk.cyan(' sudo usermod -aG docker $USER\n'));
215
+ break;
216
+
217
+ case 'pacman':
218
+ console.log(chalk.gray('For Arch Linux:\n'));
219
+ console.log(chalk.cyan(' sudo pacman -Sy docker docker-compose'));
220
+ console.log(chalk.cyan(' sudo systemctl start docker'));
221
+ console.log(chalk.cyan(' sudo usermod -aG docker $USER\n'));
222
+ break;
223
+
224
+ default:
225
+ console.log(chalk.gray('See: https://docs.docker.com/engine/install/\n'));
226
+ }
227
+
228
+ console.log(chalk.gray('After installation, log out and back in, then run "claude-phone setup" again.\n'));
229
+ }