claude-phone-local 2.1.0 → 2.1.1

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.
@@ -1,8 +1,8 @@
1
1
  import chalk from 'chalk';
2
2
  import { spawn } from 'child_process';
3
- import axios from 'axios';
4
- import { loadConfig, configExists, getDockerComposePath, getPidPath } from '../config.js';
3
+ import { loadConfig, configExists, getDockerComposePath, getPidPath, getConfigDir } from '../config.js';
5
4
  import fs from 'fs';
5
+ import path from 'path';
6
6
 
7
7
  /**
8
8
  * Logs command - Tail service logs
@@ -104,49 +104,53 @@ function tailDockerLogs(dockerComposePath) {
104
104
 
105
105
  /**
106
106
  * Tail API server logs
107
- * @param {object} config - Configuration object
107
+ * @param {object} _config - Configuration object (unused; log path is fixed)
108
108
  */
109
- function tailAPIServerLogs(config) {
110
- console.log(chalk.gray('Watching Claude API server output...\n'));
109
+ function tailAPIServerLogs(_config) {
110
+ const logPath = path.join(getConfigDir(), 'claude-api-server.log');
111
111
 
112
- // Since the server runs detached, we can't easily tail its logs
113
- // Instead, we'll monitor its health endpoint
114
- console.log(chalk.yellow('Note: API server logs are not available (runs detached)'));
115
- console.log(chalk.gray('Monitoring health endpoint instead...\n'));
112
+ if (!fs.existsSync(logPath)) {
113
+ console.log(chalk.yellow('⚠ No log file yet - the API server has not written any output'));
114
+ console.log(chalk.gray(` Expected at: ${logPath}\n`));
115
+ process.exit(1);
116
+ }
117
+
118
+ console.log(chalk.gray(`Watching ${logPath}\n`));
119
+
120
+ // Print the last ~50 lines, then follow new writes. No native `tail -f` on
121
+ // Windows, so poll the file size and read only the appended bytes.
122
+ const fullContent = fs.readFileSync(logPath, 'utf8');
123
+ const lines = fullContent.split('\n');
124
+ const tailLines = lines.slice(Math.max(0, lines.length - 50));
125
+ process.stdout.write(tailLines.join('\n'));
116
126
 
117
- let consecutiveFailures = 0;
127
+ let position = fs.statSync(logPath).size;
118
128
 
119
- const checkHealth = async () => {
129
+ const poll = setInterval(() => {
120
130
  try {
121
- const response = await axios.get(`http://localhost:${config.server.claudeApiPort}/health`, {
122
- timeout: 3000
123
- });
124
-
125
- if (response.status === 200) {
126
- console.log(chalk.green(`[${new Date().toISOString()}] ✓ API server healthy`));
127
- consecutiveFailures = 0;
128
- } else {
129
- console.log(chalk.yellow(`[${new Date().toISOString()}] ⚠ Unexpected status: ${response.status}`));
131
+ const { size } = fs.statSync(logPath);
132
+ if (size < position) {
133
+ // Log file was rotated/truncated (e.g. by a restart) - start over.
134
+ position = 0;
130
135
  }
131
- } catch (error) {
132
- consecutiveFailures++;
133
- console.log(chalk.red(`[${new Date().toISOString()}] ✗ Health check failed: ${error.message}`));
134
-
135
- if (consecutiveFailures >= 3) {
136
- console.log(chalk.red('\n✗ API server appears to be down. Stopping health checks.\n'));
137
- process.exit(1);
136
+ if (size > position) {
137
+ const fd = fs.openSync(logPath, 'r');
138
+ const buffer = Buffer.alloc(size - position);
139
+ fs.readSync(fd, buffer, 0, buffer.length, position);
140
+ fs.closeSync(fd);
141
+ process.stdout.write(buffer.toString('utf8'));
142
+ position = size;
138
143
  }
144
+ } catch (err) {
145
+ console.log(chalk.red(`\n✗ Lost access to log file: ${err.message}\n`));
146
+ clearInterval(poll);
147
+ process.exit(1);
139
148
  }
140
- };
149
+ }, 1000);
141
150
 
142
- // Check immediately, then every 5 seconds
143
- checkHealth();
144
- const interval = setInterval(checkHealth, 5000);
145
-
146
- // Handle Ctrl+C
147
151
  process.on('SIGINT', () => {
148
- clearInterval(interval);
149
- console.log(chalk.gray('\n\nStopped monitoring API server.\n'));
152
+ clearInterval(poll);
153
+ console.log(chalk.gray('\n\nStopped tailing logs.\n'));
150
154
  process.exit(0);
151
155
  });
152
156
  }
@@ -157,7 +161,7 @@ function tailAPIServerLogs(config) {
157
161
  * @param {object} _config - Configuration object (unused)
158
162
  */
159
163
  function tailBothServices(dockerComposePath, _config) {
160
- console.log(chalk.gray('Showing Docker container logs (API server logs not available)\n'));
164
+ console.log(chalk.gray('Showing Docker container logs. Run "claude-phone logs api-server" separately for the host-side API server log.\n'));
161
165
 
162
166
  const child = spawn('docker', [
163
167
  'compose',
@@ -70,17 +70,26 @@ export async function startServer(serverPath, port, pidPath = null) {
70
70
  }
71
71
 
72
72
  return new Promise((resolve, reject) => {
73
+ // Redirect stdout/stderr to a log file instead of discarding them - this
74
+ // process's own console.log is the only place query timing, session
75
+ // tracking, and CLI/SDK errors are visible; with stdio: 'ignore' there
76
+ // was no way to diagnose a slow or failed call after the fact.
77
+ const logPath = path.join(getConfigDir(), 'claude-api-server.log');
78
+ const logFd = fs.openSync(logPath, 'a');
79
+
73
80
  // Spawn detached process
74
81
  const child = spawn('node', ['server.js'], {
75
82
  cwd: serverPath,
76
83
  detached: true,
77
- stdio: 'ignore',
84
+ stdio: ['ignore', logFd, logFd],
78
85
  env: {
79
86
  ...process.env,
80
87
  PORT: port
81
88
  }
82
89
  });
83
90
 
91
+ fs.closeSync(logFd); // child holds its own reference to the fd via dup()
92
+
84
93
  // Don't wait for child process
85
94
  child.unref();
86
95
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-phone-local",
3
- "version": "2.1.0",
3
+ "version": "2.1.1",
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": {