@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.
@@ -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
+ });
@@ -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 { register } = require('node:module');
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
- register(pathToFileURL(path.join(libDir, 'bunfs-esm-loader.mjs')).href, {
694
- parentURL: pathToFileURL(__filename).href,
695
- data: {
696
- processOwnedDir: ownedDir,
697
- sourceBin,
698
- childProcessGuardPath: path.join(libDir, 'bunfs-child-process-guard.mjs'),
699
- vmGuardPath: path.join(libDir, 'bunfs-vm-guard.mjs'),
700
- wsStubPath: path.join(libDir, 'bunfs-ws-stub.mjs'),
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 { register } = require('node:module');
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
- register(pathToFileURL(path.join(libDir, 'bunfs-esm-loader.mjs')).href, {
1708
- parentURL: pathToFileURL(__filename).href,
1709
- data: {
1710
- processOwnedDir: ownedDir,
1711
- sourceBin,
1712
- childProcessGuardPath: path.join(libDir, 'bunfs-child-process-guard.mjs'),
1713
- vmGuardPath: path.join(libDir, 'bunfs-vm-guard.mjs'),
1714
- wsStubPath: path.join(libDir, 'bunfs-ws-stub.mjs'),
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.245",
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": ">=20.6.0"
18
+ "node": ">=22.15.0 <23.0.0 || >=23.8.0"
19
19
  },
20
20
  "keywords": [
21
21
  "claude-code",