@bash0816/claude-code 2.1.240 → 2.1.245

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,142 @@
1
+ 'use strict';
2
+
3
+ const test = require('node:test');
4
+ const assert = require('node:assert/strict');
5
+
6
+ test('bunfs-vm-guard exports all vm module methods', async () => {
7
+ const vmGuard = await import('./bunfs-vm-guard.mjs');
8
+
9
+ assert.equal(typeof vmGuard.createContext, 'function');
10
+ assert.equal(typeof vmGuard.isContext, 'function');
11
+ assert.equal(typeof vmGuard.runInContext, 'function');
12
+ assert.equal(typeof vmGuard.runInNewContext, 'function');
13
+ assert.equal(typeof vmGuard.runInThisContext, 'function');
14
+ assert.equal(typeof vmGuard.createScript, 'function');
15
+ assert.equal(typeof vmGuard.compileFunction, 'function');
16
+ assert.equal(typeof vmGuard.measureMemory, 'function');
17
+ assert.equal(typeof vmGuard.Script, 'function');
18
+ // SourceTextModule and SyntheticModule only exist with --experimental-vm-modules flag
19
+ assert.ok('SourceTextModule' in vmGuard);
20
+ assert.ok('SyntheticModule' in vmGuard);
21
+ assert.ok(vmGuard.constants);
22
+ });
23
+
24
+ test('bunfs-vm-guard provides default export with vm module API', async () => {
25
+ const vmGuard = await import('./bunfs-vm-guard.mjs');
26
+ const defaultExport = vmGuard.default;
27
+
28
+ assert.ok(defaultExport);
29
+ assert.equal(typeof defaultExport, 'object');
30
+ assert.equal(typeof defaultExport.createContext, 'function');
31
+ assert.equal(typeof defaultExport.isContext, 'function');
32
+ assert.equal(typeof defaultExport.runInContext, 'function');
33
+ assert.equal(typeof defaultExport.runInNewContext, 'function');
34
+ assert.equal(typeof defaultExport.runInThisContext, 'function');
35
+ assert.equal(typeof defaultExport.createScript, 'function');
36
+ assert.equal(typeof defaultExport.compileFunction, 'function');
37
+ assert.equal(typeof defaultExport.measureMemory, 'function');
38
+ assert.equal(typeof defaultExport.Script, 'function');
39
+ // SourceTextModule and SyntheticModule may not exist in default export without --experimental-vm-modules
40
+ // They are exported as named exports if they exist
41
+ assert.ok(defaultExport.constants);
42
+ });
43
+
44
+ test('bunfs-vm-guard injects Bun into context in createContext', async () => {
45
+ globalThis.Bun = { __dummy: true };
46
+ try {
47
+ const vmGuard = await import('./bunfs-vm-guard.mjs');
48
+
49
+ const context = vmGuard.createContext({});
50
+ // Check that Bun property exists in context
51
+ assert.ok(Object.prototype.hasOwnProperty.call(context, 'Bun'));
52
+
53
+ // Verify Bun is defined when we run code in context
54
+ const result = vmGuard.runInContext('typeof Bun', context);
55
+ assert.equal(result, 'object');
56
+ } finally {
57
+ delete globalThis.Bun;
58
+ }
59
+ });
60
+
61
+ test('bunfs-vm-guard injects Bun into context in runInNewContext', async () => {
62
+ globalThis.Bun = { __dummy: true };
63
+ try {
64
+ const vmGuard = await import('./bunfs-vm-guard.mjs');
65
+
66
+ const code = 'typeof Bun';
67
+ const result = vmGuard.runInNewContext(code, {});
68
+ assert.equal(result, 'object');
69
+ } finally {
70
+ delete globalThis.Bun;
71
+ }
72
+ });
73
+
74
+ test('bunfs-vm-guard injects __claudeYaml into context', async () => {
75
+ const vmGuard = await import('./bunfs-vm-guard.mjs');
76
+
77
+ const context = vmGuard.createContext({});
78
+ // Check that __claudeYaml property exists
79
+ assert.ok(Object.prototype.hasOwnProperty.call(context, '__claudeYaml'));
80
+ });
81
+
82
+ test('bunfs-vm-guard injects __claudeBun and __claudeBunShim into context', async () => {
83
+ const vmGuard = await import('./bunfs-vm-guard.mjs');
84
+
85
+ const context = vmGuard.createContext({});
86
+ // Check that shim properties exist
87
+ assert.ok(Object.prototype.hasOwnProperty.call(context, '__claudeBun'));
88
+ assert.ok(Object.prototype.hasOwnProperty.call(context, '__claudeBunShim'));
89
+ });
90
+
91
+ test('bunfs-vm-guard Script class works with context injection', async () => {
92
+ globalThis.Bun = { __dummy: true };
93
+ try {
94
+ const vmGuard = await import('./bunfs-vm-guard.mjs');
95
+
96
+ const code = 'typeof Bun';
97
+ const script = new vmGuard.Script(code);
98
+ const context = vmGuard.createContext({});
99
+ const result = script.runInContext(context);
100
+ assert.equal(result, 'object');
101
+ } finally {
102
+ delete globalThis.Bun;
103
+ }
104
+ });
105
+
106
+ test('bunfs-vm-guard handles context with existing Bun property', async () => {
107
+ globalThis.Bun = { __dummy: true };
108
+ try {
109
+ const vmGuard = await import('./bunfs-vm-guard.mjs');
110
+
111
+ // Create context with pre-existing Bun property
112
+ const context = vmGuard.createContext({ Bun: { custom: true } });
113
+ // The guard should have replaced/overwritten it with globalThis.Bun
114
+ const result = vmGuard.runInContext('typeof Bun', context);
115
+ assert.equal(result, 'object');
116
+ } finally {
117
+ delete globalThis.Bun;
118
+ }
119
+ });
120
+
121
+ test('bunfs-vm-guard does not break normal vm functionality', async () => {
122
+ const vmGuard = await import('./bunfs-vm-guard.mjs');
123
+
124
+ // Test that normal code execution still works
125
+ const code = '2 + 2';
126
+ const result = vmGuard.runInNewContext(code);
127
+ assert.equal(result, 4);
128
+ });
129
+
130
+ test('bunfs-vm-guard Script.runInNewContext works with injections', async () => {
131
+ globalThis.Bun = { __dummy: true };
132
+ try {
133
+ const vmGuard = await import('./bunfs-vm-guard.mjs');
134
+
135
+ const code = 'typeof Bun';
136
+ const script = new vmGuard.Script(code);
137
+ const result = script.runInNewContext({});
138
+ assert.equal(result, 'object');
139
+ } finally {
140
+ delete globalThis.Bun;
141
+ }
142
+ });
@@ -0,0 +1,11 @@
1
+ class WS {
2
+ on() {}
3
+ once() {}
4
+ addEventListener() {}
5
+ close() {}
6
+ send() {}
7
+ ping() {}
8
+ }
9
+
10
+ export default WS;
11
+ export { WS as WebSocket };
@@ -0,0 +1,91 @@
1
+ function parseScalar(value) {
2
+ const text = String(value ?? '').trim();
3
+ if (text === '') return '';
4
+ if (text === 'true') return true;
5
+ if (text === 'false') return false;
6
+ if (text === 'null' || text === '~') return null;
7
+ if (/^-?(?:0|[1-9]\d*)(?:\.\d+)?$/.test(text)) return Number(text);
8
+ if (
9
+ (text.startsWith('"') && text.endsWith('"')) ||
10
+ (text.startsWith("'") && text.endsWith("'"))
11
+ ) {
12
+ return text.slice(1, -1);
13
+ }
14
+ return text;
15
+ }
16
+
17
+ function parseInlineArray(value) {
18
+ const inner = String(value ?? '').trim().slice(1, -1).trim();
19
+ if (inner === '') return [];
20
+ const items = [];
21
+ let current = '';
22
+ let quote = null;
23
+
24
+ for (let i = 0; i < inner.length; i += 1) {
25
+ const ch = inner[i];
26
+ if (quote) {
27
+ if (ch === quote && inner[i - 1] !== '\\') quote = null;
28
+ current += ch;
29
+ continue;
30
+ }
31
+ if (ch === '"' || ch === "'") {
32
+ quote = ch;
33
+ current += ch;
34
+ continue;
35
+ }
36
+ if (ch === ',') {
37
+ items.push(parseScalar(current));
38
+ current = '';
39
+ continue;
40
+ }
41
+ current += ch;
42
+ }
43
+
44
+ if (current !== '') items.push(parseScalar(current));
45
+ return items;
46
+ }
47
+
48
+ export function yamlParse(text) {
49
+ const source = String(text ?? '');
50
+ const result = {};
51
+ for (const rawLine of source.split(/\r?\n/)) {
52
+ const line = rawLine.trim();
53
+ if (!line || line.startsWith('#')) continue;
54
+ const idx = line.indexOf(':');
55
+ if (idx < 0) continue;
56
+ const key = line.slice(0, idx).trim();
57
+ const rawValue = line.slice(idx + 1).trim();
58
+ if (!key) continue;
59
+ result[key] = rawValue.startsWith('[') && rawValue.endsWith(']')
60
+ ? parseInlineArray(rawValue)
61
+ : parseScalar(rawValue);
62
+ }
63
+ return result;
64
+ }
65
+
66
+ export function yamlStringify(value) {
67
+ if (!value || typeof value !== 'object') return String(value ?? '');
68
+ const lines = [];
69
+ for (const [key, raw] of Object.entries(value)) {
70
+ if (Array.isArray(raw)) {
71
+ lines.push(`${key}: [${raw.map(item => JSON.stringify(String(item))).join(', ')}]`);
72
+ } else if (raw === null) {
73
+ lines.push(`${key}: null`);
74
+ } else if (typeof raw === 'string') {
75
+ lines.push(`${key}: ${JSON.stringify(raw)}`);
76
+ } else {
77
+ lines.push(`${key}: ${String(raw)}`);
78
+ }
79
+ }
80
+ return lines.join('\n');
81
+ }
82
+
83
+ export function createYamlShim() {
84
+ const yaml = {
85
+ parse: yamlParse,
86
+ stringify: yamlStringify,
87
+ };
88
+ yaml.YAML = yaml;
89
+ yaml.default = yaml;
90
+ return yaml;
91
+ }
@@ -128,6 +128,14 @@ function verifyTarball(file, audited) {
128
128
  }
129
129
 
130
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) {
131
139
  const buf = fs.readFileSync(file);
132
140
  const start = Number(audited.entry_js_offset);
133
141
  const end = Number(audited.entry_end_offset);
@@ -144,3 +152,25 @@ function validateOffsets(file, audited) {
144
152
  throw new Error(`audited end offset validation failed for ${version}`);
145
153
  }
146
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
+ }
@@ -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,60 @@ 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 { register } = 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
+ 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
+ },
702
+ });
703
+
704
+ const entryUrl = pathToFileURL(path.join(ownedDir, entryRelPath)).href;
705
+
706
+ // 2.1.245実チャンクのエントリは、内部のmain相当処理をトップレベルでawaitせず
707
+ // fire-and-forgetで起動する(Bunランタイム前提の実装)。そのためawait import()は
708
+ // 内部の非同期処理が完了する前に解決してしまい、legacy-cjs経路のような
709
+ // process.exitパッチ+ここでの強制exit呼び出しを行うと、まだ実行中の内部処理を
710
+ // 強制終了させ出力が失われる(実機で確認済み)。process.exit/killは一切パッチせず、
711
+ // 実際のCLIコードが自ら呼ぶprocess.exit()に任せてNodeの自然なイベントループ終了を
712
+ // 待つ(この関数はawait import()完了後、何もせずreturnするだけでよい)。
713
+ // 同じ理由で、ここでglobalThis.Bun/__claudeYamlを削除するcleanupも行わない
714
+ // (fire-and-forgetの内部処理がimport()解決後も継続してBunを参照するため、
715
+ // 早期に消すと実機で"Bun is not defined"を引き起こす。プロセス終了まで残す)。
716
+ await import(entryUrl);
717
+ }
718
+
719
+ async function legacyCjsMain() {
660
720
  let extractedFile;
661
721
  extractedFile = ensureEntryFile();
662
722
  const code = fs.readFileSync(extractedFile, 'utf8');
@@ -1000,6 +1060,10 @@ async function main() {
1000
1060
  }
1001
1061
  }
1002
1062
 
1063
+ function main() {
1064
+ return process.env.ENTRY_FORMAT === 'esm-chunked' ? esmChunkedMain() : legacyCjsMain();
1065
+ }
1066
+
1003
1067
  main().catch(error => {
1004
1068
  if (error && error.code === 'CLAUDE_TERMUX_OFFICIAL_UPDATE_BLOCKED') {
1005
1069
  console.error(BLOCK_MESSAGE);
@@ -1613,7 +1677,60 @@ function rewriteNativeChunkSource(source) {
1613
1677
  return patched;
1614
1678
  }
1615
1679
 
1616
- async function main() {
1680
+ 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');
1683
+ const { pathToFileURL } = require('node:url');
1684
+
1685
+ const { ownedDir, entryRelPath } = prepareProcessOwnedDir(sourceBin, workdir);
1686
+ const libDir = path.join(process.env.CLAUDE_TERMUX_PACKAGE_DIR, 'lib');
1687
+
1688
+ globalThis.__claudeYaml = createYamlShim();
1689
+ globalThis.Bun = {
1690
+ version: '1.1.8',
1691
+ stringWidth,
1692
+ wrapAnsi,
1693
+ stripANSI,
1694
+ hash: stableHash,
1695
+ which: (cmd) => {
1696
+ try {
1697
+ return require('child_process').execFileSync('which', [String(cmd)], { encoding: 'utf8' }).trim() || null;
1698
+ } catch { return null; }
1699
+ },
1700
+ gc: () => {},
1701
+ YAML: globalThis.__claudeYaml,
1702
+ };
1703
+ Object.defineProperty(process.versions, 'bun', { value: '1.1.8', configurable: true });
1704
+ globalThis.__claudeBunShim = globalThis.Bun;
1705
+ globalThis.__claudeBun = globalThis.Bun;
1706
+
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
+ },
1716
+ });
1717
+
1718
+ const entryUrl = pathToFileURL(path.join(ownedDir, entryRelPath)).href;
1719
+
1720
+ // 2.1.245実チャンクのエントリは、内部のmain相当処理をトップレベルでawaitせず
1721
+ // fire-and-forgetで起動する(Bunランタイム前提の実装)。そのためawait import()は
1722
+ // 内部の非同期処理が完了する前に解決してしまい、legacy-cjs経路のような
1723
+ // process.exitパッチ+ここでの強制exit呼び出しを行うと、まだ実行中の内部処理を
1724
+ // 強制終了させ出力が失われる(実機で確認済み)。process.exit/killは一切パッチせず、
1725
+ // 実際のCLIコードが自ら呼ぶprocess.exit()に任せてNodeの自然なイベントループ終了を
1726
+ // 待つ(この関数はawait import()完了後、何もせずreturnするだけでよい)。
1727
+ // 同じ理由で、ここでglobalThis.Bun/__claudeYamlを削除するcleanupも行わない
1728
+ // (fire-and-forgetの内部処理がimport()解決後も継続してBunを参照するため、
1729
+ // 早期に消すと実機で"Bun is not defined"を引き起こす。プロセス終了まで残す)。
1730
+ await import(entryUrl);
1731
+ }
1732
+
1733
+ async function legacyCjsMain() {
1617
1734
  let extractedFile;
1618
1735
  extractedFile = ensureEntryFile();
1619
1736
  const code = fs.readFileSync(extractedFile, 'utf8');
@@ -1961,8 +2078,19 @@ async function main() {
1961
2078
  }
1962
2079
  }
1963
2080
 
2081
+ function main() {
2082
+ return process.env.ENTRY_FORMAT === 'esm-chunked' ? esmChunkedMain() : legacyCjsMain();
2083
+ }
2084
+
1964
2085
  main()
1965
2086
  .then(() => {
2087
+ // esm-chunked経路は内部のfire-and-forget非同期処理が実際のprocess.exit()を
2088
+ // 自ら呼ぶまでNodeのイベントループを生かしておく必要があるため、TUIモードと
2089
+ // 同様にここでは強制exitしない(実機で確認済み: 強制exitすると--helpの出力等が
2090
+ // 完了前に打ち切られる)。
2091
+ if (process.env.ENTRY_FORMAT === 'esm-chunked') {
2092
+ return;
2093
+ }
1966
2094
  if (process.env.CLAUDE_TERMUX_TUI === '1' && process.exitCode === undefined) {
1967
2095
  return;
1968
2096
  }
@@ -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.240",
3
+ "version": "2.1.245",
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": ">=20.6.0"
19
19
  },
20
20
  "keywords": [
21
21
  "claude-code",