@bash0816/claude-code 2.1.122
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 +92 -0
- package/bin/claude +61 -0
- package/config/claude-native-audited-versions.json +32 -0
- package/config/claude-termux-release-manifest.json +8 -0
- package/lib/check-updates.js +181 -0
- package/lib/postinstall.js +21 -0
- package/lib/preinstall.js +56 -0
- package/lib/prepare-native.js +75 -0
- package/lib/termux-run-claude-native.sh +181 -0
- package/package.json +22 -0
package/README.md
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# @bash0816/claude-code
|
|
2
|
+
|
|
3
|
+
Termux-native Claude Code wrapper package.
|
|
4
|
+
|
|
5
|
+
Termux 向け Claude Code native wrapper package です。
|
|
6
|
+
|
|
7
|
+
Canonical repository:
|
|
8
|
+
|
|
9
|
+
正規 repository:
|
|
10
|
+
|
|
11
|
+
- `https://github.com/bash0816/ClaudeCode-Termux`
|
|
12
|
+
|
|
13
|
+
This package is part of the move from the legacy typo repository to `ClaudeCode-Termux`.
|
|
14
|
+
|
|
15
|
+
この package は、旧 typo repository から `ClaudeCode-Termux` へ移行する正規導線です。
|
|
16
|
+
|
|
17
|
+
## Install / インストール
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
npm install -g @bash0816/claude-code@latest
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
If an older audited launcher from this repository already exists at `$PREFIX/bin/claude`, migrate through the updated launcher.
|
|
24
|
+
|
|
25
|
+
この repository の古い監査済み launcher が `$PREFIX/bin/claude` にある場合は、更新済み launcher 経由で移行します。
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
claude update
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
If npm reports `EEXIST` before package scripts run, use a one-time forced migration.
|
|
32
|
+
|
|
33
|
+
package script 実行前に npm が `EEXIST` を返す場合は、初回だけ forced migration を使います。
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
npm install -g --force @bash0816/claude-code@latest
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
After npm owns the `claude` bin link, normal `npm install -g` updates work.
|
|
40
|
+
|
|
41
|
+
npm が `claude` bin link を管理する状態になれば、通常の `npm install -g` 更新が使えます。
|
|
42
|
+
|
|
43
|
+
Specific audited versions:
|
|
44
|
+
|
|
45
|
+
監査済みの固定 version:
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
npm install -g @bash0816/claude-code@2.1.118
|
|
49
|
+
npm install -g @bash0816/claude-code@2.1.119
|
|
50
|
+
npm install -g @bash0816/claude-code@2.1.121
|
|
51
|
+
npm install -g @bash0816/claude-code@2.1.122
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Development override:
|
|
55
|
+
|
|
56
|
+
開発・検証用 override:
|
|
57
|
+
|
|
58
|
+
```sh
|
|
59
|
+
CLAUDE_TERMUX_CLAUDE_VERSION=2.1.118 claude --version
|
|
60
|
+
CLAUDE_TERMUX_CLAUDE_VERSION=2.1.119 claude --version
|
|
61
|
+
CLAUDE_TERMUX_CLAUDE_VERSION=2.1.121 claude --version
|
|
62
|
+
CLAUDE_TERMUX_CLAUDE_VERSION=2.1.122 claude --version
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Update / 更新
|
|
66
|
+
|
|
67
|
+
```sh
|
|
68
|
+
claude update
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
`claude update` checks the GitHub release manifest and installs the latest audited package version.
|
|
72
|
+
|
|
73
|
+
`claude update` は GitHub release manifest を確認し、最新の監査済み package version を install します。
|
|
74
|
+
|
|
75
|
+
```sh
|
|
76
|
+
npm install -g @bash0816/claude-code@<latest_audited_version>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Normal launch also checks the same manifest with a short timeout and prints a notice when a newer audited version is available.
|
|
80
|
+
|
|
81
|
+
通常起動時も、同じ manifest を短い timeout で確認し、新しい監査済み version があれば通知します。
|
|
82
|
+
|
|
83
|
+
## Policy / 方針
|
|
84
|
+
|
|
85
|
+
- Only audited versions in `config/claude-native-audited-versions.json` can run.
|
|
86
|
+
- `config/claude-native-audited-versions.json` にある監査済み version だけを実行できます。
|
|
87
|
+
- `2.1.118`, `2.1.119`, `2.1.121`, and `2.1.122` are included in the package metadata.
|
|
88
|
+
- `2.1.118`、`2.1.119`、`2.1.121`、`2.1.122` を package metadata に含めています。
|
|
89
|
+
- Native artifacts are cached under `${HOME}/.claude-termux-native-package`.
|
|
90
|
+
- native artifact は `${HOME}/.claude-termux-native-package` に cache します。
|
|
91
|
+
- If native preparation fails, the command exits with an error.
|
|
92
|
+
- native preparation に失敗した場合、command は error で終了します。
|
package/bin/claude
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
set -eu
|
|
3
|
+
|
|
4
|
+
SELF="$0"
|
|
5
|
+
while [ -L "${SELF}" ]; do
|
|
6
|
+
DIR=$(CDPATH= cd -- "$(dirname -- "${SELF}")" && pwd)
|
|
7
|
+
TARGET=$(readlink "${SELF}")
|
|
8
|
+
case "${TARGET}" in
|
|
9
|
+
/*) SELF="${TARGET}" ;;
|
|
10
|
+
*) SELF="${DIR}/${TARGET}" ;;
|
|
11
|
+
esac
|
|
12
|
+
done
|
|
13
|
+
|
|
14
|
+
PACKAGE_DIR=$(CDPATH= cd -- "$(dirname -- "${SELF}")/.." && pwd)
|
|
15
|
+
CONFIG_FILE="${PACKAGE_DIR}/config/claude-native-audited-versions.json"
|
|
16
|
+
PACKAGE_VERSION=$(node -e 'const fs=require("fs"); const path=require("path"); const p=JSON.parse(fs.readFileSync(path.join(process.argv[1],"package.json"),"utf8")); process.stdout.write(p.version)' "${PACKAGE_DIR}")
|
|
17
|
+
CLAUDE_VERSION="${CLAUDE_TERMUX_CLAUDE_VERSION:-${PACKAGE_VERSION}}"
|
|
18
|
+
UPDATE_HELPER="${PACKAGE_DIR}/lib/check-updates.js"
|
|
19
|
+
|
|
20
|
+
if [ "${1:-}" = "update" ]; then
|
|
21
|
+
shift
|
|
22
|
+
exec node "${UPDATE_HELPER}" update "${CLAUDE_VERSION}" "$@"
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
node "${UPDATE_HELPER}" notify "${CLAUDE_VERSION}" || true
|
|
26
|
+
|
|
27
|
+
json_field() {
|
|
28
|
+
node -e '
|
|
29
|
+
const fs = require("fs");
|
|
30
|
+
const config = JSON.parse(fs.readFileSync(process.argv[1], "utf8"));
|
|
31
|
+
const version = process.argv[2];
|
|
32
|
+
const field = process.argv[3];
|
|
33
|
+
const item = config.versions[version];
|
|
34
|
+
if (!item || item[field] === undefined) {
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
process.stdout.write(String(item[field]));
|
|
38
|
+
' "${CONFIG_FILE}" "${CLAUDE_VERSION}" "$1"
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
ENTRY_JS_OFFSET=$(json_field entry_js_offset)
|
|
42
|
+
ENTRY_END_OFFSET=$(json_field entry_end_offset)
|
|
43
|
+
CACHE_DIR="${CLAUDE_TERMUX_PACKAGE_CACHE:-${HOME}/.claude-termux-native-package}/versions/${CLAUDE_VERSION}"
|
|
44
|
+
SOURCE_BIN="${CACHE_DIR}/app/node_modules/@anthropic-ai/claude-code-linux-arm64/claude"
|
|
45
|
+
|
|
46
|
+
if [ ! -f "${SOURCE_BIN}" ]; then
|
|
47
|
+
node "${PACKAGE_DIR}/lib/prepare-native.js" "${CLAUDE_VERSION}"
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
if [ ! -f "${SOURCE_BIN}" ]; then
|
|
51
|
+
echo "Failed to prepare native Claude Code binary: ${SOURCE_BIN}" >&2
|
|
52
|
+
exit 1
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
SOURCE_BIN="${SOURCE_BIN}" \
|
|
56
|
+
WORKDIR="${CACHE_DIR}/launcher-workdir" \
|
|
57
|
+
ENTRY_JS_OFFSET="${ENTRY_JS_OFFSET}" \
|
|
58
|
+
ENTRY_END_OFFSET="${ENTRY_END_OFFSET}" \
|
|
59
|
+
CURRENT_CLAUDE_VERSION="${CLAUDE_VERSION}" \
|
|
60
|
+
CLAUDE_NATIVE_UPDATE_CHECK=0 \
|
|
61
|
+
exec sh "${PACKAGE_DIR}/lib/termux-run-claude-native.sh" "$@"
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"versions": {
|
|
3
|
+
"2.1.118": {
|
|
4
|
+
"wrapper_spec": "@anthropic-ai/claude-code@2.1.118",
|
|
5
|
+
"native_spec": "@anthropic-ai/claude-code-linux-arm64@2.1.118",
|
|
6
|
+
"entry_js_offset": 224788180,
|
|
7
|
+
"entry_end_offset": 238022890,
|
|
8
|
+
"status": "termux_verified"
|
|
9
|
+
},
|
|
10
|
+
"2.1.119": {
|
|
11
|
+
"wrapper_spec": "@anthropic-ai/claude-code@2.1.119",
|
|
12
|
+
"native_spec": "@anthropic-ai/claude-code-linux-arm64@2.1.119",
|
|
13
|
+
"entry_js_offset": 229972324,
|
|
14
|
+
"entry_end_offset": 243693409,
|
|
15
|
+
"status": "termux_verified"
|
|
16
|
+
},
|
|
17
|
+
"2.1.121": {
|
|
18
|
+
"wrapper_spec": "@anthropic-ai/claude-code@2.1.121",
|
|
19
|
+
"native_spec": "@anthropic-ai/claude-code-linux-arm64@2.1.121",
|
|
20
|
+
"entry_js_offset": 231769700,
|
|
21
|
+
"entry_end_offset": 245677986,
|
|
22
|
+
"status": "termux_verified"
|
|
23
|
+
},
|
|
24
|
+
"2.1.122": {
|
|
25
|
+
"wrapper_spec": "@anthropic-ai/claude-code@2.1.122",
|
|
26
|
+
"native_spec": "@anthropic-ai/claude-code-linux-arm64@2.1.122",
|
|
27
|
+
"entry_js_offset": 232193716,
|
|
28
|
+
"entry_end_offset": 246143358,
|
|
29
|
+
"status": "termux_verified"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
{
|
|
2
|
+
"manifest_version": 1,
|
|
3
|
+
"package_name": "@bash0816/claude-code",
|
|
4
|
+
"latest_audited_version": "2.1.122",
|
|
5
|
+
"latest_candidate_version": "2.1.122",
|
|
6
|
+
"previous_stable_version": "2.1.121",
|
|
7
|
+
"manifest_url": "https://raw.githubusercontent.com/bash0816/ClaudeCode-Termux/dev/config/claude-termux-release-manifest.json"
|
|
8
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const cp = require('child_process');
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const https = require('https');
|
|
7
|
+
const os = require('os');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
const packageDir = path.resolve(__dirname, '..');
|
|
11
|
+
const pkg = require(path.join(packageDir, 'package.json'));
|
|
12
|
+
const localManifest = require(path.join(packageDir, 'config', 'claude-termux-release-manifest.json'));
|
|
13
|
+
const mode = process.argv[2] || 'notify';
|
|
14
|
+
const currentVersion = process.argv[3] || pkg.version;
|
|
15
|
+
const dryRun = process.argv.includes('--dry-run');
|
|
16
|
+
const cacheRoot = process.env.CLAUDE_TERMUX_PACKAGE_CACHE || path.join(os.homedir(), '.claude-termux-native-package');
|
|
17
|
+
const cacheFile = path.join(cacheRoot, 'update-check.json');
|
|
18
|
+
const timeoutMs = Number(process.env.CLAUDE_TERMUX_UPDATE_TIMEOUT_MS || '1200');
|
|
19
|
+
const ttlMs = Number(process.env.CLAUDE_TERMUX_UPDATE_CACHE_TTL_MS || String(24 * 60 * 60 * 1000));
|
|
20
|
+
const skipCheck = process.env.CLAUDE_TERMUX_SKIP_UPDATE_CHECK === '1';
|
|
21
|
+
const manifestUrl = process.env.CLAUDE_TERMUX_MANIFEST_URL || localManifest.manifest_url;
|
|
22
|
+
|
|
23
|
+
function compareVersions(a, b) {
|
|
24
|
+
const aParts = String(a).split('.').map(Number);
|
|
25
|
+
const bParts = String(b).split('.').map(Number);
|
|
26
|
+
const len = Math.max(aParts.length, bParts.length);
|
|
27
|
+
for (let index = 0; index < len; index += 1) {
|
|
28
|
+
const diff = (aParts[index] || 0) - (bParts[index] || 0);
|
|
29
|
+
if (diff !== 0) return diff;
|
|
30
|
+
}
|
|
31
|
+
return 0;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function readCache() {
|
|
35
|
+
try {
|
|
36
|
+
return JSON.parse(fs.readFileSync(cacheFile, 'utf8'));
|
|
37
|
+
} catch {
|
|
38
|
+
return {};
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function writeCache(cache) {
|
|
43
|
+
fs.mkdirSync(path.dirname(cacheFile), { recursive: true });
|
|
44
|
+
fs.writeFileSync(cacheFile, JSON.stringify(cache, null, 2) + '\n');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function fetchManifest(url) {
|
|
48
|
+
return new Promise((resolve, reject) => {
|
|
49
|
+
const req = https.get(url, { timeout: timeoutMs }, response => {
|
|
50
|
+
if (response.statusCode !== 200) {
|
|
51
|
+
response.resume();
|
|
52
|
+
reject(new Error(`manifest fetch failed: HTTP ${response.statusCode}`));
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
let body = '';
|
|
57
|
+
response.setEncoding('utf8');
|
|
58
|
+
response.on('data', chunk => {
|
|
59
|
+
body += chunk;
|
|
60
|
+
});
|
|
61
|
+
response.on('end', () => {
|
|
62
|
+
try {
|
|
63
|
+
resolve(JSON.parse(body));
|
|
64
|
+
} catch (error) {
|
|
65
|
+
reject(error);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
req.on('timeout', () => {
|
|
71
|
+
req.destroy(new Error('manifest fetch timeout'));
|
|
72
|
+
});
|
|
73
|
+
req.on('error', reject);
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function resolveManifest(forceRefresh) {
|
|
78
|
+
if (skipCheck) {
|
|
79
|
+
return { manifest: localManifest, cache: readCache() };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const cache = readCache();
|
|
83
|
+
const now = Date.now();
|
|
84
|
+
if (!forceRefresh && cache.manifest && cache.fetched_at && now - cache.fetched_at < ttlMs) {
|
|
85
|
+
return { manifest: cache.manifest, cache };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
const manifest = await fetchManifest(manifestUrl);
|
|
90
|
+
const nextCache = {
|
|
91
|
+
...cache,
|
|
92
|
+
fetched_at: now,
|
|
93
|
+
manifest,
|
|
94
|
+
};
|
|
95
|
+
writeCache(nextCache);
|
|
96
|
+
return { manifest, cache: nextCache };
|
|
97
|
+
} catch {
|
|
98
|
+
if (cache.manifest) {
|
|
99
|
+
return { manifest: cache.manifest, cache };
|
|
100
|
+
}
|
|
101
|
+
return { manifest: localManifest, cache };
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function updateNoticeCache(cache, latestVersion) {
|
|
106
|
+
const nextCache = {
|
|
107
|
+
...cache,
|
|
108
|
+
last_notice: {
|
|
109
|
+
current_version: currentVersion,
|
|
110
|
+
latest_audited_version: latestVersion,
|
|
111
|
+
notified_at: Date.now(),
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
writeCache(nextCache);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function shouldNotify(cache, latestVersion) {
|
|
118
|
+
const lastNotice = cache.last_notice;
|
|
119
|
+
if (!lastNotice) return true;
|
|
120
|
+
if (lastNotice.current_version !== currentVersion) return true;
|
|
121
|
+
if (lastNotice.latest_audited_version !== latestVersion) return true;
|
|
122
|
+
return Date.now() - Number(lastNotice.notified_at || 0) >= ttlMs;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function installTarget(targetVersion) {
|
|
126
|
+
const spec = `${pkg.name}@${targetVersion}`;
|
|
127
|
+
const command = `npm install -g ${spec}`;
|
|
128
|
+
|
|
129
|
+
if (dryRun) {
|
|
130
|
+
console.log(command);
|
|
131
|
+
return 0;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
console.error(`Updating to audited version ${targetVersion}`);
|
|
135
|
+
const result = cp.spawnSync('npm', ['install', '-g', spec], {
|
|
136
|
+
stdio: 'inherit',
|
|
137
|
+
env: process.env,
|
|
138
|
+
});
|
|
139
|
+
return result.status === null ? 1 : result.status;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function runNotify() {
|
|
143
|
+
const { manifest, cache } = await resolveManifest(false);
|
|
144
|
+
const latest = manifest.latest_audited_version || localManifest.latest_audited_version || pkg.version;
|
|
145
|
+
if (compareVersions(currentVersion, latest) >= 0) return 0;
|
|
146
|
+
if (!shouldNotify(cache, latest)) return 0;
|
|
147
|
+
|
|
148
|
+
console.error(`Audited update available: ${currentVersion} -> ${latest}`);
|
|
149
|
+
console.error('Run: claude update');
|
|
150
|
+
updateNoticeCache(cache, latest);
|
|
151
|
+
return 0;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async function runUpdate() {
|
|
155
|
+
const { manifest } = await resolveManifest(true);
|
|
156
|
+
const target = manifest.latest_audited_version || localManifest.latest_audited_version || pkg.version;
|
|
157
|
+
if (compareVersions(currentVersion, target) >= 0) {
|
|
158
|
+
console.error(`Already on latest audited version: ${currentVersion}`);
|
|
159
|
+
return 0;
|
|
160
|
+
}
|
|
161
|
+
return installTarget(target);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async function main() {
|
|
165
|
+
if (mode === 'notify') {
|
|
166
|
+
process.exitCode = await runNotify();
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (mode === 'update') {
|
|
171
|
+
process.exitCode = await runUpdate();
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
throw new Error(`Unknown mode: ${mode}`);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
main().catch(error => {
|
|
179
|
+
console.error(error && error.stack ? error.stack : String(error));
|
|
180
|
+
process.exit(1);
|
|
181
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const cp = require('child_process');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
if (process.env.CLAUDE_TERMUX_SKIP_POSTINSTALL === '1') {
|
|
8
|
+
process.exit(0);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const packageDir = path.resolve(__dirname, '..');
|
|
12
|
+
const pkg = require(path.join(packageDir, 'package.json'));
|
|
13
|
+
const script = path.join(__dirname, 'prepare-native.js');
|
|
14
|
+
const version = process.env.CLAUDE_TERMUX_CLAUDE_VERSION || pkg.version;
|
|
15
|
+
|
|
16
|
+
const result = cp.spawnSync(process.execPath, [script, version], {
|
|
17
|
+
stdio: 'inherit',
|
|
18
|
+
env: process.env,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
process.exit(result.status === null ? 1 : result.status);
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const cp = require('child_process');
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const os = require('os');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
if (process.env.CLAUDE_TERMUX_SKIP_PREINSTALL === '1') {
|
|
10
|
+
process.exit(0);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const markers = [
|
|
14
|
+
'CLAUDE_NATIVE_118_CANDIDATE_MARKER',
|
|
15
|
+
'CLAUDE_NATIVE_AUDITED_CANDIDATE_MARKER',
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
const prefix = process.env.PREFIX || detectNpmPrefix();
|
|
19
|
+
if (!prefix) {
|
|
20
|
+
process.exit(0);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const target = path.join(prefix, 'bin', 'claude');
|
|
24
|
+
if (!fs.existsSync(target)) {
|
|
25
|
+
process.exit(0);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
const stat = fs.lstatSync(target);
|
|
30
|
+
if (stat.isSymbolicLink()) {
|
|
31
|
+
process.exit(0);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const head = fs.readFileSync(target, 'utf8').slice(0, 8192);
|
|
35
|
+
if (!markers.some(marker => head.includes(marker))) {
|
|
36
|
+
console.error(`Refusing to replace unmanaged existing claude binary: ${target}`);
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const backupDir = path.join(os.homedir(), '.claude-termux-native-package', 'preinstall-backups');
|
|
41
|
+
fs.mkdirSync(backupDir, { recursive: true });
|
|
42
|
+
const backup = path.join(backupDir, `claude.${new Date().toISOString().replace(/[-:]/g, '').replace(/\..+$/, 'Z')}`);
|
|
43
|
+
fs.renameSync(target, backup);
|
|
44
|
+
console.error(`Moved existing audited Claude launcher to ${backup}`);
|
|
45
|
+
} catch (error) {
|
|
46
|
+
console.error(error && error.stack ? error.stack : String(error));
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function detectNpmPrefix() {
|
|
51
|
+
const result = cp.spawnSync('npm', ['prefix', '-g'], { encoding: 'utf8' });
|
|
52
|
+
if (result.status !== 0) {
|
|
53
|
+
return '';
|
|
54
|
+
}
|
|
55
|
+
return result.stdout.trim();
|
|
56
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const cp = require('child_process');
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const os = require('os');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
const packageDir = path.resolve(__dirname, '..');
|
|
10
|
+
const config = require(path.join(packageDir, 'config', 'claude-native-audited-versions.json'));
|
|
11
|
+
const pkg = require(path.join(packageDir, 'package.json'));
|
|
12
|
+
const version = process.argv[2] || pkg.version;
|
|
13
|
+
const item = config.versions[version];
|
|
14
|
+
|
|
15
|
+
if (!item) {
|
|
16
|
+
console.error(`Unsupported audited Claude Code version: ${version}`);
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const cacheRoot = process.env.CLAUDE_TERMUX_PACKAGE_CACHE || path.join(os.homedir(), '.claude-termux-native-package');
|
|
21
|
+
const versionDir = path.join(cacheRoot, 'versions', version);
|
|
22
|
+
const nativeDest = path.join(versionDir, 'app', 'node_modules', '@anthropic-ai', 'claude-code-linux-arm64');
|
|
23
|
+
const sourceBin = path.join(nativeDest, 'claude');
|
|
24
|
+
|
|
25
|
+
if (fs.existsSync(sourceBin)) {
|
|
26
|
+
validateOffsets(sourceBin, item);
|
|
27
|
+
process.exit(0);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
fs.mkdirSync(versionDir, { recursive: true });
|
|
31
|
+
const packDir = fs.mkdtempSync(path.join(os.tmpdir(), `claude-code-${version}-`));
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
run('npm', ['pack', item.native_spec, '--pack-destination', packDir]);
|
|
35
|
+
const tgz = fs.readdirSync(packDir).find(name => name.endsWith('.tgz'));
|
|
36
|
+
if (!tgz) {
|
|
37
|
+
throw new Error('npm pack did not produce a tgz');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const extractDir = path.join(packDir, 'native');
|
|
41
|
+
fs.mkdirSync(extractDir, { recursive: true });
|
|
42
|
+
run('tar', ['-xzf', path.join(packDir, tgz), '-C', extractDir]);
|
|
43
|
+
|
|
44
|
+
fs.rmSync(nativeDest, { recursive: true, force: true });
|
|
45
|
+
fs.mkdirSync(path.dirname(nativeDest), { recursive: true });
|
|
46
|
+
fs.cpSync(path.join(extractDir, 'package'), nativeDest, { recursive: true });
|
|
47
|
+
validateOffsets(sourceBin, item);
|
|
48
|
+
} finally {
|
|
49
|
+
fs.rmSync(packDir, { recursive: true, force: true });
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function run(command, args) {
|
|
53
|
+
const result = cp.spawnSync(command, args, { stdio: 'inherit' });
|
|
54
|
+
if (result.status !== 0) {
|
|
55
|
+
throw new Error(`${command} ${args.join(' ')} failed`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function validateOffsets(file, audited) {
|
|
60
|
+
const buf = fs.readFileSync(file);
|
|
61
|
+
const start = Number(audited.entry_js_offset);
|
|
62
|
+
const end = Number(audited.entry_end_offset);
|
|
63
|
+
const startMarker = Buffer.from('function(exports, require, module, __filename, __dirname) {// Claude Code is a Beta product');
|
|
64
|
+
const endMarker = Buffer.from('/$bunfs/root/image-processor.js');
|
|
65
|
+
|
|
66
|
+
if (!(start > 0 && end > start && end <= buf.length)) {
|
|
67
|
+
throw new Error(`invalid audited offsets for ${version}`);
|
|
68
|
+
}
|
|
69
|
+
if (!buf.subarray(start, start + startMarker.length).equals(startMarker)) {
|
|
70
|
+
throw new Error(`audited start offset validation failed for ${version}`);
|
|
71
|
+
}
|
|
72
|
+
if (!buf.subarray(end, end + endMarker.length).equals(endMarker)) {
|
|
73
|
+
throw new Error(`audited end offset validation failed for ${version}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
set -eu
|
|
3
|
+
|
|
4
|
+
SOURCE_BIN="${SOURCE_BIN:?SOURCE_BIN is required}"
|
|
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}"
|
|
8
|
+
CURRENT_CLAUDE_VERSION="${CURRENT_CLAUDE_VERSION:?CURRENT_CLAUDE_VERSION is required}"
|
|
9
|
+
TERMUX_TMPDIR="${TMPDIR:-/data/data/com.termux/files/usr/tmp}"
|
|
10
|
+
SSL_CERT_DIR="${SSL_CERT_DIR:-/data/data/com.termux/files/usr/etc/tls}"
|
|
11
|
+
SSL_CERT_FILE="${SSL_CERT_FILE:-/data/data/com.termux/files/usr/etc/tls/cert.pem}"
|
|
12
|
+
DISABLE_AUTOUPDATER="${DISABLE_AUTOUPDATER:-1}"
|
|
13
|
+
|
|
14
|
+
need_cmd() {
|
|
15
|
+
if ! command -v "$1" >/dev/null 2>&1; then
|
|
16
|
+
echo "Missing required command: $1" >&2
|
|
17
|
+
exit 1
|
|
18
|
+
fi
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
need_cmd node
|
|
22
|
+
need_cmd termux-open-url
|
|
23
|
+
|
|
24
|
+
if [ ! -f "${SOURCE_BIN}" ]; then
|
|
25
|
+
echo "Missing source binary: ${SOURCE_BIN}" >&2
|
|
26
|
+
exit 1
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
mkdir -p "${WORKDIR}"
|
|
30
|
+
mkdir -p "${TERMUX_TMPDIR}"
|
|
31
|
+
|
|
32
|
+
export TMPDIR="${TERMUX_TMPDIR}"
|
|
33
|
+
export SSL_CERT_DIR
|
|
34
|
+
export SSL_CERT_FILE
|
|
35
|
+
export DISABLE_AUTOUPDATER
|
|
36
|
+
export SOURCE_BIN
|
|
37
|
+
export WORKDIR
|
|
38
|
+
export ENTRY_JS_OFFSET
|
|
39
|
+
export ENTRY_END_OFFSET
|
|
40
|
+
export CURRENT_CLAUDE_VERSION
|
|
41
|
+
|
|
42
|
+
node - "$@" <<'NODE'
|
|
43
|
+
const fs = require('fs');
|
|
44
|
+
const path = require('path');
|
|
45
|
+
|
|
46
|
+
const sourceBin = process.env.SOURCE_BIN;
|
|
47
|
+
const workdir = process.env.WORKDIR;
|
|
48
|
+
const entryJsOffset = Number(process.env.ENTRY_JS_OFFSET);
|
|
49
|
+
const entryEndOffset = Number(process.env.ENTRY_END_OFFSET);
|
|
50
|
+
const argv = process.argv.slice(2);
|
|
51
|
+
|
|
52
|
+
class RequestedExit extends Error {
|
|
53
|
+
constructor(code) {
|
|
54
|
+
super(`process.exit ${code}`);
|
|
55
|
+
this.name = 'RequestedExit';
|
|
56
|
+
this.code = code;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function ensureEntryFile() {
|
|
61
|
+
const extractedFile = path.join(workdir, `cli.${entryJsOffset}.${entryEndOffset}.bare-path.js`);
|
|
62
|
+
if (fs.existsSync(extractedFile)) return extractedFile;
|
|
63
|
+
|
|
64
|
+
const len = entryEndOffset - entryJsOffset;
|
|
65
|
+
if (!(len > 0)) throw new Error('invalid replay offsets');
|
|
66
|
+
|
|
67
|
+
const fd = fs.openSync(sourceBin, 'r');
|
|
68
|
+
const buf = Buffer.alloc(len);
|
|
69
|
+
fs.readSync(fd, buf, 0, len, entryJsOffset);
|
|
70
|
+
fs.closeSync(fd);
|
|
71
|
+
|
|
72
|
+
fs.writeFileSync(extractedFile, buf.toString('utf8').replace(/[\0\s]+$/g, ''));
|
|
73
|
+
return extractedFile;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function createFakeRequire(realRequire) {
|
|
77
|
+
const realChild = realRequire('child_process');
|
|
78
|
+
function rewriteArgs(args) {
|
|
79
|
+
if (
|
|
80
|
+
Array.isArray(args) &&
|
|
81
|
+
args[0] === 'xdg-open' &&
|
|
82
|
+
Array.isArray(args[1]) &&
|
|
83
|
+
typeof args[1][0] === 'string'
|
|
84
|
+
) {
|
|
85
|
+
return ['termux-open-url', [args[1][0]], ...args.slice(2)];
|
|
86
|
+
}
|
|
87
|
+
return args;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return function fakeRequire(id) {
|
|
91
|
+
if (id === 'ws') {
|
|
92
|
+
class WS {
|
|
93
|
+
on() {}
|
|
94
|
+
once() {}
|
|
95
|
+
addEventListener() {}
|
|
96
|
+
close() {}
|
|
97
|
+
send() {}
|
|
98
|
+
ping() {}
|
|
99
|
+
}
|
|
100
|
+
return { default: WS, WebSocket: WS };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (id === 'child_process') {
|
|
104
|
+
return {
|
|
105
|
+
spawn: (...args) => realChild.spawn(...rewriteArgs(args)),
|
|
106
|
+
execFile: (...args) => realChild.execFile(...args),
|
|
107
|
+
exec: (...args) => realChild.exec(...args),
|
|
108
|
+
spawnSync: (...args) => realChild.spawnSync(...rewriteArgs(args)),
|
|
109
|
+
execFileSync: (...args) => realChild.execFileSync(...args),
|
|
110
|
+
execSync: (...args) => realChild.execSync(...args),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (id.startsWith('/$bunfs/root/')) {
|
|
115
|
+
throw new Error('bunfs require blocked: ' + id);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return realRequire(id);
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function main() {
|
|
123
|
+
const extractedFile = ensureEntryFile();
|
|
124
|
+
const code = fs.readFileSync(extractedFile, 'utf8');
|
|
125
|
+
const fn = eval('(' + code);
|
|
126
|
+
|
|
127
|
+
const originalArgv = process.argv.slice();
|
|
128
|
+
const originalExit = process.exit;
|
|
129
|
+
const originalBun = process.versions.bun;
|
|
130
|
+
const hadGlobalBun = Object.prototype.hasOwnProperty.call(globalThis, 'Bun');
|
|
131
|
+
const originalGlobalBun = globalThis.Bun;
|
|
132
|
+
const asyncErrors = [];
|
|
133
|
+
|
|
134
|
+
const fakeRequire = createFakeRequire(require);
|
|
135
|
+
function onAsyncError(error) {
|
|
136
|
+
asyncErrors.push(error);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
process.once('uncaughtException', onAsyncError);
|
|
141
|
+
process.once('unhandledRejection', onAsyncError);
|
|
142
|
+
Object.defineProperty(process.versions, 'bun', { value: '1.1.8', configurable: true });
|
|
143
|
+
globalThis.Bun = { version: '1.1.8' };
|
|
144
|
+
process.argv = ['node', extractedFile, ...argv];
|
|
145
|
+
process.exit = code => {
|
|
146
|
+
throw new RequestedExit(code);
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const moduleLike = { exports: {} };
|
|
150
|
+
const maybePromise = fn(moduleLike.exports, fakeRequire, moduleLike, extractedFile, workdir);
|
|
151
|
+
if (maybePromise && typeof maybePromise.then === 'function') await maybePromise;
|
|
152
|
+
await new Promise(resolve => setTimeout(resolve, 200));
|
|
153
|
+
if (asyncErrors.length > 0) throw asyncErrors[0];
|
|
154
|
+
} catch (error) {
|
|
155
|
+
if (error instanceof RequestedExit) {
|
|
156
|
+
process.exitCode = error.code;
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
throw error;
|
|
160
|
+
} finally {
|
|
161
|
+
process.removeListener('uncaughtException', onAsyncError);
|
|
162
|
+
process.removeListener('unhandledRejection', onAsyncError);
|
|
163
|
+
process.argv = originalArgv;
|
|
164
|
+
process.exit = originalExit;
|
|
165
|
+
try {
|
|
166
|
+
if (originalBun === undefined) {
|
|
167
|
+
delete process.versions.bun;
|
|
168
|
+
} else {
|
|
169
|
+
Object.defineProperty(process.versions, 'bun', { value: originalBun, configurable: true });
|
|
170
|
+
}
|
|
171
|
+
} catch {}
|
|
172
|
+
if (hadGlobalBun) globalThis.Bun = originalGlobalBun;
|
|
173
|
+
else delete globalThis.Bun;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
main().catch(error => {
|
|
178
|
+
console.error(error && error.stack ? error.stack : String(error));
|
|
179
|
+
process.exit(1);
|
|
180
|
+
});
|
|
181
|
+
NODE
|
package/package.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@bash0816/claude-code",
|
|
3
|
+
"version": "2.1.122",
|
|
4
|
+
"description": "Termux-native Claude Code wrapper with audited native replay",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"bin": {
|
|
7
|
+
"claude": "bin/claude"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"preinstall": "node lib/preinstall.js",
|
|
11
|
+
"postinstall": "node lib/postinstall.js"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"bin",
|
|
15
|
+
"lib",
|
|
16
|
+
"config",
|
|
17
|
+
"README.md"
|
|
18
|
+
],
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=18"
|
|
21
|
+
}
|
|
22
|
+
}
|