@bash0816/claude-code 2.1.241 → 2.1.248

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,40 +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
- const buf = fs.readFileSync(file);
132
- const start = Number(audited.entry_js_offset);
133
- const end = Number(audited.entry_end_offset);
134
- const startMarker = Buffer.from('function(exports, require, module, __filename, __dirname) {// Claude Code is a Beta product');
135
- const endMarker = Buffer.from('/$bunfs/root/image-processor.js');
136
-
137
- if (!(start > 0 && end > start && end <= buf.length)) {
138
- throw new Error(`invalid audited offsets for ${version}`);
139
- }
140
- if (!buf.subarray(start, start + startMarker.length).equals(startMarker)) {
141
- throw new Error(`audited start offset validation failed for ${version}`);
142
- }
143
- if (!buf.subarray(end, end + endMarker.length).equals(endMarker)) {
144
- throw new Error(`audited end offset validation failed for ${version}`);
145
- }
146
- }
@@ -3,8 +3,14 @@ set -eu
3
3
 
4
4
  SOURCE_BIN="${SOURCE_BIN:?SOURCE_BIN is required}"
5
5
  WORKDIR="${WORKDIR:-${HOME}/.claude-termux-native-package/launcher-workdir}"
6
- ENTRY_JS_OFFSET="${ENTRY_JS_OFFSET:?ENTRY_JS_OFFSET is required}"
7
- ENTRY_END_OFFSET="${ENTRY_END_OFFSET:?ENTRY_END_OFFSET is required}"
6
+ ENTRY_FORMAT="${ENTRY_FORMAT:-legacy-cjs}"
7
+ if [ "${ENTRY_FORMAT}" = "esm-chunked" ]; then
8
+ ENTRY_JS_OFFSET="${ENTRY_JS_OFFSET:-0}"
9
+ ENTRY_END_OFFSET="${ENTRY_END_OFFSET:-0}"
10
+ else
11
+ ENTRY_JS_OFFSET="${ENTRY_JS_OFFSET:?ENTRY_JS_OFFSET is required}"
12
+ ENTRY_END_OFFSET="${ENTRY_END_OFFSET:?ENTRY_END_OFFSET is required}"
13
+ fi
8
14
  CURRENT_CLAUDE_VERSION="${CURRENT_CLAUDE_VERSION:?CURRENT_CLAUDE_VERSION is required}"
9
15
  CLAUDE_TERMUX_PACKAGE_DIR="${CLAUDE_TERMUX_PACKAGE_DIR:?CLAUDE_TERMUX_PACKAGE_DIR is required}"
10
16
  TERMUX_TMPDIR="${TMPDIR:-/data/data/com.termux/files/usr/tmp}"
@@ -45,6 +51,7 @@ export SSL_CERT_FILE
45
51
  export DISABLE_AUTOUPDATER
46
52
  export SOURCE_BIN
47
53
  export WORKDIR
54
+ export ENTRY_FORMAT
48
55
  export ENTRY_JS_OFFSET
49
56
  export ENTRY_END_OFFSET
50
57
  export CURRENT_CLAUDE_VERSION
@@ -656,7 +663,71 @@ function rewriteNativeChunkSource(source) {
656
663
  return patched;
657
664
  }
658
665
 
659
- async function main() {
666
+ async function esmChunkedMain() {
667
+ const { prepareProcessOwnedDir } = require(path.join(process.env.CLAUDE_TERMUX_PACKAGE_DIR, 'lib', 'bunfs-extract.js'));
668
+ const { registerHooks } = require('node:module');
669
+ const { pathToFileURL } = require('node:url');
670
+
671
+ const { ownedDir, entryRelPath } = prepareProcessOwnedDir(sourceBin, workdir);
672
+ const libDir = path.join(process.env.CLAUDE_TERMUX_PACKAGE_DIR, 'lib');
673
+
674
+ globalThis.__claudeYaml = createYamlShim();
675
+ globalThis.Bun = {
676
+ version: '1.1.8',
677
+ stringWidth,
678
+ wrapAnsi,
679
+ stripANSI,
680
+ hash: stableHash,
681
+ which: (cmd) => {
682
+ try {
683
+ return require('child_process').execFileSync('which', [String(cmd)], { encoding: 'utf8' }).trim() || null;
684
+ } catch { return null; }
685
+ },
686
+ gc: () => {},
687
+ YAML: globalThis.__claudeYaml,
688
+ };
689
+ Object.defineProperty(process.versions, 'bun', { value: '1.1.8', configurable: true });
690
+ globalThis.__claudeBunShim = globalThis.Bun;
691
+ globalThis.__claudeBun = globalThis.Bun;
692
+
693
+ let cycleHoists = [];
694
+ try {
695
+ const auditedVersions = require(path.join(process.env.CLAUDE_TERMUX_PACKAGE_DIR, 'config', 'claude-native-audited-versions.json'));
696
+ const entry = auditedVersions.versions?.[process.env.CURRENT_CLAUDE_VERSION];
697
+ if (entry && Array.isArray(entry.cycle_hoists)) {
698
+ cycleHoists = entry.cycle_hoists;
699
+ }
700
+ } catch {
701
+ // 読み込み失敗時は空配列のまま(fail-closed、既存の同期require経路にフォールバック)
702
+ }
703
+
704
+ const loaderMod = require(path.join(libDir, 'bunfs-esm-loader.mjs'));
705
+ loaderMod.initialize({
706
+ processOwnedDir: ownedDir,
707
+ sourceBin,
708
+ childProcessGuardPath: path.join(libDir, 'bunfs-child-process-guard.mjs'),
709
+ vmGuardPath: path.join(libDir, 'bunfs-vm-guard.mjs'),
710
+ wsStubPath: path.join(libDir, 'bunfs-ws-stub.mjs'),
711
+ cycleHoists,
712
+ });
713
+ registerHooks({ resolve: loaderMod.resolve, load: loaderMod.load });
714
+
715
+ const entryUrl = pathToFileURL(path.join(ownedDir, entryRelPath)).href;
716
+
717
+ // 2.1.245実チャンクのエントリは、内部のmain相当処理をトップレベルでawaitせず
718
+ // fire-and-forgetで起動する(Bunランタイム前提の実装)。そのためawait import()は
719
+ // 内部の非同期処理が完了する前に解決してしまい、legacy-cjs経路のような
720
+ // process.exitパッチ+ここでの強制exit呼び出しを行うと、まだ実行中の内部処理を
721
+ // 強制終了させ出力が失われる(実機で確認済み)。process.exit/killは一切パッチせず、
722
+ // 実際のCLIコードが自ら呼ぶprocess.exit()に任せてNodeの自然なイベントループ終了を
723
+ // 待つ(この関数はawait import()完了後、何もせずreturnするだけでよい)。
724
+ // 同じ理由で、ここでglobalThis.Bun/__claudeYamlを削除するcleanupも行わない
725
+ // (fire-and-forgetの内部処理がimport()解決後も継続してBunを参照するため、
726
+ // 早期に消すと実機で"Bun is not defined"を引き起こす。プロセス終了まで残す)。
727
+ await import(entryUrl);
728
+ }
729
+
730
+ async function legacyCjsMain() {
660
731
  let extractedFile;
661
732
  extractedFile = ensureEntryFile();
662
733
  const code = fs.readFileSync(extractedFile, 'utf8');
@@ -1000,6 +1071,10 @@ async function main() {
1000
1071
  }
1001
1072
  }
1002
1073
 
1074
+ function main() {
1075
+ return process.env.ENTRY_FORMAT === 'esm-chunked' ? esmChunkedMain() : legacyCjsMain();
1076
+ }
1077
+
1003
1078
  main().catch(error => {
1004
1079
  if (error && error.code === 'CLAUDE_TERMUX_OFFICIAL_UPDATE_BLOCKED') {
1005
1080
  console.error(BLOCK_MESSAGE);
@@ -1613,7 +1688,71 @@ function rewriteNativeChunkSource(source) {
1613
1688
  return patched;
1614
1689
  }
1615
1690
 
1616
- async function main() {
1691
+ async function esmChunkedMain() {
1692
+ const { prepareProcessOwnedDir } = require(path.join(process.env.CLAUDE_TERMUX_PACKAGE_DIR, 'lib', 'bunfs-extract.js'));
1693
+ const { registerHooks } = require('node:module');
1694
+ const { pathToFileURL } = require('node:url');
1695
+
1696
+ const { ownedDir, entryRelPath } = prepareProcessOwnedDir(sourceBin, workdir);
1697
+ const libDir = path.join(process.env.CLAUDE_TERMUX_PACKAGE_DIR, 'lib');
1698
+
1699
+ globalThis.__claudeYaml = createYamlShim();
1700
+ globalThis.Bun = {
1701
+ version: '1.1.8',
1702
+ stringWidth,
1703
+ wrapAnsi,
1704
+ stripANSI,
1705
+ hash: stableHash,
1706
+ which: (cmd) => {
1707
+ try {
1708
+ return require('child_process').execFileSync('which', [String(cmd)], { encoding: 'utf8' }).trim() || null;
1709
+ } catch { return null; }
1710
+ },
1711
+ gc: () => {},
1712
+ YAML: globalThis.__claudeYaml,
1713
+ };
1714
+ Object.defineProperty(process.versions, 'bun', { value: '1.1.8', configurable: true });
1715
+ globalThis.__claudeBunShim = globalThis.Bun;
1716
+ globalThis.__claudeBun = globalThis.Bun;
1717
+
1718
+ let cycleHoists = [];
1719
+ try {
1720
+ const auditedVersions = require(path.join(process.env.CLAUDE_TERMUX_PACKAGE_DIR, 'config', 'claude-native-audited-versions.json'));
1721
+ const entry = auditedVersions.versions?.[process.env.CURRENT_CLAUDE_VERSION];
1722
+ if (entry && Array.isArray(entry.cycle_hoists)) {
1723
+ cycleHoists = entry.cycle_hoists;
1724
+ }
1725
+ } catch {
1726
+ // 読み込み失敗時は空配列のまま(fail-closed、既存の同期require経路にフォールバック)
1727
+ }
1728
+
1729
+ const loaderMod = require(path.join(libDir, 'bunfs-esm-loader.mjs'));
1730
+ loaderMod.initialize({
1731
+ processOwnedDir: ownedDir,
1732
+ sourceBin,
1733
+ childProcessGuardPath: path.join(libDir, 'bunfs-child-process-guard.mjs'),
1734
+ vmGuardPath: path.join(libDir, 'bunfs-vm-guard.mjs'),
1735
+ wsStubPath: path.join(libDir, 'bunfs-ws-stub.mjs'),
1736
+ cycleHoists,
1737
+ });
1738
+ registerHooks({ resolve: loaderMod.resolve, load: loaderMod.load });
1739
+
1740
+ const entryUrl = pathToFileURL(path.join(ownedDir, entryRelPath)).href;
1741
+
1742
+ // 2.1.245実チャンクのエントリは、内部のmain相当処理をトップレベルでawaitせず
1743
+ // fire-and-forgetで起動する(Bunランタイム前提の実装)。そのためawait import()は
1744
+ // 内部の非同期処理が完了する前に解決してしまい、legacy-cjs経路のような
1745
+ // process.exitパッチ+ここでの強制exit呼び出しを行うと、まだ実行中の内部処理を
1746
+ // 強制終了させ出力が失われる(実機で確認済み)。process.exit/killは一切パッチせず、
1747
+ // 実際のCLIコードが自ら呼ぶprocess.exit()に任せてNodeの自然なイベントループ終了を
1748
+ // 待つ(この関数はawait import()完了後、何もせずreturnするだけでよい)。
1749
+ // 同じ理由で、ここでglobalThis.Bun/__claudeYamlを削除するcleanupも行わない
1750
+ // (fire-and-forgetの内部処理がimport()解決後も継続してBunを参照するため、
1751
+ // 早期に消すと実機で"Bun is not defined"を引き起こす。プロセス終了まで残す)。
1752
+ await import(entryUrl);
1753
+ }
1754
+
1755
+ async function legacyCjsMain() {
1617
1756
  let extractedFile;
1618
1757
  extractedFile = ensureEntryFile();
1619
1758
  const code = fs.readFileSync(extractedFile, 'utf8');
@@ -1961,8 +2100,19 @@ async function main() {
1961
2100
  }
1962
2101
  }
1963
2102
 
2103
+ function main() {
2104
+ return process.env.ENTRY_FORMAT === 'esm-chunked' ? esmChunkedMain() : legacyCjsMain();
2105
+ }
2106
+
1964
2107
  main()
1965
2108
  .then(() => {
2109
+ // esm-chunked経路は内部のfire-and-forget非同期処理が実際のprocess.exit()を
2110
+ // 自ら呼ぶまでNodeのイベントループを生かしておく必要があるため、TUIモードと
2111
+ // 同様にここでは強制exitしない(実機で確認済み: 強制exitすると--helpの出力等が
2112
+ // 完了前に打ち切られる)。
2113
+ if (process.env.ENTRY_FORMAT === 'esm-chunked') {
2114
+ return;
2115
+ }
1966
2116
  if (process.env.CLAUDE_TERMUX_TUI === '1' && process.exitCode === undefined) {
1967
2117
  return;
1968
2118
  }
@@ -81,7 +81,7 @@ function loadHelperApi() {
81
81
  const rewriteSource = extractFunction(
82
82
  helperBlock,
83
83
  'function rewriteNativeChunkSource(source) {',
84
- '\n\nasync function main() {',
84
+ '\n\nasync function esmChunkedMain() {',
85
85
  );
86
86
 
87
87
  const context = vm.createContext({ module: { exports: {} }, exports: {}, fs, path, process });
@@ -113,12 +113,12 @@ test('helper and bootstrap rewrite helpers stay identical', () => {
113
113
  const helperRewrite = extractFunction(
114
114
  helperBlock,
115
115
  'function rewriteNativeChunkSource(source) {',
116
- '\n\nasync function main() {',
116
+ '\n\nasync function esmChunkedMain() {',
117
117
  );
118
118
  const bootstrapRewrite = extractFunction(
119
119
  bootstrapBlock,
120
120
  'function rewriteNativeChunkSource(source) {',
121
- '\n\nasync function main() {',
121
+ '\n\nasync function esmChunkedMain() {',
122
122
  );
123
123
  const helperWrapAnsi = extractFunction(
124
124
  helperBlock,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bash0816/claude-code",
3
- "version": "2.1.241",
3
+ "version": "2.1.248",
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"
18
+ "node": ">=22.15.0 <23.0.0 || >=23.5.0"
19
19
  },
20
20
  "keywords": [
21
21
  "claude-code",