claude-code-termux 1.0.18 → 1.0.19
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/.github/workflows/auto-update.yml +123 -76
- package/bin/claude-termux.js +27 -21
- package/package.json +1 -1
- package/postinstall.js +20 -34
- package/scripts/verify-install.js +32 -40
- package/src/auto-update.js +3 -47
- package/src/patches/apply-all.js +38 -17
- package/src/patches/ripgrep-fallback.js +8 -6
- package/src/patches/tmpdir-config.js +47 -0
- package/src/ui.js +201 -0
|
@@ -1,113 +1,160 @@
|
|
|
1
|
-
name:
|
|
1
|
+
name: Sync & Publish
|
|
2
2
|
|
|
3
3
|
on:
|
|
4
4
|
schedule:
|
|
5
|
-
- cron: '0 6 * * *'
|
|
6
|
-
workflow_dispatch:
|
|
7
|
-
inputs:
|
|
8
|
-
force_publish:
|
|
9
|
-
description: 'Force publish (skip upstream check)'
|
|
10
|
-
required: false
|
|
11
|
-
default: false
|
|
12
|
-
type: boolean
|
|
13
|
-
version_bump:
|
|
14
|
-
description: 'Version bump type (only for force publish)'
|
|
15
|
-
required: false
|
|
16
|
-
default: 'patch'
|
|
17
|
-
type: choice
|
|
18
|
-
options:
|
|
19
|
-
- patch
|
|
20
|
-
- minor
|
|
21
|
-
- major
|
|
5
|
+
- cron: '0 6 * * *' # Daily at 6 AM UTC
|
|
6
|
+
workflow_dispatch: # Manual trigger — no inputs needed
|
|
22
7
|
|
|
23
8
|
permissions:
|
|
24
|
-
contents: write
|
|
25
|
-
id-token: write
|
|
9
|
+
contents: write
|
|
10
|
+
id-token: write
|
|
26
11
|
|
|
27
12
|
jobs:
|
|
28
|
-
|
|
13
|
+
sync-and-publish:
|
|
29
14
|
runs-on: ubuntu-latest
|
|
30
15
|
steps:
|
|
16
|
+
|
|
17
|
+
# ── Setup ──────────────────────────────────────────────
|
|
31
18
|
- uses: actions/checkout@v4
|
|
19
|
+
with:
|
|
20
|
+
fetch-depth: 0
|
|
21
|
+
fetch-tags: true
|
|
32
22
|
|
|
33
23
|
- uses: actions/setup-node@v4
|
|
34
24
|
with:
|
|
35
|
-
node-version:
|
|
36
|
-
registry-url:
|
|
25
|
+
node-version: 24
|
|
26
|
+
registry-url: https://registry.npmjs.org
|
|
37
27
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
28
|
+
# ── Detect changes ────────────────────────────────────
|
|
29
|
+
- name: Check for upstream and source changes
|
|
30
|
+
id: detect
|
|
41
31
|
run: |
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
echo "
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
echo "
|
|
32
|
+
set -e
|
|
33
|
+
|
|
34
|
+
# --- Upstream check ---
|
|
35
|
+
CURRENT_DEP=$(node -p "require('./package.json').dependencies['@anthropic-ai/claude-code'].replace('^','')")
|
|
36
|
+
LATEST_DEP=$(npm view @anthropic-ai/claude-code version)
|
|
37
|
+
echo "current_dep=$CURRENT_DEP" >> "$GITHUB_OUTPUT"
|
|
38
|
+
echo "latest_dep=$LATEST_DEP" >> "$GITHUB_OUTPUT"
|
|
39
|
+
|
|
40
|
+
UPSTREAM_CHANGED=false
|
|
41
|
+
if [ "$CURRENT_DEP" != "$LATEST_DEP" ]; then
|
|
42
|
+
UPSTREAM_CHANGED=true
|
|
43
|
+
echo "⬆️ Upstream: $CURRENT_DEP → $LATEST_DEP"
|
|
44
|
+
else
|
|
45
|
+
echo "✓ Upstream up to date ($CURRENT_DEP)"
|
|
46
|
+
fi
|
|
47
|
+
echo "upstream_changed=$UPSTREAM_CHANGED" >> "$GITHUB_OUTPUT"
|
|
48
|
+
|
|
49
|
+
# --- Source change check ---
|
|
50
|
+
REPO_VERSION=$(node -p "require('./package.json').version")
|
|
51
|
+
NPM_VERSION=$(npm view claude-code-termux version 2>/dev/null || echo "0.0.0")
|
|
52
|
+
echo "repo_version=$REPO_VERSION" >> "$GITHUB_OUTPUT"
|
|
53
|
+
echo "npm_version=$NPM_VERSION" >> "$GITHUB_OUTPUT"
|
|
54
|
+
|
|
55
|
+
SOURCE_CHANGED=false
|
|
56
|
+
if [ "$REPO_VERSION" != "$NPM_VERSION" ]; then
|
|
57
|
+
SOURCE_CHANGED=true
|
|
58
|
+
echo "📦 Source: repo v$REPO_VERSION != npm v$NPM_VERSION"
|
|
59
|
+
else
|
|
60
|
+
TAG="v$NPM_VERSION"
|
|
61
|
+
if git rev-parse "$TAG" >/dev/null 2>&1; then
|
|
62
|
+
COMMITS_SINCE=$(git log "$TAG"..HEAD --oneline -- . ':!package-lock.json' | wc -l | tr -d ' ')
|
|
63
|
+
if [ "$COMMITS_SINCE" -gt 0 ]; then
|
|
64
|
+
SOURCE_CHANGED=true
|
|
65
|
+
echo "📦 Source: $COMMITS_SINCE commit(s) since $TAG"
|
|
66
|
+
else
|
|
67
|
+
echo "✓ Source unchanged since $TAG"
|
|
68
|
+
fi
|
|
69
|
+
else
|
|
70
|
+
echo "✓ No tag $TAG found — using version comparison only"
|
|
71
|
+
fi
|
|
72
|
+
fi
|
|
73
|
+
echo "source_changed=$SOURCE_CHANGED" >> "$GITHUB_OUTPUT"
|
|
74
|
+
|
|
75
|
+
# --- Overall decision ---
|
|
76
|
+
if [ "$UPSTREAM_CHANGED" = "true" ] || [ "$SOURCE_CHANGED" = "true" ]; then
|
|
77
|
+
echo "needs_publish=true" >> "$GITHUB_OUTPUT"
|
|
54
78
|
else
|
|
55
|
-
echo "
|
|
56
|
-
echo "
|
|
79
|
+
echo "needs_publish=false" >> "$GITHUB_OUTPUT"
|
|
80
|
+
echo "✅ Everything is up to date. Nothing to publish."
|
|
57
81
|
fi
|
|
58
82
|
|
|
59
|
-
|
|
60
|
-
|
|
83
|
+
# ── Update upstream dependency ────────────────────────
|
|
84
|
+
- name: Update upstream dependency
|
|
85
|
+
if: steps.detect.outputs.upstream_changed == 'true'
|
|
61
86
|
run: |
|
|
62
|
-
npm pkg set "dependencies.@anthropic-ai/claude-code=^${{ steps.
|
|
63
|
-
npm version patch --no-git-tag-version
|
|
64
|
-
echo "Updated package.json:"
|
|
65
|
-
cat package.json | head -35
|
|
87
|
+
npm pkg set "dependencies.@anthropic-ai/claude-code=^${{ steps.detect.outputs.latest_dep }}"
|
|
66
88
|
|
|
67
|
-
|
|
68
|
-
|
|
89
|
+
# ── Bump version if needed ────────────────────────────
|
|
90
|
+
- name: Bump version
|
|
91
|
+
if: steps.detect.outputs.needs_publish == 'true'
|
|
92
|
+
id: version
|
|
69
93
|
run: |
|
|
70
|
-
|
|
94
|
+
REPO_VERSION=${{ steps.detect.outputs.repo_version }}
|
|
95
|
+
NPM_VERSION=${{ steps.detect.outputs.npm_version }}
|
|
96
|
+
|
|
97
|
+
if [ "$REPO_VERSION" = "$NPM_VERSION" ]; then
|
|
98
|
+
npm version patch --no-git-tag-version
|
|
99
|
+
echo "bumped=true" >> "$GITHUB_OUTPUT"
|
|
100
|
+
else
|
|
101
|
+
echo "bumped=false" >> "$GITHUB_OUTPUT"
|
|
102
|
+
fi
|
|
103
|
+
|
|
71
104
|
NEW_VERSION=$(node -p "require('./package.json').version")
|
|
72
|
-
echo "
|
|
105
|
+
echo "new_version=$NEW_VERSION" >> "$GITHUB_OUTPUT"
|
|
106
|
+
echo "📌 Will publish v$NEW_VERSION"
|
|
73
107
|
|
|
74
|
-
|
|
75
|
-
|
|
108
|
+
# ── Commit, tag, push ─────────────────────────────────
|
|
109
|
+
- name: Commit and push
|
|
110
|
+
if: steps.detect.outputs.needs_publish == 'true'
|
|
76
111
|
run: |
|
|
77
112
|
git config user.name "github-actions[bot]"
|
|
78
113
|
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
114
|
+
|
|
115
|
+
VERSION=${{ steps.version.outputs.new_version }}
|
|
116
|
+
UPSTREAM=${{ steps.detect.outputs.upstream_changed }}
|
|
117
|
+
SOURCE=${{ steps.detect.outputs.source_changed }}
|
|
118
|
+
|
|
119
|
+
MSG="Release v${VERSION}"
|
|
120
|
+
DETAILS=""
|
|
121
|
+
if [ "$UPSTREAM" = "true" ]; then
|
|
122
|
+
DETAILS="${DETAILS}\n- Bump @anthropic-ai/claude-code to ${{ steps.detect.outputs.latest_dep }}"
|
|
123
|
+
fi
|
|
124
|
+
if [ "$SOURCE" = "true" ]; then
|
|
125
|
+
DETAILS="${DETAILS}\n- Include source changes"
|
|
87
126
|
fi
|
|
88
|
-
git push
|
|
89
127
|
|
|
128
|
+
git add -A
|
|
129
|
+
git commit -m "$(printf "${MSG}\n${DETAILS}")" || echo "Nothing to commit"
|
|
130
|
+
git tag "v${VERSION}"
|
|
131
|
+
git push --follow-tags
|
|
132
|
+
|
|
133
|
+
# ── Publish ───────────────────────────────────────────
|
|
90
134
|
- name: Install dependencies
|
|
91
|
-
if:
|
|
135
|
+
if: steps.detect.outputs.needs_publish == 'true'
|
|
92
136
|
run: npm install
|
|
93
137
|
|
|
94
|
-
- name: Publish to npm
|
|
95
|
-
if:
|
|
138
|
+
- name: Publish to npm
|
|
139
|
+
if: steps.detect.outputs.needs_publish == 'true'
|
|
96
140
|
run: npm publish --access public
|
|
97
141
|
|
|
142
|
+
# ── Summary ───────────────────────────────────────────
|
|
98
143
|
- name: Summary
|
|
99
144
|
run: |
|
|
100
|
-
|
|
101
|
-
if [ "$
|
|
102
|
-
|
|
103
|
-
echo "" >> $GITHUB_STEP_SUMMARY
|
|
104
|
-
echo "
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
145
|
+
NEEDS=${{ steps.detect.outputs.needs_publish }}
|
|
146
|
+
if [ "$NEEDS" = "true" ]; then
|
|
147
|
+
VERSION=${{ steps.version.outputs.new_version }}
|
|
148
|
+
echo "### ✅ Published v${VERSION}" >> "$GITHUB_STEP_SUMMARY"
|
|
149
|
+
echo "" >> "$GITHUB_STEP_SUMMARY"
|
|
150
|
+
if [ "${{ steps.detect.outputs.upstream_changed }}" = "true" ]; then
|
|
151
|
+
echo "- Upstream: \`@anthropic-ai/claude-code\` → ${{ steps.detect.outputs.latest_dep }}" >> "$GITHUB_STEP_SUMMARY"
|
|
152
|
+
fi
|
|
153
|
+
if [ "${{ steps.detect.outputs.source_changed }}" = "true" ]; then
|
|
154
|
+
echo "- Source changes included" >> "$GITHUB_STEP_SUMMARY"
|
|
155
|
+
fi
|
|
109
156
|
else
|
|
110
|
-
echo "
|
|
111
|
-
echo "" >> $GITHUB_STEP_SUMMARY
|
|
112
|
-
echo "
|
|
157
|
+
echo "### ✅ No Update Needed" >> "$GITHUB_STEP_SUMMARY"
|
|
158
|
+
echo "Upstream: \`${{ steps.detect.outputs.current_dep }}\` (latest)" >> "$GITHUB_STEP_SUMMARY"
|
|
159
|
+
echo "Package: \`v${{ steps.detect.outputs.repo_version }}\` (published)" >> "$GITHUB_STEP_SUMMARY"
|
|
113
160
|
fi
|
package/bin/claude-termux.js
CHANGED
|
@@ -15,21 +15,21 @@
|
|
|
15
15
|
|
|
16
16
|
const path = require('path');
|
|
17
17
|
const fs = require('fs');
|
|
18
|
+
const ui = require('../src/ui');
|
|
18
19
|
|
|
19
|
-
//
|
|
20
|
-
const
|
|
21
|
-
process.env.PREFIX?.includes('com.termux') ||
|
|
22
|
-
process.env.HOME?.includes('com.termux');
|
|
20
|
+
// Read version from package.json
|
|
21
|
+
const pkg = require('../package.json');
|
|
23
22
|
|
|
24
|
-
//
|
|
25
|
-
|
|
26
|
-
console.log('[claude-code-termux] Detected Termux environment, applying patches...');
|
|
23
|
+
// Show the banner (always — both Termux and desktop)
|
|
24
|
+
ui.printBanner(pkg.version);
|
|
27
25
|
|
|
26
|
+
// Apply runtime boosts for Termux
|
|
27
|
+
let boostResult = null;
|
|
28
|
+
if (ui.isTermux) {
|
|
28
29
|
try {
|
|
29
|
-
|
|
30
|
-
require('../src/patches/apply-all');
|
|
30
|
+
boostResult = require('../src/patches/apply-all');
|
|
31
31
|
} catch (err) {
|
|
32
|
-
|
|
32
|
+
ui.logError(`Boost loading failed: ${err.message}`);
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
|
|
@@ -66,18 +66,22 @@ try {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
if (!claudeCodePath) {
|
|
69
|
-
|
|
69
|
+
ui.logError('Could not find @anthropic-ai/claude-code');
|
|
70
70
|
console.error('');
|
|
71
|
-
console.error('
|
|
72
|
-
|
|
71
|
+
console.error(' Install it with:');
|
|
72
|
+
ui.logDim('npm install -g @anthropic-ai/claude-code');
|
|
73
73
|
console.error('');
|
|
74
|
-
console.error('Searched paths:');
|
|
74
|
+
console.error(' Searched paths:');
|
|
75
75
|
possiblePaths.forEach(p => {
|
|
76
|
-
if (p)
|
|
76
|
+
if (p) ui.logDim(p);
|
|
77
77
|
});
|
|
78
78
|
process.exit(1);
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
// Show ready status
|
|
82
|
+
const hasWarnings = boostResult && boostResult.failedCount > 0;
|
|
83
|
+
ui.logReady(hasWarnings);
|
|
84
|
+
|
|
81
85
|
// Auto-update check (runs before loading Claude Code)
|
|
82
86
|
// This checks for updates and may restart the process if an update is found
|
|
83
87
|
const { checkAndUpdate } = require('../src/auto-update');
|
|
@@ -92,17 +96,19 @@ checkAndUpdate().then(updated => {
|
|
|
92
96
|
// Use dynamic import since cli.js is an ES module
|
|
93
97
|
return import(claudeCodePath);
|
|
94
98
|
}).catch(err => {
|
|
95
|
-
|
|
99
|
+
ui.logError(`Error loading Claude Code: ${err.message}`);
|
|
96
100
|
|
|
97
101
|
if (err.message.includes('sharp')) {
|
|
98
|
-
console.error('
|
|
99
|
-
|
|
100
|
-
|
|
102
|
+
console.error('');
|
|
103
|
+
ui.logWarn('Sharp module error detected. Try running:');
|
|
104
|
+
ui.logDim('npm install @img/sharp-wasm32 --force');
|
|
105
|
+
ui.logDim('npm install sharp --force');
|
|
101
106
|
}
|
|
102
107
|
|
|
103
108
|
if (err.message.includes('ENOENT') && err.message.includes('ripgrep')) {
|
|
104
|
-
console.error('
|
|
105
|
-
|
|
109
|
+
console.error('');
|
|
110
|
+
ui.logWarn('Ripgrep binary not found. Try running:');
|
|
111
|
+
ui.logDim('pkg install ripgrep');
|
|
106
112
|
}
|
|
107
113
|
|
|
108
114
|
process.exit(1);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-code-termux",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.19",
|
|
4
4
|
"description": "Claude Code CLI with Termux/Android compatibility fixes - a wrapper that patches issues with Sharp, ripgrep, and path resolution on ARM64 Android",
|
|
5
5
|
"author": "Jimoh Ovbiagele <findingjimoh@gmail.com>",
|
|
6
6
|
"license": "MIT",
|
package/postinstall.js
CHANGED
|
@@ -15,6 +15,10 @@
|
|
|
15
15
|
const fs = require('fs');
|
|
16
16
|
const path = require('path');
|
|
17
17
|
const { execSync } = require('child_process');
|
|
18
|
+
const ui = require('./src/ui');
|
|
19
|
+
|
|
20
|
+
// Read version
|
|
21
|
+
const pkg = require('./package.json');
|
|
18
22
|
|
|
19
23
|
// Detect Termux environment
|
|
20
24
|
const isTermux = process.platform === 'android' ||
|
|
@@ -23,9 +27,8 @@ const isTermux = process.platform === 'android' ||
|
|
|
23
27
|
|
|
24
28
|
const isARM64 = process.arch === 'arm64';
|
|
25
29
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
console.log(`[claude-code-termux] Termux detected: ${isTermux}`);
|
|
30
|
+
ui.printBanner(pkg.version);
|
|
31
|
+
ui.logArrow('Running post-install setup...');
|
|
29
32
|
|
|
30
33
|
/**
|
|
31
34
|
* Find the Claude Code installation directory
|
|
@@ -50,12 +53,9 @@ function findClaudeCodePath() {
|
|
|
50
53
|
*/
|
|
51
54
|
function setupRipgrep(claudeCodePath) {
|
|
52
55
|
if (!isTermux || !isARM64) {
|
|
53
|
-
console.log('[claude-code-termux] Skipping ripgrep setup (not Termux ARM64)');
|
|
54
56
|
return;
|
|
55
57
|
}
|
|
56
58
|
|
|
57
|
-
console.log('[claude-code-termux] Setting up ripgrep for ARM64 Android...');
|
|
58
|
-
|
|
59
59
|
const vendorDir = path.join(claudeCodePath, 'vendor', 'ripgrep', 'arm64-android');
|
|
60
60
|
|
|
61
61
|
// Create directory if it doesn't exist
|
|
@@ -68,10 +68,8 @@ function setupRipgrep(claudeCodePath) {
|
|
|
68
68
|
// Check if we have a bundled binary
|
|
69
69
|
const bundledBinary = path.join(__dirname, 'src', 'binaries', 'rg');
|
|
70
70
|
if (fs.existsSync(bundledBinary)) {
|
|
71
|
-
console.log('[claude-code-termux] Using bundled ripgrep binary');
|
|
72
71
|
fs.copyFileSync(bundledBinary, targetBinary);
|
|
73
72
|
fs.chmodSync(targetBinary, 0o755);
|
|
74
|
-
console.log('[claude-code-termux] Ripgrep binary installed successfully');
|
|
75
73
|
return;
|
|
76
74
|
}
|
|
77
75
|
|
|
@@ -79,13 +77,11 @@ function setupRipgrep(claudeCodePath) {
|
|
|
79
77
|
try {
|
|
80
78
|
const systemRg = execSync('which rg', { encoding: 'utf8' }).trim();
|
|
81
79
|
if (systemRg && fs.existsSync(systemRg)) {
|
|
82
|
-
console.log(`[claude-code-termux] Found system ripgrep at ${systemRg}`);
|
|
83
80
|
// Create symlink to system ripgrep
|
|
84
81
|
if (fs.existsSync(targetBinary)) {
|
|
85
82
|
fs.unlinkSync(targetBinary);
|
|
86
83
|
}
|
|
87
84
|
fs.symlinkSync(systemRg, targetBinary);
|
|
88
|
-
console.log('[claude-code-termux] Linked to system ripgrep');
|
|
89
85
|
return;
|
|
90
86
|
}
|
|
91
87
|
} catch (err) {
|
|
@@ -93,7 +89,6 @@ function setupRipgrep(claudeCodePath) {
|
|
|
93
89
|
}
|
|
94
90
|
|
|
95
91
|
// Try to download ripgrep
|
|
96
|
-
console.log('[claude-code-termux] Attempting to download ripgrep...');
|
|
97
92
|
try {
|
|
98
93
|
// Use curl to download (available in Termux)
|
|
99
94
|
const rgUrl = 'https://github.com/BurntSushi/ripgrep/releases/download/14.1.1/ripgrep-14.1.1-aarch64-unknown-linux-gnu.tar.gz';
|
|
@@ -114,15 +109,14 @@ function setupRipgrep(claudeCodePath) {
|
|
|
114
109
|
if (fs.existsSync(extractedBinary)) {
|
|
115
110
|
fs.copyFileSync(extractedBinary, targetBinary);
|
|
116
111
|
fs.chmodSync(targetBinary, 0o755);
|
|
117
|
-
console.log('[claude-code-termux] Ripgrep downloaded and installed successfully');
|
|
118
112
|
}
|
|
119
113
|
}
|
|
120
114
|
|
|
121
115
|
// Cleanup
|
|
122
116
|
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
123
117
|
} catch (err) {
|
|
124
|
-
|
|
125
|
-
|
|
118
|
+
ui.logError(`Failed to download ripgrep: ${err.message}`);
|
|
119
|
+
ui.logDim('Install ripgrep manually: pkg install ripgrep');
|
|
126
120
|
}
|
|
127
121
|
}
|
|
128
122
|
|
|
@@ -147,11 +141,10 @@ function copyLinuxBinariesAsAndroidFallback(claudeCodePath) {
|
|
|
147
141
|
const androidPath = path.join(vendorDir, tool, 'arm64-android');
|
|
148
142
|
|
|
149
143
|
if (fs.existsSync(linuxPath) && !fs.existsSync(androidPath)) {
|
|
150
|
-
console.log(`[claude-code-termux] Copying ${tool} linux-arm64 as android-arm64 fallback`);
|
|
151
144
|
try {
|
|
152
145
|
fs.cpSync(linuxPath, androidPath, { recursive: true });
|
|
153
146
|
} catch (err) {
|
|
154
|
-
|
|
147
|
+
ui.logError(`Failed to copy ${tool}: ${err.message}`);
|
|
155
148
|
}
|
|
156
149
|
}
|
|
157
150
|
}
|
|
@@ -166,7 +159,6 @@ function setupOAuthStorage() {
|
|
|
166
159
|
|
|
167
160
|
if (!fs.existsSync(claudeDir)) {
|
|
168
161
|
fs.mkdirSync(claudeDir, { recursive: true, mode: 0o700 });
|
|
169
|
-
console.log('[claude-code-termux] Created .claude directory');
|
|
170
162
|
}
|
|
171
163
|
}
|
|
172
164
|
|
|
@@ -179,14 +171,11 @@ function installSharpWasm() {
|
|
|
179
171
|
return;
|
|
180
172
|
}
|
|
181
173
|
|
|
182
|
-
console.log('[claude-code-termux] Installing Sharp WASM for image support...');
|
|
183
|
-
|
|
184
174
|
const nodeModulesDir = path.join(__dirname, 'node_modules', '@img');
|
|
185
175
|
const sharpWasmDir = path.join(nodeModulesDir, 'sharp-wasm32');
|
|
186
176
|
|
|
187
177
|
// Check if already installed
|
|
188
178
|
if (fs.existsSync(path.join(sharpWasmDir, 'package.json'))) {
|
|
189
|
-
console.log('[claude-code-termux] Sharp WASM already installed');
|
|
190
179
|
return;
|
|
191
180
|
}
|
|
192
181
|
|
|
@@ -200,7 +189,6 @@ function installSharpWasm() {
|
|
|
200
189
|
const tarballUrl = 'https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz';
|
|
201
190
|
const tmpTar = path.join(__dirname, 'sharp-wasm32.tgz');
|
|
202
191
|
|
|
203
|
-
console.log('[claude-code-termux] Downloading Sharp WASM from npm registry...');
|
|
204
192
|
execSync(`curl -sL "${tarballUrl}" -o "${tmpTar}"`, { stdio: 'inherit' });
|
|
205
193
|
|
|
206
194
|
// Extract to node_modules/@img/sharp-wasm32
|
|
@@ -218,12 +206,10 @@ function installSharpWasm() {
|
|
|
218
206
|
|
|
219
207
|
// Cleanup
|
|
220
208
|
fs.unlinkSync(tmpTar);
|
|
221
|
-
|
|
222
|
-
console.log('[claude-code-termux] Sharp WASM installed successfully');
|
|
223
209
|
} catch (err) {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
210
|
+
ui.logError(`Failed to install Sharp WASM: ${err.message}`);
|
|
211
|
+
ui.logDim('Image reading may not work. You can try manually:');
|
|
212
|
+
ui.logDim('curl -sL https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz | tar -xz -C node_modules/@img/ && mv node_modules/@img/package node_modules/@img/sharp-wasm32');
|
|
227
213
|
}
|
|
228
214
|
}
|
|
229
215
|
|
|
@@ -234,12 +220,10 @@ function main() {
|
|
|
234
220
|
const claudeCodePath = findClaudeCodePath();
|
|
235
221
|
|
|
236
222
|
if (!claudeCodePath) {
|
|
237
|
-
|
|
223
|
+
ui.logDim('Claude Code not found yet (will be installed next)');
|
|
238
224
|
return;
|
|
239
225
|
}
|
|
240
226
|
|
|
241
|
-
console.log(`[claude-code-termux] Found Claude Code at: ${claudeCodePath}`);
|
|
242
|
-
|
|
243
227
|
// Run setup tasks
|
|
244
228
|
if (isTermux) {
|
|
245
229
|
setupRipgrep(claudeCodePath);
|
|
@@ -248,13 +232,15 @@ function main() {
|
|
|
248
232
|
installSharpWasm();
|
|
249
233
|
}
|
|
250
234
|
|
|
251
|
-
|
|
235
|
+
ui.logStep('Setup complete!');
|
|
252
236
|
|
|
253
237
|
if (isTermux) {
|
|
254
|
-
console.log('
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
238
|
+
console.log('');
|
|
239
|
+
ui.logDim('Termux tips:');
|
|
240
|
+
ui.logDim(' If you encounter ripgrep errors, run: pkg install ripgrep');
|
|
241
|
+
ui.logDim(' Use API key auth: export ANTHROPIC_API_KEY=your-key');
|
|
242
|
+
console.log('');
|
|
243
|
+
ui.logStep('Run "claude" to start Claude Code!');
|
|
258
244
|
}
|
|
259
245
|
}
|
|
260
246
|
|
|
@@ -12,19 +12,13 @@
|
|
|
12
12
|
const fs = require('fs');
|
|
13
13
|
const path = require('path');
|
|
14
14
|
const { execSync } = require('child_process');
|
|
15
|
+
const { c, symbols, printBanner } = require('../src/ui');
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
const colors = {
|
|
18
|
-
reset: '\x1b[0m',
|
|
19
|
-
red: '\x1b[31m',
|
|
20
|
-
green: '\x1b[32m',
|
|
21
|
-
yellow: '\x1b[33m',
|
|
22
|
-
blue: '\x1b[34m',
|
|
23
|
-
};
|
|
17
|
+
const pkg = require('../package.json');
|
|
24
18
|
|
|
25
|
-
const PASS = `${
|
|
26
|
-
const FAIL = `${
|
|
27
|
-
const WARN = `${
|
|
19
|
+
const PASS = `${c.green}${symbols.check}${c.reset}`;
|
|
20
|
+
const FAIL = `${c.brightRed}${symbols.cross}${c.reset}`;
|
|
21
|
+
const WARN = `${c.yellow}${symbols.warn}${c.reset}`;
|
|
28
22
|
|
|
29
23
|
let passCount = 0;
|
|
30
24
|
let failCount = 0;
|
|
@@ -32,15 +26,15 @@ let warnCount = 0;
|
|
|
32
26
|
|
|
33
27
|
function check(name, condition, warning = false) {
|
|
34
28
|
if (condition) {
|
|
35
|
-
console.log(
|
|
29
|
+
console.log(` ${PASS} ${name}`);
|
|
36
30
|
passCount++;
|
|
37
31
|
return true;
|
|
38
32
|
} else if (warning) {
|
|
39
|
-
console.log(
|
|
33
|
+
console.log(` ${WARN} ${name}`);
|
|
40
34
|
warnCount++;
|
|
41
35
|
return false;
|
|
42
36
|
} else {
|
|
43
|
-
console.log(
|
|
37
|
+
console.log(` ${FAIL} ${name}`);
|
|
44
38
|
failCount++;
|
|
45
39
|
return false;
|
|
46
40
|
}
|
|
@@ -54,14 +48,12 @@ function runCommand(cmd) {
|
|
|
54
48
|
}
|
|
55
49
|
}
|
|
56
50
|
|
|
57
|
-
|
|
58
|
-
console.log(
|
|
59
|
-
console.log(' Claude Code Termux - Installation Verification');
|
|
60
|
-
console.log('='.repeat(50));
|
|
51
|
+
printBanner(pkg.version);
|
|
52
|
+
console.log(` ${c.bold}${c.cyan}Installation Verification${c.reset}`);
|
|
61
53
|
console.log('');
|
|
62
54
|
|
|
63
55
|
// Environment checks
|
|
64
|
-
console.log(
|
|
56
|
+
console.log(` ${c.bold}${c.cyan}Environment:${c.reset}`);
|
|
65
57
|
|
|
66
58
|
const isTermux = process.platform === 'android' ||
|
|
67
59
|
process.env.PREFIX?.includes('com.termux') ||
|
|
@@ -69,37 +61,37 @@ const isTermux = process.platform === 'android' ||
|
|
|
69
61
|
|
|
70
62
|
check('Termux environment detected', isTermux, true);
|
|
71
63
|
check('HOME directory exists', fs.existsSync(process.env.HOME || ''));
|
|
72
|
-
console.log(`
|
|
73
|
-
console.log(`
|
|
74
|
-
console.log(`
|
|
64
|
+
console.log(` ${c.dim}Platform: ${process.platform}${c.reset}`);
|
|
65
|
+
console.log(` ${c.dim}Architecture: ${process.arch}${c.reset}`);
|
|
66
|
+
console.log(` ${c.dim}HOME: ${process.env.HOME}${c.reset}`);
|
|
75
67
|
console.log('');
|
|
76
68
|
|
|
77
69
|
// Node.js checks
|
|
78
|
-
console.log(
|
|
70
|
+
console.log(` ${c.bold}${c.cyan}Node.js:${c.reset}`);
|
|
79
71
|
|
|
80
72
|
const nodeVersion = process.version;
|
|
81
73
|
const nodeMajor = parseInt(nodeVersion.slice(1).split('.')[0]);
|
|
82
74
|
|
|
83
75
|
check('Node.js version >= 18', nodeMajor >= 18);
|
|
84
76
|
check('Node.js version < 25', nodeMajor < 25, true);
|
|
85
|
-
console.log(`
|
|
77
|
+
console.log(` ${c.dim}Version: ${nodeVersion}${c.reset}`);
|
|
86
78
|
console.log('');
|
|
87
79
|
|
|
88
80
|
// Ripgrep checks
|
|
89
|
-
console.log(
|
|
81
|
+
console.log(` ${c.bold}${c.cyan}Ripgrep:${c.reset}`);
|
|
90
82
|
|
|
91
83
|
const rgPath = runCommand('which rg');
|
|
92
84
|
const rgVersion = runCommand('rg --version');
|
|
93
85
|
|
|
94
86
|
check('Ripgrep installed', !!rgPath);
|
|
95
87
|
if (rgPath) {
|
|
96
|
-
console.log(`
|
|
97
|
-
console.log(`
|
|
88
|
+
console.log(` ${c.dim}Path: ${rgPath}${c.reset}`);
|
|
89
|
+
console.log(` ${c.dim}Version: ${rgVersion?.split('\n')[0] || 'unknown'}${c.reset}`);
|
|
98
90
|
}
|
|
99
91
|
console.log('');
|
|
100
92
|
|
|
101
93
|
// Claude Code checks
|
|
102
|
-
console.log(
|
|
94
|
+
console.log(` ${c.bold}${c.cyan}Claude Code:${c.reset}`);
|
|
103
95
|
|
|
104
96
|
let claudeCodePath = null;
|
|
105
97
|
try {
|
|
@@ -112,9 +104,9 @@ check('Claude Code installed', !!claudeCodePath);
|
|
|
112
104
|
|
|
113
105
|
if (claudeCodePath) {
|
|
114
106
|
const pkgPath = path.dirname(claudeCodePath);
|
|
115
|
-
const
|
|
116
|
-
console.log(`
|
|
117
|
-
console.log(`
|
|
107
|
+
const ccPkg = require(claudeCodePath);
|
|
108
|
+
console.log(` ${c.dim}Version: ${ccPkg.version}${c.reset}`);
|
|
109
|
+
console.log(` ${c.dim}Path: ${pkgPath}${c.reset}`);
|
|
118
110
|
|
|
119
111
|
// Check vendor binaries
|
|
120
112
|
const vendorRg = path.join(pkgPath, 'vendor', 'ripgrep', 'arm64-android', 'rg');
|
|
@@ -126,7 +118,7 @@ if (claudeCodePath) {
|
|
|
126
118
|
console.log('');
|
|
127
119
|
|
|
128
120
|
// Sharp checks
|
|
129
|
-
console.log(
|
|
121
|
+
console.log(` ${c.bold}${c.cyan}Sharp (Image Support):${c.reset}`);
|
|
130
122
|
|
|
131
123
|
let sharpWasm = false;
|
|
132
124
|
let sharpNative = false;
|
|
@@ -150,7 +142,7 @@ check('Sharp native available', sharpNative, true);
|
|
|
150
142
|
console.log('');
|
|
151
143
|
|
|
152
144
|
// Configuration checks
|
|
153
|
-
console.log(
|
|
145
|
+
console.log(` ${c.bold}${c.cyan}Configuration:${c.reset}`);
|
|
154
146
|
|
|
155
147
|
const homeDir = process.env.HOME || '/data/data/com.termux/files/home';
|
|
156
148
|
const claudeDir = path.join(homeDir, '.claude');
|
|
@@ -163,26 +155,26 @@ const hasApiKey = !!process.env.ANTHROPIC_API_KEY;
|
|
|
163
155
|
check('ANTHROPIC_API_KEY set', hasApiKey, true);
|
|
164
156
|
|
|
165
157
|
if (!hasApiKey) {
|
|
166
|
-
console.log(`
|
|
158
|
+
console.log(` ${c.dim}Tip: export ANTHROPIC_API_KEY=your-key${c.reset}`);
|
|
167
159
|
}
|
|
168
160
|
console.log('');
|
|
169
161
|
|
|
170
162
|
// Summary
|
|
171
|
-
console.log(
|
|
172
|
-
console.log(
|
|
173
|
-
console.log(
|
|
163
|
+
console.log(` ${c.cyan}${symbols.boxH.repeat(40)}${c.reset}`);
|
|
164
|
+
console.log(` ${c.bold}Summary${c.reset}`);
|
|
165
|
+
console.log(` ${c.cyan}${symbols.boxH.repeat(40)}${c.reset}`);
|
|
174
166
|
console.log(` ${PASS} Passed: ${passCount}`);
|
|
175
167
|
console.log(` ${WARN} Warnings: ${warnCount}`);
|
|
176
168
|
console.log(` ${FAIL} Failed: ${failCount}`);
|
|
177
169
|
console.log('');
|
|
178
170
|
|
|
179
171
|
if (failCount > 0) {
|
|
180
|
-
console.log(
|
|
172
|
+
console.log(` ${c.brightRed}Some checks failed. Please review the errors above.${c.reset}`);
|
|
181
173
|
process.exit(1);
|
|
182
174
|
} else if (warnCount > 0) {
|
|
183
|
-
console.log(
|
|
175
|
+
console.log(` ${c.yellow}Installation OK with some warnings.${c.reset}`);
|
|
184
176
|
process.exit(0);
|
|
185
177
|
} else {
|
|
186
|
-
console.log(
|
|
178
|
+
console.log(` ${c.green}All checks passed! Claude Code is ready to use.${c.reset}`);
|
|
187
179
|
process.exit(0);
|
|
188
180
|
}
|
package/src/auto-update.js
CHANGED
|
@@ -4,10 +4,9 @@ const { execSync, spawn } = require('child_process');
|
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
const path = require('path');
|
|
6
6
|
const https = require('https');
|
|
7
|
+
const { logArrow, logStep } = require('./ui');
|
|
7
8
|
|
|
8
9
|
const PACKAGE_NAME = 'claude-code-termux';
|
|
9
|
-
const CACHE_FILE = path.join(process.env.HOME || '', '.claude', '.auto-update-cache');
|
|
10
|
-
const CACHE_DURATION_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
11
10
|
const FETCH_TIMEOUT_MS = 10000; // 10 seconds
|
|
12
11
|
|
|
13
12
|
/**
|
|
@@ -68,41 +67,6 @@ function fetchLatestVersion() {
|
|
|
68
67
|
});
|
|
69
68
|
}
|
|
70
69
|
|
|
71
|
-
/**
|
|
72
|
-
* Check if we should skip the update check based on cache
|
|
73
|
-
*/
|
|
74
|
-
function shouldSkipCheck() {
|
|
75
|
-
try {
|
|
76
|
-
if (!fs.existsSync(CACHE_FILE)) {
|
|
77
|
-
return false;
|
|
78
|
-
}
|
|
79
|
-
const cacheContent = fs.readFileSync(CACHE_FILE, 'utf8');
|
|
80
|
-
const cache = JSON.parse(cacheContent);
|
|
81
|
-
const lastCheck = new Date(cache.lastCheck).getTime();
|
|
82
|
-
const now = Date.now();
|
|
83
|
-
return (now - lastCheck) < CACHE_DURATION_MS;
|
|
84
|
-
} catch (err) {
|
|
85
|
-
return false;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Update the cache file with current timestamp
|
|
91
|
-
*/
|
|
92
|
-
function updateCache() {
|
|
93
|
-
try {
|
|
94
|
-
const cacheDir = path.dirname(CACHE_FILE);
|
|
95
|
-
if (!fs.existsSync(cacheDir)) {
|
|
96
|
-
fs.mkdirSync(cacheDir, { recursive: true });
|
|
97
|
-
}
|
|
98
|
-
fs.writeFileSync(CACHE_FILE, JSON.stringify({
|
|
99
|
-
lastCheck: new Date().toISOString()
|
|
100
|
-
}));
|
|
101
|
-
} catch (err) {
|
|
102
|
-
// Silently ignore cache write errors
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
70
|
/**
|
|
107
71
|
* Compare semantic versions
|
|
108
72
|
* Returns true if latestVersion > installedVersion
|
|
@@ -123,7 +87,7 @@ function isNewerVersion(installedVersion, latestVersion) {
|
|
|
123
87
|
*/
|
|
124
88
|
function runUpdate(latestVersion) {
|
|
125
89
|
return new Promise((resolve, reject) => {
|
|
126
|
-
|
|
90
|
+
logArrow(`Updating to v${latestVersion}...`);
|
|
127
91
|
|
|
128
92
|
try {
|
|
129
93
|
// Use npm install with --force to bypass platform checks for sharp-wasm32
|
|
@@ -142,7 +106,7 @@ function runUpdate(latestVersion) {
|
|
|
142
106
|
* Re-execute the current process with the same arguments
|
|
143
107
|
*/
|
|
144
108
|
function relaunchProcess() {
|
|
145
|
-
|
|
109
|
+
logStep('Update complete, restarting...');
|
|
146
110
|
|
|
147
111
|
// Filter out any auto-update related args to prevent infinite loops
|
|
148
112
|
const args = process.argv.slice(2).filter(arg => arg !== '--no-auto-update');
|
|
@@ -168,11 +132,6 @@ async function checkAndUpdate() {
|
|
|
168
132
|
return false;
|
|
169
133
|
}
|
|
170
134
|
|
|
171
|
-
// Check cache
|
|
172
|
-
if (shouldSkipCheck()) {
|
|
173
|
-
return false;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
135
|
try {
|
|
177
136
|
// Get installed version
|
|
178
137
|
const installedVersion = getInstalledVersion();
|
|
@@ -183,9 +142,6 @@ async function checkAndUpdate() {
|
|
|
183
142
|
// Fetch latest version from npm
|
|
184
143
|
const latestVersion = await fetchLatestVersion();
|
|
185
144
|
|
|
186
|
-
// Update cache regardless of whether update is needed
|
|
187
|
-
updateCache();
|
|
188
|
-
|
|
189
145
|
// Check if update is needed
|
|
190
146
|
if (!isNewerVersion(installedVersion, latestVersion)) {
|
|
191
147
|
return false;
|
package/src/patches/apply-all.js
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Apply All Termux
|
|
2
|
+
* Apply All Termux Boosts
|
|
3
3
|
*
|
|
4
|
-
* This module applies all necessary runtime
|
|
4
|
+
* This module applies all necessary runtime boosts for Termux/Android compatibility.
|
|
5
5
|
* It must be loaded before the main Claude Code CLI.
|
|
6
|
+
*
|
|
7
|
+
* Shows an animated progress bar during loading. Only surfaces failures.
|
|
6
8
|
*/
|
|
7
9
|
|
|
8
10
|
'use strict';
|
|
9
11
|
|
|
10
12
|
const path = require('path');
|
|
13
|
+
const { boostName, renderProgressBar, finishProgressBar, sleep, logWarn } = require('../ui');
|
|
11
14
|
|
|
12
|
-
//
|
|
13
|
-
const
|
|
15
|
+
// Boost modules to load
|
|
16
|
+
const boosts = [
|
|
14
17
|
'./suppress-migration-ui', // Must be first - sets env vars before CLI loads
|
|
18
|
+
'./tmpdir-config', // Must be early - /tmp is inaccessible on Termux
|
|
15
19
|
'./sharp-fallback',
|
|
16
20
|
'./ripgrep-fallback',
|
|
17
21
|
'./path-normalization',
|
|
@@ -19,28 +23,45 @@ const patches = [
|
|
|
19
23
|
'./hook-events',
|
|
20
24
|
];
|
|
21
25
|
|
|
22
|
-
|
|
23
|
-
|
|
26
|
+
const total = boosts.length;
|
|
24
27
|
let appliedCount = 0;
|
|
25
28
|
let failedCount = 0;
|
|
29
|
+
const failures = [];
|
|
26
30
|
|
|
27
|
-
|
|
31
|
+
// Show initial empty progress bar
|
|
32
|
+
renderProgressBar(0, total);
|
|
33
|
+
sleep(60);
|
|
34
|
+
|
|
35
|
+
for (let i = 0; i < boosts.length; i++) {
|
|
36
|
+
const boostPath = boosts[i];
|
|
28
37
|
try {
|
|
29
|
-
const
|
|
30
|
-
if (typeof
|
|
31
|
-
|
|
38
|
+
const boost = require(boostPath);
|
|
39
|
+
if (typeof boost.apply === 'function') {
|
|
40
|
+
boost.apply();
|
|
32
41
|
appliedCount++;
|
|
33
|
-
console.log(`[claude-code-termux] Applied: ${path.basename(patchPath)}`);
|
|
34
42
|
}
|
|
35
43
|
} catch (err) {
|
|
36
44
|
failedCount++;
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
45
|
+
failures.push({
|
|
46
|
+
name: boostName(boostPath),
|
|
47
|
+
error: err.message,
|
|
48
|
+
});
|
|
41
49
|
}
|
|
50
|
+
|
|
51
|
+
// Update progress bar
|
|
52
|
+
renderProgressBar(i + 1, total);
|
|
53
|
+
sleep(60);
|
|
42
54
|
}
|
|
43
55
|
|
|
44
|
-
|
|
56
|
+
// Move past the progress bar line
|
|
57
|
+
finishProgressBar();
|
|
58
|
+
|
|
59
|
+
// Surface failures only
|
|
60
|
+
for (const f of failures) {
|
|
61
|
+
logWarn(`${f.name} failed`);
|
|
62
|
+
if (process.env.DEBUG) {
|
|
63
|
+
logWarn(` ${f.error}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
45
66
|
|
|
46
|
-
module.exports = { appliedCount, failedCount };
|
|
67
|
+
module.exports = { appliedCount, failedCount, failures };
|
|
@@ -55,10 +55,12 @@ function apply() {
|
|
|
55
55
|
// Find system ripgrep
|
|
56
56
|
systemRipgrepPath = findSystemRipgrep();
|
|
57
57
|
|
|
58
|
-
if (
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
58
|
+
if (process.env.DEBUG) {
|
|
59
|
+
if (systemRipgrepPath) {
|
|
60
|
+
console.log(`[claude-code-termux] Found system ripgrep: ${systemRipgrepPath}`);
|
|
61
|
+
} else {
|
|
62
|
+
console.warn('[claude-code-termux] System ripgrep not found. Install with: pkg install ripgrep');
|
|
63
|
+
}
|
|
62
64
|
}
|
|
63
65
|
|
|
64
66
|
// Monkey-patch spawn to intercept ripgrep calls
|
|
@@ -70,7 +72,7 @@ function apply() {
|
|
|
70
72
|
if (typeof command === 'string' && command.includes('ripgrep') && command.includes('arm64-android')) {
|
|
71
73
|
// Replace with system ripgrep if available
|
|
72
74
|
if (systemRipgrepPath) {
|
|
73
|
-
console.log('[claude-code-termux] Redirecting ripgrep call to system binary');
|
|
75
|
+
if (process.env.DEBUG) console.log('[claude-code-termux] Redirecting ripgrep call to system binary');
|
|
74
76
|
return originalSpawn(systemRipgrepPath, args, options);
|
|
75
77
|
}
|
|
76
78
|
}
|
|
@@ -78,7 +80,7 @@ function apply() {
|
|
|
78
80
|
// Also intercept direct 'rg' calls with invalid paths
|
|
79
81
|
if (typeof command === 'string' && command.endsWith('/rg') && !fs.existsSync(command)) {
|
|
80
82
|
if (systemRipgrepPath) {
|
|
81
|
-
console.log('[claude-code-termux] Redirecting invalid rg path to system binary');
|
|
83
|
+
if (process.env.DEBUG) console.log('[claude-code-termux] Redirecting invalid rg path to system binary');
|
|
82
84
|
return originalSpawn(systemRipgrepPath, args, options);
|
|
83
85
|
}
|
|
84
86
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TMPDIR Configuration Patch
|
|
3
|
+
*
|
|
4
|
+
* Termux doesn't expose /tmp (permission denied). Claude Code's sandbox
|
|
5
|
+
* and MCP servers create temp directories under /tmp, which fails on Termux.
|
|
6
|
+
* This patch sets TMPDIR to the Termux-accessible temp directory so that
|
|
7
|
+
* os.tmpdir() and all child processes use the correct path.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
'use strict';
|
|
11
|
+
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
|
|
14
|
+
const isTermux = process.platform === 'android' ||
|
|
15
|
+
process.env.PREFIX?.includes('com.termux') ||
|
|
16
|
+
process.env.HOME?.includes('com.termux');
|
|
17
|
+
|
|
18
|
+
const TERMUX_TMP = '/data/data/com.termux/files/usr/tmp';
|
|
19
|
+
|
|
20
|
+
function apply() {
|
|
21
|
+
if (!isTermux) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Don't override if the user has already set TMPDIR
|
|
26
|
+
if (process.env.TMPDIR) {
|
|
27
|
+
if (process.env.DEBUG) {
|
|
28
|
+
console.log('[claude-code-termux] TMPDIR already set:', process.env.TMPDIR);
|
|
29
|
+
}
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Ensure the directory exists
|
|
34
|
+
if (!fs.existsSync(TERMUX_TMP)) {
|
|
35
|
+
fs.mkdirSync(TERMUX_TMP, { recursive: true });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
process.env.TMPDIR = TERMUX_TMP;
|
|
39
|
+
process.env.TMP = TERMUX_TMP;
|
|
40
|
+
process.env.TEMP = TERMUX_TMP;
|
|
41
|
+
|
|
42
|
+
if (process.env.DEBUG) {
|
|
43
|
+
console.log('[claude-code-termux] TMPDIR set to:', TERMUX_TMP);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
module.exports = { apply };
|
package/src/ui.js
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code Termux - UI Output Utilities
|
|
3
|
+
*
|
|
4
|
+
* Shared module for all user-facing terminal output.
|
|
5
|
+
* Provides colors, symbols, banner, progress bar, and logging helpers.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
const path = require('path');
|
|
11
|
+
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Color support detection
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
function supportsColor() {
|
|
17
|
+
if (process.env.NO_COLOR !== undefined) return false;
|
|
18
|
+
if (process.env.FORCE_COLOR !== undefined) return true;
|
|
19
|
+
if (!process.stderr.isTTY) return false;
|
|
20
|
+
const term = process.env.TERM || '';
|
|
21
|
+
if (term === 'dumb') return false;
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const useColor = supportsColor();
|
|
26
|
+
|
|
27
|
+
function esc(code) {
|
|
28
|
+
return useColor ? `\x1b[${code}m` : '';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
// Color palette
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
|
|
35
|
+
const c = {
|
|
36
|
+
reset: esc('0'),
|
|
37
|
+
bold: esc('1'),
|
|
38
|
+
dim: esc('2'),
|
|
39
|
+
red: esc('31'),
|
|
40
|
+
green: esc('32'),
|
|
41
|
+
yellow: esc('33'),
|
|
42
|
+
blue: esc('34'),
|
|
43
|
+
magenta: esc('35'),
|
|
44
|
+
cyan: esc('36'),
|
|
45
|
+
white: esc('37'),
|
|
46
|
+
brightRed: esc('91'),
|
|
47
|
+
brightCyan: esc('96'),
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
// Symbols
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
|
|
54
|
+
const symbols = {
|
|
55
|
+
check: '\u2714', // ✔
|
|
56
|
+
cross: '\u2716', // ✖
|
|
57
|
+
arrow: '\u2192', // →
|
|
58
|
+
bolt: '\u26A1', // ⚡
|
|
59
|
+
rocket: '\uD83D\uDE80', // 🚀
|
|
60
|
+
warn: '!',
|
|
61
|
+
boxTL: '\u256D', // ╭
|
|
62
|
+
boxTR: '\u256E', // ╮
|
|
63
|
+
boxBL: '\u2570', // ╰
|
|
64
|
+
boxBR: '\u256F', // ╯
|
|
65
|
+
boxH: '\u2500', // ─
|
|
66
|
+
boxV: '\u2502', // │
|
|
67
|
+
blockFull: '\u2588', // █
|
|
68
|
+
blockEmpty: '\u2591', // ░
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
// Termux detection (shared helper)
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
|
|
75
|
+
const isTermux = process.platform === 'android' ||
|
|
76
|
+
(process.env.PREFIX || '').includes('com.termux') ||
|
|
77
|
+
(process.env.HOME || '').includes('com.termux');
|
|
78
|
+
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
// Banner
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
|
|
83
|
+
function printBanner(version) {
|
|
84
|
+
const tag = isTermux ? 'Termux Edition' : 'Desktop';
|
|
85
|
+
const inner = ` Claude Code ${c.dim}\u00B7${c.reset}${c.brightCyan} ${tag} ${c.dim}v${version}${c.reset}`;
|
|
86
|
+
const rawInner = ` Claude Code \u00B7 ${tag} v${version}`;
|
|
87
|
+
const width = rawInner.length + 2;
|
|
88
|
+
|
|
89
|
+
const top = ` ${c.cyan}${symbols.boxTL}${symbols.boxH.repeat(width)}${symbols.boxTR}${c.reset}`;
|
|
90
|
+
const mid = ` ${c.cyan}${symbols.boxV}${c.reset}${inner} ${c.cyan}${symbols.boxV}${c.reset}`;
|
|
91
|
+
const bot = ` ${c.cyan}${symbols.boxBL}${symbols.boxH.repeat(width)}${symbols.boxBR}${c.reset}`;
|
|
92
|
+
|
|
93
|
+
console.log('');
|
|
94
|
+
console.log(top);
|
|
95
|
+
console.log(mid);
|
|
96
|
+
console.log(bot);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
// Boost (patch) name map
|
|
101
|
+
// ---------------------------------------------------------------------------
|
|
102
|
+
|
|
103
|
+
const BOOST_NAMES = {
|
|
104
|
+
'suppress-migration-ui': 'Suppress migration prompts',
|
|
105
|
+
'tmpdir-config': 'Temp directory (Termux)',
|
|
106
|
+
'sharp-fallback': 'Image support (Sharp WASM)',
|
|
107
|
+
'ripgrep-fallback': 'Code search (ripgrep)',
|
|
108
|
+
'path-normalization': 'Termux path resolution',
|
|
109
|
+
'oauth-storage': 'Token storage fallback',
|
|
110
|
+
'hook-events': 'Event hook compatibility',
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
function boostName(patchPath) {
|
|
114
|
+
const base = path.basename(patchPath);
|
|
115
|
+
return BOOST_NAMES[base] || base;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
// Progress bar
|
|
120
|
+
// ---------------------------------------------------------------------------
|
|
121
|
+
|
|
122
|
+
const BAR_WIDTH = 10;
|
|
123
|
+
|
|
124
|
+
function renderProgressBar(current, total) {
|
|
125
|
+
const filled = Math.round((current / total) * BAR_WIDTH);
|
|
126
|
+
const empty = BAR_WIDTH - filled;
|
|
127
|
+
const bar = symbols.blockFull.repeat(filled) + symbols.blockEmpty.repeat(empty);
|
|
128
|
+
const line = ` ${c.cyan}${symbols.bolt}${c.reset} Powering up... ${c.cyan}[${bar}]${c.reset} ${current}/${total} boosts`;
|
|
129
|
+
process.stdout.write(`\r${line}`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function finishProgressBar() {
|
|
133
|
+
process.stdout.write('\n');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ---------------------------------------------------------------------------
|
|
137
|
+
// Synchronous sleep (for animation timing)
|
|
138
|
+
// ---------------------------------------------------------------------------
|
|
139
|
+
|
|
140
|
+
function sleep(ms) {
|
|
141
|
+
const end = Date.now() + ms;
|
|
142
|
+
while (Date.now() < end) {
|
|
143
|
+
// busy-wait — keeps total under 500ms for 6 boosts
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ---------------------------------------------------------------------------
|
|
148
|
+
// Logging helpers
|
|
149
|
+
// ---------------------------------------------------------------------------
|
|
150
|
+
|
|
151
|
+
function logStep(message) {
|
|
152
|
+
console.log(` ${c.green}${symbols.check}${c.reset} ${message}`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function logArrow(message) {
|
|
156
|
+
console.log(` ${c.cyan}${symbols.arrow}${c.reset} ${message}`);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function logWarn(message) {
|
|
160
|
+
console.log(` ${c.yellow}${symbols.warn}${c.reset} ${message}`);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function logError(message) {
|
|
164
|
+
console.error(` ${c.brightRed}${symbols.cross}${c.reset} ${message}`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function logDim(message) {
|
|
168
|
+
console.log(` ${c.dim}${message}${c.reset}`);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function logReady(hasWarnings) {
|
|
172
|
+
if (hasWarnings) {
|
|
173
|
+
console.log(` ${symbols.rocket} ${c.yellow}Ready with warnings${c.reset}`);
|
|
174
|
+
} else {
|
|
175
|
+
console.log(` ${symbols.rocket} ${c.green}Ready to go!${c.reset}`);
|
|
176
|
+
}
|
|
177
|
+
console.log('');
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// ---------------------------------------------------------------------------
|
|
181
|
+
// Exports
|
|
182
|
+
// ---------------------------------------------------------------------------
|
|
183
|
+
|
|
184
|
+
module.exports = {
|
|
185
|
+
c,
|
|
186
|
+
symbols,
|
|
187
|
+
useColor,
|
|
188
|
+
isTermux,
|
|
189
|
+
printBanner,
|
|
190
|
+
BOOST_NAMES,
|
|
191
|
+
boostName,
|
|
192
|
+
renderProgressBar,
|
|
193
|
+
finishProgressBar,
|
|
194
|
+
sleep,
|
|
195
|
+
logStep,
|
|
196
|
+
logArrow,
|
|
197
|
+
logWarn,
|
|
198
|
+
logError,
|
|
199
|
+
logDim,
|
|
200
|
+
logReady,
|
|
201
|
+
};
|