@nemoobc/opencode-termux 1.18.21 → 1.18.23
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/bin/opencode.js +34 -5
- package/install.mjs +40 -25
- package/package.json +16 -4
package/bin/opencode.js
CHANGED
|
@@ -5,13 +5,42 @@ const path = require("path")
|
|
|
5
5
|
const vendor = path.join(__dirname, "..", "vendor")
|
|
6
6
|
const loader = path.join(vendor, "ld-musl.so")
|
|
7
7
|
const bin = path.join(vendor, "opencode")
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
process.exit(1)
|
|
8
|
+
|
|
9
|
+
function ready() {
|
|
10
|
+
return fs.existsSync(loader) && fs.existsSync(bin)
|
|
12
11
|
}
|
|
12
|
+
|
|
13
|
+
// Auto-heal: kalau postinstall terlewat (mis. --ignore-scripts), pasang sekarang.
|
|
14
|
+
if (!ready()) {
|
|
15
|
+
console.log("[opencode-termux] vendor belum ada — menjalankan installer…")
|
|
16
|
+
const r = spawnSync(process.execPath, [path.join(__dirname, "..", "install.mjs")], {
|
|
17
|
+
stdio: "inherit",
|
|
18
|
+
env: process.env,
|
|
19
|
+
})
|
|
20
|
+
if (r.status !== 0 || !ready()) {
|
|
21
|
+
console.error("[opencode-termux] instalasi bundle gagal. Coba manual:")
|
|
22
|
+
console.error(" npm rebuild @nemoobc/opencode-termux")
|
|
23
|
+
process.exit(1)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (!ready()) process.exit(1)
|
|
28
|
+
|
|
29
|
+
// Diagnosa awal: Android sering tidak punya /etc/resolv.conf yang dibaca musl.
|
|
30
|
+
try {
|
|
31
|
+
if (!fs.existsSync("/etc/resolv.conf")) {
|
|
32
|
+
console.error("[opencode-termux] ⚠️ /etc/resolv.conf tidak ada di device ini;")
|
|
33
|
+
console.error(" kalau perintah jaringan (models/login) gagal/gantung, itu sebabnya.")
|
|
34
|
+
console.error(" Solusi sementara (butuh akses root):")
|
|
35
|
+
console.error(' su -c \'mkdir -p /etc && echo "nameserver 8.8.8.8" > /etc/resolv.conf\'')
|
|
36
|
+
}
|
|
37
|
+
} catch {}
|
|
38
|
+
|
|
39
|
+
// LD_PRELOAD bawaan Termux (libtermux-exec) dibuat untuk Bionic dan akan
|
|
40
|
+
// gagal relokasi jika ikut dimuat ke proses musl — jadi selalu dibersihkan.
|
|
41
|
+
const { LD_PRELOAD, LD_PRELOAD_32BIT, ...cleanEnv } = process.env
|
|
13
42
|
const r = spawnSync(loader, [bin, ...process.argv.slice(2)], {
|
|
14
43
|
stdio: "inherit",
|
|
15
|
-
env: { ...
|
|
44
|
+
env: { ...cleanEnv, LD_LIBRARY_PATH: vendor },
|
|
16
45
|
})
|
|
17
46
|
process.exit(r.status ?? 1)
|
package/install.mjs
CHANGED
|
@@ -1,40 +1,54 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* postinstall untuk @nemoobc/opencode-termux
|
|
4
|
-
* Menyusun bundle native: loader musl + libgcc/libstdc++ + binary opencode
|
|
4
|
+
* Menyusun bundle native: loader musl + libgcc/libstdc++ + binary opencode.
|
|
5
|
+
* Tanpa dependensi curl — unduhan memakai fetch bawaan Node >=18.
|
|
5
6
|
* Target: Termux (android/arm64). Override uji: OCX_ARCH=x64 OCX_FORCE=1
|
|
6
7
|
*/
|
|
7
8
|
import fs from "fs"
|
|
8
9
|
import path from "path"
|
|
9
10
|
import { execFileSync } from "child_process"
|
|
11
|
+
import { Readable } from "stream"
|
|
12
|
+
import { pipeline } from "stream/promises"
|
|
10
13
|
import { fileURLToPath } from "url"
|
|
11
14
|
|
|
12
15
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
13
|
-
const
|
|
16
|
+
const pkgJson = JSON.parse(fs.readFileSync(path.join(__dirname, "package.json"), "utf8"))
|
|
14
17
|
const ARCH = process.env.OCX_ARCH || "arm64"
|
|
15
18
|
const FORCE = !!process.env.OCX_FORCE
|
|
16
19
|
const IS_ANDROID = process.platform === "android"
|
|
17
|
-
|
|
20
|
+
|
|
21
|
+
// Versi upstream: env > package.json > otomatis ambil terbaru dari registry
|
|
22
|
+
let V = process.env.OCX_UPSTREAM || pkgJson.opencodeUpstream
|
|
23
|
+
if (!V) {
|
|
24
|
+
const latest = await (await fetch("https://registry.npmjs.org/opencode-ai/latest")).json()
|
|
25
|
+
V = latest.version
|
|
26
|
+
console.log(`[opencode-termux] upstream opencode-ai terbaru: ${V}`)
|
|
27
|
+
}
|
|
28
|
+
const GLOBAL_HINT =
|
|
29
|
+
process.platform === "win32" ? "" : ""
|
|
18
30
|
|
|
19
31
|
if (!IS_ANDROID && !FORCE) {
|
|
20
32
|
console.log("[opencode-termux] Bukan Termux/Android — instalasi dilewati (pakai opencode-ai resmi).")
|
|
21
33
|
process.exit(0)
|
|
22
34
|
}
|
|
23
|
-
|
|
24
|
-
console.log(`[opencode-termux] arsitektur ${process.arch} belum didukung.`)
|
|
25
|
-
process.exit(0)
|
|
26
|
-
}
|
|
35
|
+
|
|
27
36
|
const A = ARCH === "x64" ? "x86_64" : "aarch64"
|
|
28
37
|
|
|
29
|
-
|
|
38
|
+
async function dl(url, dest) {
|
|
30
39
|
console.log(`[opencode-termux] download ${url.split("/").pop()}`)
|
|
31
|
-
|
|
40
|
+
const res = await fetch(url)
|
|
41
|
+
if (!res.ok) throw new Error(`HTTP ${res.status} — ${url}`)
|
|
42
|
+
await pipeline(Readable.fromWeb(res.body), fs.createWriteStream(dest))
|
|
32
43
|
}
|
|
33
44
|
const untar = (tgz, dest, members = []) => {
|
|
34
45
|
fs.mkdirSync(dest, { recursive: true })
|
|
35
|
-
const
|
|
36
|
-
try {
|
|
37
|
-
catch {
|
|
46
|
+
const run = m => execFileSync("tar", ["xzf", tgz, "-C", dest, ...m], { stdio: ["ignore", "ignore", "pipe"] })
|
|
47
|
+
try { run(members.map(x => "./" + x)) }
|
|
48
|
+
catch { try { run(members) } catch (e) {
|
|
49
|
+
console.error("[opencode-termux] 'tar' tidak ditemukan. Jalankan: pkg install tar")
|
|
50
|
+
throw e
|
|
51
|
+
} }
|
|
38
52
|
}
|
|
39
53
|
|
|
40
54
|
const work = path.join(__dirname, ".build")
|
|
@@ -42,37 +56,38 @@ fs.rmSync(work, { recursive: true, force: true })
|
|
|
42
56
|
fs.mkdirSync(work, { recursive: true })
|
|
43
57
|
|
|
44
58
|
// 1) binary opencode (musl) dari npm resmi
|
|
45
|
-
dl(`https://registry.npmjs.org/opencode-linux-${ARCH}-musl/-/opencode-linux-${ARCH}-musl-${V}.tgz`, `${work}/oc.tgz`)
|
|
59
|
+
await dl(`https://registry.npmjs.org/opencode-linux-${ARCH}-musl/-/opencode-linux-${ARCH}-musl-${V}.tgz`, `${work}/oc.tgz`)
|
|
46
60
|
untar(`${work}/oc.tgz`, `${work}/oc`)
|
|
47
61
|
|
|
48
62
|
// 2) loader+libc musl dari Alpine minirootfs
|
|
49
63
|
const AV = "v3.21", AL = "3.21.3"
|
|
50
|
-
dl(`https://dl-cdn.alpinelinux.org/alpine/${AV}/releases/${A}/alpine-minirootfs-${AL}-${A}.tar.gz`, `${work}/ap.tgz`)
|
|
51
|
-
untar(`${work}/ap.tgz`, `${work}/ap`, ["
|
|
64
|
+
await dl(`https://dl-cdn.alpinelinux.org/alpine/${AV}/releases/${A}/alpine-minirootfs-${AL}-${A}.tar.gz`, `${work}/ap.tgz`)
|
|
65
|
+
untar(`${work}/ap.tgz`, `${work}/ap`, ["lib"])
|
|
52
66
|
|
|
53
67
|
// 3) libgcc + libstdc++
|
|
54
68
|
const apkDir = `${work}/apk`; fs.mkdirSync(apkDir, { recursive: true })
|
|
55
69
|
for (const f of [`libgcc-14.2.0-r4.apk`, `libstdc%2B%2B-14.2.0-r4.apk`]) {
|
|
56
|
-
dl(`https://dl-cdn.alpinelinux.org/alpine/${AV}/main/${A}/${f}`, `${apkDir}/${f}`)
|
|
70
|
+
await dl(`https://dl-cdn.alpinelinux.org/alpine/${AV}/main/${A}/${f}`, `${apkDir}/${f}`)
|
|
57
71
|
untar(`${apkDir}/${f}`, apkDir)
|
|
58
72
|
}
|
|
59
73
|
|
|
60
74
|
// 4) rakit vendor/
|
|
61
75
|
const vendor = path.join(__dirname, "vendor")
|
|
62
76
|
fs.rmSync(vendor, { recursive: true, force: true }); fs.mkdirSync(vendor)
|
|
63
|
-
const cp = (from, name) => fs.copyFileSync(from, path.join(vendor, name))
|
|
64
|
-
cp(`${work}/ap/lib
|
|
65
|
-
cp(`${work}/oc/package/bin
|
|
66
|
-
|
|
67
|
-
if (/^libstdc\+\+\.so|^libgcc_s\.so/.test(f)) cp(`${apkDir}/usr/lib/${f}`, f)
|
|
68
|
-
}
|
|
77
|
+
const cp = (from, name) => fs.copyFileSync(path.join(from, name), path.join(vendor, name))
|
|
78
|
+
cp(`${work}/ap/lib`, `ld-musl-${A}.so.1`); fs.renameSync(path.join(vendor, `ld-musl-${A}.so.1`), path.join(vendor, "ld-musl.so"))
|
|
79
|
+
cp(`${work}/oc/package/bin`, "opencode")
|
|
80
|
+
cp(`${apkDir}/usr/lib`, "libstdc++.so.6"); cp(`${apkDir}/usr/lib`, "libstdc++.so.6.0.33"); cp(`${apkDir}/usr/lib`, "libgcc_s.so.1")
|
|
69
81
|
for (const f of fs.readdirSync(vendor)) fs.chmodSync(path.join(vendor, f), 0o755)
|
|
70
82
|
|
|
71
|
-
// 5) smoke test
|
|
83
|
+
// 5) smoke test (LD_PRELOAD termux-exec dibuang: tidak kompatibel dengan musl)
|
|
72
84
|
console.log("[opencode-termux] smoke test…")
|
|
85
|
+
const { LD_PRELOAD, LD_PRELOAD_32BIT, ...cleanEnv } = process.env
|
|
73
86
|
execFileSync(path.join(vendor, "ld-musl.so"),
|
|
74
87
|
[path.join(vendor, "opencode"), "--version"],
|
|
75
|
-
{ stdio: "inherit", env: { ...
|
|
88
|
+
{ stdio: "inherit", env: { ...cleanEnv, LD_LIBRARY_PATH: vendor } })
|
|
76
89
|
|
|
77
90
|
fs.rmSync(work, { recursive: true, force: true })
|
|
78
|
-
console.log(`[opencode-termux] ✅ siap!
|
|
91
|
+
console.log(`[opencode-termux] ✅ siap!
|
|
92
|
+
• global : jalankan 'opencode-termux'
|
|
93
|
+
• lokal : 'npx opencode-termux' dari folder project ini`)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nemoobc/opencode-termux",
|
|
3
|
-
"version": "1.18.
|
|
3
|
+
"version": "1.18.23",
|
|
4
4
|
"description": "opencode CLI native untuk Termux/Android tanpa proot — membundel loader musl + binary opencode resmi (upstream opencode-ai)",
|
|
5
5
|
"bin": {
|
|
6
6
|
"opencode-termux": "./bin/opencode.js"
|
|
@@ -8,8 +8,20 @@
|
|
|
8
8
|
"scripts": {
|
|
9
9
|
"postinstall": "node install.mjs"
|
|
10
10
|
},
|
|
11
|
-
"keywords": [
|
|
11
|
+
"keywords": [
|
|
12
|
+
"opencode",
|
|
13
|
+
"termux",
|
|
14
|
+
"android",
|
|
15
|
+
"cli",
|
|
16
|
+
"ai"
|
|
17
|
+
],
|
|
12
18
|
"license": "MIT",
|
|
13
|
-
"engines": {
|
|
14
|
-
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=18"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"bin",
|
|
24
|
+
"install.mjs",
|
|
25
|
+
"README.md"
|
|
26
|
+
]
|
|
15
27
|
}
|