@znmin/claude-remote-cli 0.3.1 → 0.4.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/src/installer.js CHANGED
@@ -1,96 +1,96 @@
1
- import * as fs from 'node:fs/promises';
2
- import { createWriteStream, createReadStream } from 'node:fs';
3
- import { createHash } from 'node:crypto';
4
- import { Readable, Transform } from 'node:stream';
5
- import { pipeline } from 'node:stream/promises';
6
- import os from 'node:os';
7
- import path from 'node:path';
8
- import { execute, exists, rename, which, withLock } from './system.js';
9
-
10
- export const VERSION = '0.18.1';
11
- // SHA256SUMS from the official v0.18.1 release, pinned in this package.
12
- export const RELEASES = {
13
- 'darwin-x64': ['darwin_amd64', '7d06f7d8fcfe90bc7e55cc834a2f2f20c2e0af9ea9bc35911fc4341ad56a9bbf'],
14
- 'darwin-arm64': ['darwin_arm64', '6f810416d9e5fc4fd5e18431146f8b3c5a2056ba5a24f76c1e66da86eb3257e2'],
15
- 'linux-x64': ['linux_amd64', '7735286c778cc438418209f24d03a64f3a0151c8065ef0fe079cfaf093af6f8f'],
16
- 'linux-arm64': ['linux_arm64', 'bcba735aebf8cbc11da9b3742118a665599ac697fa06bc5751cac8dcd540db8a'],
17
- 'win32-x64': ['windows_amd64', '3e237e77f69959ed520a0f877330a431507bb0a85d9da7919764ba0c87b702c7'],
18
- 'win32-arm64': ['windows_arm64', '9ac53447e46f019be9d37f49c00eeed8635966b885ed29ef06b3ff19afdee532']
19
- };
20
-
21
- const executableName = (platform = process.platform) => platform === 'win32' ? 'mutagen.exe' : 'mutagen';
22
-
23
- // Windows ships bsdtar in System32, which reads these archives. A GNU tar
24
- // earlier in PATH (e.g. from Git) would treat "C:" in a path as a remote host.
25
- export async function findTar(env = process.env, platform = process.platform) {
26
- if (platform === 'win32') {
27
- const system = path.win32.join(env.SystemRoot || env.SYSTEMROOT || 'C:\\Windows', 'System32', 'tar.exe');
28
- if (await exists(system)) return system;
29
- }
30
- return which('tar', env.PATH, { platform });
31
- }
32
-
33
- export function cachedBinary(env = process.env) {
34
- return path.join(env.CLAUDE_REMOTE_CACHE_DIR || path.join(os.homedir(), '.cache', 'claude-remote'),
35
- `mutagen-${VERSION}-${process.platform}-${process.arch}`, executableName());
36
- }
37
-
38
- export async function verifyChecksum(file, expected) {
39
- const hash = createHash('sha256');
40
- for await (const chunk of createReadStream(file)) hash.update(chunk);
41
- if (hash.digest('hex') !== expected) throw new Error('同步工具 SHA-256 校验失败,已拒绝安装。');
42
- }
43
-
44
- async function download(url, file) {
45
- const response = await fetch(url, { signal: AbortSignal.timeout(180000) });
46
- if (!response.ok || !response.body) throw new Error(`下载同步工具失败:HTTP ${response.status}`);
47
- let bytes = 0;
48
- const limit = new Transform({ transform(chunk, encoding, done) {
49
- bytes += chunk.length;
50
- done(bytes > 200 * 1024 * 1024 ? new Error('同步工具下载大小异常。') : null, chunk);
51
- } });
52
- await pipeline(Readable.fromWeb(response.body), limit, createWriteStream(file, { flags: 'wx', mode: 0o600 }));
53
- }
54
-
55
- export async function ensureMutagen({ env = process.env, forceDownload = false } = {}) {
56
- if (!RELEASES[`${process.platform}-${process.arch}`]) throw new Error('同步工具支持 macOS、Linux 和 Windows 的 x64、arm64。');
57
- if (env.CLAUDE_REMOTE_MUTAGEN) {
58
- const binary = path.resolve(env.CLAUDE_REMOTE_MUTAGEN);
59
- await execute(binary, ['version'], { capture: true, timeout: 10000, env });
60
- return binary;
61
- }
62
- const binary = cachedBinary(env);
63
- if (await exists(binary) && await exists(path.join(path.dirname(binary), 'mutagen-agents.tar.gz'))) return binary;
64
- if (!forceDownload) {
65
- const system = await which('mutagen', env.PATH);
66
- if (system) {
67
- const result = await execute(system, ['version'], { capture: true, timeout: 10000, env, check: false });
68
- if (result.code === 0 && result.stdout.trim() === VERSION) return system;
69
- }
70
- }
71
- const destination = path.dirname(binary);
72
- return withLock(destination + '.lock', async () => {
73
- if (await exists(binary) && await exists(path.join(destination, 'mutagen-agents.tar.gz'))) return binary;
74
- const tar = await findTar(env);
75
- if (!tar) throw new Error('找不到系统 tar,无法解压同步工具。请安装 tar 后重试(Windows 10 1803 及以上自带)。');
76
- const [platform, checksum] = RELEASES[`${process.platform}-${process.arch}`];
77
- const archiveName = `mutagen_${platform}_v${VERSION}.tar.gz`;
78
- const temporary = await fs.mkdtemp(path.join(path.dirname(destination), '.mutagen-install-'));
79
- try {
80
- const archive = path.join(temporary, 'download.tar.gz');
81
- console.error('正在下载同步工具…');
82
- await download(`https://github.com/mutagen-io/mutagen/releases/download/v${VERSION}/${archiveName}`, archive);
83
- await verifyChecksum(archive, checksum);
84
- // Extract only the two required files from the authenticated archive.
85
- const unpacked = path.join(temporary, 'unpacked');
86
- await fs.mkdir(unpacked);
87
- const name = executableName();
88
- await execute(tar, ['-xzf', archive, '-C', unpacked, name, 'mutagen-agents.tar.gz'], { timeout: 60000, env });
89
- await fs.chmod(path.join(unpacked, name), 0o755);
90
- await execute(path.join(unpacked, name), ['version'], { capture: true, timeout: 10000, env });
91
- if (await exists(destination)) await rename(destination, path.join(temporary, 'old-cache'));
92
- await rename(unpacked, destination);
93
- return binary;
94
- } finally { await fs.rm(temporary, { recursive: true, force: true }); }
95
- });
96
- }
1
+ import * as fs from 'node:fs/promises';
2
+ import { createWriteStream, createReadStream } from 'node:fs';
3
+ import { createHash } from 'node:crypto';
4
+ import { Readable, Transform } from 'node:stream';
5
+ import { pipeline } from 'node:stream/promises';
6
+ import os from 'node:os';
7
+ import path from 'node:path';
8
+ import { execute, exists, rename, which, withLock } from './system.js';
9
+
10
+ export const VERSION = '0.18.1';
11
+ // SHA256SUMS from the official v0.18.1 release, pinned in this package.
12
+ export const RELEASES = {
13
+ 'darwin-x64': ['darwin_amd64', '7d06f7d8fcfe90bc7e55cc834a2f2f20c2e0af9ea9bc35911fc4341ad56a9bbf'],
14
+ 'darwin-arm64': ['darwin_arm64', '6f810416d9e5fc4fd5e18431146f8b3c5a2056ba5a24f76c1e66da86eb3257e2'],
15
+ 'linux-x64': ['linux_amd64', '7735286c778cc438418209f24d03a64f3a0151c8065ef0fe079cfaf093af6f8f'],
16
+ 'linux-arm64': ['linux_arm64', 'bcba735aebf8cbc11da9b3742118a665599ac697fa06bc5751cac8dcd540db8a'],
17
+ 'win32-x64': ['windows_amd64', '3e237e77f69959ed520a0f877330a431507bb0a85d9da7919764ba0c87b702c7'],
18
+ 'win32-arm64': ['windows_arm64', '9ac53447e46f019be9d37f49c00eeed8635966b885ed29ef06b3ff19afdee532']
19
+ };
20
+
21
+ const executableName = (platform = process.platform) => platform === 'win32' ? 'mutagen.exe' : 'mutagen';
22
+
23
+ // Windows ships bsdtar in System32, which reads these archives. A GNU tar
24
+ // earlier in PATH (e.g. from Git) would treat "C:" in a path as a remote host.
25
+ export async function findTar(env = process.env, platform = process.platform) {
26
+ if (platform === 'win32') {
27
+ const system = path.win32.join(env.SystemRoot || env.SYSTEMROOT || 'C:\\Windows', 'System32', 'tar.exe');
28
+ if (await exists(system)) return system;
29
+ }
30
+ return which('tar', env.PATH, { platform });
31
+ }
32
+
33
+ export function cachedBinary(env = process.env) {
34
+ return path.join(env.CLAUDE_REMOTE_CACHE_DIR || path.join(os.homedir(), '.cache', 'claude-remote'),
35
+ `mutagen-${VERSION}-${process.platform}-${process.arch}`, executableName());
36
+ }
37
+
38
+ export async function verifyChecksum(file, expected) {
39
+ const hash = createHash('sha256');
40
+ for await (const chunk of createReadStream(file)) hash.update(chunk);
41
+ if (hash.digest('hex') !== expected) throw new Error('同步工具 SHA-256 校验失败,已拒绝安装。');
42
+ }
43
+
44
+ async function download(url, file) {
45
+ const response = await fetch(url, { signal: AbortSignal.timeout(180000) });
46
+ if (!response.ok || !response.body) throw new Error(`下载同步工具失败:HTTP ${response.status}`);
47
+ let bytes = 0;
48
+ const limit = new Transform({ transform(chunk, encoding, done) {
49
+ bytes += chunk.length;
50
+ done(bytes > 200 * 1024 * 1024 ? new Error('同步工具下载大小异常。') : null, chunk);
51
+ } });
52
+ await pipeline(Readable.fromWeb(response.body), limit, createWriteStream(file, { flags: 'wx', mode: 0o600 }));
53
+ }
54
+
55
+ export async function ensureMutagen({ env = process.env, forceDownload = false } = {}) {
56
+ if (!RELEASES[`${process.platform}-${process.arch}`]) throw new Error('同步工具支持 macOS、Linux 和 Windows 的 x64、arm64。');
57
+ if (env.CLAUDE_REMOTE_MUTAGEN) {
58
+ const binary = path.resolve(env.CLAUDE_REMOTE_MUTAGEN);
59
+ await execute(binary, ['version'], { capture: true, timeout: 10000, env });
60
+ return binary;
61
+ }
62
+ const binary = cachedBinary(env);
63
+ if (await exists(binary) && await exists(path.join(path.dirname(binary), 'mutagen-agents.tar.gz'))) return binary;
64
+ if (!forceDownload) {
65
+ const system = await which('mutagen', env.PATH);
66
+ if (system) {
67
+ const result = await execute(system, ['version'], { capture: true, timeout: 10000, env, check: false });
68
+ if (result.code === 0 && result.stdout.trim() === VERSION) return system;
69
+ }
70
+ }
71
+ const destination = path.dirname(binary);
72
+ return withLock(destination + '.lock', async () => {
73
+ if (await exists(binary) && await exists(path.join(destination, 'mutagen-agents.tar.gz'))) return binary;
74
+ const tar = await findTar(env);
75
+ if (!tar) throw new Error('找不到系统 tar,无法解压同步工具。请安装 tar 后重试(Windows 10 1803 及以上自带)。');
76
+ const [platform, checksum] = RELEASES[`${process.platform}-${process.arch}`];
77
+ const archiveName = `mutagen_${platform}_v${VERSION}.tar.gz`;
78
+ const temporary = await fs.mkdtemp(path.join(path.dirname(destination), '.mutagen-install-'));
79
+ try {
80
+ const archive = path.join(temporary, 'download.tar.gz');
81
+ console.error('正在下载同步工具…');
82
+ await download(`https://github.com/mutagen-io/mutagen/releases/download/v${VERSION}/${archiveName}`, archive);
83
+ await verifyChecksum(archive, checksum);
84
+ // Extract only the two required files from the authenticated archive.
85
+ const unpacked = path.join(temporary, 'unpacked');
86
+ await fs.mkdir(unpacked);
87
+ const name = executableName();
88
+ await execute(tar, ['-xzf', archive, '-C', unpacked, name, 'mutagen-agents.tar.gz'], { timeout: 60000, env });
89
+ await fs.chmod(path.join(unpacked, name), 0o755);
90
+ await execute(path.join(unpacked, name), ['version'], { capture: true, timeout: 10000, env });
91
+ if (await exists(destination)) await rename(destination, path.join(temporary, 'old-cache'));
92
+ await rename(unpacked, destination);
93
+ return binary;
94
+ } finally { await fs.rm(temporary, { recursive: true, force: true }); }
95
+ });
96
+ }