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 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).
@@ -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
- // Determine config directory
35
- const configDir = getArg('--config')
36
- ? path.dirname(getArg('--config'))
37
- : path.join(process.env.HOME || process.env.USERPROFILE || '~', '.config', 'claude-remote-cli');
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 = getArg('--config') || path.join(configDir, 'config.json');
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-remote-cli",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Remote web interface for Claude Code CLI sessions",
5
5
  "main": "server/index.js",
6
6
  "bin": {
@@ -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 };