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.
- package/.env.example +108 -0
- package/Dockerfile +84 -0
- package/LICENSE +22 -0
- package/README.md +231 -0
- package/claude-api-server/package-lock.json +832 -0
- package/claude-api-server/package.json +20 -0
- package/claude-api-server/server.js +540 -0
- package/claude-api-server/structured.js +208 -0
- package/cli/README.md +231 -0
- package/cli/bin/check-publish-safety.js +53 -0
- package/cli/bin/claude-phone.js +23 -0
- package/cli/bin/cli-main.js +284 -0
- package/cli/bin/postinstall.js +20 -0
- package/cli/lib/commands/api-server.js +77 -0
- package/cli/lib/commands/backup.js +69 -0
- package/cli/lib/commands/config/path.js +30 -0
- package/cli/lib/commands/config/reset.js +73 -0
- package/cli/lib/commands/config/show.js +86 -0
- package/cli/lib/commands/device/add.js +143 -0
- package/cli/lib/commands/device/list.js +55 -0
- package/cli/lib/commands/device/remove.js +83 -0
- package/cli/lib/commands/doctor.js +422 -0
- package/cli/lib/commands/logs.js +186 -0
- package/cli/lib/commands/restore.js +172 -0
- package/cli/lib/commands/setup.js +1246 -0
- package/cli/lib/commands/start.js +346 -0
- package/cli/lib/commands/status.js +139 -0
- package/cli/lib/commands/stop.js +101 -0
- package/cli/lib/commands/uninstall.js +183 -0
- package/cli/lib/commands/update.js +205 -0
- package/cli/lib/config.js +113 -0
- package/cli/lib/docker.js +384 -0
- package/cli/lib/mcp-register.js +108 -0
- package/cli/lib/network.js +109 -0
- package/cli/lib/platform.js +85 -0
- package/cli/lib/port-check.js +135 -0
- package/cli/lib/prereqs/checks/compose.js +147 -0
- package/cli/lib/prereqs/checks/disk.js +89 -0
- package/cli/lib/prereqs/checks/docker.js +155 -0
- package/cli/lib/prereqs/checks/network.js +78 -0
- package/cli/lib/prereqs/checks/node.js +95 -0
- package/cli/lib/prereqs/installers/docker-desktop.js +248 -0
- package/cli/lib/prereqs/installers/docker.js +229 -0
- package/cli/lib/prereqs/installers/node.js +254 -0
- package/cli/lib/prereqs/platform.js +175 -0
- package/cli/lib/prereqs/utils/execute.js +208 -0
- package/cli/lib/prereqs/utils/rollback.js +223 -0
- package/cli/lib/prereqs/utils/sudo.js +177 -0
- package/cli/lib/prereqs.js +228 -0
- package/cli/lib/prerequisites.js +115 -0
- package/cli/lib/process-manager.js +176 -0
- package/cli/lib/utils.js +78 -0
- package/cli/lib/validators.js +219 -0
- package/cli/lib/voice-downloader.js +79 -0
- package/docker/entrypoint.sh +110 -0
- package/docker/supervisord.conf +78 -0
- package/docker-compose.yml +40 -0
- package/docs/CLAUDE-CODE-SKILL.md +45 -0
- package/docs/LANGUAGES.md +113 -0
- package/docs/MCP-SERVER.md +111 -0
- package/docs/TROUBLESHOOTING.md +288 -0
- package/mcp-server/index.js +159 -0
- package/mcp-server/package-lock.json +1201 -0
- package/mcp-server/package.json +9 -0
- package/package.json +73 -0
- package/stt-local/Dockerfile +12 -0
- package/stt-local/server.py +45 -0
- package/tts-local/Dockerfile +9 -0
- package/tts-local/server.py +83 -0
- package/voice-app/API-QUERY-CONTRACT.md +238 -0
- package/voice-app/DEPLOYMENT.md +176 -0
- package/voice-app/Dockerfile +20 -0
- package/voice-app/README-OUTBOUND.md +316 -0
- package/voice-app/config/devices.json.example +18 -0
- package/voice-app/index.js +273 -0
- package/voice-app/lib/audio-fork.js +465 -0
- package/voice-app/lib/claude-bridge.js +113 -0
- package/voice-app/lib/connection-retry.js +58 -0
- package/voice-app/lib/conversation-loop.js +490 -0
- package/voice-app/lib/device-registry.js +171 -0
- package/voice-app/lib/http-server.js +242 -0
- package/voice-app/lib/logger.js +35 -0
- package/voice-app/lib/multi-registrar.js +133 -0
- package/voice-app/lib/outbound-handler.js +299 -0
- package/voice-app/lib/outbound-routes.js +417 -0
- package/voice-app/lib/outbound-session.js +324 -0
- package/voice-app/lib/query-routes.js +484 -0
- package/voice-app/lib/registrar.js +144 -0
- package/voice-app/lib/sip-handler.js +490 -0
- package/voice-app/lib/tts-service.js +205 -0
- package/voice-app/lib/whisper-client.js +140 -0
- package/voice-app/package.json +38 -0
- package/voice-app/static/gotit-beep.wav +0 -0
- package/voice-app/static/hold-music.wav +0 -0
- package/voice-app/static/ready-beep.wav +0 -0
- package/voice-app/test/freeswitch-retry.test.js +100 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import ora from 'ora';
|
|
6
|
+
import inquirer from 'inquirer';
|
|
7
|
+
import { getConfigDir, configExists } from '../config.js';
|
|
8
|
+
import { stopContainers } from '../docker.js';
|
|
9
|
+
import { stopServer, isServerRunning } from '../process-manager.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Get the CLI install directory path
|
|
13
|
+
* @returns {string} Path to ~/.claude-phone-cli
|
|
14
|
+
*/
|
|
15
|
+
function getCliInstallDir() {
|
|
16
|
+
return path.join(os.homedir(), '.claude-phone-cli');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Find symlink location
|
|
21
|
+
* @returns {Promise<string|null>} Symlink path or null if not found
|
|
22
|
+
*/
|
|
23
|
+
async function findSymlink() {
|
|
24
|
+
const possiblePaths = [
|
|
25
|
+
'/usr/local/bin/claude-phone',
|
|
26
|
+
path.join(os.homedir(), '.local/bin/claude-phone')
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
for (const symlinkPath of possiblePaths) {
|
|
30
|
+
try {
|
|
31
|
+
const stats = await fs.promises.lstat(symlinkPath);
|
|
32
|
+
if (stats.isSymbolicLink()) {
|
|
33
|
+
return symlinkPath;
|
|
34
|
+
}
|
|
35
|
+
} catch (error) {
|
|
36
|
+
// Path doesn't exist, continue
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Remove directory recursively
|
|
45
|
+
* @param {string} dirPath - Directory path to remove
|
|
46
|
+
* @returns {Promise<void>}
|
|
47
|
+
*/
|
|
48
|
+
async function removeDirectory(dirPath) {
|
|
49
|
+
if (fs.existsSync(dirPath)) {
|
|
50
|
+
await fs.promises.rm(dirPath, { recursive: true, force: true });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Uninstall command - Complete removal of Claude Phone
|
|
56
|
+
* @returns {Promise<void>}
|
|
57
|
+
*/
|
|
58
|
+
export async function uninstallCommand() {
|
|
59
|
+
console.log(chalk.bold.red('\n🗑️ Uninstall Claude Phone\n'));
|
|
60
|
+
|
|
61
|
+
const configDir = getConfigDir();
|
|
62
|
+
const cliDir = getCliInstallDir();
|
|
63
|
+
const hasConfig = configExists();
|
|
64
|
+
const symlinkPath = await findSymlink();
|
|
65
|
+
|
|
66
|
+
// Show what will be removed
|
|
67
|
+
console.log(chalk.bold('The following will be removed:\n'));
|
|
68
|
+
|
|
69
|
+
if (hasConfig || fs.existsSync(configDir)) {
|
|
70
|
+
console.log(chalk.yellow(' • Configuration directory:'));
|
|
71
|
+
console.log(chalk.gray(` ${configDir}`));
|
|
72
|
+
console.log(chalk.gray(' (includes config.json, backups, generated files)'));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (fs.existsSync(cliDir)) {
|
|
76
|
+
console.log(chalk.yellow('\n • CLI installation:'));
|
|
77
|
+
console.log(chalk.gray(` ${cliDir}`));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (symlinkPath) {
|
|
81
|
+
console.log(chalk.yellow('\n • Symlink:'));
|
|
82
|
+
console.log(chalk.gray(` ${symlinkPath}`));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
console.log(chalk.yellow('\n • Docker containers:'));
|
|
86
|
+
console.log(chalk.gray(' voice-app, drachtio, freeswitch'));
|
|
87
|
+
|
|
88
|
+
// First confirmation: Remove Claude Phone?
|
|
89
|
+
console.log(chalk.bold.red('\n⚠️ WARNING: This action cannot be undone!\n'));
|
|
90
|
+
|
|
91
|
+
const { confirmUninstall } = await inquirer.prompt([
|
|
92
|
+
{
|
|
93
|
+
type: 'confirm',
|
|
94
|
+
name: 'confirmUninstall',
|
|
95
|
+
message: 'Remove Claude Phone?',
|
|
96
|
+
default: false
|
|
97
|
+
}
|
|
98
|
+
]);
|
|
99
|
+
|
|
100
|
+
if (!confirmUninstall) {
|
|
101
|
+
console.log(chalk.gray('\nUninstall cancelled.\n'));
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Second confirmation: Delete configuration and backups?
|
|
106
|
+
if (hasConfig || fs.existsSync(configDir)) {
|
|
107
|
+
const { confirmConfig } = await inquirer.prompt([
|
|
108
|
+
{
|
|
109
|
+
type: 'confirm',
|
|
110
|
+
name: 'confirmConfig',
|
|
111
|
+
message: 'Delete configuration and backups? (This will remove API keys and all backups)',
|
|
112
|
+
default: false
|
|
113
|
+
}
|
|
114
|
+
]);
|
|
115
|
+
|
|
116
|
+
if (!confirmConfig) {
|
|
117
|
+
console.log(chalk.gray('\nUninstall cancelled. Configuration preserved.\n'));
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
console.log(chalk.bold.cyan('\n🧹 Removing Claude Phone...\n'));
|
|
123
|
+
|
|
124
|
+
// Step 1: Stop services
|
|
125
|
+
let spinner = ora('Stopping Claude API server...').start();
|
|
126
|
+
try {
|
|
127
|
+
if (await isServerRunning()) {
|
|
128
|
+
await stopServer();
|
|
129
|
+
spinner.succeed('Claude API server stopped');
|
|
130
|
+
} else {
|
|
131
|
+
spinner.info('Claude API server not running');
|
|
132
|
+
}
|
|
133
|
+
} catch (error) {
|
|
134
|
+
spinner.warn(`Could not stop server: ${error.message}`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
spinner = ora('Stopping Docker containers...').start();
|
|
138
|
+
try {
|
|
139
|
+
await stopContainers();
|
|
140
|
+
spinner.succeed('Docker containers stopped');
|
|
141
|
+
} catch (error) {
|
|
142
|
+
spinner.warn(`Could not stop containers: ${error.message}`);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Step 2: Remove configuration directory
|
|
146
|
+
if (fs.existsSync(configDir)) {
|
|
147
|
+
spinner = ora('Removing configuration directory...').start();
|
|
148
|
+
try {
|
|
149
|
+
await removeDirectory(configDir);
|
|
150
|
+
spinner.succeed('Configuration directory removed');
|
|
151
|
+
} catch (error) {
|
|
152
|
+
spinner.fail(`Failed to remove config directory: ${error.message}`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Step 3: Remove CLI installation
|
|
157
|
+
if (fs.existsSync(cliDir)) {
|
|
158
|
+
spinner = ora('Removing CLI installation...').start();
|
|
159
|
+
try {
|
|
160
|
+
await removeDirectory(cliDir);
|
|
161
|
+
spinner.succeed('CLI installation removed');
|
|
162
|
+
} catch (error) {
|
|
163
|
+
spinner.fail(`Failed to remove CLI directory: ${error.message}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Step 4: Remove symlink
|
|
168
|
+
if (symlinkPath) {
|
|
169
|
+
spinner = ora('Removing symlink...').start();
|
|
170
|
+
try {
|
|
171
|
+
await fs.promises.unlink(symlinkPath);
|
|
172
|
+
spinner.succeed('Symlink removed');
|
|
173
|
+
} catch (error) {
|
|
174
|
+
spinner.warn(`Could not remove symlink: ${error.message}`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
console.log(chalk.bold.green('\n✓ Claude Phone uninstalled\n'));
|
|
179
|
+
console.log(chalk.gray('Thank you for using Claude Phone! 👋\n'));
|
|
180
|
+
console.log(chalk.gray('Note: Docker images were not removed. To clean them up, run:'));
|
|
181
|
+
console.log(chalk.gray(' docker image rm drachtio/drachtio-server:latest'));
|
|
182
|
+
console.log(chalk.gray(' docker image rm drachtio/drachtio-freeswitch-mrf:latest\n'));
|
|
183
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import inquirer from 'inquirer';
|
|
6
|
+
import { execSync } from 'child_process';
|
|
7
|
+
import { loadConfig, saveConfig, configExists } from '../config.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Check for git repository
|
|
11
|
+
* @param {string} projectRoot - Root directory of claude-phone
|
|
12
|
+
* @returns {boolean} True if git repo
|
|
13
|
+
*/
|
|
14
|
+
function isGitRepo(projectRoot) {
|
|
15
|
+
try {
|
|
16
|
+
execSync('git rev-parse --is-inside-work-tree', {
|
|
17
|
+
cwd: projectRoot,
|
|
18
|
+
stdio: 'ignore'
|
|
19
|
+
});
|
|
20
|
+
return true;
|
|
21
|
+
} catch {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Get current git branch
|
|
28
|
+
* @param {string} projectRoot - Root directory of claude-phone
|
|
29
|
+
* @returns {string} Branch name
|
|
30
|
+
*/
|
|
31
|
+
function getCurrentBranch(projectRoot) {
|
|
32
|
+
try {
|
|
33
|
+
return execSync('git rev-parse --abbrev-ref HEAD', {
|
|
34
|
+
cwd: projectRoot,
|
|
35
|
+
encoding: 'utf8'
|
|
36
|
+
}).trim();
|
|
37
|
+
} catch {
|
|
38
|
+
return 'unknown';
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Get project root directory (where package.json is)
|
|
44
|
+
* @returns {string} Project root path
|
|
45
|
+
*/
|
|
46
|
+
function getProjectRoot() {
|
|
47
|
+
// CLI is in cli/lib/commands/, project root is three levels up
|
|
48
|
+
const currentFile = fileURLToPath(import.meta.url);
|
|
49
|
+
const commandsDir = path.dirname(currentFile);
|
|
50
|
+
const libDir = path.dirname(commandsDir);
|
|
51
|
+
const cliDir = path.dirname(libDir);
|
|
52
|
+
const projectRoot = path.dirname(cliDir);
|
|
53
|
+
return fs.realpathSync(projectRoot);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Fetch latest release info from GitHub
|
|
58
|
+
* @returns {Promise<object>} Release info
|
|
59
|
+
*/
|
|
60
|
+
async function fetchLatestRelease() {
|
|
61
|
+
try {
|
|
62
|
+
const response = await fetch(
|
|
63
|
+
'https://api.github.com/repos/masterdeepak15/claude-phone-local/releases/latest',
|
|
64
|
+
{
|
|
65
|
+
headers: {
|
|
66
|
+
'User-Agent': 'claude-phone-cli',
|
|
67
|
+
'Accept': 'application/vnd.github.v3+json'
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
if (!response.ok) {
|
|
73
|
+
throw new Error(`GitHub API error: ${response.statusText}`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return await response.json();
|
|
77
|
+
} catch (error) {
|
|
78
|
+
throw new Error(`Failed to fetch latest release: ${error.message}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Update via git pull
|
|
84
|
+
* @param {string} projectRoot - Root directory
|
|
85
|
+
* @returns {Promise<void>}
|
|
86
|
+
*/
|
|
87
|
+
async function updateViaGit(projectRoot) {
|
|
88
|
+
console.log(chalk.bold('\nUpdating from git...\n'));
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
// Check current branch
|
|
92
|
+
const branch = getCurrentBranch(projectRoot);
|
|
93
|
+
console.log(chalk.gray(`Current branch: ${branch}`));
|
|
94
|
+
|
|
95
|
+
// Fetch latest
|
|
96
|
+
console.log(chalk.gray('Fetching latest changes...'));
|
|
97
|
+
execSync('git fetch origin', {
|
|
98
|
+
cwd: projectRoot,
|
|
99
|
+
stdio: 'inherit'
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// Check for uncommitted changes
|
|
103
|
+
const status = execSync('git status --porcelain', {
|
|
104
|
+
cwd: projectRoot,
|
|
105
|
+
encoding: 'utf8'
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
if (status.trim()) {
|
|
109
|
+
console.log(chalk.yellow('\n⚠️ You have uncommitted changes:'));
|
|
110
|
+
console.log(chalk.gray(status));
|
|
111
|
+
const { proceed } = await inquirer.prompt([
|
|
112
|
+
{
|
|
113
|
+
type: 'confirm',
|
|
114
|
+
name: 'proceed',
|
|
115
|
+
message: 'Continue with update? (changes will be stashed)',
|
|
116
|
+
default: false
|
|
117
|
+
}
|
|
118
|
+
]);
|
|
119
|
+
|
|
120
|
+
if (!proceed) {
|
|
121
|
+
console.log(chalk.gray('\n✗ Update cancelled\n'));
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Stash changes
|
|
126
|
+
execSync('git stash', { cwd: projectRoot, stdio: 'inherit' });
|
|
127
|
+
console.log(chalk.green('✓ Changes stashed'));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Pull latest
|
|
131
|
+
console.log(chalk.gray('\nPulling latest changes...'));
|
|
132
|
+
execSync('git pull origin main', {
|
|
133
|
+
cwd: projectRoot,
|
|
134
|
+
stdio: 'inherit'
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
console.log(chalk.green('\n✓ Update complete\n'));
|
|
138
|
+
console.log(chalk.gray('Run "claude-phone status" to verify services\n'));
|
|
139
|
+
} catch (error) {
|
|
140
|
+
throw new Error(`Git update failed: ${error.message}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Show manual update instructions
|
|
146
|
+
* @param {object} release - Latest release info
|
|
147
|
+
* @returns {void}
|
|
148
|
+
*/
|
|
149
|
+
function showManualInstructions(release) {
|
|
150
|
+
console.log(chalk.bold('\n📥 Manual Update Instructions\n'));
|
|
151
|
+
console.log(chalk.gray('Latest version:'), chalk.bold(release.tag_name));
|
|
152
|
+
console.log(chalk.gray('Released:'), release.published_at.split('T')[0]);
|
|
153
|
+
console.log();
|
|
154
|
+
|
|
155
|
+
console.log(chalk.bold('To update manually:\n'));
|
|
156
|
+
console.log(chalk.gray('1. Stop all services:'));
|
|
157
|
+
console.log(chalk.bold(' claude-phone stop\n'));
|
|
158
|
+
|
|
159
|
+
console.log(chalk.gray('2. Backup your configuration:'));
|
|
160
|
+
console.log(chalk.bold(' cp ~/.claude-phone/config.json ~/config.json.backup\n'));
|
|
161
|
+
|
|
162
|
+
console.log(chalk.gray('3. Run the installer:'));
|
|
163
|
+
console.log(chalk.bold(' npm install -g claude-phone-local\n'));
|
|
164
|
+
|
|
165
|
+
console.log(chalk.gray('4. Start services:'));
|
|
166
|
+
console.log(chalk.bold(' claude-phone start\n'));
|
|
167
|
+
|
|
168
|
+
console.log(chalk.yellow('⚠️ Your configuration will be preserved automatically\n'));
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Update command - Update Claude Phone to latest version
|
|
173
|
+
* @returns {Promise<void>}
|
|
174
|
+
*/
|
|
175
|
+
export async function updateCommand() {
|
|
176
|
+
console.log(chalk.bold.cyan('\n🔄 Update Claude Phone\n'));
|
|
177
|
+
|
|
178
|
+
const projectRoot = getProjectRoot();
|
|
179
|
+
|
|
180
|
+
// Backup config before update
|
|
181
|
+
if (configExists()) {
|
|
182
|
+
console.log(chalk.gray('Backing up configuration...'));
|
|
183
|
+
const config = await loadConfig();
|
|
184
|
+
const backupPath = `${process.env.HOME}/.claude-phone/config.json.pre-update`;
|
|
185
|
+
await saveConfig(config); // This creates a backup automatically
|
|
186
|
+
console.log(chalk.green(`✓ Config backed up to: ${backupPath}`));
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Check if git repo
|
|
190
|
+
if (isGitRepo(projectRoot)) {
|
|
191
|
+
console.log(chalk.gray('Detected git installation\n'));
|
|
192
|
+
await updateViaGit(projectRoot);
|
|
193
|
+
} else {
|
|
194
|
+
// Non-git installation - show manual instructions
|
|
195
|
+
console.log(chalk.gray('Checking for latest release...\n'));
|
|
196
|
+
|
|
197
|
+
try {
|
|
198
|
+
const release = await fetchLatestRelease();
|
|
199
|
+
showManualInstructions(release);
|
|
200
|
+
} catch (error) {
|
|
201
|
+
console.log(chalk.red(`\n✗ ${error.message}\n`));
|
|
202
|
+
console.log(chalk.gray('Visit https://github.com/masterdeepak15/claude-phone-local for manual update\n'));
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Get the config directory path
|
|
7
|
+
* @returns {string} Path to ~/.claude-phone
|
|
8
|
+
*/
|
|
9
|
+
export function getConfigDir() {
|
|
10
|
+
return path.join(os.homedir(), '.claude-phone');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Get the config file path
|
|
15
|
+
* @returns {string} Path to ~/.claude-phone/config.json
|
|
16
|
+
*/
|
|
17
|
+
export function getConfigPath() {
|
|
18
|
+
return path.join(getConfigDir(), 'config.json');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Check if config file exists
|
|
23
|
+
* @returns {boolean} True if config exists
|
|
24
|
+
*/
|
|
25
|
+
export function configExists() {
|
|
26
|
+
return fs.existsSync(getConfigPath());
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Load configuration from disk
|
|
31
|
+
* @returns {Promise<object>} Configuration object
|
|
32
|
+
*/
|
|
33
|
+
export async function loadConfig() {
|
|
34
|
+
const configPath = getConfigPath();
|
|
35
|
+
|
|
36
|
+
if (!fs.existsSync(configPath)) {
|
|
37
|
+
throw new Error('Configuration not found. Run "claude-phone setup" first.');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const data = await fs.promises.readFile(configPath, 'utf8');
|
|
41
|
+
const config = JSON.parse(data);
|
|
42
|
+
|
|
43
|
+
// Ensure installationType exists for backward compatibility
|
|
44
|
+
if (!config.installationType) {
|
|
45
|
+
config.installationType = 'both';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return config;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Get the installation type from config
|
|
53
|
+
* @param {object} config - Configuration object
|
|
54
|
+
* @returns {string} Installation type ('voice-server' | 'api-server' | 'both')
|
|
55
|
+
*/
|
|
56
|
+
export function getInstallationType(config) {
|
|
57
|
+
return config.installationType || 'both';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Save configuration to disk
|
|
62
|
+
* @param {object} config - Configuration object
|
|
63
|
+
* @returns {Promise<void>}
|
|
64
|
+
*/
|
|
65
|
+
export async function saveConfig(config) {
|
|
66
|
+
const configDir = getConfigDir();
|
|
67
|
+
const configPath = getConfigPath();
|
|
68
|
+
|
|
69
|
+
// Create directory if it doesn't exist
|
|
70
|
+
if (!fs.existsSync(configDir)) {
|
|
71
|
+
await fs.promises.mkdir(configDir, { recursive: true, mode: 0o700 });
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Backup existing config if it exists
|
|
75
|
+
if (fs.existsSync(configPath)) {
|
|
76
|
+
const backupPath = configPath + '.backup';
|
|
77
|
+
await fs.promises.copyFile(configPath, backupPath);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Add security warning to config
|
|
81
|
+
const configWithWarning = {
|
|
82
|
+
_WARNING: 'DO NOT SHARE THIS FILE - Contains API keys and passwords',
|
|
83
|
+
...config
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// Write config file
|
|
87
|
+
const data = JSON.stringify(configWithWarning, null, 2);
|
|
88
|
+
await fs.promises.writeFile(configPath, data, { mode: 0o600 });
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Get the PID file path
|
|
93
|
+
* @returns {string} Path to server.pid
|
|
94
|
+
*/
|
|
95
|
+
export function getPidPath() {
|
|
96
|
+
return path.join(getConfigDir(), 'server.pid');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Get the docker-compose.yml path
|
|
101
|
+
* @returns {string} Path to generated docker-compose.yml
|
|
102
|
+
*/
|
|
103
|
+
export function getDockerComposePath() {
|
|
104
|
+
return path.join(getConfigDir(), 'docker-compose.yml');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Get the .env file path
|
|
109
|
+
* @returns {string} Path to generated .env
|
|
110
|
+
*/
|
|
111
|
+
export function getEnvPath() {
|
|
112
|
+
return path.join(getConfigDir(), '.env');
|
|
113
|
+
}
|