@bash0816/claude-code 2.1.161 → 2.1.177-1

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 CHANGED
@@ -37,7 +37,7 @@ npm が `claude` bin link を管理する状態になれば、通常の `npm ins
37
37
  Latest audited version / 最新監査済み版:
38
38
 
39
39
  ```sh
40
- npm install -g @bash0816/claude-code@2.1.161
40
+ npm install -g @bash0816/claude-code@2.1.161-2
41
41
  ```
42
42
 
43
43
  ## Update / 更新
@@ -54,6 +54,14 @@ claude update
54
54
  npm install -g @bash0816/claude-code@<latest_audited_version>
55
55
  ```
56
56
 
57
+ If you already have the repo's `2.1.161` release or the official upstream
58
+ `2.1.165` installed, install `2.1.159-13` explicitly. `claude update` will not
59
+ roll a newer installed terminal back to this repo's current audited release.
60
+
61
+ この repo の `2.1.161` release または official upstream の `2.1.165` を
62
+ すでに入れている端末では、`2.1.159-13` を明示 install してください。
63
+ `claude update` だけでは、より新しい端末をこの repo の現在の audited release に戻しません。
64
+
57
65
  Normal launch also checks the same manifest with a short timeout and prints a notice when a newer audited version is available.
58
66
 
59
67
  通常起動時も、同じ manifest を短い timeout で確認し、新しい監査済み version があれば通知します。
@@ -229,6 +229,26 @@
229
229
  "status": "termux_verified"
230
230
  },
231
231
  "2.1.161": {
232
+ "wrapper_spec": "@anthropic-ai/claude-code@2.1.161",
233
+ "native_spec": "@anthropic-ai/claude-code-linux-arm64@2.1.161",
234
+ "entry_js_offset": 225551924,
235
+ "entry_end_offset": 241503454,
236
+ "tarball_integrity": "sha512-rLEzd8/q4UnTLDSAd3eYgog+G0iZ8y6K8yh0gSxf82d7uOkypYaBUkbmFENuHuzIzCIf9Yy/utCJfp6kQFjUow==",
237
+ "tarball_sha256": "e87d973bbe58593e06c101196f6417f7d950bf777cb9f1a8ff002ebf987ea066",
238
+ "tarball_size": 74314760,
239
+ "status": "offset_discovered"
240
+ },
241
+ "2.1.161-1": {
242
+ "wrapper_spec": "@anthropic-ai/claude-code@2.1.161",
243
+ "native_spec": "@anthropic-ai/claude-code-linux-arm64@2.1.161",
244
+ "entry_js_offset": 225551924,
245
+ "entry_end_offset": 241503454,
246
+ "tarball_integrity": "sha512-rLEzd8/q4UnTLDSAd3eYgog+G0iZ8y6K8yh0gSxf82d7uOkypYaBUkbmFENuHuzIzCIf9Yy/utCJfp6kQFjUow==",
247
+ "tarball_sha256": "e87d973bbe58593e06c101196f6417f7d950bf777cb9f1a8ff002ebf987ea066",
248
+ "tarball_size": 74314760,
249
+ "status": "reverted"
250
+ },
251
+ "2.1.161-2": {
232
252
  "wrapper_spec": "@anthropic-ai/claude-code@2.1.161",
233
253
  "native_spec": "@anthropic-ai/claude-code-linux-arm64@2.1.161",
234
254
  "entry_js_offset": 225551924,
@@ -237,6 +257,33 @@
237
257
  "tarball_sha256": "e87d973bbe58593e06c101196f6417f7d950bf777cb9f1a8ff002ebf987ea066",
238
258
  "tarball_size": 74314760,
239
259
  "status": "termux_verified"
260
+ },
261
+ "2.1.165": {
262
+ "wrapper_spec": "@anthropic-ai/claude-code@2.1.165",
263
+ "native_spec": "@anthropic-ai/claude-code-linux-arm64@2.1.165",
264
+ "entry_js_offset": 226798932,
265
+ "entry_end_offset": 242980856,
266
+ "tarball_integrity": "sha512-uGwKxRQGTxh3BZGnX096CLf/2us5lUFnk4KerWhG4WCpRiEUSu4TUDojp/S3SUzA//FpM57fViUW07XL3T19lw==",
267
+ "tarball_sha256": "6dc6d84624bedaf5ff0948b178fdfc9c2724d8051cb00e70559e292055f7155e",
268
+ "status": "reverted"
269
+ },
270
+ "2.1.177": {
271
+ "wrapper_spec": "@anthropic-ai/claude-code@2.1.177",
272
+ "native_spec": "@anthropic-ai/claude-code-linux-arm64@2.1.177",
273
+ "entry_js_offset": 231509988,
274
+ "entry_end_offset": 248542413,
275
+ "tarball_integrity": "sha512-qb0K+/xwaZFz9spBUcDX3vNgEFTSNdbDze8AOpU4R9weVrwBY9zAO/OPqxC9qNhb5ZIRnJAZLKiICEeEUHowzA==",
276
+ "tarball_sha256": "dc6f28ab95d8748528a92e207d0f2d1dcd173855d18f11b8f82c5827bbdcb234",
277
+ "status": "offset_discovered"
278
+ },
279
+ "2.1.177-1": {
280
+ "wrapper_spec": "@anthropic-ai/claude-code@2.1.177",
281
+ "native_spec": "@anthropic-ai/claude-code-linux-arm64@2.1.177",
282
+ "entry_js_offset": 231509988,
283
+ "entry_end_offset": 248542413,
284
+ "tarball_integrity": "sha512-qb0K+/xwaZFz9spBUcDX3vNgEFTSNdbDze8AOpU4R9weVrwBY9zAO/OPqxC9qNhb5ZIRnJAZLKiICEeEUHowzA==",
285
+ "tarball_sha256": "dc6f28ab95d8748528a92e207d0f2d1dcd173855d18f11b8f82c5827bbdcb234",
286
+ "status": "offset_discovered"
240
287
  }
241
288
  }
242
289
  }
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "manifest_version": 1,
3
3
  "package_name": "@bash0816/claude-code",
4
- "latest_audited_version": "2.1.161",
5
- "latest_candidate_version": "2.1.161",
4
+ "latest_audited_version": "2.1.161-2",
5
+ "latest_candidate_version": "2.1.177-1",
6
6
  "previous_stable_version": "2.1.159-13",
7
7
  "manifest_url": "https://raw.githubusercontent.com/bash0816/ClaudeCode-Termux/main/config/claude-termux-release-manifest.json"
8
8
  }
@@ -49,7 +49,7 @@ for _a in "$@"; do
49
49
  done
50
50
 
51
51
  if [ "$_pf" = "1" ] && [ "${CLAUDE_TERMUX_STDIN:-}" != "inherit" ]; then
52
- _helper=$(mktemp "${TMPDIR:-/tmp}/claude-helper.XXXXXX.js")
52
+ _helper=$(mktemp "${TERMUX_TMPDIR}/claude-helper.XXXXXX.js")
53
53
  trap 'rm -f "$_helper"' EXIT HUP INT TERM
54
54
  cat <<'NODE' > "$_helper"
55
55
  const fs = require('fs');
@@ -73,8 +73,39 @@ class RequestedExit extends Error {
73
73
  }
74
74
  }
75
75
 
76
+ function cleanupStaleEntryFiles(currentWorkdir = workdir, currentEntryJsOffset = entryJsOffset, currentEntryEndOffset = entryEndOffset, now = Date.now()) {
77
+ const prefix = `cli.${currentEntryJsOffset}.${currentEntryEndOffset}.`;
78
+ const suffix = '.bare-path.js';
79
+ const maxAgeMs = 24 * 60 * 60 * 1000;
80
+ let entries;
81
+ try {
82
+ entries = fs.readdirSync(currentWorkdir, { withFileTypes: true });
83
+ } catch {
84
+ return;
85
+ }
86
+ for (const entry of entries) {
87
+ if (!entry.isFile() && !entry.isSymbolicLink()) continue;
88
+ if (!entry.name.startsWith(prefix) || !entry.name.endsWith(suffix)) continue;
89
+ const filePath = path.join(currentWorkdir, entry.name);
90
+ let stats;
91
+ try {
92
+ stats = fs.statSync(filePath);
93
+ } catch {
94
+ continue;
95
+ }
96
+ if (Number.isFinite(stats.mtimeMs) && now - stats.mtimeMs < maxAgeMs) continue;
97
+ try {
98
+ fs.rmSync(filePath, { force: true });
99
+ } catch {}
100
+ }
101
+ }
102
+
76
103
  function ensureEntryFile() {
77
- const extractedFile = path.join(workdir, `cli.${entryJsOffset}.${entryEndOffset}.bare-path.js`);
104
+ cleanupStaleEntryFiles();
105
+ const extractedFile = path.join(
106
+ workdir,
107
+ `cli.${entryJsOffset}.${entryEndOffset}.${process.pid}.${Date.now().toString(36)}.${Math.random().toString(36).slice(2)}.bare-path.js`,
108
+ );
78
109
  const len = entryEndOffset - entryJsOffset;
79
110
  if (!(len > 0)) throw new Error('invalid replay offsets');
80
111
 
@@ -87,15 +118,165 @@ function ensureEntryFile() {
87
118
  return extractedFile;
88
119
  }
89
120
 
121
+ function isFullWidthCodePoint(codePoint) {
122
+ return Number.isFinite(codePoint) && (
123
+ codePoint >= 0x1100 && (
124
+ codePoint <= 0x115f ||
125
+ codePoint === 0x2329 ||
126
+ codePoint === 0x232a ||
127
+ (codePoint >= 0x2e80 && codePoint <= 0xa4cf && codePoint !== 0x303f) ||
128
+ (codePoint >= 0xac00 && codePoint <= 0xd7a3) ||
129
+ (codePoint >= 0xf900 && codePoint <= 0xfaff) ||
130
+ (codePoint >= 0xfe10 && codePoint <= 0xfe19) ||
131
+ (codePoint >= 0xfe30 && codePoint <= 0xfe6f) ||
132
+ (codePoint >= 0xff00 && codePoint <= 0xff60) ||
133
+ (codePoint >= 0xffe0 && codePoint <= 0xffe6) ||
134
+ (codePoint >= 0x1f300 && codePoint <= 0x1f6ff) ||
135
+ (codePoint >= 0x1fa70 && codePoint <= 0x1faff) ||
136
+ (codePoint >= 0x1f900 && codePoint <= 0x1f9ff) ||
137
+ (codePoint >= 0x20000 && codePoint <= 0x3fffd)
138
+ )
139
+ );
140
+ }
141
+
142
+ function graphemeWidth(grapheme) {
143
+ let width = 0;
144
+ const symbols = Array.from(String(grapheme ?? ''));
145
+ const codePoints = symbols.map(symbol => symbol.codePointAt(0)).filter(codePoint => Number.isFinite(codePoint));
146
+ if (codePoints.length === 0) return 0;
147
+ if (codePoints.length > 1 && codePoints.every(codePoint => codePoint >= 0x1f1e6 && codePoint <= 0x1f1ff)) {
148
+ return 2;
149
+ }
150
+ if (codePoints.includes(0x20e3) || codePoints.includes(0x200d)) {
151
+ return 2;
152
+ }
153
+ if (codePoints.includes(0xfe0f) || codePoints.some(codePoint => codePoint >= 0x2600 && codePoint <= 0x27bf)) {
154
+ return 2;
155
+ }
156
+ for (const symbol of symbols) {
157
+ const codePoint = symbol.codePointAt(0);
158
+ if (codePoint === undefined || codePoint === 0) continue;
159
+ if (codePoint < 32 || (codePoint >= 0x7f && codePoint < 0xa0)) continue;
160
+ if (codePoint === 0x200d || codePoint === 0xfe0f) continue;
161
+ if (/\p{M}/u.test(symbol)) continue;
162
+ if (isFullWidthCodePoint(codePoint)) return 2;
163
+ width = 1;
164
+ }
165
+ return width;
166
+ }
167
+
90
168
  function stringWidth(value) {
91
- const text = String(value ?? '');
169
+ const text = stripANSI(value);
92
170
  if (typeof Intl !== 'undefined' && typeof Intl.Segmenter === 'function') {
93
171
  const segmenter = new Intl.Segmenter('en', { granularity: 'grapheme' });
94
172
  let width = 0;
95
- for (const _segment of segmenter.segment(text)) width += 1;
173
+ for (const segment of segmenter.segment(text)) width += graphemeWidth(segment.segment);
96
174
  return width;
97
175
  }
98
- return Array.from(text).length;
176
+ return Array.from(text).reduce((width, symbol) => width + graphemeWidth(symbol), 0);
177
+ }
178
+
179
+ function stripANSI(value) {
180
+ return String(value ?? '')
181
+ .replace(/\x1B\[[0-?]*[ -/]*[@-~]/g, '')
182
+ .replace(/\x1B\][^\x07]*(?:\x07|\x1B\\)/g, '');
183
+ }
184
+
185
+ function wrapAnsi(value, columns, options = {}) {
186
+ const text = String(value ?? '');
187
+ const width = Number(columns);
188
+ const hard = options.hard !== false;
189
+ const trim = options.trim === true;
190
+ const wordWrap = options.wordWrap !== false;
191
+ if (!Number.isFinite(width) || width <= 0) return trim ? text.trimEnd() : text;
192
+
193
+ const ansiPattern = /\x1B\[[0-?]*[ -/]*[@-~]|\x1B\][^\x07]*(?:\x07|\x1B\\)/g;
194
+ const segmenter = typeof Intl !== 'undefined' && typeof Intl.Segmenter === 'function'
195
+ ? new Intl.Segmenter('en', { granularity: 'grapheme' })
196
+ : null;
197
+ const splitVisible = (chunk) => {
198
+ if (chunk === '') return [];
199
+ if (!segmenter) return Array.from(chunk);
200
+ return Array.from(segmenter.segment(chunk), part => part.segment);
201
+ };
202
+ const splitByGrapheme = hard || !wordWrap;
203
+ let result = '';
204
+ let currentWidth = 0;
205
+ let lastIndex = 0;
206
+ const appendVisible = (chunk) => {
207
+ const tokens = splitByGrapheme ? splitVisible(chunk) : (chunk.match(/\s+|[^\s]+/gu) || []);
208
+ for (const token of tokens) {
209
+ if (token === '\n') {
210
+ if (trim) result = result.replace(/[ \t]+$/g, '');
211
+ result += token;
212
+ currentWidth = 0;
213
+ continue;
214
+ }
215
+ const tokenWidth = stringWidth(token);
216
+ const isWhitespace = /^\s+$/u.test(token);
217
+ if (trim && isWhitespace && currentWidth === 0) continue;
218
+ if (currentWidth > 0 && currentWidth + tokenWidth > width) {
219
+ if (!splitByGrapheme && !isWhitespace) {
220
+ result = result.replace(/[ \t]+$/g, '');
221
+ result += '\n';
222
+ currentWidth = 0;
223
+ } else {
224
+ for (const piece of splitVisible(token)) {
225
+ const pieceWidth = stringWidth(piece);
226
+ if (currentWidth > 0 && currentWidth + pieceWidth > width) {
227
+ if (trim) result = result.replace(/[ \t]+$/g, '');
228
+ result += '\n';
229
+ currentWidth = 0;
230
+ }
231
+ if (trim && /^\s+$/u.test(piece) && currentWidth === 0) continue;
232
+ result += piece;
233
+ currentWidth += pieceWidth;
234
+ }
235
+ continue;
236
+ }
237
+ }
238
+ result += token;
239
+ currentWidth += tokenWidth;
240
+ }
241
+ };
242
+ for (const match of text.matchAll(ansiPattern)) {
243
+ appendVisible(text.slice(lastIndex, match.index ?? 0));
244
+ result += match[0];
245
+ lastIndex = (match.index ?? 0) + match[0].length;
246
+ }
247
+ appendVisible(text.slice(lastIndex));
248
+ return trim ? result.replace(/[ \t]+$/gm, '') : result;
249
+ }
250
+
251
+ function stableHash(value, seed) {
252
+ const text = String(value ?? '');
253
+ let hash = 2166136261;
254
+ if (seed !== undefined) {
255
+ const seedText = String(seed ?? '');
256
+ for (let i = 0; i < seedText.length; i += 1) {
257
+ hash ^= seedText.charCodeAt(i);
258
+ hash = Math.imul(hash, 16777619);
259
+ }
260
+ hash ^= 0x9e3779b9;
261
+ hash = Math.imul(hash, 16777619);
262
+ }
263
+ for (let i = 0; i < text.length; i += 1) {
264
+ hash ^= text.charCodeAt(i);
265
+ hash = Math.imul(hash, 16777619);
266
+ }
267
+ return hash >>> 0;
268
+ }
269
+
270
+ function replaceRequired(source, pattern, replacement, label, expectedCount) {
271
+ const text = String(source);
272
+ const matches = text.match(pattern);
273
+ if (!matches || matches.length === 0) {
274
+ throw new Error(`rewriteNativeChunkSource: missing ${label}`);
275
+ }
276
+ if (expectedCount !== undefined && matches.length !== expectedCount) {
277
+ throw new Error(`rewriteNativeChunkSource: unexpected ${label} count ${matches.length}`);
278
+ }
279
+ return text.replace(pattern, replacement);
99
280
  }
100
281
 
101
282
  function parseScalar(value) {
@@ -244,6 +425,9 @@ function createFakeRequire(realRequire) {
244
425
  writable: true,
245
426
  });
246
427
  }
428
+ if (context.Bun && globalThis.__claudeYaml) {
429
+ context.Bun.YAML = globalThis.__claudeYaml;
430
+ }
247
431
  } catch {}
248
432
  return context;
249
433
  }
@@ -344,25 +528,60 @@ function createFakeRequire(realRequire) {
344
528
  };
345
529
  }
346
530
 
347
- async function main() {
348
- const extractedFile = ensureEntryFile();
349
- const code = fs.readFileSync(extractedFile, 'utf8');
350
- const patchedCode = code.replace(
351
- /^function\(exports, require, module, __filename, __dirname\) \{/,
352
- 'function(exports, require, module, __filename, __dirname) {var __claudeBun = globalThis.__claudeBunShim;',
353
- ).replace(
531
+ function rewriteNativeChunkSource(source) {
532
+ const rawPrefix = 'function(exports, require, module, __filename, __dirname) {';
533
+ const injectedPrefix = rawPrefix + 'var __claudeBun = globalThis.__claudeBunShim;';
534
+ let patched = String(source);
535
+ patched = replaceRequired(
536
+ patched,
537
+ /^function\(exports, require, module, __filename, __dirname\) \{(?:var __claudeBun = globalThis\.__claudeBunShim;)?/,
538
+ injectedPrefix,
539
+ 'module wrapper prefix',
540
+ 1,
541
+ );
542
+ patched = replaceRequired(
543
+ patched,
354
544
  /\btypeof Bun\b/g,
355
545
  'typeof __claudeBun',
356
- ).replace(
546
+ 'typeof Bun',
547
+ 3,
548
+ );
549
+ patched = replaceRequired(
550
+ patched,
551
+ /\btypeof globalThis\.Bun\b/g,
552
+ 'typeof globalThis.__claudeBun',
553
+ 'typeof globalThis.Bun',
554
+ 1,
555
+ );
556
+ patched = replaceRequired(
557
+ patched,
558
+ /\bglobalThis\.Bun\b/g,
559
+ 'globalThis.__claudeBun',
560
+ 'globalThis.Bun',
561
+ 1,
562
+ );
563
+ patched = replaceRequired(
564
+ patched,
357
565
  /\bBun\./g,
358
566
  '__claudeBun.',
359
- ).replace(
360
- /function t5q\(q\)\{return Bun\.YAML\.parse\(q\)\}/g,
361
- 'function t5q(q){return globalThis.__claudeYaml.parse(q)}',
362
- ).replace(
363
- /function VK6\(q\)\{return Bun\.YAML\.stringify\(q,null,2\)\+`/g,
364
- 'function VK6(q){return globalThis.__claudeYaml.stringify(q,null,2)+`',
567
+ 'Bun property access',
568
+ 33,
569
+ );
570
+ patched = replaceRequired(
571
+ patched,
572
+ /\bnpmInstallDeprecated:!0\b/g,
573
+ 'npmInstallDeprecated:!1',
574
+ 'npmInstallDeprecated flag',
575
+ 1,
365
576
  );
577
+ return patched;
578
+ }
579
+
580
+ async function main() {
581
+ let extractedFile;
582
+ extractedFile = ensureEntryFile();
583
+ const code = fs.readFileSync(extractedFile, 'utf8');
584
+ const patchedCode = rewriteNativeChunkSource(code);
366
585
  const fn = eval('(' + patchedCode.replace(/\)\s*$/, '') + ')');
367
586
 
368
587
  const originalArgv = process.argv.slice();
@@ -389,6 +608,9 @@ async function main() {
389
608
  globalThis.Bun = {
390
609
  version: '1.1.8',
391
610
  stringWidth,
611
+ wrapAnsi,
612
+ stripANSI,
613
+ hash: stableHash,
392
614
  which: (cmd) => {
393
615
  try {
394
616
  return _realChild.execFileSync('which', [String(cmd)], { encoding: 'utf8' }).trim() || null;
@@ -424,9 +646,9 @@ async function main() {
424
646
  gte: (a, b) => _cmp(a, b) >= 0,
425
647
  lt: (a, b) => _cmp(a, b) < 0,
426
648
  lte: (a, b) => _cmp(a, b) <= 0,
427
- };
428
- })(),
429
- YAML: globalThis.__claudeYaml,
649
+ };
650
+ })(),
651
+ YAML: globalThis.__claudeYaml,
430
652
  };
431
653
  Object.assign(globalThis.__claudeBunShim, globalThis.Bun);
432
654
  if (typeof globalThis.__claudeBunShim.gc !== 'function') {
@@ -439,10 +661,13 @@ async function main() {
439
661
  throw new RequestedExit(code);
440
662
  };
441
663
 
664
+ const printWaitMs = Number(process.env.CLAUDE_TERMUX_PRINT_WAIT_MS || 5000);
442
665
  const moduleLike = { exports: {} };
443
666
  const maybePromise = fn(moduleLike.exports, fakeRequire, moduleLike, extractedFile, workdir);
444
667
  if (maybePromise && typeof maybePromise.then === 'function') await maybePromise;
445
- await new Promise(resolve => setTimeout(resolve, 5000));
668
+ if (Number.isFinite(printWaitMs) && printWaitMs > 0) {
669
+ await new Promise(resolve => setTimeout(resolve, printWaitMs));
670
+ }
446
671
  if (asyncErrors.length > 0) throw asyncErrors[0];
447
672
  } catch (error) {
448
673
  if (error instanceof RequestedExit) {
@@ -453,6 +678,11 @@ async function main() {
453
678
  } finally {
454
679
  process.removeListener('uncaughtException', onAsyncError);
455
680
  process.removeListener('unhandledRejection', onAsyncError);
681
+ if (extractedFile) {
682
+ try {
683
+ fs.rmSync(extractedFile, { force: true });
684
+ } catch {}
685
+ }
456
686
  process.argv = originalArgv;
457
687
  process.exit = originalExit;
458
688
  try {
@@ -482,13 +712,14 @@ NODE
482
712
  export CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC="${CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC:-1}"
483
713
  export ENABLE_CLAUDEAI_MCP_SERVERS="${ENABLE_CLAUDEAI_MCP_SERVERS:-0}"
484
714
  export CLAUDE_CODE_SIMPLE="${CLAUDE_CODE_SIMPLE:-0}"
715
+ export DISABLE_INSTALLATION_CHECKS="${DISABLE_INSTALLATION_CHECKS:-true}"
485
716
  node "$_helper" "$@" </dev/null
486
717
  _status=$?
487
718
  rm -f "$_helper"
488
719
  trap - EXIT HUP INT TERM
489
720
  exit "$_status"
490
721
  else
491
- _bootstrap=$(mktemp "${TMPDIR:-/tmp}/claude-bootstrap.XXXXXX.js")
722
+ _bootstrap=$(mktemp "${TERMUX_TMPDIR}/claude-bootstrap.XXXXXX.js")
492
723
  trap 'rm -f "$_bootstrap"' EXIT HUP INT TERM
493
724
  cat <<'NODE' > "$_bootstrap"
494
725
  const fs = require('fs');
@@ -512,8 +743,39 @@ class RequestedExit extends Error {
512
743
  }
513
744
  }
514
745
 
746
+ function cleanupStaleEntryFiles(currentWorkdir = workdir, currentEntryJsOffset = entryJsOffset, currentEntryEndOffset = entryEndOffset, now = Date.now()) {
747
+ const prefix = `cli.${currentEntryJsOffset}.${currentEntryEndOffset}.`;
748
+ const suffix = '.bare-path.js';
749
+ const maxAgeMs = 24 * 60 * 60 * 1000;
750
+ let entries;
751
+ try {
752
+ entries = fs.readdirSync(currentWorkdir, { withFileTypes: true });
753
+ } catch {
754
+ return;
755
+ }
756
+ for (const entry of entries) {
757
+ if (!entry.isFile() && !entry.isSymbolicLink()) continue;
758
+ if (!entry.name.startsWith(prefix) || !entry.name.endsWith(suffix)) continue;
759
+ const filePath = path.join(currentWorkdir, entry.name);
760
+ let stats;
761
+ try {
762
+ stats = fs.statSync(filePath);
763
+ } catch {
764
+ continue;
765
+ }
766
+ if (Number.isFinite(stats.mtimeMs) && now - stats.mtimeMs < maxAgeMs) continue;
767
+ try {
768
+ fs.rmSync(filePath, { force: true });
769
+ } catch {}
770
+ }
771
+ }
772
+
515
773
  function ensureEntryFile() {
516
- const extractedFile = path.join(workdir, `cli.${entryJsOffset}.${entryEndOffset}.bare-path.js`);
774
+ cleanupStaleEntryFiles();
775
+ const extractedFile = path.join(
776
+ workdir,
777
+ `cli.${entryJsOffset}.${entryEndOffset}.${process.pid}.${Date.now().toString(36)}.${Math.random().toString(36).slice(2)}.bare-path.js`,
778
+ );
517
779
  const len = entryEndOffset - entryJsOffset;
518
780
  if (!(len > 0)) throw new Error('invalid replay offsets');
519
781
 
@@ -526,15 +788,165 @@ function ensureEntryFile() {
526
788
  return extractedFile;
527
789
  }
528
790
 
791
+ function isFullWidthCodePoint(codePoint) {
792
+ return Number.isFinite(codePoint) && (
793
+ codePoint >= 0x1100 && (
794
+ codePoint <= 0x115f ||
795
+ codePoint === 0x2329 ||
796
+ codePoint === 0x232a ||
797
+ (codePoint >= 0x2e80 && codePoint <= 0xa4cf && codePoint !== 0x303f) ||
798
+ (codePoint >= 0xac00 && codePoint <= 0xd7a3) ||
799
+ (codePoint >= 0xf900 && codePoint <= 0xfaff) ||
800
+ (codePoint >= 0xfe10 && codePoint <= 0xfe19) ||
801
+ (codePoint >= 0xfe30 && codePoint <= 0xfe6f) ||
802
+ (codePoint >= 0xff00 && codePoint <= 0xff60) ||
803
+ (codePoint >= 0xffe0 && codePoint <= 0xffe6) ||
804
+ (codePoint >= 0x1f300 && codePoint <= 0x1f6ff) ||
805
+ (codePoint >= 0x1fa70 && codePoint <= 0x1faff) ||
806
+ (codePoint >= 0x1f900 && codePoint <= 0x1f9ff) ||
807
+ (codePoint >= 0x20000 && codePoint <= 0x3fffd)
808
+ )
809
+ );
810
+ }
811
+
812
+ function graphemeWidth(grapheme) {
813
+ let width = 0;
814
+ const symbols = Array.from(String(grapheme ?? ''));
815
+ const codePoints = symbols.map(symbol => symbol.codePointAt(0)).filter(codePoint => Number.isFinite(codePoint));
816
+ if (codePoints.length === 0) return 0;
817
+ if (codePoints.length > 1 && codePoints.every(codePoint => codePoint >= 0x1f1e6 && codePoint <= 0x1f1ff)) {
818
+ return 2;
819
+ }
820
+ if (codePoints.includes(0x20e3) || codePoints.includes(0x200d)) {
821
+ return 2;
822
+ }
823
+ if (codePoints.includes(0xfe0f) || codePoints.some(codePoint => codePoint >= 0x2600 && codePoint <= 0x27bf)) {
824
+ return 2;
825
+ }
826
+ for (const symbol of symbols) {
827
+ const codePoint = symbol.codePointAt(0);
828
+ if (codePoint === undefined || codePoint === 0) continue;
829
+ if (codePoint < 32 || (codePoint >= 0x7f && codePoint < 0xa0)) continue;
830
+ if (codePoint === 0x200d || codePoint === 0xfe0f) continue;
831
+ if (/\p{M}/u.test(symbol)) continue;
832
+ if (isFullWidthCodePoint(codePoint)) return 2;
833
+ width = 1;
834
+ }
835
+ return width;
836
+ }
837
+
529
838
  function stringWidth(value) {
530
- const text = String(value ?? '');
839
+ const text = stripANSI(value);
531
840
  if (typeof Intl !== 'undefined' && typeof Intl.Segmenter === 'function') {
532
841
  const segmenter = new Intl.Segmenter('en', { granularity: 'grapheme' });
533
842
  let width = 0;
534
- for (const _segment of segmenter.segment(text)) width += 1;
843
+ for (const segment of segmenter.segment(text)) width += graphemeWidth(segment.segment);
535
844
  return width;
536
845
  }
537
- return Array.from(text).length;
846
+ return Array.from(text).reduce((width, symbol) => width + graphemeWidth(symbol), 0);
847
+ }
848
+
849
+ function stripANSI(value) {
850
+ return String(value ?? '')
851
+ .replace(/\x1B\[[0-?]*[ -/]*[@-~]/g, '')
852
+ .replace(/\x1B\][^\x07]*(?:\x07|\x1B\\)/g, '');
853
+ }
854
+
855
+ function wrapAnsi(value, columns, options = {}) {
856
+ const text = String(value ?? '');
857
+ const width = Number(columns);
858
+ const hard = options.hard !== false;
859
+ const trim = options.trim === true;
860
+ const wordWrap = options.wordWrap !== false;
861
+ if (!Number.isFinite(width) || width <= 0) return trim ? text.trimEnd() : text;
862
+
863
+ const ansiPattern = /\x1B\[[0-?]*[ -/]*[@-~]|\x1B\][^\x07]*(?:\x07|\x1B\\)/g;
864
+ const segmenter = typeof Intl !== 'undefined' && typeof Intl.Segmenter === 'function'
865
+ ? new Intl.Segmenter('en', { granularity: 'grapheme' })
866
+ : null;
867
+ const splitVisible = (chunk) => {
868
+ if (chunk === '') return [];
869
+ if (!segmenter) return Array.from(chunk);
870
+ return Array.from(segmenter.segment(chunk), part => part.segment);
871
+ };
872
+ const splitByGrapheme = hard || !wordWrap;
873
+ let result = '';
874
+ let currentWidth = 0;
875
+ let lastIndex = 0;
876
+ const appendVisible = (chunk) => {
877
+ const tokens = splitByGrapheme ? splitVisible(chunk) : (chunk.match(/\s+|[^\s]+/gu) || []);
878
+ for (const token of tokens) {
879
+ if (token === '\n') {
880
+ if (trim) result = result.replace(/[ \t]+$/g, '');
881
+ result += token;
882
+ currentWidth = 0;
883
+ continue;
884
+ }
885
+ const tokenWidth = stringWidth(token);
886
+ const isWhitespace = /^\s+$/u.test(token);
887
+ if (trim && isWhitespace && currentWidth === 0) continue;
888
+ if (currentWidth > 0 && currentWidth + tokenWidth > width) {
889
+ if (!splitByGrapheme && !isWhitespace) {
890
+ result = result.replace(/[ \t]+$/g, '');
891
+ result += '\n';
892
+ currentWidth = 0;
893
+ } else {
894
+ for (const piece of splitVisible(token)) {
895
+ const pieceWidth = stringWidth(piece);
896
+ if (currentWidth > 0 && currentWidth + pieceWidth > width) {
897
+ if (trim) result = result.replace(/[ \t]+$/g, '');
898
+ result += '\n';
899
+ currentWidth = 0;
900
+ }
901
+ if (trim && /^\s+$/u.test(piece) && currentWidth === 0) continue;
902
+ result += piece;
903
+ currentWidth += pieceWidth;
904
+ }
905
+ continue;
906
+ }
907
+ }
908
+ result += token;
909
+ currentWidth += tokenWidth;
910
+ }
911
+ };
912
+ for (const match of text.matchAll(ansiPattern)) {
913
+ appendVisible(text.slice(lastIndex, match.index ?? 0));
914
+ result += match[0];
915
+ lastIndex = (match.index ?? 0) + match[0].length;
916
+ }
917
+ appendVisible(text.slice(lastIndex));
918
+ return trim ? result.replace(/[ \t]+$/gm, '') : result;
919
+ }
920
+
921
+ function stableHash(value, seed) {
922
+ const text = String(value ?? '');
923
+ let hash = 2166136261;
924
+ if (seed !== undefined) {
925
+ const seedText = String(seed ?? '');
926
+ for (let i = 0; i < seedText.length; i += 1) {
927
+ hash ^= seedText.charCodeAt(i);
928
+ hash = Math.imul(hash, 16777619);
929
+ }
930
+ hash ^= 0x9e3779b9;
931
+ hash = Math.imul(hash, 16777619);
932
+ }
933
+ for (let i = 0; i < text.length; i += 1) {
934
+ hash ^= text.charCodeAt(i);
935
+ hash = Math.imul(hash, 16777619);
936
+ }
937
+ return hash >>> 0;
938
+ }
939
+
940
+ function replaceRequired(source, pattern, replacement, label, expectedCount) {
941
+ const text = String(source);
942
+ const matches = text.match(pattern);
943
+ if (!matches || matches.length === 0) {
944
+ throw new Error(`rewriteNativeChunkSource: missing ${label}`);
945
+ }
946
+ if (expectedCount !== undefined && matches.length !== expectedCount) {
947
+ throw new Error(`rewriteNativeChunkSource: unexpected ${label} count ${matches.length}`);
948
+ }
949
+ return text.replace(pattern, replacement);
538
950
  }
539
951
 
540
952
  function parseScalar(value) {
@@ -633,6 +1045,63 @@ function createFakeRequire(realRequire) {
633
1045
  const realChild = realRequire('child_process');
634
1046
  const realVm = realRequire('vm');
635
1047
 
1048
+ function injectBunIntoContext(context) {
1049
+ if (!context || typeof context !== 'object') return context;
1050
+ try {
1051
+ if (!Object.prototype.hasOwnProperty.call(context, '__claudeYaml')) {
1052
+ Object.defineProperty(context, '__claudeYaml', {
1053
+ value: globalThis.__claudeYaml,
1054
+ configurable: true,
1055
+ writable: true,
1056
+ });
1057
+ }
1058
+ if (!Object.prototype.hasOwnProperty.call(context, '__claudeBunShim')) {
1059
+ Object.defineProperty(context, '__claudeBunShim', {
1060
+ value: globalThis.__claudeBunShim,
1061
+ configurable: true,
1062
+ writable: true,
1063
+ });
1064
+ }
1065
+ if (!Object.prototype.hasOwnProperty.call(context, '__claudeBun')) {
1066
+ Object.defineProperty(context, '__claudeBun', {
1067
+ value: globalThis.__claudeBunShim,
1068
+ configurable: true,
1069
+ writable: true,
1070
+ });
1071
+ }
1072
+ if (Object.prototype.hasOwnProperty.call(context, 'Bun')) {
1073
+ if (context.Bun && typeof context.Bun === 'object' && context.Bun !== globalThis.Bun) {
1074
+ try {
1075
+ context.Bun = globalThis.Bun;
1076
+ } catch {
1077
+ Object.defineProperty(context, 'Bun', {
1078
+ value: globalThis.Bun,
1079
+ configurable: true,
1080
+ writable: true,
1081
+ });
1082
+ }
1083
+ }
1084
+ if (!context.Bun || typeof context.Bun !== 'object') {
1085
+ Object.defineProperty(context, 'Bun', {
1086
+ value: globalThis.Bun,
1087
+ configurable: true,
1088
+ writable: true,
1089
+ });
1090
+ }
1091
+ } else {
1092
+ Object.defineProperty(context, 'Bun', {
1093
+ value: globalThis.Bun,
1094
+ configurable: true,
1095
+ writable: true,
1096
+ });
1097
+ }
1098
+ if (context.Bun && globalThis.__claudeYaml) {
1099
+ context.Bun.YAML = globalThis.__claudeYaml;
1100
+ }
1101
+ } catch {}
1102
+ return context;
1103
+ }
1104
+
636
1105
  function rewriteArgs(args) {
637
1106
  if (
638
1107
  Array.isArray(args) &&
@@ -729,25 +1198,60 @@ function createFakeRequire(realRequire) {
729
1198
  };
730
1199
  }
731
1200
 
732
- async function main() {
733
- const extractedFile = ensureEntryFile();
734
- const code = fs.readFileSync(extractedFile, 'utf8');
735
- const patchedCode = code.replace(
736
- /^function\(exports, require, module, __filename, __dirname\) \{/,
737
- 'function(exports, require, module, __filename, __dirname) {var __claudeBun = globalThis.__claudeBunShim;',
738
- ).replace(
1201
+ function rewriteNativeChunkSource(source) {
1202
+ const rawPrefix = 'function(exports, require, module, __filename, __dirname) {';
1203
+ const injectedPrefix = rawPrefix + 'var __claudeBun = globalThis.__claudeBunShim;';
1204
+ let patched = String(source);
1205
+ patched = replaceRequired(
1206
+ patched,
1207
+ /^function\(exports, require, module, __filename, __dirname\) \{(?:var __claudeBun = globalThis\.__claudeBunShim;)?/,
1208
+ injectedPrefix,
1209
+ 'module wrapper prefix',
1210
+ 1,
1211
+ );
1212
+ patched = replaceRequired(
1213
+ patched,
739
1214
  /\btypeof Bun\b/g,
740
1215
  'typeof __claudeBun',
741
- ).replace(
1216
+ 'typeof Bun',
1217
+ 3,
1218
+ );
1219
+ patched = replaceRequired(
1220
+ patched,
1221
+ /\btypeof globalThis\.Bun\b/g,
1222
+ 'typeof globalThis.__claudeBun',
1223
+ 'typeof globalThis.Bun',
1224
+ 1,
1225
+ );
1226
+ patched = replaceRequired(
1227
+ patched,
1228
+ /\bglobalThis\.Bun\b/g,
1229
+ 'globalThis.__claudeBun',
1230
+ 'globalThis.Bun',
1231
+ 1,
1232
+ );
1233
+ patched = replaceRequired(
1234
+ patched,
742
1235
  /\bBun\./g,
743
1236
  '__claudeBun.',
744
- ).replace(
745
- /function t5q\(q\)\{return Bun\.YAML\.parse\(q\)\}/g,
746
- 'function t5q(q){return globalThis.__claudeYaml.parse(q)}',
747
- ).replace(
748
- /function VK6\(q\)\{return Bun\.YAML\.stringify\(q,null,2\)\+`/g,
749
- 'function VK6(q){return globalThis.__claudeYaml.stringify(q,null,2)+`',
1237
+ 'Bun property access',
1238
+ 33,
1239
+ );
1240
+ patched = replaceRequired(
1241
+ patched,
1242
+ /\bnpmInstallDeprecated:!0\b/g,
1243
+ 'npmInstallDeprecated:!1',
1244
+ 'npmInstallDeprecated flag',
1245
+ 1,
750
1246
  );
1247
+ return patched;
1248
+ }
1249
+
1250
+ async function main() {
1251
+ let extractedFile;
1252
+ extractedFile = ensureEntryFile();
1253
+ const code = fs.readFileSync(extractedFile, 'utf8');
1254
+ const patchedCode = rewriteNativeChunkSource(code);
751
1255
  const fn = eval('(' + patchedCode.replace(/\)\s*$/, '') + ')');
752
1256
 
753
1257
  const originalArgv = process.argv.slice();
@@ -774,6 +1278,9 @@ async function main() {
774
1278
  globalThis.Bun = {
775
1279
  version: '1.1.8',
776
1280
  stringWidth,
1281
+ wrapAnsi,
1282
+ stripANSI,
1283
+ hash: stableHash,
777
1284
  which: (cmd) => {
778
1285
  try {
779
1286
  return _realChild.execFileSync('which', [String(cmd)], { encoding: 'utf8' }).trim() || null;
@@ -810,8 +1317,8 @@ async function main() {
810
1317
  lt: (a, b) => _cmp(a, b) < 0,
811
1318
  lte: (a, b) => _cmp(a, b) <= 0,
812
1319
  };
813
- })(),
814
- YAML: globalThis.__claudeYaml,
1320
+ })(),
1321
+ YAML: globalThis.__claudeYaml,
815
1322
  };
816
1323
  Object.assign(globalThis.__claudeBunShim, globalThis.Bun);
817
1324
  if (typeof globalThis.__claudeBunShim.gc !== 'function') {
@@ -827,8 +1334,6 @@ async function main() {
827
1334
  const moduleLike = { exports: {} };
828
1335
  const maybePromise = fn(moduleLike.exports, fakeRequire, moduleLike, extractedFile, workdir);
829
1336
  if (maybePromise && typeof maybePromise.then === 'function') await maybePromise;
830
- const _waitMs = Number(process.env.CLAUDE_TERMUX_PRINT_WAIT_MS || 200);
831
- await new Promise(resolve => setTimeout(resolve, _waitMs));
832
1337
  if (asyncErrors.length > 0) throw asyncErrors[0];
833
1338
  } catch (error) {
834
1339
  if (error instanceof RequestedExit) {
@@ -841,17 +1346,25 @@ async function main() {
841
1346
  process.removeListener('unhandledRejection', onAsyncError);
842
1347
  process.argv = originalArgv;
843
1348
  process.exit = originalExit;
844
- try {
845
- if (originalBun === undefined) {
846
- delete process.versions.bun;
847
- } else {
848
- Object.defineProperty(process.versions, 'bun', { value: originalBun, configurable: true });
1349
+ process.once('exit', () => {
1350
+ if (extractedFile) {
1351
+ try {
1352
+ fs.rmSync(extractedFile, { force: true });
1353
+ } catch {}
849
1354
  }
850
- } catch {}
851
- delete globalThis.__claudeYaml;
852
- delete globalThis.__claudeBunShim;
853
- delete globalThis.__claudeBun;
854
- if (hadGlobalBun) globalThis.Bun = originalGlobalBun;
1355
+ try {
1356
+ if (originalBun === undefined) {
1357
+ delete process.versions.bun;
1358
+ } else {
1359
+ Object.defineProperty(process.versions, 'bun', { value: originalBun, configurable: true });
1360
+ }
1361
+ } catch {}
1362
+ delete globalThis.__claudeYaml;
1363
+ delete globalThis.__claudeBunShim;
1364
+ delete globalThis.__claudeBun;
1365
+ if (hadGlobalBun) globalThis.Bun = originalGlobalBun;
1366
+ else delete globalThis.Bun;
1367
+ });
855
1368
  }
856
1369
  }
857
1370
 
@@ -866,6 +1379,7 @@ main().catch(error => {
866
1379
  NODE
867
1380
  export CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC="${CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC:-1}"
868
1381
  export ENABLE_CLAUDEAI_MCP_SERVERS="${ENABLE_CLAUDEAI_MCP_SERVERS:-0}"
1382
+ export DISABLE_INSTALLATION_CHECKS="${DISABLE_INSTALLATION_CHECKS:-true}"
869
1383
  node "$_bootstrap" "$@"
870
1384
  _status=$?
871
1385
  rm -f "$_bootstrap"
@@ -0,0 +1,323 @@
1
+ 'use strict';
2
+
3
+ const test = require('node:test');
4
+ const assert = require('node:assert/strict');
5
+ const crypto = require('crypto');
6
+ const child_process = require('child_process');
7
+ const fs = require('fs');
8
+ const os = require('os');
9
+ const path = require('path');
10
+ const vm = require('vm');
11
+
12
+ const scriptPath = path.join(__dirname, 'termux-run-claude-native.sh');
13
+ const script = fs.readFileSync(scriptPath, 'utf8');
14
+
15
+ function extractBlock(marker, trailer) {
16
+ const start = script.indexOf(marker);
17
+ assert.notEqual(start, -1, `missing marker: ${marker}`);
18
+ const bodyStart = script.indexOf('\n', start);
19
+ assert.notEqual(bodyStart, -1, `missing body start: ${marker}`);
20
+ const end = script.indexOf(trailer, bodyStart + 1);
21
+ assert.notEqual(end, -1, `missing trailer: ${trailer}`);
22
+ return script.slice(bodyStart + 1, end);
23
+ }
24
+
25
+ function extractFunction(block, startName, endName) {
26
+ const start = block.indexOf(startName);
27
+ assert.notEqual(start, -1, `missing function: ${startName}`);
28
+ const end = block.indexOf(endName, start);
29
+ assert.notEqual(end, -1, `missing end marker: ${endName}`);
30
+ return block.slice(start, end).trimEnd();
31
+ }
32
+
33
+ function loadHelperApi() {
34
+ const helperBlock = extractBlock('cat <<\'NODE\' > "$_helper"', '\n export CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=');
35
+ const replaceSource = extractFunction(
36
+ helperBlock,
37
+ 'function replaceRequired(source, pattern, replacement, label, expectedCount) {',
38
+ '\n\nfunction parseScalar(value) {',
39
+ );
40
+ const fullWidthSource = extractFunction(
41
+ helperBlock,
42
+ 'function isFullWidthCodePoint(codePoint) {',
43
+ '\n\nfunction graphemeWidth(grapheme) {',
44
+ );
45
+ const graphemeWidthSource = extractFunction(
46
+ helperBlock,
47
+ 'function graphemeWidth(grapheme) {',
48
+ '\n\nfunction stringWidth(value) {',
49
+ );
50
+ const stringWidthSource = extractFunction(
51
+ helperBlock,
52
+ 'function stringWidth(value) {',
53
+ '\n\nfunction stripANSI(value) {',
54
+ );
55
+ const stripAnsiSource = extractFunction(
56
+ helperBlock,
57
+ 'function stripANSI(value) {',
58
+ '\n\nfunction wrapAnsi(value, columns, options = {}) {',
59
+ );
60
+ const cleanupSource = extractFunction(
61
+ helperBlock,
62
+ 'function cleanupStaleEntryFiles(currentWorkdir = workdir, currentEntryJsOffset = entryJsOffset, currentEntryEndOffset = entryEndOffset, now = Date.now()) {',
63
+ '\n\nfunction ensureEntryFile() {',
64
+ );
65
+ const wrapAnsiSource = extractFunction(
66
+ helperBlock,
67
+ 'function wrapAnsi(value, columns, options = {}) {',
68
+ '\n\nfunction stableHash(value, seed) {',
69
+ );
70
+ const stableHashSource = extractFunction(
71
+ helperBlock,
72
+ 'function stableHash(value, seed) {',
73
+ '\n\nfunction replaceRequired(source, pattern, replacement, label, expectedCount) {',
74
+ );
75
+ const rewriteSource = extractFunction(
76
+ helperBlock,
77
+ 'function rewriteNativeChunkSource(source) {',
78
+ '\n\nasync function main() {',
79
+ );
80
+
81
+ const context = vm.createContext({ module: { exports: {} }, exports: {}, fs, path });
82
+ vm.runInContext(
83
+ `${replaceSource}\n${cleanupSource}\n${fullWidthSource}\n${graphemeWidthSource}\n${stripAnsiSource}\n${stringWidthSource}\n${wrapAnsiSource}\n${stableHashSource}\n${rewriteSource}\nmodule.exports = { replaceRequired, cleanupStaleEntryFiles, isFullWidthCodePoint, graphemeWidth, stripANSI, stringWidth, wrapAnsi, stableHash, rewriteNativeChunkSource };`,
84
+ context,
85
+ );
86
+ return context.module.exports;
87
+ }
88
+
89
+ function sha256(filePath) {
90
+ return crypto.createHash('sha256').update(fs.readFileSync(filePath)).digest('hex');
91
+ }
92
+
93
+ test('helper and bootstrap rewrite helpers stay identical', () => {
94
+ const helperBlock = extractBlock('cat <<\'NODE\' > "$_helper"', '\n export CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=');
95
+ const bootstrapBlock = extractBlock('cat <<\'NODE\' > "$_bootstrap"', '\n export CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=');
96
+
97
+ const helperReplace = extractFunction(
98
+ helperBlock,
99
+ 'function replaceRequired(source, pattern, replacement, label, expectedCount) {',
100
+ '\n\nfunction parseScalar(value) {',
101
+ );
102
+ const bootstrapReplace = extractFunction(
103
+ bootstrapBlock,
104
+ 'function replaceRequired(source, pattern, replacement, label, expectedCount) {',
105
+ '\n\nfunction parseScalar(value) {',
106
+ );
107
+ const helperRewrite = extractFunction(
108
+ helperBlock,
109
+ 'function rewriteNativeChunkSource(source) {',
110
+ '\n\nasync function main() {',
111
+ );
112
+ const bootstrapRewrite = extractFunction(
113
+ bootstrapBlock,
114
+ 'function rewriteNativeChunkSource(source) {',
115
+ '\n\nasync function main() {',
116
+ );
117
+ const helperWrapAnsi = extractFunction(
118
+ helperBlock,
119
+ 'function wrapAnsi(value, columns, options = {}) {',
120
+ '\n\nfunction stableHash(value, seed) {',
121
+ );
122
+ const helperCleanup = extractFunction(
123
+ helperBlock,
124
+ 'function cleanupStaleEntryFiles(currentWorkdir = workdir, currentEntryJsOffset = entryJsOffset, currentEntryEndOffset = entryEndOffset, now = Date.now()) {',
125
+ '\n\nfunction ensureEntryFile() {',
126
+ );
127
+ const bootstrapWrapAnsi = extractFunction(
128
+ bootstrapBlock,
129
+ 'function wrapAnsi(value, columns, options = {}) {',
130
+ '\n\nfunction stableHash(value, seed) {',
131
+ );
132
+ const bootstrapCleanup = extractFunction(
133
+ bootstrapBlock,
134
+ 'function cleanupStaleEntryFiles(currentWorkdir = workdir, currentEntryJsOffset = entryJsOffset, currentEntryEndOffset = entryEndOffset, now = Date.now()) {',
135
+ '\n\nfunction ensureEntryFile() {',
136
+ );
137
+ const helperStableHash = extractFunction(
138
+ helperBlock,
139
+ 'function stableHash(value, seed) {',
140
+ '\n\nfunction replaceRequired(source, pattern, replacement, label, expectedCount) {',
141
+ );
142
+ const bootstrapStableHash = extractFunction(
143
+ bootstrapBlock,
144
+ 'function stableHash(value, seed) {',
145
+ '\n\nfunction replaceRequired(source, pattern, replacement, label, expectedCount) {',
146
+ );
147
+ const helperStringWidth = extractFunction(
148
+ helperBlock,
149
+ 'function isFullWidthCodePoint(codePoint) {',
150
+ '\n\nfunction graphemeWidth(grapheme) {',
151
+ ) + '\n\n' + extractFunction(
152
+ helperBlock,
153
+ 'function graphemeWidth(grapheme) {',
154
+ '\n\nfunction stringWidth(value) {',
155
+ ) + '\n\n' + extractFunction(
156
+ helperBlock,
157
+ 'function stringWidth(value) {',
158
+ '\n\nfunction stripANSI(value) {',
159
+ );
160
+ const bootstrapStringWidth = extractFunction(
161
+ bootstrapBlock,
162
+ 'function isFullWidthCodePoint(codePoint) {',
163
+ '\n\nfunction graphemeWidth(grapheme) {',
164
+ ) + '\n\n' + extractFunction(
165
+ bootstrapBlock,
166
+ 'function graphemeWidth(grapheme) {',
167
+ '\n\nfunction stringWidth(value) {',
168
+ ) + '\n\n' + extractFunction(
169
+ bootstrapBlock,
170
+ 'function stringWidth(value) {',
171
+ '\n\nfunction stripANSI(value) {',
172
+ );
173
+
174
+ assert.equal(helperReplace, bootstrapReplace);
175
+ assert.equal(helperRewrite, bootstrapRewrite);
176
+ assert.equal(helperWrapAnsi, bootstrapWrapAnsi);
177
+ assert.equal(helperCleanup, bootstrapCleanup);
178
+ assert.equal(helperStableHash, bootstrapStableHash);
179
+ assert.equal(helperStringWidth, bootstrapStringWidth);
180
+ });
181
+
182
+ test('print path does not defer cleanup to exit', () => {
183
+ const helperBlock = extractBlock('cat <<\'NODE\' > "$_helper"', '\n export CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=');
184
+
185
+ assert.equal(helperBlock.includes('CLAUDE_TERMUX_PRINT_WAIT_MS'), true);
186
+ assert.equal(helperBlock.includes('setTimeout(resolve, printWaitMs)'), true);
187
+ assert.equal(helperBlock.includes('process.once(\'exit\''), false);
188
+ assert.equal(helperBlock.includes('process.removeListener(\'uncaughtException\''), true);
189
+ });
190
+
191
+ test('bootstrap path defers cleanup to exit', () => {
192
+ const bootstrapBlock = extractBlock('cat <<\'NODE\' > "$_bootstrap"', '\n export CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=');
193
+
194
+ assert.equal(bootstrapBlock.includes('CLAUDE_TERMUX_PRINT_WAIT_MS'), false);
195
+ assert.equal(bootstrapBlock.includes('setTimeout(resolve, printWaitMs)'), false);
196
+ assert.equal(bootstrapBlock.includes('process.once(\'exit\''), true);
197
+ assert.equal(bootstrapBlock.includes('process.removeListener(\'uncaughtException\''), true);
198
+ });
199
+
200
+ test('entry extraction uses a process-unique filename', () => {
201
+ const helperBlock = extractBlock('cat <<\'NODE\' > "$_helper"', '\n export CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=');
202
+ const bootstrapBlock = extractBlock('cat <<\'NODE\' > "$_bootstrap"', '\n export CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=');
203
+
204
+ assert.equal(helperBlock.includes('process.pid'), true);
205
+ assert.equal(helperBlock.includes('Math.random'), true);
206
+ assert.equal(bootstrapBlock.includes('process.pid'), true);
207
+ assert.equal(bootstrapBlock.includes('Math.random'), true);
208
+ });
209
+
210
+ test('replaceRequired enforces the expected replacement count', () => {
211
+ const { replaceRequired } = loadHelperApi();
212
+
213
+ assert.equal(replaceRequired('abc abc', /abc/g, 'x', 'abc', 2), 'x x');
214
+ assert.throws(
215
+ () => replaceRequired('abc abc', /abc/g, 'x', 'abc', 1),
216
+ /unexpected abc count 2/,
217
+ );
218
+ });
219
+
220
+ test('stringWidth treats full-width and combining text as expected', () => {
221
+ const { stringWidth } = loadHelperApi();
222
+
223
+ assert.equal(stringWidth('abc'), 3);
224
+ assert.equal(stringWidth('あ'), 2);
225
+ assert.equal(stringWidth('a\u0301'), 1);
226
+ assert.equal(stringWidth('🙂'), 2);
227
+ assert.equal(stringWidth('🚀'), 2);
228
+ assert.equal(stringWidth('🚗'), 2);
229
+ assert.equal(stringWidth('❤️'), 2);
230
+ assert.equal(stringWidth('☕️'), 2);
231
+ assert.equal(stringWidth('🇯🇵'), 2);
232
+ assert.equal(stringWidth('#️⃣'), 2);
233
+ assert.equal(stringWidth('👨‍👩‍👧‍👦'), 2);
234
+ assert.equal(stringWidth('\u001b[31mあ\u001b[0m'), 2);
235
+ assert.equal(stringWidth('\u001b[36m🇯🇵\u001b[0m'), 2);
236
+ });
237
+
238
+ test('stableHash incorporates the optional seed', () => {
239
+ const { stableHash } = loadHelperApi();
240
+
241
+ assert.equal(stableHash('abc', 123), stableHash('abc', 123));
242
+ assert.notEqual(stableHash('abc', 123), stableHash('abc', 456));
243
+ });
244
+
245
+ test('wrapAnsi respects soft wrap, trim, and no-wrap options', () => {
246
+ const { wrapAnsi } = loadHelperApi();
247
+
248
+ assert.equal(wrapAnsi('abc def', 4, { hard: false }), 'abc\ndef');
249
+ assert.equal(wrapAnsi(' abc', 10, { trim: true }), 'abc');
250
+ assert.equal(wrapAnsi('abc ', 10, { trim: true }), 'abc');
251
+ assert.equal(wrapAnsi('abc def', 3, { wordWrap: false }).includes('\n'), true);
252
+ assert.equal(wrapAnsi('ab\u001b[31mcd', 3).includes('\n'), true);
253
+ });
254
+
255
+ test('cleanupStaleEntryFiles removes only stale extracted files for the same offsets', () => {
256
+ const { cleanupStaleEntryFiles } = loadHelperApi();
257
+ const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'claude-termux-'));
258
+ const tmpdir = fs.mkdtempSync(path.join(tmpRoot, 'work-'));
259
+ const stale = path.join(tmpdir, 'cli.11.22.123.old.bare-path.js');
260
+ const fresh = path.join(tmpdir, 'cli.11.22.456.new.bare-path.js');
261
+ const otherOffsets = path.join(tmpdir, 'cli.33.44.999.old.bare-path.js');
262
+ try {
263
+ fs.writeFileSync(stale, 'stale');
264
+ fs.writeFileSync(fresh, 'fresh');
265
+ fs.writeFileSync(otherOffsets, 'other');
266
+ const oldTime = new Date(Date.now() - (2 * 24 * 60 * 60 * 1000));
267
+ fs.utimesSync(stale, oldTime, oldTime);
268
+ fs.utimesSync(otherOffsets, oldTime, oldTime);
269
+
270
+ cleanupStaleEntryFiles(tmpdir, 11, 22, Date.now());
271
+
272
+ assert.equal(fs.existsSync(stale), false);
273
+ assert.equal(fs.existsSync(fresh), true);
274
+ assert.equal(fs.existsSync(otherOffsets), true);
275
+ } finally {
276
+ fs.rmSync(tmpRoot, { recursive: true, force: true });
277
+ }
278
+ });
279
+
280
+ function buildSyntheticBundleSource() {
281
+ const typeofBun = Array.from({ length: 3 }, () => 'typeof Bun').join('; ');
282
+ const bunProps = Array.from({ length: 33 }, (_, index) => `Bun.p${index}`).join('; ');
283
+ return `function(exports, require, module, __filename, __dirname) { ${typeofBun}; typeof globalThis.Bun; globalThis.Bun; ${bunProps}; npmInstallDeprecated:!0 }`;
284
+ }
285
+
286
+ test('rewriteNativeChunkSource rewrites the synthetic bundle slice', () => {
287
+ const { rewriteNativeChunkSource } = loadHelperApi();
288
+ const source = buildSyntheticBundleSource();
289
+ const patched = rewriteNativeChunkSource(source);
290
+
291
+ assert.match(patched, /var __claudeBun = globalThis\.__claudeBunShim;/);
292
+ assert.match(patched, /typeof __claudeBun/);
293
+ assert.match(patched, /typeof globalThis\.__claudeBun/);
294
+ assert.match(patched, /globalThis\.__claudeBun/);
295
+ assert.match(patched, /__claudeBun\./);
296
+ assert.match(patched, /npmInstallDeprecated:!1/);
297
+ assert.doesNotMatch(patched, /npmInstallDeprecated:!0/);
298
+ });
299
+
300
+ test('rewriteNativeChunkSource rejects unexpected replacement counts', () => {
301
+ const { rewriteNativeChunkSource } = loadHelperApi();
302
+ const source = buildSyntheticBundleSource().replace('Bun.p30;', '');
303
+
304
+ assert.throws(
305
+ () => rewriteNativeChunkSource(source),
306
+ /unexpected Bun property access count 32/,
307
+ );
308
+ });
309
+
310
+ const tarballPath = path.join(__dirname, '..', 'bash0816-claude-code-2.1.177.tgz');
311
+
312
+ test('tarball contents match the workspace runner and test file', { skip: !fs.existsSync(tarballPath) }, () => {
313
+ const tarRunner = child_process.execFileSync('tar', ['-xOf', tarballPath, 'package/lib/termux-run-claude-native.sh']);
314
+ const tarTest = child_process.execFileSync('tar', ['-xOf', tarballPath, 'package/lib/termux-run-claude-native.test.js']);
315
+
316
+ const worktreeRunner = fs.readFileSync(path.join(__dirname, 'termux-run-claude-native.sh'));
317
+ const worktreeTest = fs.readFileSync(path.join(__dirname, 'termux-run-claude-native.test.js'));
318
+
319
+ assert.equal(crypto.createHash('sha256').update(tarRunner).digest('hex'), crypto.createHash('sha256').update(worktreeRunner).digest('hex'));
320
+ assert.equal(crypto.createHash('sha256').update(tarTest).digest('hex'), crypto.createHash('sha256').update(worktreeTest).digest('hex'));
321
+ assert.equal(tarRunner.length, worktreeRunner.length);
322
+ assert.equal(tarTest.length, worktreeTest.length);
323
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bash0816/claude-code",
3
- "version": "2.1.161",
3
+ "version": "2.1.177-1",
4
4
  "description": "Unofficial Termux-native Claude Code wrapper with audited native replay",
5
5
  "license": "Apache-2.0",
6
6
  "bin": {