claude-remote-cli 0.2.0 → 0.3.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/README.md +35 -2
- package/bin/claude-remote-cli.js +54 -5
- package/package.json +1 -1
- package/server/service.js +181 -0
package/README.md
CHANGED
|
@@ -34,16 +34,49 @@ Tested on **macOS** and **Linux**. Windows is not currently tested — file watc
|
|
|
34
34
|
## CLI Usage
|
|
35
35
|
|
|
36
36
|
```
|
|
37
|
-
claude-remote-cli [options]
|
|
37
|
+
Usage: claude-remote-cli [options]
|
|
38
|
+
claude-remote-cli <command>
|
|
39
|
+
|
|
40
|
+
Commands:
|
|
41
|
+
install Install as a background service (survives reboot)
|
|
42
|
+
uninstall Stop and remove the background service
|
|
43
|
+
status Show whether the service is running
|
|
38
44
|
|
|
39
45
|
Options:
|
|
46
|
+
--bg Shortcut: install and start as background service
|
|
40
47
|
--port <port> Override server port (default: 3456)
|
|
41
48
|
--host <host> Override bind address (default: 0.0.0.0)
|
|
42
|
-
--config <path> Path to config.json
|
|
49
|
+
--config <path> Path to config.json (default: ~/.config/claude-remote-cli/config.json)
|
|
43
50
|
--version, -v Show version
|
|
44
51
|
--help, -h Show this help
|
|
45
52
|
```
|
|
46
53
|
|
|
54
|
+
## Background Service
|
|
55
|
+
|
|
56
|
+
Run as a persistent service that starts on login and restarts on crash:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
claude-remote-cli --bg
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Or with custom options:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
claude-remote-cli install --port 4000
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Manage the service:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
claude-remote-cli status # Check if running
|
|
72
|
+
claude-remote-cli uninstall # Stop and remove
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
- **macOS**: Uses launchd (`~/Library/LaunchAgents/`)
|
|
76
|
+
- **Linux**: Uses systemd user units (`~/.config/systemd/user/`)
|
|
77
|
+
- **Logs (macOS)**: `~/.config/claude-remote-cli/logs/`
|
|
78
|
+
- **Logs (Linux)**: `journalctl --user -u claude-remote-cli -f`
|
|
79
|
+
|
|
47
80
|
## Configuration
|
|
48
81
|
|
|
49
82
|
Config is stored at `~/.config/claude-remote-cli/config.json` (created on first run).
|
package/bin/claude-remote-cli.js
CHANGED
|
@@ -9,8 +9,15 @@ const args = process.argv.slice(2);
|
|
|
9
9
|
|
|
10
10
|
if (args.includes('--help') || args.includes('-h')) {
|
|
11
11
|
console.log(`Usage: claude-remote-cli [options]
|
|
12
|
+
claude-remote-cli <command>
|
|
13
|
+
|
|
14
|
+
Commands:
|
|
15
|
+
install Install as a background service (survives reboot)
|
|
16
|
+
uninstall Stop and remove the background service
|
|
17
|
+
status Show whether the service is running
|
|
12
18
|
|
|
13
19
|
Options:
|
|
20
|
+
--bg Shortcut: install and start as background service
|
|
14
21
|
--port <port> Override server port (default: 3456)
|
|
15
22
|
--host <host> Override bind address (default: 0.0.0.0)
|
|
16
23
|
--config <path> Path to config.json (default: ~/.config/claude-remote-cli/config.json)
|
|
@@ -31,12 +38,54 @@ function getArg(flag) {
|
|
|
31
38
|
return args[idx + 1];
|
|
32
39
|
}
|
|
33
40
|
|
|
34
|
-
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
41
|
+
function resolveConfigPath() {
|
|
42
|
+
const explicit = getArg('--config');
|
|
43
|
+
if (explicit) return explicit;
|
|
44
|
+
const { CONFIG_DIR } = require('../server/service');
|
|
45
|
+
return path.join(CONFIG_DIR, 'config.json');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function runServiceCommand(fn) {
|
|
49
|
+
try {
|
|
50
|
+
fn();
|
|
51
|
+
} catch (e) {
|
|
52
|
+
console.error(e.message);
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
process.exit(0);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const command = args[0];
|
|
59
|
+
if (command === 'install' || command === 'uninstall' || command === 'status' || args.includes('--bg')) {
|
|
60
|
+
const service = require('../server/service');
|
|
61
|
+
|
|
62
|
+
if (command === 'uninstall') {
|
|
63
|
+
runServiceCommand(function () { service.uninstall(); });
|
|
64
|
+
} else if (command === 'status') {
|
|
65
|
+
runServiceCommand(function () {
|
|
66
|
+
const st = service.status();
|
|
67
|
+
if (!st.installed) {
|
|
68
|
+
console.log('Service is not installed.');
|
|
69
|
+
} else if (st.running) {
|
|
70
|
+
console.log('Service is installed and running.');
|
|
71
|
+
} else {
|
|
72
|
+
console.log('Service is installed but not running.');
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
} else {
|
|
76
|
+
runServiceCommand(function () {
|
|
77
|
+
const { DEFAULTS } = require('../server/config');
|
|
78
|
+
service.install({
|
|
79
|
+
configPath: resolveConfigPath(),
|
|
80
|
+
port: getArg('--port') || String(DEFAULTS.port),
|
|
81
|
+
host: getArg('--host') || DEFAULTS.host,
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
38
86
|
|
|
39
|
-
const configPath =
|
|
87
|
+
const configPath = resolveConfigPath();
|
|
88
|
+
const configDir = path.dirname(configPath);
|
|
40
89
|
|
|
41
90
|
// Ensure config directory exists
|
|
42
91
|
if (!fs.existsSync(configDir)) {
|
package/package.json
CHANGED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { execSync } = require('child_process');
|
|
4
|
+
const { DEFAULTS } = require('./config');
|
|
5
|
+
|
|
6
|
+
const SERVICE_LABEL = 'com.claude-remote-cli';
|
|
7
|
+
const HOME = process.env.HOME || process.env.USERPROFILE || '~';
|
|
8
|
+
const CONFIG_DIR = path.join(HOME, '.config', 'claude-remote-cli');
|
|
9
|
+
|
|
10
|
+
function getPlatform() {
|
|
11
|
+
if (process.platform === 'darwin') return 'macos';
|
|
12
|
+
if (process.platform === 'linux') return 'linux';
|
|
13
|
+
throw new Error('Unsupported platform: ' + process.platform + '. Only macOS and Linux are supported.');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function getServicePaths() {
|
|
17
|
+
const platform = getPlatform();
|
|
18
|
+
if (platform === 'macos') {
|
|
19
|
+
return {
|
|
20
|
+
servicePath: path.join(HOME, 'Library', 'LaunchAgents', SERVICE_LABEL + '.plist'),
|
|
21
|
+
logDir: path.join(CONFIG_DIR, 'logs'),
|
|
22
|
+
label: SERVICE_LABEL,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
servicePath: path.join(HOME, '.config', 'systemd', 'user', 'claude-remote-cli.service'),
|
|
27
|
+
logDir: null,
|
|
28
|
+
label: 'claude-remote-cli',
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function generateServiceFile(platform, opts) {
|
|
33
|
+
const { nodePath, scriptPath, configPath, port, host, logDir } = opts;
|
|
34
|
+
|
|
35
|
+
if (platform === 'macos') {
|
|
36
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
37
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
38
|
+
<plist version="1.0">
|
|
39
|
+
<dict>
|
|
40
|
+
<key>Label</key>
|
|
41
|
+
<string>${SERVICE_LABEL}</string>
|
|
42
|
+
<key>ProgramArguments</key>
|
|
43
|
+
<array>
|
|
44
|
+
<string>${nodePath}</string>
|
|
45
|
+
<string>${scriptPath}</string>
|
|
46
|
+
<string>--config</string>
|
|
47
|
+
<string>${configPath}</string>
|
|
48
|
+
<string>--port</string>
|
|
49
|
+
<string>${port}</string>
|
|
50
|
+
<string>--host</string>
|
|
51
|
+
<string>${host}</string>
|
|
52
|
+
</array>
|
|
53
|
+
<key>RunAtLoad</key>
|
|
54
|
+
<true/>
|
|
55
|
+
<key>KeepAlive</key>
|
|
56
|
+
<true/>
|
|
57
|
+
<key>StandardOutPath</key>
|
|
58
|
+
<string>${path.join(logDir, 'stdout.log')}</string>
|
|
59
|
+
<key>StandardErrorPath</key>
|
|
60
|
+
<string>${path.join(logDir, 'stderr.log')}</string>
|
|
61
|
+
<key>EnvironmentVariables</key>
|
|
62
|
+
<dict>
|
|
63
|
+
<key>PATH</key>
|
|
64
|
+
<string>${process.env.PATH}</string>
|
|
65
|
+
</dict>
|
|
66
|
+
</dict>
|
|
67
|
+
</plist>`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return `[Unit]
|
|
71
|
+
Description=Claude Remote CLI
|
|
72
|
+
After=network.target
|
|
73
|
+
|
|
74
|
+
[Service]
|
|
75
|
+
Type=simple
|
|
76
|
+
ExecStart=${nodePath} ${scriptPath} --config ${configPath} --port ${port} --host ${host}
|
|
77
|
+
Restart=on-failure
|
|
78
|
+
RestartSec=5
|
|
79
|
+
Environment=PATH=${process.env.PATH}
|
|
80
|
+
|
|
81
|
+
[Install]
|
|
82
|
+
WantedBy=default.target`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function isInstalled() {
|
|
86
|
+
const { servicePath } = getServicePaths();
|
|
87
|
+
return fs.existsSync(servicePath);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function install(opts) {
|
|
91
|
+
const platform = getPlatform();
|
|
92
|
+
const { servicePath, logDir } = getServicePaths();
|
|
93
|
+
|
|
94
|
+
if (isInstalled()) {
|
|
95
|
+
throw new Error('Service is already installed. Run `claude-remote-cli uninstall` first.');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const nodePath = process.execPath;
|
|
99
|
+
const scriptPath = path.resolve(__dirname, '..', 'bin', 'claude-remote-cli.js');
|
|
100
|
+
const configPath = opts.configPath || path.join(CONFIG_DIR, 'config.json');
|
|
101
|
+
const port = opts.port || String(DEFAULTS.port);
|
|
102
|
+
const host = opts.host || DEFAULTS.host;
|
|
103
|
+
|
|
104
|
+
const content = generateServiceFile(platform, { nodePath, scriptPath, configPath, port, host, logDir });
|
|
105
|
+
|
|
106
|
+
fs.mkdirSync(path.dirname(servicePath), { recursive: true });
|
|
107
|
+
if (logDir) fs.mkdirSync(logDir, { recursive: true });
|
|
108
|
+
|
|
109
|
+
fs.writeFileSync(servicePath, content, 'utf8');
|
|
110
|
+
|
|
111
|
+
if (platform === 'macos') {
|
|
112
|
+
execSync('launchctl load -w ' + servicePath, { stdio: 'inherit' });
|
|
113
|
+
} else {
|
|
114
|
+
execSync('systemctl --user daemon-reload', { stdio: 'inherit' });
|
|
115
|
+
execSync('systemctl --user enable --now claude-remote-cli', { stdio: 'inherit' });
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
console.log('Service installed and started.');
|
|
119
|
+
if (logDir) {
|
|
120
|
+
console.log('Logs: ' + logDir);
|
|
121
|
+
} else {
|
|
122
|
+
console.log('Logs: journalctl --user -u claude-remote-cli -f');
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function uninstall() {
|
|
127
|
+
const platform = getPlatform();
|
|
128
|
+
const { servicePath } = getServicePaths();
|
|
129
|
+
|
|
130
|
+
if (!isInstalled()) {
|
|
131
|
+
throw new Error('Service is not installed.');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (platform === 'macos') {
|
|
135
|
+
try {
|
|
136
|
+
execSync('launchctl unload ' + servicePath, { stdio: 'inherit' });
|
|
137
|
+
} catch (_) {
|
|
138
|
+
// Ignore errors from already-unloaded services
|
|
139
|
+
}
|
|
140
|
+
} else {
|
|
141
|
+
try {
|
|
142
|
+
execSync('systemctl --user disable --now claude-remote-cli', { stdio: 'inherit' });
|
|
143
|
+
} catch (_) {
|
|
144
|
+
// Ignore errors from already-disabled services
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
fs.unlinkSync(servicePath);
|
|
149
|
+
console.log('Service uninstalled.');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function status() {
|
|
153
|
+
const platform = getPlatform();
|
|
154
|
+
|
|
155
|
+
if (!isInstalled()) {
|
|
156
|
+
return { installed: false, running: false };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const running = checkRunning(platform);
|
|
160
|
+
return { installed: true, running };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function checkRunning(platform) {
|
|
164
|
+
if (platform === 'macos') {
|
|
165
|
+
try {
|
|
166
|
+
const out = execSync('launchctl list ' + SERVICE_LABEL, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] });
|
|
167
|
+
return !out.includes('"LastExitStatus" = -1');
|
|
168
|
+
} catch (_) {
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
try {
|
|
174
|
+
execSync('systemctl --user is-active claude-remote-cli', { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
175
|
+
return true;
|
|
176
|
+
} catch (_) {
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
module.exports = { getPlatform, getServicePaths, generateServiceFile, isInstalled, install, uninstall, status, SERVICE_LABEL, CONFIG_DIR };
|