@bash0816/claude-code 2.1.245 → 2.1.251
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 +46 -1
- package/bin/claude +10 -0
- package/config/claude-native-audited-versions.json +167 -1
- package/config/claude-termux-release-manifest.json +3 -3
- package/lib/bunfs-esm-loader.mjs +234 -21
- package/lib/bunfs-esm-loader.test.js +1048 -14
- package/lib/native-validators.js +90 -0
- package/lib/native-validators.test.js +160 -0
- package/lib/prepare-native.js +5 -71
- package/lib/termux-run-claude-native.sh +55 -22
- package/lib/termux-run-claude-native.test.js +123 -0
- package/package.json +2 -2
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const crypto = require('crypto');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
const packageDir = path.resolve(__dirname, '..');
|
|
8
|
+
|
|
9
|
+
function verifyTarball(file, audited, version) {
|
|
10
|
+
const buf = fs.readFileSync(file);
|
|
11
|
+
if (audited.tarball_sha256) {
|
|
12
|
+
const sha256 = crypto.createHash('sha256').update(buf).digest('hex');
|
|
13
|
+
if (sha256 !== audited.tarball_sha256) {
|
|
14
|
+
throw new Error(`tarball sha256 mismatch for ${version}`);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
if (audited.tarball_integrity) {
|
|
18
|
+
const sha512 = `sha512-${crypto.createHash('sha512').update(buf).digest('base64')}`;
|
|
19
|
+
if (sha512 !== audited.tarball_integrity) {
|
|
20
|
+
throw new Error(`tarball integrity mismatch for ${version}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
if (audited.tarball_size !== undefined && Number(audited.tarball_size) !== buf.length) {
|
|
24
|
+
throw new Error(`tarball size mismatch for ${version}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function validateOffsets(file, audited, version) {
|
|
29
|
+
if (audited.entry_format === 'esm-chunked') {
|
|
30
|
+
validateEsmChunkedOffsets(file, audited, version);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
validateLegacyCjsOffsets(file, audited, version);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function validateLegacyCjsOffsets(file, audited, version) {
|
|
37
|
+
const buf = fs.readFileSync(file);
|
|
38
|
+
const start = Number(audited.entry_js_offset);
|
|
39
|
+
const end = Number(audited.entry_end_offset);
|
|
40
|
+
const startMarker = Buffer.from('function(exports, require, module, __filename, __dirname) {// Claude Code is a Beta product');
|
|
41
|
+
const endMarker = Buffer.from('/$bunfs/root/image-processor.js');
|
|
42
|
+
|
|
43
|
+
if (!(start > 0 && end > start && end <= buf.length)) {
|
|
44
|
+
throw new Error(`invalid audited offsets for ${version}`);
|
|
45
|
+
}
|
|
46
|
+
if (!buf.subarray(start, start + startMarker.length).equals(startMarker)) {
|
|
47
|
+
throw new Error(`audited start offset validation failed for ${version}`);
|
|
48
|
+
}
|
|
49
|
+
if (!buf.subarray(end, end + endMarker.length).equals(endMarker)) {
|
|
50
|
+
throw new Error(`audited end offset validation failed for ${version}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function validateEsmChunkedOffsets(file, audited, version) {
|
|
55
|
+
// 371MB超のバイナリ全体をreadFileSyncしない(実機でOOM確認済み)。
|
|
56
|
+
// discoverModuleGraphは範囲readSyncのみでトレイラー・モジュールテーブルを検証する。
|
|
57
|
+
const { discoverModuleGraph, readEntryContentPrefix } = require(path.join(packageDir, 'lib', 'bunfs-extract.js'));
|
|
58
|
+
const graph = discoverModuleGraph(file);
|
|
59
|
+
try {
|
|
60
|
+
if (!(graph.numModules > 0)) {
|
|
61
|
+
throw new Error(`esm-chunked module graph is empty for ${version}`);
|
|
62
|
+
}
|
|
63
|
+
if (graph.entryName !== '/$bunfs/root/cli') {
|
|
64
|
+
throw new Error(`esm-chunked entry module name mismatch for ${version}: ${graph.entryName}`);
|
|
65
|
+
}
|
|
66
|
+
if (!Object.prototype.hasOwnProperty.call(audited, 'cycle_hoists')) {
|
|
67
|
+
throw new Error(`esm-chunked audited metadata for ${version} is missing cycle_hoists field`);
|
|
68
|
+
}
|
|
69
|
+
if (audited.num_modules !== undefined && graph.numModules !== audited.num_modules) {
|
|
70
|
+
throw new Error(`esm-chunked num_modules mismatch for ${version}: expected ${audited.num_modules}, got ${graph.numModules}`);
|
|
71
|
+
}
|
|
72
|
+
if (audited.byte_count !== undefined && graph.byteCount !== audited.byte_count) {
|
|
73
|
+
throw new Error(`esm-chunked byte_count mismatch for ${version}: expected ${audited.byte_count}, got ${graph.byteCount}`);
|
|
74
|
+
}
|
|
75
|
+
const prefix = readEntryContentPrefix(graph.fd, graph.entryModule, 256).toString('utf8');
|
|
76
|
+
const codeStart = prefix.replace(/^(\s*\/\/[^\n]*\n)+/, '').replace(/^\(/, '');
|
|
77
|
+
if (codeStart.startsWith('function(exports, require, module, __filename, __dirname) {')) {
|
|
78
|
+
throw new Error(`entry module for ${version} is legacy-cjs wrapped, but audited entry_format is esm-chunked`);
|
|
79
|
+
}
|
|
80
|
+
} finally {
|
|
81
|
+
fs.closeSync(graph.fd);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
module.exports = {
|
|
86
|
+
validateEsmChunkedOffsets,
|
|
87
|
+
validateLegacyCjsOffsets,
|
|
88
|
+
validateOffsets,
|
|
89
|
+
verifyTarball,
|
|
90
|
+
};
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const test = require('node:test');
|
|
4
|
+
const assert = require('node:assert/strict');
|
|
5
|
+
const fs = require('node:fs');
|
|
6
|
+
const os = require('node:os');
|
|
7
|
+
const path = require('node:path');
|
|
8
|
+
|
|
9
|
+
const {
|
|
10
|
+
validateEsmChunkedOffsets,
|
|
11
|
+
} = require('./native-validators.js');
|
|
12
|
+
|
|
13
|
+
const {
|
|
14
|
+
discoverModuleGraph,
|
|
15
|
+
} = require('./bunfs-extract.js');
|
|
16
|
+
|
|
17
|
+
const TRAILER = '\n---- Bun! ----\n';
|
|
18
|
+
|
|
19
|
+
// StandaloneModuleGraphの最小合成バイナリを構築する。
|
|
20
|
+
// レイアウト: [preamble padding][module contents][module table][Offsets(32byte)][trailer]
|
|
21
|
+
function buildSyntheticBinary({ modules, entryPointId, corruptTrailer = false, preamblePadding = 64 }) {
|
|
22
|
+
const nameBuffers = modules.map((m) => Buffer.from(m.name, 'utf8'));
|
|
23
|
+
const contentBuffers = modules.map((m) => Buffer.from(m.content ?? '', 'utf8'));
|
|
24
|
+
|
|
25
|
+
const dataParts = [];
|
|
26
|
+
const nameOffsets = [];
|
|
27
|
+
const contOffsets = [];
|
|
28
|
+
let cursor = 0;
|
|
29
|
+
for (let i = 0; i < modules.length; i += 1) {
|
|
30
|
+
nameOffsets.push(cursor);
|
|
31
|
+
dataParts.push(nameBuffers[i]);
|
|
32
|
+
cursor += nameBuffers[i].length;
|
|
33
|
+
}
|
|
34
|
+
for (let i = 0; i < modules.length; i += 1) {
|
|
35
|
+
contOffsets.push(cursor);
|
|
36
|
+
dataParts.push(contentBuffers[i]);
|
|
37
|
+
cursor += contentBuffers[i].length;
|
|
38
|
+
}
|
|
39
|
+
const byteCountBeforeTable = cursor;
|
|
40
|
+
|
|
41
|
+
const MODULE_TABLE_ENTRY_SIZE = 52;
|
|
42
|
+
const modTable = Buffer.alloc(MODULE_TABLE_ENTRY_SIZE * modules.length);
|
|
43
|
+
for (let i = 0; i < modules.length; i += 1) {
|
|
44
|
+
const base = i * MODULE_TABLE_ENTRY_SIZE;
|
|
45
|
+
modTable.writeUInt32LE(nameOffsets[i], base);
|
|
46
|
+
modTable.writeUInt32LE(nameBuffers[i].length, base + 4);
|
|
47
|
+
modTable.writeUInt32LE(contOffsets[i], base + 8);
|
|
48
|
+
modTable.writeUInt32LE(contentBuffers[i].length, base + 12);
|
|
49
|
+
modTable[base + 49] = modules[i].loader ?? 1; // 1 = js
|
|
50
|
+
}
|
|
51
|
+
const modulesOffset = byteCountBeforeTable;
|
|
52
|
+
const modulesLength = modTable.length;
|
|
53
|
+
const byteCount = byteCountBeforeTable + modulesLength;
|
|
54
|
+
|
|
55
|
+
const offsetsBuf = Buffer.alloc(32);
|
|
56
|
+
offsetsBuf.writeBigUInt64LE(BigInt(byteCount), 0);
|
|
57
|
+
offsetsBuf.writeUInt32LE(modulesOffset, 8);
|
|
58
|
+
offsetsBuf.writeUInt32LE(modulesLength, 12);
|
|
59
|
+
offsetsBuf.writeUInt32LE(entryPointId, 16);
|
|
60
|
+
|
|
61
|
+
const trailerBuf = Buffer.from(corruptTrailer ? '\n---- NOT BUN ----\n' : TRAILER, 'utf8');
|
|
62
|
+
|
|
63
|
+
return Buffer.concat([
|
|
64
|
+
Buffer.alloc(preamblePadding),
|
|
65
|
+
...dataParts,
|
|
66
|
+
modTable,
|
|
67
|
+
offsetsBuf,
|
|
68
|
+
trailerBuf,
|
|
69
|
+
]);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function writeTempBinary(buf) {
|
|
73
|
+
const file = path.join(os.tmpdir(), `native-validators-test-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}.bin`);
|
|
74
|
+
fs.writeFileSync(file, buf);
|
|
75
|
+
return file;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
test('validateEsmChunkedOffsets accepts matching num_modules and byte_count', () => {
|
|
79
|
+
const buf = buildSyntheticBinary({
|
|
80
|
+
modules: [
|
|
81
|
+
{ name: '/$bunfs/root/cli', content: 'export default 1;' },
|
|
82
|
+
],
|
|
83
|
+
entryPointId: 0,
|
|
84
|
+
});
|
|
85
|
+
const file = writeTempBinary(buf);
|
|
86
|
+
try {
|
|
87
|
+
const graph = discoverModuleGraph(file);
|
|
88
|
+
try {
|
|
89
|
+
const audited = {
|
|
90
|
+
num_modules: graph.numModules,
|
|
91
|
+
byte_count: graph.byteCount,
|
|
92
|
+
entry_format: 'esm-chunked',
|
|
93
|
+
cycle_hoists: [],
|
|
94
|
+
};
|
|
95
|
+
assert.doesNotThrow(() => validateEsmChunkedOffsets(file, audited, '9.9.9'));
|
|
96
|
+
} finally {
|
|
97
|
+
fs.closeSync(graph.fd);
|
|
98
|
+
}
|
|
99
|
+
} finally {
|
|
100
|
+
fs.rmSync(file, { force: true });
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test('validateEsmChunkedOffsets rejects mismatched num_modules', () => {
|
|
105
|
+
const buf = buildSyntheticBinary({
|
|
106
|
+
modules: [
|
|
107
|
+
{ name: '/$bunfs/root/cli', content: 'export default 1;' },
|
|
108
|
+
],
|
|
109
|
+
entryPointId: 0,
|
|
110
|
+
});
|
|
111
|
+
const file = writeTempBinary(buf);
|
|
112
|
+
try {
|
|
113
|
+
const graph = discoverModuleGraph(file);
|
|
114
|
+
try {
|
|
115
|
+
const audited = {
|
|
116
|
+
num_modules: graph.numModules + 1, // intentionally wrong
|
|
117
|
+
byte_count: graph.byteCount,
|
|
118
|
+
entry_format: 'esm-chunked',
|
|
119
|
+
cycle_hoists: [],
|
|
120
|
+
};
|
|
121
|
+
assert.throws(
|
|
122
|
+
() => validateEsmChunkedOffsets(file, audited, '9.9.9'),
|
|
123
|
+
/num_modules mismatch/,
|
|
124
|
+
);
|
|
125
|
+
} finally {
|
|
126
|
+
fs.closeSync(graph.fd);
|
|
127
|
+
}
|
|
128
|
+
} finally {
|
|
129
|
+
fs.rmSync(file, { force: true });
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test('validateEsmChunkedOffsets rejects mismatched byte_count', () => {
|
|
134
|
+
const buf = buildSyntheticBinary({
|
|
135
|
+
modules: [
|
|
136
|
+
{ name: '/$bunfs/root/cli', content: 'export default 1;' },
|
|
137
|
+
],
|
|
138
|
+
entryPointId: 0,
|
|
139
|
+
});
|
|
140
|
+
const file = writeTempBinary(buf);
|
|
141
|
+
try {
|
|
142
|
+
const graph = discoverModuleGraph(file);
|
|
143
|
+
try {
|
|
144
|
+
const audited = {
|
|
145
|
+
num_modules: graph.numModules,
|
|
146
|
+
byte_count: graph.byteCount + 1, // intentionally wrong
|
|
147
|
+
entry_format: 'esm-chunked',
|
|
148
|
+
cycle_hoists: [],
|
|
149
|
+
};
|
|
150
|
+
assert.throws(
|
|
151
|
+
() => validateEsmChunkedOffsets(file, audited, '9.9.9'),
|
|
152
|
+
/byte_count mismatch/,
|
|
153
|
+
);
|
|
154
|
+
} finally {
|
|
155
|
+
fs.closeSync(graph.fd);
|
|
156
|
+
}
|
|
157
|
+
} finally {
|
|
158
|
+
fs.rmSync(file, { force: true });
|
|
159
|
+
}
|
|
160
|
+
});
|
package/lib/prepare-native.js
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
const cp = require('child_process');
|
|
5
|
-
const crypto = require('crypto');
|
|
6
5
|
const fs = require('fs');
|
|
7
6
|
const os = require('os');
|
|
8
7
|
const path = require('path');
|
|
@@ -16,6 +15,8 @@ const curlRetries = process.env.CLAUDE_TERMUX_FETCH_RETRIES || '4';
|
|
|
16
15
|
const curlConnectTimeout = process.env.CLAUDE_TERMUX_FETCH_CONNECT_TIMEOUT || '20';
|
|
17
16
|
const curlMaxTime = process.env.CLAUDE_TERMUX_FETCH_MAX_TIME || '300';
|
|
18
17
|
|
|
18
|
+
const { validateOffsets, verifyTarball } = require(path.join(packageDir, 'lib', 'native-validators.js'));
|
|
19
|
+
|
|
19
20
|
if (!item) {
|
|
20
21
|
console.error(`Unsupported audited Claude Code version: ${version}`);
|
|
21
22
|
process.exit(1);
|
|
@@ -27,7 +28,7 @@ const nativeDest = path.join(versionDir, 'app', 'node_modules', '@anthropic-ai',
|
|
|
27
28
|
const sourceBin = path.join(nativeDest, 'claude');
|
|
28
29
|
|
|
29
30
|
if (fs.existsSync(sourceBin)) {
|
|
30
|
-
validateOffsets(sourceBin, item);
|
|
31
|
+
validateOffsets(sourceBin, item, version);
|
|
31
32
|
process.exit(0);
|
|
32
33
|
}
|
|
33
34
|
|
|
@@ -36,7 +37,7 @@ const packDir = fs.mkdtempSync(path.join(os.tmpdir(), `claude-code-${version}-`)
|
|
|
36
37
|
|
|
37
38
|
try {
|
|
38
39
|
const tgzPath = fetchNativeTarball(item.native_spec, packDir);
|
|
39
|
-
verifyTarball(tgzPath, item);
|
|
40
|
+
verifyTarball(tgzPath, item, version);
|
|
40
41
|
|
|
41
42
|
const extractDir = path.join(packDir, 'native');
|
|
42
43
|
fs.mkdirSync(extractDir, { recursive: true });
|
|
@@ -45,7 +46,7 @@ try {
|
|
|
45
46
|
fs.rmSync(nativeDest, { recursive: true, force: true });
|
|
46
47
|
fs.mkdirSync(path.dirname(nativeDest), { recursive: true });
|
|
47
48
|
fs.cpSync(path.join(extractDir, 'package'), nativeDest, { recursive: true });
|
|
48
|
-
validateOffsets(sourceBin, item);
|
|
49
|
+
validateOffsets(sourceBin, item, version);
|
|
49
50
|
} finally {
|
|
50
51
|
fs.rmSync(packDir, { recursive: true, force: true });
|
|
51
52
|
}
|
|
@@ -107,70 +108,3 @@ function fetchNativeTarball(spec, packDir) {
|
|
|
107
108
|
}
|
|
108
109
|
return path.join(packDir, packEntry.filename);
|
|
109
110
|
}
|
|
110
|
-
|
|
111
|
-
function verifyTarball(file, audited) {
|
|
112
|
-
const buf = fs.readFileSync(file);
|
|
113
|
-
if (audited.tarball_sha256) {
|
|
114
|
-
const sha256 = crypto.createHash('sha256').update(buf).digest('hex');
|
|
115
|
-
if (sha256 !== audited.tarball_sha256) {
|
|
116
|
-
throw new Error(`tarball sha256 mismatch for ${version}`);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
if (audited.tarball_integrity) {
|
|
120
|
-
const sha512 = `sha512-${crypto.createHash('sha512').update(buf).digest('base64')}`;
|
|
121
|
-
if (sha512 !== audited.tarball_integrity) {
|
|
122
|
-
throw new Error(`tarball integrity mismatch for ${version}`);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
if (audited.tarball_size !== undefined && Number(audited.tarball_size) !== buf.length) {
|
|
126
|
-
throw new Error(`tarball size mismatch for ${version}`);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
function validateOffsets(file, audited) {
|
|
131
|
-
if (audited.entry_format === 'esm-chunked') {
|
|
132
|
-
validateEsmChunkedOffsets(file, audited);
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
validateLegacyCjsOffsets(file, audited);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
function validateLegacyCjsOffsets(file, audited) {
|
|
139
|
-
const buf = fs.readFileSync(file);
|
|
140
|
-
const start = Number(audited.entry_js_offset);
|
|
141
|
-
const end = Number(audited.entry_end_offset);
|
|
142
|
-
const startMarker = Buffer.from('function(exports, require, module, __filename, __dirname) {// Claude Code is a Beta product');
|
|
143
|
-
const endMarker = Buffer.from('/$bunfs/root/image-processor.js');
|
|
144
|
-
|
|
145
|
-
if (!(start > 0 && end > start && end <= buf.length)) {
|
|
146
|
-
throw new Error(`invalid audited offsets for ${version}`);
|
|
147
|
-
}
|
|
148
|
-
if (!buf.subarray(start, start + startMarker.length).equals(startMarker)) {
|
|
149
|
-
throw new Error(`audited start offset validation failed for ${version}`);
|
|
150
|
-
}
|
|
151
|
-
if (!buf.subarray(end, end + endMarker.length).equals(endMarker)) {
|
|
152
|
-
throw new Error(`audited end offset validation failed for ${version}`);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
function validateEsmChunkedOffsets(file, audited) {
|
|
157
|
-
// 371MB超のバイナリ全体をreadFileSyncしない(実機でOOM確認済み)。
|
|
158
|
-
// discoverModuleGraphは範囲readSyncのみでトレイラー・モジュールテーブルを検証する。
|
|
159
|
-
const { discoverModuleGraph, readEntryContentPrefix } = require(path.join(packageDir, 'lib', 'bunfs-extract.js'));
|
|
160
|
-
const graph = discoverModuleGraph(file);
|
|
161
|
-
try {
|
|
162
|
-
if (!(graph.numModules > 0)) {
|
|
163
|
-
throw new Error(`esm-chunked module graph is empty for ${version}`);
|
|
164
|
-
}
|
|
165
|
-
if (graph.entryName !== '/$bunfs/root/cli') {
|
|
166
|
-
throw new Error(`esm-chunked entry module name mismatch for ${version}: ${graph.entryName}`);
|
|
167
|
-
}
|
|
168
|
-
const prefix = readEntryContentPrefix(graph.fd, graph.entryModule, 256).toString('utf8');
|
|
169
|
-
const codeStart = prefix.replace(/^(\s*\/\/[^\n]*\n)+/, '').replace(/^\(/, '');
|
|
170
|
-
if (codeStart.startsWith('function(exports, require, module, __filename, __dirname) {')) {
|
|
171
|
-
throw new Error(`entry module for ${version} is legacy-cjs wrapped, but audited entry_format is esm-chunked`);
|
|
172
|
-
}
|
|
173
|
-
} finally {
|
|
174
|
-
fs.closeSync(graph.fd);
|
|
175
|
-
}
|
|
176
|
-
}
|
|
@@ -55,6 +55,7 @@ export ENTRY_FORMAT
|
|
|
55
55
|
export ENTRY_JS_OFFSET
|
|
56
56
|
export ENTRY_END_OFFSET
|
|
57
57
|
export CURRENT_CLAUDE_VERSION
|
|
58
|
+
export ENABLE_TOOL_SEARCH="${ENABLE_TOOL_SEARCH:-false}"
|
|
58
59
|
|
|
59
60
|
_pf=0
|
|
60
61
|
for _a in "$@"; do
|
|
@@ -664,8 +665,8 @@ function rewriteNativeChunkSource(source) {
|
|
|
664
665
|
}
|
|
665
666
|
|
|
666
667
|
async function esmChunkedMain() {
|
|
667
|
-
const { prepareProcessOwnedDir } = require(path.join(process.env.CLAUDE_TERMUX_PACKAGE_DIR, 'lib', 'bunfs-extract.js'));
|
|
668
|
-
const {
|
|
668
|
+
const { prepareProcessOwnedDir, extractToProcessOwnedDir } = require(path.join(process.env.CLAUDE_TERMUX_PACKAGE_DIR, 'lib', 'bunfs-extract.js'));
|
|
669
|
+
const { registerHooks } = require('node:module');
|
|
669
670
|
const { pathToFileURL } = require('node:url');
|
|
670
671
|
|
|
671
672
|
const { ownedDir, entryRelPath } = prepareProcessOwnedDir(sourceBin, workdir);
|
|
@@ -685,21 +686,37 @@ async function esmChunkedMain() {
|
|
|
685
686
|
},
|
|
686
687
|
gc: () => {},
|
|
687
688
|
YAML: globalThis.__claudeYaml,
|
|
689
|
+
zstdDecompressSync: (buf) => require('node:zlib').zstdDecompressSync(buf),
|
|
690
|
+
zstdDecompress: (buf) => new Promise((res, rej) =>
|
|
691
|
+
require('node:zlib').zstdDecompress(buf, (e, r) => (e ? rej(e) : res(r)))),
|
|
688
692
|
};
|
|
689
693
|
Object.defineProperty(process.versions, 'bun', { value: '1.1.8', configurable: true });
|
|
690
694
|
globalThis.__claudeBunShim = globalThis.Bun;
|
|
691
695
|
globalThis.__claudeBun = globalThis.Bun;
|
|
692
696
|
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
697
|
+
let cycleHoists = [];
|
|
698
|
+
try {
|
|
699
|
+
const auditedVersions = require(path.join(process.env.CLAUDE_TERMUX_PACKAGE_DIR, 'config', 'claude-native-audited-versions.json'));
|
|
700
|
+
const entry = auditedVersions.versions?.[process.env.CURRENT_CLAUDE_VERSION];
|
|
701
|
+
if (entry && Array.isArray(entry.cycle_hoists)) {
|
|
702
|
+
cycleHoists = entry.cycle_hoists;
|
|
703
|
+
}
|
|
704
|
+
} catch {
|
|
705
|
+
// 読み込み失敗時は空配列のまま(fail-closed、既存の同期require経路にフォールバック)
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
const loaderMod = require(path.join(libDir, 'bunfs-esm-loader.mjs'));
|
|
709
|
+
loaderMod.initialize({
|
|
710
|
+
processOwnedDir: ownedDir,
|
|
711
|
+
sourceBin,
|
|
712
|
+
childProcessGuardPath: path.join(libDir, 'bunfs-child-process-guard.mjs'),
|
|
713
|
+
vmGuardPath: path.join(libDir, 'bunfs-vm-guard.mjs'),
|
|
714
|
+
wsStubPath: path.join(libDir, 'bunfs-ws-stub.mjs'),
|
|
715
|
+
cycleHoists,
|
|
716
|
+
reExtract: (sb, od) => extractToProcessOwnedDir(sb, od),
|
|
702
717
|
});
|
|
718
|
+
loaderMod.installFsBunfsInterception();
|
|
719
|
+
registerHooks({ resolve: loaderMod.resolve, load: loaderMod.load });
|
|
703
720
|
|
|
704
721
|
const entryUrl = pathToFileURL(path.join(ownedDir, entryRelPath)).href;
|
|
705
722
|
|
|
@@ -1678,8 +1695,8 @@ function rewriteNativeChunkSource(source) {
|
|
|
1678
1695
|
}
|
|
1679
1696
|
|
|
1680
1697
|
async function esmChunkedMain() {
|
|
1681
|
-
const { prepareProcessOwnedDir } = require(path.join(process.env.CLAUDE_TERMUX_PACKAGE_DIR, 'lib', 'bunfs-extract.js'));
|
|
1682
|
-
const {
|
|
1698
|
+
const { prepareProcessOwnedDir, extractToProcessOwnedDir } = require(path.join(process.env.CLAUDE_TERMUX_PACKAGE_DIR, 'lib', 'bunfs-extract.js'));
|
|
1699
|
+
const { registerHooks } = require('node:module');
|
|
1683
1700
|
const { pathToFileURL } = require('node:url');
|
|
1684
1701
|
|
|
1685
1702
|
const { ownedDir, entryRelPath } = prepareProcessOwnedDir(sourceBin, workdir);
|
|
@@ -1699,21 +1716,37 @@ async function esmChunkedMain() {
|
|
|
1699
1716
|
},
|
|
1700
1717
|
gc: () => {},
|
|
1701
1718
|
YAML: globalThis.__claudeYaml,
|
|
1719
|
+
zstdDecompressSync: (buf) => require('node:zlib').zstdDecompressSync(buf),
|
|
1720
|
+
zstdDecompress: (buf) => new Promise((res, rej) =>
|
|
1721
|
+
require('node:zlib').zstdDecompress(buf, (e, r) => (e ? rej(e) : res(r)))),
|
|
1702
1722
|
};
|
|
1703
1723
|
Object.defineProperty(process.versions, 'bun', { value: '1.1.8', configurable: true });
|
|
1704
1724
|
globalThis.__claudeBunShim = globalThis.Bun;
|
|
1705
1725
|
globalThis.__claudeBun = globalThis.Bun;
|
|
1706
1726
|
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1727
|
+
let cycleHoists = [];
|
|
1728
|
+
try {
|
|
1729
|
+
const auditedVersions = require(path.join(process.env.CLAUDE_TERMUX_PACKAGE_DIR, 'config', 'claude-native-audited-versions.json'));
|
|
1730
|
+
const entry = auditedVersions.versions?.[process.env.CURRENT_CLAUDE_VERSION];
|
|
1731
|
+
if (entry && Array.isArray(entry.cycle_hoists)) {
|
|
1732
|
+
cycleHoists = entry.cycle_hoists;
|
|
1733
|
+
}
|
|
1734
|
+
} catch {
|
|
1735
|
+
// 読み込み失敗時は空配列のまま(fail-closed、既存の同期require経路にフォールバック)
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1738
|
+
const loaderMod = require(path.join(libDir, 'bunfs-esm-loader.mjs'));
|
|
1739
|
+
loaderMod.initialize({
|
|
1740
|
+
processOwnedDir: ownedDir,
|
|
1741
|
+
sourceBin,
|
|
1742
|
+
childProcessGuardPath: path.join(libDir, 'bunfs-child-process-guard.mjs'),
|
|
1743
|
+
vmGuardPath: path.join(libDir, 'bunfs-vm-guard.mjs'),
|
|
1744
|
+
wsStubPath: path.join(libDir, 'bunfs-ws-stub.mjs'),
|
|
1745
|
+
cycleHoists,
|
|
1746
|
+
reExtract: (sb, od) => extractToProcessOwnedDir(sb, od),
|
|
1716
1747
|
});
|
|
1748
|
+
loaderMod.installFsBunfsInterception();
|
|
1749
|
+
registerHooks({ resolve: loaderMod.resolve, load: loaderMod.load });
|
|
1717
1750
|
|
|
1718
1751
|
const entryUrl = pathToFileURL(path.join(ownedDir, entryRelPath)).href;
|
|
1719
1752
|
|
|
@@ -1692,3 +1692,126 @@ test('Bun.spawn forwards top-level stdin in the first stdio position', () => {
|
|
|
1692
1692
|
}
|
|
1693
1693
|
}
|
|
1694
1694
|
});
|
|
1695
|
+
|
|
1696
|
+
// New tests for fs interception and zstd support
|
|
1697
|
+
|
|
1698
|
+
// esmChunkedMain() (esm-chunked 形式、今回の fs-intercept 修正の対象) の関数本体だけを抽出する。
|
|
1699
|
+
// legacyCjsMain() の Bun shim (zstd 非対応でよい、意図的に無変更) を誤って対象に含めないため、
|
|
1700
|
+
// 「both esmChunkedMain blocks have correct hook registration order」テストと同じ境界抽出方式を使う。
|
|
1701
|
+
function extractEsmChunkedMainBlocks() {
|
|
1702
|
+
const marker = 'async function esmChunkedMain()';
|
|
1703
|
+
const blocks = [];
|
|
1704
|
+
let offset = 0;
|
|
1705
|
+
while ((offset = script.indexOf(marker, offset)) !== -1) {
|
|
1706
|
+
const blockStart = offset;
|
|
1707
|
+
const blockEnd = script.indexOf('\nasync function', offset + 1);
|
|
1708
|
+
const actualBlockEnd = blockEnd !== -1 ? blockEnd : script.length;
|
|
1709
|
+
blocks.push(script.slice(blockStart, actualBlockEnd));
|
|
1710
|
+
offset = actualBlockEnd;
|
|
1711
|
+
}
|
|
1712
|
+
return blocks;
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1715
|
+
test('both esmChunkedMain Bun shim blocks contain zstdDecompressSync and zstdDecompress fields', () => {
|
|
1716
|
+
const blocks = extractEsmChunkedMainBlocks();
|
|
1717
|
+
assert.ok(blocks.length >= 2, 'should have at least 2 esmChunkedMain blocks (helper and bootstrap)');
|
|
1718
|
+
|
|
1719
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
1720
|
+
const block = blocks[i];
|
|
1721
|
+
assert.ok(
|
|
1722
|
+
block.includes('zstdDecompressSync:'),
|
|
1723
|
+
`Block ${i}: missing zstdDecompressSync field`,
|
|
1724
|
+
);
|
|
1725
|
+
assert.ok(
|
|
1726
|
+
block.includes('zstdDecompress:'),
|
|
1727
|
+
`Block ${i}: missing zstdDecompress field`,
|
|
1728
|
+
);
|
|
1729
|
+
}
|
|
1730
|
+
});
|
|
1731
|
+
|
|
1732
|
+
test('zstd functions are properly defined for sync decompression', () => {
|
|
1733
|
+
const blocks = extractEsmChunkedMainBlocks();
|
|
1734
|
+
assert.ok(blocks.length >= 1, 'should have at least 1 esmChunkedMain block');
|
|
1735
|
+
|
|
1736
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
1737
|
+
const block = blocks[i];
|
|
1738
|
+
const syncMatch = block.match(/zstdDecompressSync:\s*\([^)]*\)\s*=>\s*require\('node:zlib'\)\.zstdDecompressSync\([^)]*\)/);
|
|
1739
|
+
assert.ok(syncMatch, `Block ${i}: zstdDecompressSync should call require("node:zlib").zstdDecompressSync`);
|
|
1740
|
+
|
|
1741
|
+
const asyncMatch = block.match(/zstdDecompress:\s*\([^)]*\)\s*=>\s*new Promise/);
|
|
1742
|
+
assert.ok(asyncMatch, `Block ${i}: zstdDecompress should return a Promise`);
|
|
1743
|
+
}
|
|
1744
|
+
});
|
|
1745
|
+
|
|
1746
|
+
test('zstd decompression works with real zlib.zstd APIs', async () => {
|
|
1747
|
+
const zlib = require('node:zlib');
|
|
1748
|
+
|
|
1749
|
+
// Skip if zstd not available
|
|
1750
|
+
if (typeof zlib.zstdCompressSync !== 'function') {
|
|
1751
|
+
return;
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1754
|
+
const testData = Buffer.from('Hello, compression world!');
|
|
1755
|
+
const compressed = zlib.zstdCompressSync(testData);
|
|
1756
|
+
|
|
1757
|
+
// Test sync decompression
|
|
1758
|
+
const decompressedSync = zlib.zstdDecompressSync(compressed);
|
|
1759
|
+
assert.deepEqual(decompressedSync, testData);
|
|
1760
|
+
|
|
1761
|
+
// Test async decompression
|
|
1762
|
+
const decompressedAsync = await new Promise((resolve, reject) => {
|
|
1763
|
+
zlib.zstdDecompress(compressed, (err, result) => {
|
|
1764
|
+
if (err) reject(err);
|
|
1765
|
+
else resolve(result);
|
|
1766
|
+
});
|
|
1767
|
+
});
|
|
1768
|
+
assert.deepEqual(decompressedAsync, testData);
|
|
1769
|
+
});
|
|
1770
|
+
|
|
1771
|
+
test('both esmChunkedMain blocks have correct hook registration order', () => {
|
|
1772
|
+
const esmChunkedMarker = 'async function esmChunkedMain()';
|
|
1773
|
+
let blockCount = 0;
|
|
1774
|
+
let offset = 0;
|
|
1775
|
+
|
|
1776
|
+
while ((offset = script.indexOf(esmChunkedMarker, offset)) !== -1) {
|
|
1777
|
+
blockCount++;
|
|
1778
|
+
const blockStart = offset;
|
|
1779
|
+
const blockEnd = script.indexOf('\nasync function', offset + 1);
|
|
1780
|
+
const actualBlockEnd = blockEnd !== -1 ? blockEnd : script.length;
|
|
1781
|
+
const block = script.slice(blockStart, actualBlockEnd);
|
|
1782
|
+
|
|
1783
|
+
// Find the three key operations
|
|
1784
|
+
const initIdx = block.indexOf('loaderMod.initialize({');
|
|
1785
|
+
const interceptIdx = block.indexOf('loaderMod.installFsBunfsInterception()');
|
|
1786
|
+
const registerIdx = block.indexOf('registerHooks({');
|
|
1787
|
+
|
|
1788
|
+
assert.ok(initIdx !== -1, `Block ${blockCount}: missing initialize call`);
|
|
1789
|
+
assert.ok(interceptIdx !== -1, `Block ${blockCount}: missing installFsBunfsInterception call`);
|
|
1790
|
+
assert.ok(registerIdx !== -1, `Block ${blockCount}: missing registerHooks call`);
|
|
1791
|
+
|
|
1792
|
+
// Verify order: initialize < installFsBunfsInterception < registerHooks
|
|
1793
|
+
assert.ok(
|
|
1794
|
+
initIdx < interceptIdx && interceptIdx < registerIdx,
|
|
1795
|
+
`Block ${blockCount}: initialization order incorrect (initialize=${initIdx}, intercept=${interceptIdx}, register=${registerIdx})`,
|
|
1796
|
+
);
|
|
1797
|
+
|
|
1798
|
+
offset = actualBlockEnd;
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1801
|
+
assert.ok(blockCount >= 2, 'should have at least 2 esmChunkedMain blocks');
|
|
1802
|
+
});
|
|
1803
|
+
|
|
1804
|
+
test('termux-run-claude-native.sh maintains compatibility with new zstd fields', () => {
|
|
1805
|
+
// Verify that the script structure is preserved
|
|
1806
|
+
const hasSourceBin = script.includes('SOURCE_BIN=');
|
|
1807
|
+
const hasWorkdir = script.includes('WORKDIR=');
|
|
1808
|
+
const hasGlobalThis = script.includes('globalThis');
|
|
1809
|
+
|
|
1810
|
+
assert.ok(hasSourceBin, 'script should set SOURCE_BIN');
|
|
1811
|
+
assert.ok(hasWorkdir, 'script should set WORKDIR');
|
|
1812
|
+
assert.ok(hasGlobalThis, 'script should manipulate globalThis');
|
|
1813
|
+
|
|
1814
|
+
// Verify both blocks exist and are distinct
|
|
1815
|
+
const blocks = (script.match(/globalThis\.Bun\s*=\s*{/g) || []);
|
|
1816
|
+
assert.ok(blocks.length >= 2, 'should have at least 2 Bun initializations for helper and bootstrap');
|
|
1817
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bash0816/claude-code",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.251",
|
|
4
4
|
"description": "Unofficial Termux-native Claude Code wrapper with audited native replay",
|
|
5
5
|
"license": "GPL-3.0-only",
|
|
6
6
|
"bin": {
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"LICENSE"
|
|
16
16
|
],
|
|
17
17
|
"engines": {
|
|
18
|
-
"node": ">=
|
|
18
|
+
"node": ">=22.15.0 <23.0.0 || >=23.8.0"
|
|
19
19
|
},
|
|
20
20
|
"keywords": [
|
|
21
21
|
"claude-code",
|