@bash0816/claude-code 2.1.156 → 2.1.159-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 +17 -23
- package/bin/claude +2 -2
- package/config/claude-native-audited-versions.json +5 -12
- package/config/claude-termux-release-manifest.json +3 -8
- package/lib/check-updates.js +14 -92
- package/lib/native-update-guard.js +257 -0
- package/lib/native-update-guard.test.js +310 -0
- package/lib/postinstall.js +1 -2
- package/lib/preinstall.js +0 -6
- package/lib/prepare-native.js +51 -9
- package/lib/termux-run-claude-native.sh +191 -328
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,19 +1,17 @@
|
|
|
1
|
-
# @bash0816/
|
|
1
|
+
# @bash0816/claude-code
|
|
2
2
|
|
|
3
3
|
Termux-native Claude Code wrapper package.
|
|
4
4
|
|
|
5
5
|
Termux 向け Claude Code native wrapper package です。
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
This package is the current Termux wrapper package line.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
- canonical package: `@bash0816/claude-code`
|
|
9
|
+
この package は現行の Termux wrapper package 系です。
|
|
12
10
|
|
|
13
11
|
## Install / インストール
|
|
14
12
|
|
|
15
13
|
```sh
|
|
16
|
-
npm install -g @bash0816/
|
|
14
|
+
npm install -g @bash0816/claude-code@latest
|
|
17
15
|
```
|
|
18
16
|
|
|
19
17
|
If an older audited launcher from this repository already exists at `$PREFIX/bin/claude`, migrate through the updated launcher.
|
|
@@ -29,19 +27,23 @@ If npm reports `EEXIST` before package scripts run, use a one-time forced migrat
|
|
|
29
27
|
package script 実行前に npm が `EEXIST` を返す場合は、初回だけ forced migration を使います。
|
|
30
28
|
|
|
31
29
|
```sh
|
|
32
|
-
npm install -g --force @bash0816/
|
|
30
|
+
npm install -g --force @bash0816/claude-code@latest
|
|
33
31
|
```
|
|
34
32
|
|
|
35
33
|
After npm owns the `claude` bin link, normal `npm install -g` updates work.
|
|
36
34
|
|
|
37
35
|
npm が `claude` bin link を管理する状態になれば、通常の `npm install -g` 更新が使えます。
|
|
38
36
|
|
|
39
|
-
Specific audited
|
|
37
|
+
Specific audited versions:
|
|
40
38
|
|
|
41
39
|
監査済みの固定 version:
|
|
42
40
|
|
|
41
|
+
Current quick examples:
|
|
42
|
+
|
|
43
|
+
現在の quick example:
|
|
44
|
+
|
|
43
45
|
```sh
|
|
44
|
-
npm install -g @bash0816/
|
|
46
|
+
npm install -g @bash0816/claude-code@2.1.157
|
|
45
47
|
```
|
|
46
48
|
|
|
47
49
|
## Update / 更新
|
|
@@ -50,17 +52,9 @@ npm install -g @bash0816/cluade-code@2.1.123
|
|
|
50
52
|
claude update
|
|
51
53
|
```
|
|
52
54
|
|
|
53
|
-
`claude update`
|
|
54
|
-
|
|
55
|
-
`claude update` は GitHub release manifest を確認し、最新の監査済み package version を install します。
|
|
56
|
-
|
|
57
|
-
For the final legacy bridge release, `claude update` migrates this typo package to the canonical package.
|
|
58
|
-
|
|
59
|
-
最終 legacy bridge release では、`claude update` がこの typo package から canonical package へ移行します。
|
|
60
|
-
|
|
61
|
-
This final bridge package is frozen at package version `2.1.123` and launches audited native `2.1.150` by default from the synced metadata set.
|
|
55
|
+
`claude update` installs the latest audited package version.
|
|
62
56
|
|
|
63
|
-
|
|
57
|
+
`claude update` は最新の監査済み package version を install します。
|
|
64
58
|
|
|
65
59
|
```sh
|
|
66
60
|
npm install -g @bash0816/claude-code@<latest_audited_version>
|
|
@@ -74,10 +68,10 @@ Normal launch also checks the same manifest with a short timeout and prints a no
|
|
|
74
68
|
|
|
75
69
|
- Only audited versions in `config/claude-native-audited-versions.json` can run.
|
|
76
70
|
- `config/claude-native-audited-versions.json` にある監査済み version だけを実行できます。
|
|
77
|
-
-
|
|
78
|
-
-
|
|
79
|
-
- The metadata file
|
|
80
|
-
-
|
|
71
|
+
- `2.1.150-1`, `2.1.150-2`, `2.1.150-3`, `2.1.153-1`, `2.1.153-2`, `2.1.153-3`, `2.1.153-4`, `2.1.118`, `2.1.119`, `2.1.121`, `2.1.122`, `2.1.123`, `2.1.126`, `2.1.128`, `2.1.136`, `2.1.137`, `2.1.138`, `2.1.139`, `2.1.140`, `2.1.141`, `2.1.142`, `2.1.143`, `2.1.144`, `2.1.150`, `2.1.153`, `2.1.157` are included in the package metadata.
|
|
72
|
+
- `2.1.150-1`、`2.1.150-2`、`2.1.150-3`、`2.1.153-1`、`2.1.153-2`、`2.1.153-3`、`2.1.153-4`、`2.1.118`、`2.1.119`、`2.1.121`、`2.1.122`、`2.1.123`、`2.1.126`、`2.1.128`、`2.1.136`、`2.1.137`、`2.1.138`、`2.1.139`、`2.1.140`、`2.1.141`、`2.1.142`、`2.1.143`、`2.1.144`、`2.1.150`、`2.1.153`、`2.1.157` を package metadata に含めています。
|
|
73
|
+
- The metadata file is the source of truth for the currently audited set.
|
|
74
|
+
- 現在の監査済み version 集合の source of truth は metadata file です。
|
|
81
75
|
- Native artifacts are cached under `${HOME}/.claude-termux-native-package`.
|
|
82
76
|
- native artifact は `${HOME}/.claude-termux-native-package` に cache します。
|
|
83
77
|
- If native preparation fails, the command exits with an error.
|
package/bin/claude
CHANGED
|
@@ -14,8 +14,7 @@ done
|
|
|
14
14
|
PACKAGE_DIR=$(CDPATH= cd -- "$(dirname -- "${SELF}")/.." && pwd)
|
|
15
15
|
CONFIG_FILE="${PACKAGE_DIR}/config/claude-native-audited-versions.json"
|
|
16
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
|
-
|
|
18
|
-
CLAUDE_VERSION="${CLAUDE_TERMUX_CLAUDE_VERSION:-${DEFAULT_NATIVE_VERSION:-${PACKAGE_VERSION}}}"
|
|
17
|
+
CLAUDE_VERSION="${CLAUDE_TERMUX_CLAUDE_VERSION:-${PACKAGE_VERSION}}"
|
|
19
18
|
UPDATE_HELPER="${PACKAGE_DIR}/lib/check-updates.js"
|
|
20
19
|
|
|
21
20
|
if [ "${1:-}" = "update" ]; then
|
|
@@ -58,5 +57,6 @@ WORKDIR="${CACHE_DIR}/launcher-workdir" \
|
|
|
58
57
|
ENTRY_JS_OFFSET="${ENTRY_JS_OFFSET}" \
|
|
59
58
|
ENTRY_END_OFFSET="${ENTRY_END_OFFSET}" \
|
|
60
59
|
CURRENT_CLAUDE_VERSION="${CLAUDE_VERSION}" \
|
|
60
|
+
CLAUDE_TERMUX_PACKAGE_DIR="${PACKAGE_DIR}" \
|
|
61
61
|
CLAUDE_NATIVE_UPDATE_CHECK=0 \
|
|
62
62
|
exec sh "${PACKAGE_DIR}/lib/termux-run-claude-native.sh" "$@"
|
|
@@ -175,18 +175,11 @@
|
|
|
175
175
|
"entry_end_offset": 237958345,
|
|
176
176
|
"status": "termux_verified"
|
|
177
177
|
},
|
|
178
|
-
"2.1.
|
|
179
|
-
"wrapper_spec": "@anthropic-ai/claude-code@2.1.
|
|
180
|
-
"native_spec": "@anthropic-ai/claude-code-linux-arm64@2.1.
|
|
181
|
-
"entry_js_offset":
|
|
182
|
-
"entry_end_offset":
|
|
183
|
-
"status": "termux_verified"
|
|
184
|
-
},
|
|
185
|
-
"2.1.156": {
|
|
186
|
-
"wrapper_spec": "@anthropic-ai/claude-code@2.1.156",
|
|
187
|
-
"native_spec": "@anthropic-ai/claude-code-linux-arm64@2.1.156",
|
|
188
|
-
"entry_js_offset": 222957812,
|
|
189
|
-
"entry_end_offset": 238483519,
|
|
178
|
+
"2.1.157": {
|
|
179
|
+
"wrapper_spec": "@anthropic-ai/claude-code@2.1.157",
|
|
180
|
+
"native_spec": "@anthropic-ai/claude-code-linux-arm64@2.1.157",
|
|
181
|
+
"entry_js_offset": 223210916,
|
|
182
|
+
"entry_end_offset": 238725072,
|
|
190
183
|
"status": "termux_verified"
|
|
191
184
|
}
|
|
192
185
|
}
|
|
@@ -1,13 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest_version": 1,
|
|
3
3
|
"package_name": "@bash0816/claude-code",
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"bridge_package_version": "2.1.156",
|
|
8
|
-
"default_native_version": "2.1.156",
|
|
9
|
-
"latest_audited_version": "2.1.156",
|
|
10
|
-
"latest_candidate_version": "2.1.156",
|
|
11
|
-
"previous_stable_version": "2.1.154",
|
|
4
|
+
"latest_audited_version": "2.1.157",
|
|
5
|
+
"latest_candidate_version": "2.1.157",
|
|
6
|
+
"previous_stable_version": "2.1.153-4",
|
|
12
7
|
"manifest_url": "https://raw.githubusercontent.com/bash0816/ClaudeCode-Termux/main/config/claude-termux-release-manifest.json"
|
|
13
8
|
}
|
package/lib/check-updates.js
CHANGED
|
@@ -19,19 +19,10 @@ const timeoutMs = Number(process.env.CLAUDE_TERMUX_UPDATE_TIMEOUT_MS || '1200');
|
|
|
19
19
|
const ttlMs = Number(process.env.CLAUDE_TERMUX_UPDATE_CACHE_TTL_MS || String(24 * 60 * 60 * 1000));
|
|
20
20
|
const skipCheck = process.env.CLAUDE_TERMUX_SKIP_UPDATE_CHECK === '1';
|
|
21
21
|
const manifestUrl = process.env.CLAUDE_TERMUX_MANIFEST_URL || localManifest.manifest_url;
|
|
22
|
-
const canonicalManifestUrl = process.env.CLAUDE_TERMUX_CANONICAL_MANIFEST_URL || localManifest.canonical_manifest_url || '';
|
|
23
|
-
const canonicalPackageName = process.env.CLAUDE_TERMUX_CANONICAL_PACKAGE_NAME || localManifest.canonical_package_name || '';
|
|
24
|
-
const canonicalRepositoryUrl = process.env.CLAUDE_TERMUX_CANONICAL_REPOSITORY_URL || localManifest.canonical_repository_url || '';
|
|
25
22
|
|
|
26
23
|
function compareVersions(a, b) {
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
const parts = main.split('.').map(Number);
|
|
30
|
-
parts.push(patch !== undefined ? Number(patch) : 0);
|
|
31
|
-
return parts;
|
|
32
|
-
};
|
|
33
|
-
const aParts = parse(a);
|
|
34
|
-
const bParts = parse(b);
|
|
24
|
+
const aParts = String(a).split('.').map(Number);
|
|
25
|
+
const bParts = String(b).split('.').map(Number);
|
|
35
26
|
const len = Math.max(aParts.length, bParts.length);
|
|
36
27
|
for (let index = 0; index < len; index += 1) {
|
|
37
28
|
const diff = (aParts[index] || 0) - (bParts[index] || 0);
|
|
@@ -111,64 +102,10 @@ async function resolveManifest(forceRefresh) {
|
|
|
111
102
|
}
|
|
112
103
|
}
|
|
113
104
|
|
|
114
|
-
|
|
115
|
-
if (!canonicalManifestUrl || !canonicalPackageName || canonicalPackageName === pkg.name) {
|
|
116
|
-
return null;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
if (skipCheck) {
|
|
120
|
-
return {
|
|
121
|
-
packageName: canonicalPackageName,
|
|
122
|
-
latestVersion: localManifest.latest_audited_version || pkg.version,
|
|
123
|
-
repositoryUrl: canonicalRepositoryUrl,
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
try {
|
|
128
|
-
const manifest = await fetchManifest(canonicalManifestUrl);
|
|
129
|
-
return {
|
|
130
|
-
packageName: manifest.package_name || canonicalPackageName,
|
|
131
|
-
latestVersion: manifest.latest_audited_version || localManifest.latest_audited_version || pkg.version,
|
|
132
|
-
repositoryUrl: canonicalRepositoryUrl,
|
|
133
|
-
};
|
|
134
|
-
} catch {
|
|
135
|
-
return {
|
|
136
|
-
packageName: canonicalPackageName,
|
|
137
|
-
latestVersion: localManifest.latest_audited_version || pkg.version,
|
|
138
|
-
repositoryUrl: canonicalRepositoryUrl,
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
async function resolveTarget(forceRefresh) {
|
|
144
|
-
const base = await resolveManifest(forceRefresh);
|
|
145
|
-
const canonical = await resolveCanonicalManifest();
|
|
146
|
-
if (!canonical) {
|
|
147
|
-
return {
|
|
148
|
-
manifest: base.manifest,
|
|
149
|
-
cache: base.cache,
|
|
150
|
-
packageName: base.manifest.package_name || pkg.name,
|
|
151
|
-
targetVersion: base.manifest.latest_audited_version || localManifest.latest_audited_version || pkg.version,
|
|
152
|
-
repositoryUrl: '',
|
|
153
|
-
migration: false,
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
return {
|
|
158
|
-
manifest: base.manifest,
|
|
159
|
-
cache: base.cache,
|
|
160
|
-
packageName: canonical.packageName,
|
|
161
|
-
targetVersion: canonical.latestVersion,
|
|
162
|
-
repositoryUrl: canonical.repositoryUrl,
|
|
163
|
-
migration: canonical.packageName !== pkg.name,
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
function updateNoticeCache(cache, packageName, latestVersion) {
|
|
105
|
+
function updateNoticeCache(cache, latestVersion) {
|
|
168
106
|
const nextCache = {
|
|
169
107
|
...cache,
|
|
170
108
|
last_notice: {
|
|
171
|
-
package_name: packageName,
|
|
172
109
|
current_version: currentVersion,
|
|
173
110
|
latest_audited_version: latestVersion,
|
|
174
111
|
notified_at: Date.now(),
|
|
@@ -177,10 +114,9 @@ function updateNoticeCache(cache, packageName, latestVersion) {
|
|
|
177
114
|
writeCache(nextCache);
|
|
178
115
|
}
|
|
179
116
|
|
|
180
|
-
function shouldNotify(cache,
|
|
117
|
+
function shouldNotify(cache, latestVersion) {
|
|
181
118
|
const lastNotice = cache.last_notice;
|
|
182
119
|
if (!lastNotice) return true;
|
|
183
|
-
if (lastNotice.package_name !== packageName) return true;
|
|
184
120
|
if (lastNotice.current_version !== currentVersion) return true;
|
|
185
121
|
if (lastNotice.latest_audited_version !== latestVersion) return true;
|
|
186
122
|
return Date.now() - Number(lastNotice.notified_at || 0) >= ttlMs;
|
|
@@ -204,40 +140,26 @@ function installTarget(packageName, targetVersion) {
|
|
|
204
140
|
}
|
|
205
141
|
|
|
206
142
|
async function runNotify() {
|
|
207
|
-
const
|
|
208
|
-
const latest =
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
if (!migration && compareVersions(currentVersion, latest) >= 0) return 0;
|
|
212
|
-
if (!shouldNotify(target.cache, packageName, latest)) return 0;
|
|
213
|
-
|
|
214
|
-
if (migration) {
|
|
215
|
-
console.error(`Legacy package detected: ${pkg.name}`);
|
|
216
|
-
console.error(`Canonical package available: ${packageName}@${latest}`);
|
|
217
|
-
if (target.repositoryUrl) {
|
|
218
|
-
console.error(`Canonical repository: ${target.repositoryUrl}`);
|
|
219
|
-
}
|
|
220
|
-
console.error('Run: claude update');
|
|
221
|
-
updateNoticeCache(target.cache, packageName, latest);
|
|
222
|
-
return 0;
|
|
223
|
-
}
|
|
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;
|
|
224
147
|
|
|
225
148
|
console.error(`Audited update available: ${currentVersion} -> ${latest}`);
|
|
226
149
|
console.error('Run: claude update');
|
|
227
|
-
updateNoticeCache(
|
|
150
|
+
updateNoticeCache(cache, latest);
|
|
228
151
|
return 0;
|
|
229
152
|
}
|
|
230
153
|
|
|
231
154
|
async function runUpdate() {
|
|
232
|
-
const
|
|
233
|
-
|
|
155
|
+
const { manifest } = await resolveManifest(true);
|
|
156
|
+
const packageName = manifest.package_name || localManifest.package_name;
|
|
157
|
+
const target = manifest.latest_audited_version || localManifest.latest_audited_version || pkg.version;
|
|
158
|
+
if (compareVersions(currentVersion, target) >= 0) {
|
|
234
159
|
console.error(`Already on latest audited version: ${currentVersion}`);
|
|
235
160
|
return 0;
|
|
236
161
|
}
|
|
237
|
-
|
|
238
|
-
console.error(`Migrating from legacy package ${pkg.name} to canonical package ${target.packageName}@${target.targetVersion}`);
|
|
239
|
-
}
|
|
240
|
-
return installTarget(target.packageName, target.targetVersion);
|
|
162
|
+
return installTarget(packageName, target);
|
|
241
163
|
}
|
|
242
164
|
|
|
243
165
|
async function main() {
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const EventEmitter = require('events');
|
|
5
|
+
|
|
6
|
+
const OFFICIAL_PACKAGE = '@anthropic-ai/claude-code';
|
|
7
|
+
const BLOCK_MESSAGE = [
|
|
8
|
+
'Official native self-update is disabled on Termux.',
|
|
9
|
+
'Run: claude update',
|
|
10
|
+
].join('\n');
|
|
11
|
+
|
|
12
|
+
function lastPathSegment(value) {
|
|
13
|
+
return String(value || '').replace(/\\/g, '/').split('/').pop();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function isNpmCommand(command) {
|
|
17
|
+
const text = String(command || '');
|
|
18
|
+
const leaf = lastPathSegment(text);
|
|
19
|
+
return leaf === 'npm' || leaf === 'npm-cli.js';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function isPnpmCommand(command) {
|
|
23
|
+
return lastPathSegment(command) === 'pnpm';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function isYarnCommand(command) {
|
|
27
|
+
const leaf = lastPathSegment(command);
|
|
28
|
+
return leaf === 'yarn' || leaf === 'yarnpkg';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function isNpxCommand(command) {
|
|
32
|
+
const leaf = lastPathSegment(command);
|
|
33
|
+
return leaf === 'npx' || leaf === 'pnpx';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function isEnvCommand(command) {
|
|
37
|
+
return lastPathSegment(command) === 'env';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function isCorepackCommand(command) {
|
|
41
|
+
return lastPathSegment(command) === 'corepack';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function isShellCommand(command) {
|
|
45
|
+
const leaf = lastPathSegment(command);
|
|
46
|
+
return leaf === 'sh' || leaf === 'bash';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function isSupportedOperation(value) {
|
|
50
|
+
return ['install', 'i', 'add', 'update', 'up', 'upgrade'].includes(String(value || ''));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function isOfficialTarget(value) {
|
|
54
|
+
return String(value || '') === OFFICIAL_PACKAGE || String(value || '').startsWith(`${OFFICIAL_PACKAGE}@`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function normalizeCommand(command, args) {
|
|
58
|
+
let normalizedCommand = command;
|
|
59
|
+
let normalizedArgs = Array.isArray(args) ? args.slice() : [];
|
|
60
|
+
|
|
61
|
+
if (isEnvCommand(normalizedCommand)) {
|
|
62
|
+
while (normalizedArgs.length > 0 && /^[A-Za-z_][A-Za-z0-9_]*=.*/.test(String(normalizedArgs[0]))) {
|
|
63
|
+
normalizedArgs.shift();
|
|
64
|
+
}
|
|
65
|
+
if (normalizedArgs.length === 0) return { command: normalizedCommand, args: normalizedArgs };
|
|
66
|
+
normalizedCommand = normalizedArgs.shift();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (isCorepackCommand(normalizedCommand)) {
|
|
70
|
+
if (normalizedArgs.length === 0) return { command: normalizedCommand, args: normalizedArgs };
|
|
71
|
+
normalizedCommand = normalizedArgs.shift();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return { command: normalizedCommand, args: normalizedArgs };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function hasOfficialTarget(args) {
|
|
78
|
+
return args.some(isOfficialTarget);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function shouldBlockNpmLike(command, args) {
|
|
82
|
+
if (!(isNpmCommand(command) || isPnpmCommand(command))) return false;
|
|
83
|
+
if (!args.find(isSupportedOperation)) return false;
|
|
84
|
+
return hasOfficialTarget(args);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function shouldBlockYarn(command, args) {
|
|
88
|
+
if (!isYarnCommand(command)) return false;
|
|
89
|
+
return String(args[0] || '') === 'global' && String(args[1] || '') === 'add' && hasOfficialTarget(args.slice(2));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function shouldBlockNpx(command, args) {
|
|
93
|
+
if (!isNpxCommand(command)) return false;
|
|
94
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
95
|
+
const token = String(args[index] || '');
|
|
96
|
+
if (token === '-p' || token === '--package') {
|
|
97
|
+
if (isOfficialTarget(args[index + 1])) return true;
|
|
98
|
+
index += 1;
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (token.startsWith('--package=')) {
|
|
102
|
+
if (isOfficialTarget(token.slice('--package='.length))) return true;
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (!token.startsWith('-')) {
|
|
106
|
+
return isOfficialTarget(token);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function shouldBlockCommand(command, args) {
|
|
113
|
+
if (!Array.isArray(args)) return false;
|
|
114
|
+
const normalized = normalizeCommand(command, args);
|
|
115
|
+
return (
|
|
116
|
+
shouldBlockNpmLike(normalized.command, normalized.args) ||
|
|
117
|
+
shouldBlockYarn(normalized.command, normalized.args) ||
|
|
118
|
+
shouldBlockNpx(normalized.command, normalized.args)
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function normalizeTokensForExec(tokens) {
|
|
123
|
+
if (tokens.length === 0) return tokens;
|
|
124
|
+
const normalized = normalizeCommand(tokens[0], tokens.slice(1));
|
|
125
|
+
return [normalized.command, ...normalized.args].filter(value => value !== undefined);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function unwrapShellCommand(command, args) {
|
|
129
|
+
if (!isShellCommand(command) || !Array.isArray(args) || args.length < 2) return null;
|
|
130
|
+
const first = String(args[0] || '');
|
|
131
|
+
const second = String(args[1] || '');
|
|
132
|
+
if ((first === '-c' || first === '-lc') && second) return second;
|
|
133
|
+
if (first === '-l' && String(args[1] || '') === '-c' && String(args[2] || '')) return String(args[2]);
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function tokenizeShellString(command) {
|
|
138
|
+
const text = String(command || '').trim();
|
|
139
|
+
if (!text) return [];
|
|
140
|
+
return text.match(/(?:[^\s'"]+|'[^']*'|"[^"]*")+/g) || [];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function unquote(token) {
|
|
144
|
+
if (
|
|
145
|
+
(token.startsWith('"') && token.endsWith('"')) ||
|
|
146
|
+
(token.startsWith("'") && token.endsWith("'"))
|
|
147
|
+
) {
|
|
148
|
+
return token.slice(1, -1);
|
|
149
|
+
}
|
|
150
|
+
return token;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function shouldBlockExecString(command) {
|
|
154
|
+
const tokens = normalizeTokensForExec(tokenizeShellString(command).map(unquote));
|
|
155
|
+
if (tokens.length === 0) return false;
|
|
156
|
+
const shellInner = unwrapShellCommand(tokens[0], tokens.slice(1));
|
|
157
|
+
if (shellInner) return shouldBlockExecString(shellInner);
|
|
158
|
+
return shouldBlockCommand(tokens[0], tokens.slice(1));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function createBlockedError(blockedStderr) {
|
|
162
|
+
const error = new Error(BLOCK_MESSAGE);
|
|
163
|
+
error.code = 'CLAUDE_TERMUX_OFFICIAL_UPDATE_BLOCKED';
|
|
164
|
+
error.status = 1;
|
|
165
|
+
error.stdout = Buffer.from('');
|
|
166
|
+
error.stderr = Buffer.from(blockedStderr);
|
|
167
|
+
return error;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function createBlockedSpawn(blockedStderr, stderrWriter, options) {
|
|
171
|
+
const child = new EventEmitter();
|
|
172
|
+
child.stdout = null;
|
|
173
|
+
child.stderr = null;
|
|
174
|
+
child.stdin = null;
|
|
175
|
+
child.pid = 0;
|
|
176
|
+
child.killed = false;
|
|
177
|
+
child.kill = () => false;
|
|
178
|
+
process.nextTick(() => {
|
|
179
|
+
if (!(options && options.stdio === 'ignore')) {
|
|
180
|
+
stderrWriter(blockedStderr);
|
|
181
|
+
}
|
|
182
|
+
child.emit('close', 1, null);
|
|
183
|
+
child.emit('exit', 1, null);
|
|
184
|
+
});
|
|
185
|
+
return child;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function createBlockedSpawnSync(blockedStderr, stderrWriter) {
|
|
189
|
+
stderrWriter(blockedStderr);
|
|
190
|
+
return {
|
|
191
|
+
pid: 0,
|
|
192
|
+
output: [null, Buffer.from(''), Buffer.from(blockedStderr)],
|
|
193
|
+
stdout: Buffer.from(''),
|
|
194
|
+
stderr: Buffer.from(blockedStderr),
|
|
195
|
+
status: 1,
|
|
196
|
+
signal: null,
|
|
197
|
+
error: createBlockedError(blockedStderr),
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function createBlockedExecResult(blockedStderr, stderrWriter, callback) {
|
|
202
|
+
const child = createBlockedSpawn(blockedStderr, stderrWriter);
|
|
203
|
+
const error = createBlockedError(blockedStderr);
|
|
204
|
+
process.nextTick(() => {
|
|
205
|
+
if (typeof callback === 'function') {
|
|
206
|
+
callback(error, '', blockedStderr);
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
return child;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function shouldBlockSpawnArgs(args) {
|
|
213
|
+
if (!Array.isArray(args) || args.length === 0) return false;
|
|
214
|
+
const command = args[0];
|
|
215
|
+
const commandArgs = Array.isArray(args[1]) ? args[1] : [];
|
|
216
|
+
const shellInner = unwrapShellCommand(command, commandArgs);
|
|
217
|
+
if (shellInner) return shouldBlockExecString(shellInner);
|
|
218
|
+
return shouldBlockCommand(command, commandArgs);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function createGuardedChildProcess(realChild, stderrWriter = value => process.stderr.write(value)) {
|
|
222
|
+
const blockedStderr = `${BLOCK_MESSAGE}\n`;
|
|
223
|
+
return {
|
|
224
|
+
spawn: (...args) => {
|
|
225
|
+
if (shouldBlockSpawnArgs(args)) return createBlockedSpawn(blockedStderr, stderrWriter, args[2]);
|
|
226
|
+
return realChild.spawn(...args);
|
|
227
|
+
},
|
|
228
|
+
execFile: (...args) => {
|
|
229
|
+
if (shouldBlockSpawnArgs(args)) return createBlockedExecResult(blockedStderr, stderrWriter, args[args.length - 1]);
|
|
230
|
+
return realChild.execFile(...args);
|
|
231
|
+
},
|
|
232
|
+
exec: (...args) => {
|
|
233
|
+
if (shouldBlockExecString(args[0])) return createBlockedExecResult(blockedStderr, stderrWriter, args[args.length - 1]);
|
|
234
|
+
return realChild.exec(...args);
|
|
235
|
+
},
|
|
236
|
+
spawnSync: (...args) => {
|
|
237
|
+
if (shouldBlockSpawnArgs(args)) return createBlockedSpawnSync(blockedStderr, stderrWriter);
|
|
238
|
+
return realChild.spawnSync(...args);
|
|
239
|
+
},
|
|
240
|
+
execFileSync: (...args) => {
|
|
241
|
+
if (shouldBlockSpawnArgs(args)) throw createBlockedError(blockedStderr);
|
|
242
|
+
return realChild.execFileSync(...args);
|
|
243
|
+
},
|
|
244
|
+
execSync: (...args) => {
|
|
245
|
+
if (shouldBlockExecString(args[0])) throw createBlockedError(blockedStderr);
|
|
246
|
+
return realChild.execSync(...args);
|
|
247
|
+
},
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
module.exports = {
|
|
252
|
+
BLOCK_MESSAGE,
|
|
253
|
+
OFFICIAL_PACKAGE,
|
|
254
|
+
createGuardedChildProcess,
|
|
255
|
+
shouldBlockCommand,
|
|
256
|
+
shouldBlockExecString,
|
|
257
|
+
};
|