@znmin/claude-remote-cli 0.2.6 → 0.2.7
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 +4 -3
- package/package.json +1 -1
- package/src/client.js +4 -1
- package/src/local-mode.js +4 -1
- package/src/system.js +23 -1
package/README.md
CHANGED
|
@@ -25,10 +25,10 @@ npm install -g git+https://github.com/znmin/claude-remote-cli.git
|
|
|
25
25
|
也可以从已下载的分发包安装:
|
|
26
26
|
|
|
27
27
|
```bash
|
|
28
|
-
npm install -g ./znmin-claude-remote-cli-0.2.
|
|
28
|
+
npm install -g ./znmin-claude-remote-cli-0.2.7.tgz
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
若系统 npm 全局目录不可写,可使用 `npm install -g --prefix "$HOME/.local" ./znmin-claude-remote-cli-0.2.
|
|
31
|
+
若系统 npm 全局目录不可写,可使用 `npm install -g --prefix "$HOME/.local" ./znmin-claude-remote-cli-0.2.7.tgz`,并确保 `~/.local/bin` 在 PATH 中;卸载时也使用相同的 `--prefix`。
|
|
32
32
|
|
|
33
33
|
npm 中不带作用域的 `claude-remote-cli` 属于其他项目。本项目使用独立的 `@znmin/claude-remote-cli` 作用域包名;请安装完整包名,或使用上述 GitHub 地址及本项目分发的 `.tgz` 文件。
|
|
34
34
|
|
|
@@ -131,6 +131,7 @@ claude-remote configure my-server --mode local
|
|
|
131
131
|
- 浏览器元素选择器在多次快照间保持不变;快照包含 iframe(选择器形如 `frame=1 >> …`)和开放的 Shadow DOM 元素;截图支持整页和单个元素。
|
|
132
132
|
- 本地项目的 `CLAUDE.md`、`.claude/CLAUDE.md`、`CLAUDE.local.md` 在启动时读取并放入系统提示(合计最多 64 KiB),不再依赖模型自行读取。
|
|
133
133
|
- 关闭终端窗口(SIGHUP)或被 `kill`(SIGTERM)时也会清理浏览器、后台命令、SSH 隧道和服务器临时文件。
|
|
134
|
+
- 0.2.7 起,SSH 断线等原因导致远程 Claude 异常结束时,会自动关闭它遗留的鼠标上报、焦点事件、括号粘贴和扩展键盘等终端模式,避免移动鼠标或按键时输出乱码。若仍显示异常(例如停留在全屏界面),可运行 `reset`。
|
|
134
135
|
- 启动时浏览器检查与 SSH 连接并行,浏览器已就绪时不再调用 Playwright 安装程序;服务器上的版本检查和配置写入合并为一次往返。
|
|
135
136
|
|
|
136
137
|
Git、npm、pnpm、Python、Go、Docker、数据库 CLI、`gh` 等共用本地终端,不需要逐项适配,仍需在本机安装并具备必要的登录、连接配置及权限。终端继承启动用户环境和登录 shell,默认工作目录为当前项目。长任务使用后台进程,再查询日志和停止;退出会话会停止仍受本工具管理的进程组,自行 daemonize 或逃离原进程组的程序不在自动清理保证内。
|
|
@@ -235,7 +236,7 @@ npm test
|
|
|
235
236
|
CLAUDE_REMOTE_INTEGRATION=1 CLAUDE_REMOTE_BROWSER_TEST=1 npm test
|
|
236
237
|
|
|
237
238
|
npm pack
|
|
238
|
-
npm install -g ./znmin-claude-remote-cli-0.2.
|
|
239
|
+
npm install -g ./znmin-claude-remote-cli-0.2.7.tgz
|
|
239
240
|
|
|
240
241
|
# 确定包名、版本和使用条款后,使用有发布权限的 npm 账号发布
|
|
241
242
|
npm login
|
package/package.json
CHANGED
package/src/client.js
CHANGED
|
@@ -2,7 +2,7 @@ import * as fs from 'node:fs/promises';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
import { createHash, randomUUID } from 'node:crypto';
|
|
5
|
-
import { execute, exists, quote, readJSON, shellJoin, which, withLock, writeJSON } from './system.js';
|
|
5
|
+
import { execute, exists, quote, readJSON, restoreTerminal, shellJoin, which, withLock, writeJSON } from './system.js';
|
|
6
6
|
import { ensureMutagen } from './installer.js';
|
|
7
7
|
import { ensureRemoteClaude, remoteEnvironment as prefix } from './remote-claude.js';
|
|
8
8
|
import { sshArgs } from './ssh.js';
|
|
@@ -67,6 +67,7 @@ export class Client {
|
|
|
67
67
|
if (this.env.CLAUDE_REMOTE_DEBUG === '1') this.log(...messages);
|
|
68
68
|
};
|
|
69
69
|
this.tty = options.tty ?? Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
70
|
+
this.restoreTerminal = options.restoreTerminal || (() => restoreTerminal());
|
|
70
71
|
}
|
|
71
72
|
|
|
72
73
|
async configure(input) {
|
|
@@ -204,6 +205,8 @@ export class Client {
|
|
|
204
205
|
try {
|
|
205
206
|
code = (await this.remote(config, prefix + 'cd -- ' + remotePath(item.remote) +
|
|
206
207
|
' && exec ' + shellJoin([config.claude, ...extra]), true)).code;
|
|
208
|
+
// A dropped connection leaves the remote UI's terminal modes switched on.
|
|
209
|
+
if (code !== 0) this.restoreTerminal();
|
|
207
210
|
} finally {
|
|
208
211
|
try { await this.flush(item.name); }
|
|
209
212
|
catch (error) {
|
package/src/local-mode.js
CHANGED
|
@@ -169,7 +169,10 @@ export async function launchLocal(client, config, extra, dependencies = {}) {
|
|
|
169
169
|
const script = remoteEnvironment + 'cd -- ' + remotePath(remoteDirectory) + ' || exit 1; ' +
|
|
170
170
|
'trap ' + quote('rm -f -- ' + remotePath(configFile) + ' ' + remotePath(promptFile)) + ' EXIT; ' +
|
|
171
171
|
shellJoin([config.claude, ...args]);
|
|
172
|
-
|
|
172
|
+
const code = (await remote(script, { interactive: true, check: false })).code;
|
|
173
|
+
// A dropped connection leaves the remote UI's terminal modes switched on.
|
|
174
|
+
if (code !== 0) client.restoreTerminal();
|
|
175
|
+
return code;
|
|
173
176
|
} finally {
|
|
174
177
|
removeCleanup();
|
|
175
178
|
await cleanup();
|
package/src/system.js
CHANGED
|
@@ -57,7 +57,7 @@ export function onCleanup(callback) {
|
|
|
57
57
|
return () => cleanups.delete(callback);
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
export function terminate(signal, { exit = code => process.exit(code), graceMs = 10000 } = {}) {
|
|
60
|
+
export function terminate(signal, { exit = code => process.exit(code), graceMs = 10000, terminal = process.stdout } = {}) {
|
|
61
61
|
if (terminating) return terminating;
|
|
62
62
|
const code = 128 + (os.constants.signals[signal] || 15);
|
|
63
63
|
terminating = (async () => {
|
|
@@ -71,11 +71,33 @@ export function terminate(signal, { exit = code => process.exit(code), graceMs =
|
|
|
71
71
|
for (const child of trackedChildren) {
|
|
72
72
|
try { child.kill('SIGTERM'); } catch { /* already gone */ }
|
|
73
73
|
}
|
|
74
|
+
restoreTerminal(terminal);
|
|
74
75
|
exit(code);
|
|
75
76
|
})();
|
|
76
77
|
return terminating;
|
|
77
78
|
}
|
|
78
79
|
|
|
80
|
+
// A remote full-screen program that loses its connection cannot switch off the
|
|
81
|
+
// terminal modes it enabled, so mouse moves, focus changes and keys show up as
|
|
82
|
+
// stray escape codes. Every sequence here only turns a mode off and is a no-op
|
|
83
|
+
// when the mode is already off. The alternate screen (?1049l) is deliberately
|
|
84
|
+
// left alone: leaving it when not active can move the cursor on some terminals.
|
|
85
|
+
export const TERMINAL_RESET = [
|
|
86
|
+
'\x1b[?1000l', '\x1b[?1002l', '\x1b[?1003l', '\x1b[?1005l', '\x1b[?1006l', '\x1b[?1015l', // mouse reporting
|
|
87
|
+
'\x1b[?1004l', // focus in/out events
|
|
88
|
+
'\x1b[?2004l', // bracketed paste
|
|
89
|
+
'\x1b[=0;1u', '\x1b[<99u', // kitty keyboard protocol: clear flags, pop pushed levels
|
|
90
|
+
'\x1b[>4m', // xterm modifyOtherKeys
|
|
91
|
+
'\x1b[?1l', '\x1b>', // normal cursor keys and keypad
|
|
92
|
+
'\x1b[0m', '\x1b[?25h' // plain text attributes, visible cursor
|
|
93
|
+
].join('');
|
|
94
|
+
|
|
95
|
+
export function restoreTerminal(stream = process.stdout) {
|
|
96
|
+
if (!stream?.isTTY) return false;
|
|
97
|
+
try { stream.write(TERMINAL_RESET); return true; }
|
|
98
|
+
catch { return false; } // The terminal may already be gone (SIGHUP).
|
|
99
|
+
}
|
|
100
|
+
|
|
79
101
|
export function installSignalHandlers() {
|
|
80
102
|
for (const signal of ['SIGHUP', 'SIGTERM']) process.on(signal, () => { terminate(signal); });
|
|
81
103
|
}
|