@nemoobc/opencode-termux 1.19.1 → 1.20.4
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/LICENSE +21 -0
- package/README.md +275 -39
- package/agents/apk-builder.md +3 -2
- package/agents/autodev.md +422 -32
- package/agents/coder.md +171 -0
- package/agents/fixer.md +2 -1
- package/agents/orchestrator.md +165 -0
- package/agents/termux-coder.md +2 -1
- package/agents/tester.md +3 -2
- package/bin/opencode-termux.js +185 -0
- package/bin/opencode-termux.ts +179 -0
- package/commands/audit.md +6 -0
- package/commands/coder.md +24 -0
- package/commands/orchestrator.md +15 -0
- package/commands/test-all.md +6 -0
- package/config/opencode.json +1 -1
- package/config/plugins/README.md +39 -0
- package/config/plugins/strip-parameter.js +55 -0
- package/install.mjs +121 -74
- package/lib/alpine.mjs +30 -0
- package/lib/alpine.ts +39 -0
- package/lib/integrity.mjs +24 -0
- package/lib/integrity.ts +28 -0
- package/lib/net.mjs +30 -0
- package/lib/net.ts +51 -0
- package/package.json +17 -4
- package/prebuilt/README.md +77 -0
- package/bin/opencode.js +0 -48
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Orchestrator — coordinates audit, test, monitor, fix, compact in sequence
|
|
3
|
+
mode: primary
|
|
4
|
+
model: opencode/big-pickle
|
|
5
|
+
tools:
|
|
6
|
+
bash: true
|
|
7
|
+
read: true
|
|
8
|
+
edit: true
|
|
9
|
+
write: true
|
|
10
|
+
glob: true
|
|
11
|
+
grep: true
|
|
12
|
+
task: true
|
|
13
|
+
---
|
|
14
|
+
> ATURAN: JANGAN PERNAH menulis raw tool-call XML sebagai teks (tarif: tag literal seperti <parameter>, <parameter name="...">, </parameter>, <invoke>, <function_calls>, <antml:...>). Selalu panggil tool beneran; kalau perlu menyebut nilai opsi, tulis sebagai teks biasa, JANGAN sebagai tag mentah. Ini mencegah tag literal bocor ke tampilan TUI.
|
|
15
|
+
|
|
16
|
+
# ORCHESTRATOR — Full Lifecycle Coordinator
|
|
17
|
+
|
|
18
|
+
Kamu adalah **ORCHESTRATOR**: mengoordinasikan 5 skill (audit, test, monitor, fix, compact) menjalankan siklus penuh otomatis. Input: command + project root. Output: laporan lengkap per tahap.
|
|
19
|
+
|
|
20
|
+
## COMMAND INTERFACE
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
# User bilang:
|
|
24
|
+
orchestrator full # Jalankan SEMUA tahap berurutan (default)
|
|
25
|
+
orchestrator audit # Hanya audit
|
|
26
|
+
orchestrator test # Hanya test (asumsi audit done)
|
|
27
|
+
orchestrator monitor # Start monitor daemon
|
|
28
|
+
orchestrator fix # Perbaiki failure dari test/audit/monitor
|
|
29
|
+
orchestrator compact # Optimasi + cleanup
|
|
30
|
+
orchestrator status # Tampilkan state saat ini
|
|
31
|
+
orchestrator resume # Lanjut dari tengah (kalau crash)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## STATE MANAGEMENT (`.orchestrator-state.json`)
|
|
35
|
+
|
|
36
|
+
```json
|
|
37
|
+
{
|
|
38
|
+
"projectRoot": "/path/to/project",
|
|
39
|
+
"startedAt": "2026-08-27T10:00:00Z",
|
|
40
|
+
"currentPhase": "test",
|
|
41
|
+
"completedPhases": ["audit"],
|
|
42
|
+
"languages": ["typescript", "python", "go"],
|
|
43
|
+
"audit": {"report": "audit-report-20260827-100000.md", "summary": "audit-summary.json", "critical": 3, "warning": 12},
|
|
44
|
+
"test": {"report": "test-report-20260827-101500.md", "summary": "test-summary.json", "passed": 1247, "failed": 3},
|
|
45
|
+
"monitor": {"pid": 12345, "dashboard": "monitor-dashboard.md", "status": "running"},
|
|
46
|
+
"fix": {"report": "fix-log-20260827-110000.md", "summary": "fix-summary.json", "fixed": 2, "failed": 1},
|
|
47
|
+
"compact": {"report": "compact-report-20260827-113000.md", "summary": "compact-summary.json", "bundleReduction": "25%"},
|
|
48
|
+
"overallStatus": "completed"
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## EXECUTION FLOW
|
|
53
|
+
|
|
54
|
+
### FULL MODE (orchestrator full)
|
|
55
|
+
```
|
|
56
|
+
1. AUDIT → scan project, detect languages, output audit-summary.json
|
|
57
|
+
2. TEST → run all tests per language, output test-summary.json
|
|
58
|
+
3. MONITOR → start daemon (background), return PID, continue
|
|
59
|
+
4. FIX → consume failures from audit + test, auto-fix loop, output fix-summary.json
|
|
60
|
+
5. REGRESSION TEST → full test suite after fix
|
|
61
|
+
6. COMPACT → optimize, cleanup, output compact-summary.json
|
|
62
|
+
7. FINAL REPORT → aggregate all, show dashboard
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### INDIVIDUAL MODE
|
|
66
|
+
- `audit`: jalankan skill audit, simpan state, stop
|
|
67
|
+
- `test`: load state (butuh audit dulu), jalankan skill test
|
|
68
|
+
- `monitor`: start daemon, simpan PID, detach (user stop manual)
|
|
69
|
+
- `fix`: load failures dari state (audit/test/monitor), jalankan skill fix
|
|
70
|
+
- `compact`: load state, jalankan skill compact
|
|
71
|
+
|
|
72
|
+
## SKILL INVOCATION (INTERNAL)
|
|
73
|
+
|
|
74
|
+
Gunakan **Task tool** untuk spawn subagent per skill:
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
# Contoh internal logic (kamu eksekusi via bash + task):
|
|
78
|
+
# 1. Audit
|
|
79
|
+
task("audit", "Run audit skill on /project", subagent_type="general")
|
|
80
|
+
|
|
81
|
+
# 2. Test (paralel per bahasa)
|
|
82
|
+
task("test-ts", "Run test skill for typescript", subagent_type="general")
|
|
83
|
+
task("test-py", "Run test skill for python", subagent_type="general")
|
|
84
|
+
task("test-go", "Run test skill for go", subagent_type="general")
|
|
85
|
+
wait(all)
|
|
86
|
+
|
|
87
|
+
# 3. Monitor (daemon)
|
|
88
|
+
bash("nohup coder monitor start > monitor.log 2>&1 & echo $! > .monitor.pid")
|
|
89
|
+
|
|
90
|
+
# 4. Fix (sequential per failure)
|
|
91
|
+
for failure in failures:
|
|
92
|
+
task("fix-1", "Fix failure fix-001", subagent_type="general")
|
|
93
|
+
|
|
94
|
+
# 5. Compact
|
|
95
|
+
task("compact", "Run compact skill", subagent_type="general")
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## ERROR HANDLING & RESUME
|
|
99
|
+
|
|
100
|
+
- Setiap phase: wrap try/catch, simpan error ke state
|
|
101
|
+
- Kalau crash di tengah: user jalan `orchestrator resume` → lanjut dari `currentPhase`
|
|
102
|
+
- Kalau phase gagal: mark `failed`, lanjut ke phase berikut (kecuali fix butuh test pass)
|
|
103
|
+
- Timeout per phase: 30 menit (audit), 20 menit (test), 60 menit (fix), 15 menit (compact)
|
|
104
|
+
|
|
105
|
+
## OUTPUT AGGREGATION
|
|
106
|
+
|
|
107
|
+
### final-report-<timestamp>.md
|
|
108
|
+
```markdown
|
|
109
|
+
# Orchestrator Final Report — <project> — <timestamp>
|
|
110
|
+
|
|
111
|
+
## Eksekusi
|
|
112
|
+
- Mode: FULL
|
|
113
|
+
- Durasi total: 12m 34s
|
|
114
|
+
- Status: ✅ COMPLETED
|
|
115
|
+
|
|
116
|
+
## Ringkasan Per Tahap
|
|
117
|
+
|
|
118
|
+
| Tahap | Status | Durasi | Key Metrics |
|
|
119
|
+
|-------|--------|--------|-------------|
|
|
120
|
+
| Audit | ✅ | 45s | 3 critical, 12 warning, 45 info |
|
|
121
|
+
| Test | ✅ | 3m 12s | 1247 pass, 3 fail, 86.2% coverage |
|
|
122
|
+
| Monitor | 🟢 Running | - | PID 12345, dashboard active |
|
|
123
|
+
| Fix | ✅ | 4m 20s | 2 fixed, 1 failed (5 iter), regression ✅ |
|
|
124
|
+
| Compact | ✅ | 2m 15s | -25% bundle, -23% build time |
|
|
125
|
+
|
|
126
|
+
## Failure yang Diperbaiki
|
|
127
|
+
1. ✅ TypeScript auth middleware expiry check
|
|
128
|
+
2. ✅ Python DB connection pool leak
|
|
129
|
+
3. ❌ npm audit lodash major (butuh manual decision)
|
|
130
|
+
|
|
131
|
+
## Optimasi Compact
|
|
132
|
+
- Bundle: 2.4MB → 1.8MB (-25%)
|
|
133
|
+
- Build: 18.5s → 14.2s (-23%)
|
|
134
|
+
- Dead code: 4 exports, 3 deps, 4 funcs removed
|
|
135
|
+
- Deps: 15 updated (3 minor, 12 patch)
|
|
136
|
+
|
|
137
|
+
## Rekomendasi Lanjutan
|
|
138
|
+
1. Manual review lodash v5 migration (breaking)
|
|
139
|
+
2. Enable monitor daemon untuk CI/perf tracking
|
|
140
|
+
3. Schedule compact mingguan via CI
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## ORCHESTRATION RULES
|
|
144
|
+
|
|
145
|
+
1. **Sequential dependency**: test butuh audit, fix butuh test/audit, compact butuh test pass
|
|
146
|
+
2. **Parallel where possible**: test per bahasa paralel, fix independen bisa paralel
|
|
147
|
+
3. **State persistence**: update `.orchestrator-state.json` SETIAP step
|
|
148
|
+
4. **Cleanup**: kalau monitor daemon jalan, simpan PID, user stop manual
|
|
149
|
+
5. **Notification**: optional webhook di akhir (Slack/Discord)
|
|
150
|
+
6. **Language**: laporan Bahasa Indonesia, ringkas
|
|
151
|
+
|
|
152
|
+
## STARTUP SEQUENCE
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
# 1. Validasi project root
|
|
156
|
+
# 2. Load existing state (kalau resume)
|
|
157
|
+
# 3. Detect languages (kalau fresh)
|
|
158
|
+
# 4. Create state file
|
|
159
|
+
# 5. Execute phases per command
|
|
160
|
+
# 6. Final report
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
**Mulai:** Parse command → load state → execute phases → final report.
|
package/agents/termux-coder.md
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: Ahli coding di Termux/Android — paham PATH, pkg, dan lingkungan mobile
|
|
3
3
|
mode: primary
|
|
4
|
-
model: opencode/
|
|
4
|
+
model: opencode/big-pickle
|
|
5
5
|
tools:
|
|
6
6
|
write: true
|
|
7
7
|
edit: true
|
|
8
8
|
bash: true
|
|
9
9
|
---
|
|
10
|
+
> ATURAN: JANGAN PERNAH menulis raw tool-call XML sebagai teks (tarif: tag literal seperti <parameter>, <parameter name="...">, </parameter>, <invoke>, <function_calls>, <antml:...>). Selalu panggil tool beneran; kalau perlu menyebut nilai opsi, tulis sebagai teks biasa, JANGAN sebagai tag mentah. Ini mencegah tag literal bocor ke tampilan TUI.
|
|
10
11
|
|
|
11
12
|
Kamu adalah coding assistant yang berjalan di Termux/Android.
|
|
12
13
|
|
package/agents/tester.md
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: Menjalankan seluruh test suite dan menganalisis hasil
|
|
3
3
|
mode: subagent
|
|
4
|
-
model: opencode/
|
|
4
|
+
model: opencode/big-pickle
|
|
5
5
|
tools:
|
|
6
6
|
bash: true
|
|
7
7
|
---
|
|
8
|
+
> ATURAN: JANGAN PERNAH menulis raw tool-call XML sebagai teks (tarif: tag literal seperti <parameter>, <parameter name="...">, </parameter>, <invoke>, <function_calls>, <antml:...>). Selalu panggil tool beneran; kalau perlu menyebut nilai opsi, tulis sebagai teks biasa, JANGAN sebagai tag mentah. Ini mencegah tag literal bocor ke tampilan TUI.
|
|
8
9
|
|
|
9
10
|
Kamu adalah test engineer untuk project di folder aktif.
|
|
10
11
|
|
|
@@ -17,7 +18,7 @@ TEST MESIN (server opencode di 127.0.0.1:4096, app harus terbuka):
|
|
|
17
18
|
1. curl -s http://127.0.0.1:4096/ → 200 = server hidup
|
|
18
19
|
2. Buat sesi: POST /session {"title":"tes"}
|
|
19
20
|
3. Kirim: POST /session/{id}/message {"parts":[{"type":"text","text":"..."}],
|
|
20
|
-
"model":{"providerID":"opencode","modelID":"
|
|
21
|
+
"model":{"providerID":"opencode","modelID":"big-pickle"}}
|
|
21
22
|
4. Verifikasi: respons JSON berisi parts dengan teks jawaban
|
|
22
23
|
5. Abort: POST /session/{id}/abort → 200
|
|
23
24
|
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawnSync } from "child_process"
|
|
3
|
+
import fs from "fs"
|
|
4
|
+
import path from "path"
|
|
5
|
+
import { fileURLToPath } from "url"
|
|
6
|
+
|
|
7
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
8
|
+
const root = path.join(__dirname, "..")
|
|
9
|
+
const vendor = path.join(root, "vendor")
|
|
10
|
+
const loader = path.join(vendor, "ld-musl.so")
|
|
11
|
+
const bin = path.join(vendor, "opencode")
|
|
12
|
+
const PKG = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8"))
|
|
13
|
+
const PREFIX = process.env.TERMUX_PREFIX || "/data/data/com.termux/files/usr"
|
|
14
|
+
|
|
15
|
+
function ready() {
|
|
16
|
+
return fs.existsSync(loader) && fs.existsSync(bin)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Auto-heal: kalau postinstall terlewat (mis. --ignore-scripts), pasang sekarang.
|
|
20
|
+
function heal() {
|
|
21
|
+
if (ready()) return true
|
|
22
|
+
console.log("[opencode-termux] vendor belum ada — menjalankan installer…")
|
|
23
|
+
const r = spawnSync(process.execPath, [path.join(root, "install.mjs")], {
|
|
24
|
+
stdio: "inherit",
|
|
25
|
+
env: process.env,
|
|
26
|
+
})
|
|
27
|
+
if (r.status !== 0 || !ready()) {
|
|
28
|
+
console.error("[opencode-termux] instalasi bundle gagal. Coba manual:")
|
|
29
|
+
console.error(" npm rebuild @nemoobc/opencode-termux")
|
|
30
|
+
return false
|
|
31
|
+
}
|
|
32
|
+
return true
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// DNS fix: musl hasil build kita membaca config dari prefix Termux —
|
|
36
|
+
// pastikan filenya ada (bisa ditulis tanpa root).
|
|
37
|
+
function ensureDns() {
|
|
38
|
+
try {
|
|
39
|
+
const etc = path.join(PREFIX, "etc")
|
|
40
|
+
fs.mkdirSync(etc, { recursive: true })
|
|
41
|
+
const rc = path.join(etc, "resolv.conf")
|
|
42
|
+
if (!fs.existsSync(rc)) fs.writeFileSync(rc, "nameserver 1.1.1.1\nnameserver 8.8.8.8\n")
|
|
43
|
+
const hh = path.join(etc, "hosts")
|
|
44
|
+
if (!fs.existsSync(hh)) fs.writeFileSync(hh, "127.0.0.1 localhost\n")
|
|
45
|
+
} catch {}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function runBinary(args) {
|
|
49
|
+
// LD_PRELOAD bawaan Termux (libtermux-exec) dibuat untuk Bionic dan akan
|
|
50
|
+
// gagal relokasi jika ikut dimuat ke proses musl — jadi selalu dibersihkan.
|
|
51
|
+
const { LD_PRELOAD, LD_PRELOAD_32BIT, ...cleanEnv } = process.env
|
|
52
|
+
ensureDns()
|
|
53
|
+
const r = spawnSync(loader, [bin, ...args], {
|
|
54
|
+
stdio: "inherit",
|
|
55
|
+
env: { ...cleanEnv, LD_LIBRARY_PATH: vendor },
|
|
56
|
+
})
|
|
57
|
+
if (r.error) {
|
|
58
|
+
console.error("[opencode-termux] gagal menjalankan binary:", r.error.message)
|
|
59
|
+
return 1
|
|
60
|
+
}
|
|
61
|
+
const sigExit = { SIGINT: 130, SIGQUIT: 131, SIGTERM: 143 }
|
|
62
|
+
return r.status ?? sigExit[r.signal] ?? 1
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function cmdUpdate() {
|
|
66
|
+
console.log(`[opencode-termux] memperbarui bundle (paket v${PKG.version})…`)
|
|
67
|
+
const env = { ...process.env }
|
|
68
|
+
delete env.LD_PRELOAD
|
|
69
|
+
delete env.LD_PRELOAD_32BIT
|
|
70
|
+
const r = spawnSync(process.execPath, [path.join(root, "install.mjs")], {
|
|
71
|
+
stdio: "inherit",
|
|
72
|
+
env: env,
|
|
73
|
+
})
|
|
74
|
+
if (r.status !== 0) {
|
|
75
|
+
console.error("[opencode-termux] ❌ update gagal.")
|
|
76
|
+
return 1
|
|
77
|
+
}
|
|
78
|
+
// sinkronkan pin di package.json modul agar auto-heal berikutnya konsisten
|
|
79
|
+
try {
|
|
80
|
+
const latest = await (await fetch("https://registry.npmjs.org/opencode-ai/latest")).json()
|
|
81
|
+
if (latest.version && latest.version !== PKG.opencodeUpstream) {
|
|
82
|
+
PKG.opencodeUpstream = latest.version
|
|
83
|
+
fs.writeFileSync(path.join(root, "package.json"), JSON.stringify(PKG, null, 2) + "\n")
|
|
84
|
+
}
|
|
85
|
+
} catch {}
|
|
86
|
+
console.log("[opencode-termux] ✅ update selesai.")
|
|
87
|
+
return 0
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function cmdDoctor() {
|
|
91
|
+
let critical = 0
|
|
92
|
+
const cek = (name, fn, { crit = true } = {}) => {
|
|
93
|
+
try {
|
|
94
|
+
const info = fn()
|
|
95
|
+
console.log(`✅ ${name}${info ? ` — ${info}` : ""}`)
|
|
96
|
+
} catch (e) {
|
|
97
|
+
if (crit) critical++
|
|
98
|
+
console.log(`${crit ? "❌" : "⚠️ "} ${name} — ${e.message}`)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
console.log(`[opencode-termux] doctor v${PKG.version} (upstream ${PKG.opencodeUpstream})`)
|
|
103
|
+
cek("platform", () => {
|
|
104
|
+
if (process.platform !== "android") throw new Error(`process.platform=${process.platform} (bukan android)`)
|
|
105
|
+
return "android"
|
|
106
|
+
}, { crit: false })
|
|
107
|
+
cek("arsitektur", () => {
|
|
108
|
+
if (process.arch !== "arm64" && process.arch !== "x64") throw new Error(`${process.arch} tidak didukung`)
|
|
109
|
+
return process.arch
|
|
110
|
+
})
|
|
111
|
+
cek("node >= 18", () => {
|
|
112
|
+
const [M] = process.versions.node.split(".").map(Number)
|
|
113
|
+
if (M < 18) throw new Error(`node ${process.versions.node}`)
|
|
114
|
+
return process.versions.node
|
|
115
|
+
})
|
|
116
|
+
cek("tar tersedia", () => {
|
|
117
|
+
const r = spawnSync("tar", ["--version"], { stdio: "ignore" })
|
|
118
|
+
if (r.error || r.status !== 0) throw new Error("tidak ditemukan — pkg install tar")
|
|
119
|
+
})
|
|
120
|
+
cek("vendor lengkap", () => {
|
|
121
|
+
if (!ready()) throw new Error("vendor/ tidak lengkap — jalankan 'opencode-termux update'")
|
|
122
|
+
return `${fs.readdirSync(vendor).length} file`
|
|
123
|
+
})
|
|
124
|
+
cek("DNS resolv.conf", () => {
|
|
125
|
+
const rc = path.join(PREFIX, "etc", "resolv.conf")
|
|
126
|
+
if (!fs.existsSync(rc)) throw new Error(`${rc} hilang`)
|
|
127
|
+
return "ada"
|
|
128
|
+
}, { crit: false })
|
|
129
|
+
cek("jaringan registry npm", () => {
|
|
130
|
+
const r = spawnSync(process.execPath, ["-e", "fetch('https://registry.npmjs.org/-/ping').then(r=>{if(!r.ok)process.exit(1)})"], {
|
|
131
|
+
timeout: 10000,
|
|
132
|
+
})
|
|
133
|
+
if (r.status !== 0) throw new Error("registry tak terjangkau")
|
|
134
|
+
})
|
|
135
|
+
cek("binary opencode", () => {
|
|
136
|
+
if (!ready()) throw new Error("binary belum terpasang")
|
|
137
|
+
const { LD_PRELOAD, LD_PRELOAD_32BIT, ...cleanEnv } = process.env
|
|
138
|
+
const out = spawnSync(loader, [bin, "--version"], {
|
|
139
|
+
encoding: "utf8",
|
|
140
|
+
env: { ...cleanEnv, LD_LIBRARY_PATH: vendor },
|
|
141
|
+
})
|
|
142
|
+
if (out.status !== 0) throw new Error("gagal dieksekusi")
|
|
143
|
+
return `v${out.stdout.trim()}`
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
console.log(critical === 0 ? "[opencode-termux] ✅ semua komponen kritis sehat" : `[opencode-termux] ❌ ${critical} masalah kritis`)
|
|
147
|
+
return critical === 0 ? 0 : 1
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function cmdVersion() {
|
|
151
|
+
let binVer = "(belum terpasang)"
|
|
152
|
+
if (ready()) {
|
|
153
|
+
const { LD_PRELOAD, LD_PRELOAD_32BIT, ...cleanEnv } = process.env
|
|
154
|
+
const out = spawnSync(loader, [bin, "--version"], {
|
|
155
|
+
encoding: "utf8",
|
|
156
|
+
env: { ...cleanEnv, LD_LIBRARY_PATH: vendor },
|
|
157
|
+
})
|
|
158
|
+
if (out.status === 0 && out.stdout.trim()) binVer = out.stdout.trim()
|
|
159
|
+
}
|
|
160
|
+
console.log(`opencode-termux v${PKG.version} (upstream opencode ${PKG.opencodeUpstream}, binary ${binVer})`)
|
|
161
|
+
return 0
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async function main() {
|
|
165
|
+
const arg = process.argv[2]
|
|
166
|
+
if (arg === "update") process.exit(await cmdUpdate())
|
|
167
|
+
if (arg === "doctor") process.exit(cmdDoctor())
|
|
168
|
+
if (arg === "version") process.exit(cmdVersion())
|
|
169
|
+
if (arg === "help" || arg === "--help" || arg === "-h") {
|
|
170
|
+
console.log(`opencode-termux v${PKG.version}
|
|
171
|
+
pakai:
|
|
172
|
+
opencode-termux jalankan CLI opencode (argumen diteruskan)
|
|
173
|
+
opencode-termux update perbarui binary ke upstream terbaru
|
|
174
|
+
opencode-termux doctor diagnosis lingkungan & bundle
|
|
175
|
+
opencode-termux version info versi paket + binary`)
|
|
176
|
+
process.exit(0)
|
|
177
|
+
}
|
|
178
|
+
if (!heal()) process.exit(1)
|
|
179
|
+
process.exit(runBinary(process.argv.slice(2)))
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
main().catch(e => {
|
|
183
|
+
console.error("[opencode-termux]", e.message)
|
|
184
|
+
process.exit(1)
|
|
185
|
+
})
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawnSync } from "child_process"
|
|
3
|
+
import fs from "fs"
|
|
4
|
+
import path from "path"
|
|
5
|
+
import { fileURLToPath } from "url"
|
|
6
|
+
|
|
7
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
8
|
+
const root = path.join(__dirname, "..")
|
|
9
|
+
const vendor = path.join(root, "vendor")
|
|
10
|
+
const loader = path.join(vendor, "ld-musl.so")
|
|
11
|
+
const bin = path.join(vendor, "opencode")
|
|
12
|
+
const PKG = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8")) as { version: string; opencodeUpstream: string }
|
|
13
|
+
const PREFIX = process.env.TERMUX_PREFIX || "/data/data/com.termux/files/usr"
|
|
14
|
+
|
|
15
|
+
function ready(): boolean {
|
|
16
|
+
return fs.existsSync(loader) && fs.existsSync(bin)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function heal(): boolean {
|
|
20
|
+
if (ready()) return true
|
|
21
|
+
console.log("[opencode-termux] vendor belum ada — menjalankan installer…")
|
|
22
|
+
const r = spawnSync(process.execPath, [path.join(root, "install.mjs")], {
|
|
23
|
+
stdio: "inherit",
|
|
24
|
+
env: process.env,
|
|
25
|
+
})
|
|
26
|
+
if (r.status !== 0 || !ready()) {
|
|
27
|
+
console.error("[opencode-termux] instalasi bundle gagal. Coba manual:")
|
|
28
|
+
console.error(" npm rebuild @nemoobc/opencode-termux")
|
|
29
|
+
return false
|
|
30
|
+
}
|
|
31
|
+
return true
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function ensureDns(): void {
|
|
35
|
+
try {
|
|
36
|
+
const etc = path.join(PREFIX, "etc")
|
|
37
|
+
fs.mkdirSync(etc, { recursive: true })
|
|
38
|
+
const rc = path.join(etc, "resolv.conf")
|
|
39
|
+
if (!fs.existsSync(rc)) fs.writeFileSync(rc, "nameserver 1.1.1.1\nnameserver 8.8.8.8\n")
|
|
40
|
+
const hh = path.join(etc, "hosts")
|
|
41
|
+
if (!fs.existsSync(hh)) fs.writeFileSync(hh, "127.0.0.1 localhost\n")
|
|
42
|
+
} catch {}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function runBinary(args: string[]): number {
|
|
46
|
+
const { LD_PRELOAD, LD_PRELOAD_32BIT, ...cleanEnv } = process.env
|
|
47
|
+
ensureDns()
|
|
48
|
+
const r = spawnSync(loader, [bin, ...args], {
|
|
49
|
+
stdio: "inherit",
|
|
50
|
+
env: { ...cleanEnv, LD_LIBRARY_PATH: vendor },
|
|
51
|
+
})
|
|
52
|
+
if (r.error) {
|
|
53
|
+
console.error("[opencode-termux] gagal menjalankan binary:", r.error.message)
|
|
54
|
+
return 1
|
|
55
|
+
}
|
|
56
|
+
const sigExit: Record<string, number> = { SIGINT: 130, SIGQUIT: 131, SIGTERM: 143 }
|
|
57
|
+
return r.status ?? sigExit[r.signal ?? ""] ?? 1
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function cmdUpdate(): Promise<number> {
|
|
61
|
+
console.log(`[opencode-termux] memperbarui bundle (paket v${PKG.version})…`)
|
|
62
|
+
const env = { ...process.env }
|
|
63
|
+
delete env.LD_PRELOAD
|
|
64
|
+
delete env.LD_PRELOAD_32BIT
|
|
65
|
+
const r = spawnSync(process.execPath, [path.join(root, "install.mjs")], {
|
|
66
|
+
stdio: "inherit",
|
|
67
|
+
env: env,
|
|
68
|
+
})
|
|
69
|
+
if (r.status !== 0) {
|
|
70
|
+
console.error("[opencode-termux] ❌ update gagal.")
|
|
71
|
+
return 1
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
const latest = await (await fetch("https://registry.npmjs.org/opencode-ai/latest")).json() as { version: string }
|
|
75
|
+
if (latest.version && latest.version !== PKG.opencodeUpstream) {
|
|
76
|
+
PKG.opencodeUpstream = latest.version
|
|
77
|
+
fs.writeFileSync(path.join(root, "package.json"), JSON.stringify(PKG, null, 2) + "\n")
|
|
78
|
+
}
|
|
79
|
+
} catch {}
|
|
80
|
+
console.log("[opencode-termux] ✅ update selesai.")
|
|
81
|
+
return 0
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function cmdDoctor(): number {
|
|
85
|
+
let critical = 0
|
|
86
|
+
const cek = (name: string, fn: () => string | void, { crit = true } = {}): void => {
|
|
87
|
+
try {
|
|
88
|
+
const info = fn()
|
|
89
|
+
console.log(`✅ ${name}${info ? ` — ${info}` : ""}`)
|
|
90
|
+
} catch (e) {
|
|
91
|
+
if (crit) critical++
|
|
92
|
+
console.log(`${crit ? "❌" : "⚠️ "} ${name} — ${(e as Error).message}`)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
console.log(`[opencode-termux] doctor v${PKG.version} (upstream ${PKG.opencodeUpstream})`)
|
|
97
|
+
cek("platform", () => {
|
|
98
|
+
if (process.platform !== "android") throw new Error(`process.platform=${process.platform} (bukan android)`)
|
|
99
|
+
return "android"
|
|
100
|
+
}, { crit: false })
|
|
101
|
+
cek("arsitektur", () => {
|
|
102
|
+
if (process.arch !== "arm64" && process.arch !== "x64") throw new Error(`${process.arch} tidak didukung`)
|
|
103
|
+
return process.arch
|
|
104
|
+
})
|
|
105
|
+
cek("node >= 18", () => {
|
|
106
|
+
const [M] = process.versions.node.split(".").map(Number)
|
|
107
|
+
if (M < 18) throw new Error(`node ${process.versions.node}`)
|
|
108
|
+
return process.versions.node
|
|
109
|
+
})
|
|
110
|
+
cek("tar tersedia", () => {
|
|
111
|
+
const r = spawnSync("tar", ["--version"], { stdio: "ignore" })
|
|
112
|
+
if (r.error || r.status !== 0) throw new Error("tidak ditemukan — pkg install tar")
|
|
113
|
+
})
|
|
114
|
+
cek("vendor lengkap", () => {
|
|
115
|
+
if (!ready()) throw new Error("vendor/ tidak lengkap — jalankan 'opencode-termux update'")
|
|
116
|
+
return `${fs.readdirSync(vendor).length} file`
|
|
117
|
+
})
|
|
118
|
+
cek("DNS resolv.conf", () => {
|
|
119
|
+
const rc = path.join(PREFIX, "etc", "resolv.conf")
|
|
120
|
+
if (!fs.existsSync(rc)) throw new Error(`${rc} hilang`)
|
|
121
|
+
return "ada"
|
|
122
|
+
}, { crit: false })
|
|
123
|
+
cek("jaringan registry npm", () => {
|
|
124
|
+
const r = spawnSync(process.execPath, ["-e", "fetch('https://registry.npmjs.org/-/ping').then(r=>{if(!r.ok)process.exit(1)})"], {
|
|
125
|
+
timeout: 10000,
|
|
126
|
+
})
|
|
127
|
+
if (r.status !== 0) throw new Error("registry tak terjangkau")
|
|
128
|
+
})
|
|
129
|
+
cek("binary opencode", () => {
|
|
130
|
+
if (!ready()) throw new Error("binary belum terpasang")
|
|
131
|
+
const { LD_PRELOAD, LD_PRELOAD_32BIT, ...cleanEnv } = process.env
|
|
132
|
+
const out = spawnSync(loader, [bin, "--version"], {
|
|
133
|
+
encoding: "utf8",
|
|
134
|
+
env: { ...cleanEnv, LD_LIBRARY_PATH: vendor },
|
|
135
|
+
})
|
|
136
|
+
if (out.status !== 0) throw new Error("gagal dieksekusi")
|
|
137
|
+
return `v${out.stdout.trim()}`
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
console.log(critical === 0 ? "[opencode-termux] ✅ semua komponen kritis sehat" : `[opencode-termux] ❌ ${critical} masalah kritis`)
|
|
141
|
+
return critical === 0 ? 0 : 1
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function cmdVersion(): number {
|
|
145
|
+
let binVer = "(belum terpasang)"
|
|
146
|
+
if (ready()) {
|
|
147
|
+
const { LD_PRELOAD, LD_PRELOAD_32BIT, ...cleanEnv } = process.env
|
|
148
|
+
const out = spawnSync(loader, [bin, "--version"], {
|
|
149
|
+
encoding: "utf8",
|
|
150
|
+
env: { ...cleanEnv, LD_LIBRARY_PATH: vendor },
|
|
151
|
+
})
|
|
152
|
+
if (out.status === 0 && out.stdout.trim()) binVer = out.stdout.trim()
|
|
153
|
+
}
|
|
154
|
+
console.log(`opencode-termux v${PKG.version} (upstream opencode ${PKG.opencodeUpstream}, binary ${binVer})`)
|
|
155
|
+
return 0
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async function main(): Promise<void> {
|
|
159
|
+
const arg = process.argv[2]
|
|
160
|
+
if (arg === "update") process.exit(await cmdUpdate())
|
|
161
|
+
if (arg === "doctor") process.exit(cmdDoctor())
|
|
162
|
+
if (arg === "version") process.exit(cmdVersion())
|
|
163
|
+
if (arg === "help" || arg === "--help" || arg === "-h") {
|
|
164
|
+
console.log(`opencode-termux v${PKG.version}
|
|
165
|
+
pakai:
|
|
166
|
+
opencode-termux jalankan CLI opencode (argumen diteruskan)
|
|
167
|
+
opencode-termux update perbarui binary ke upstream terbaru
|
|
168
|
+
opencode-termux doctor diagnosis lingkungan & bundle
|
|
169
|
+
opencode-termux version info versi paket + binary`)
|
|
170
|
+
process.exit(0)
|
|
171
|
+
}
|
|
172
|
+
if (!heal()) process.exit(1)
|
|
173
|
+
process.exit(runBinary(process.argv.slice(2)))
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
main().catch(e => {
|
|
177
|
+
console.error("[opencode-termux]", e.message)
|
|
178
|
+
process.exit(1)
|
|
179
|
+
})
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Master coder — multi-language full lifecycle (audit, test, monitor, fix, compact)
|
|
3
|
+
agent: coder
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
CODER — Developer otonom universal multi-bahasa.
|
|
7
|
+
|
|
8
|
+
Perintah:
|
|
9
|
+
- coder full # Audit → Test → Monitor → Fix → Compact (default)
|
|
10
|
+
- coder audit # Scan project lengkap
|
|
11
|
+
- coder test # Jalankan semua test
|
|
12
|
+
- coder monitor # Start monitor daemon (watch + CI + perf)
|
|
13
|
+
- coder fix # Auto-fix failure (loop max 5x)
|
|
14
|
+
- coder compact # Optimasi: dead code, format, deps, bundle
|
|
15
|
+
- coder status # Lihat state & progress
|
|
16
|
+
|
|
17
|
+
Args: $ARGUMENTS (opsional: bahasa spesifik, path, filter)
|
|
18
|
+
|
|
19
|
+
Contoh:
|
|
20
|
+
coder full
|
|
21
|
+
coder audit
|
|
22
|
+
coder test --language=typescript
|
|
23
|
+
coder fix --id=fix-001
|
|
24
|
+
coder compact --aggressive
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Jalankan orchestrator full lifecycle: audit → test → monitor → fix → compact
|
|
3
|
+
agent: orchestrator
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Jalankan siklus penuh otomatis:
|
|
7
|
+
1. Audit project (struktur, deps, security, style, arch)
|
|
8
|
+
2. Test semua bahasa (unit, integration, coverage)
|
|
9
|
+
3. Monitor daemon (file watch, CI, perf baseline)
|
|
10
|
+
4. Fix semua failure (auto-loop max 5x per bug)
|
|
11
|
+
5. Compact & optimasi (dead code, format, deps, bundle)
|
|
12
|
+
|
|
13
|
+
Args: $ARGUMENTS (optional: full|audit|test|monitor|fix|compact|status|resume)
|
|
14
|
+
|
|
15
|
+
Default: full
|
package/config/opencode.json
CHANGED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Plugin `strip-parameter`
|
|
2
|
+
|
|
3
|
+
Plugin opencode global untuk **mensupresi raw XML tool-call tags literal** (mis.
|
|
4
|
+
`<parameter>`, `</function_calls>`, `<tool_call>`, `<antml:...>`) dari output teks
|
|
5
|
+
model **sebelum dirender TUI**, agar tag mentah tidak bocor ke layar.
|
|
6
|
+
|
|
7
|
+
## Mengapa
|
|
8
|
+
|
|
9
|
+
opencode kadang menampilkan tag tool-call XML mentah sebagai teks (issue upstream
|
|
10
|
+
`anomalyco/opencode#24316`, fix upstream PR #27984/#30633 belum masuk release).
|
|
11
|
+
Plugin ini adalah mitigasi lokal versi 1.18.23.
|
|
12
|
+
|
|
13
|
+
## Cara pakai
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# 1. Salin ke folder plugin global opencode
|
|
17
|
+
mkdir -p ~/.config/opencode/plugins
|
|
18
|
+
cp config/plugins/strip-parameter.js ~/.config/opencode/plugins/
|
|
19
|
+
|
|
20
|
+
# 2. Pastikan config package.json bertipe ESM (opsional, menghilangkan warning)
|
|
21
|
+
# di ~/.config/opencode/package.json tambahkan: "type": "module"
|
|
22
|
+
|
|
23
|
+
# 3. Restart opencode (plugin dimuat saat start). Efek aktif di sesi baru.
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Verifikasi
|
|
27
|
+
|
|
28
|
+
Jalankan log opencode; harus muncul:
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
Plugin initialized — mensupresi raw XML tool-call tags dari output teks
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Catatan
|
|
35
|
+
|
|
36
|
+
- Plugin memuat semua blok/penutup tool-call XML yang "nyasar" sebagai teks.
|
|
37
|
+
- Hanya menyentuh output **teks** (hook `experimental.text.complete`),
|
|
38
|
+
tidak mengubah eksekusi tool.
|
|
39
|
+
- Hook ini eksperimental; jika berubah di versi opencode mendatang, sesuaikan.
|