@nemoobc/opencode-termux 1.20.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/README.md +266 -67
- 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 +14 -11
- 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 +25 -1
- package/lib/alpine.ts +39 -0
- package/lib/integrity.ts +28 -0
- package/lib/net.mjs +6 -0
- package/lib/net.ts +51 -0
- package/package.json +12 -3
- package/prebuilt/README.md +77 -0
|
@@ -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
|
|
package/bin/opencode-termux.js
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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))
|
|
5
8
|
const root = path.join(__dirname, "..")
|
|
6
9
|
const vendor = path.join(root, "vendor")
|
|
7
10
|
const loader = path.join(vendor, "ld-musl.so")
|
|
8
11
|
const bin = path.join(vendor, "opencode")
|
|
9
|
-
const PKG =
|
|
12
|
+
const PKG = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8"))
|
|
10
13
|
const PREFIX = process.env.TERMUX_PREFIX || "/data/data/com.termux/files/usr"
|
|
11
14
|
|
|
12
15
|
function ready() {
|
|
@@ -160,9 +163,9 @@ function cmdVersion() {
|
|
|
160
163
|
|
|
161
164
|
async function main() {
|
|
162
165
|
const arg = process.argv[2]
|
|
163
|
-
if (arg === "update")
|
|
164
|
-
if (arg === "doctor")
|
|
165
|
-
if (arg === "version")
|
|
166
|
+
if (arg === "update") process.exit(await cmdUpdate())
|
|
167
|
+
if (arg === "doctor") process.exit(cmdDoctor())
|
|
168
|
+
if (arg === "version") process.exit(cmdVersion())
|
|
166
169
|
if (arg === "help" || arg === "--help" || arg === "-h") {
|
|
167
170
|
console.log(`opencode-termux v${PKG.version}
|
|
168
171
|
pakai:
|
|
@@ -170,13 +173,13 @@ pakai:
|
|
|
170
173
|
opencode-termux update perbarui binary ke upstream terbaru
|
|
171
174
|
opencode-termux doctor diagnosis lingkungan & bundle
|
|
172
175
|
opencode-termux version info versi paket + binary`)
|
|
173
|
-
|
|
176
|
+
process.exit(0)
|
|
174
177
|
}
|
|
175
|
-
if (!heal())
|
|
176
|
-
|
|
178
|
+
if (!heal()) process.exit(1)
|
|
179
|
+
process.exit(runBinary(process.argv.slice(2)))
|
|
177
180
|
}
|
|
178
181
|
|
|
179
|
-
main().
|
|
182
|
+
main().catch(e => {
|
|
180
183
|
console.error("[opencode-termux]", e.message)
|
|
181
184
|
process.exit(1)
|
|
182
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.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// strip-parameter.js — opencode plugin (global: ~/.config/opencode/plugins/)
|
|
2
|
+
// Membersihkan raw XML tool-call tags literal (mis. <parameter>, </function_calls>,
|
|
3
|
+
// <parameter name="...">) dari output TEKS model SEBELUM dirender TUI, supaya
|
|
4
|
+
// tag mentah tidak bocor ke layar.
|
|
5
|
+
//
|
|
6
|
+
// Mitigasi lokal atas issue opencode #24316 (bug "<parameter>" literal di TUI),
|
|
7
|
+
// sejalan dengan pendekatan PR #27984 (strip dangling XML artifacts).
|
|
8
|
+
//
|
|
9
|
+
// CATATAN: Hook "experimental.text.complete" bersifat eksperimental & hanya
|
|
10
|
+
// memodifikasi output teks. Plugin murni ESM tanpa dependensi eksternal.
|
|
11
|
+
|
|
12
|
+
export const StripParameterPlugin = async (ctx) => {
|
|
13
|
+
try {
|
|
14
|
+
await ctx?.client?.app.log?.({
|
|
15
|
+
body: {
|
|
16
|
+
service: "strip-parameter",
|
|
17
|
+
level: "info",
|
|
18
|
+
message: "Plugin initialized — mensupresi raw XML tool-call tags dari output teks",
|
|
19
|
+
},
|
|
20
|
+
})
|
|
21
|
+
} catch {}
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
"experimental.text.complete": async (_input, output) => {
|
|
25
|
+
const before = output.text
|
|
26
|
+
if (typeof before !== "string" || !before.includes("<")) return
|
|
27
|
+
|
|
28
|
+
let text = before
|
|
29
|
+
|
|
30
|
+
// 1) Hapus blok tool-call XML lengkap (tag + konten)
|
|
31
|
+
text = text.replace(/<function_calls>[\s\S]*?<\/function_calls>/g, "")
|
|
32
|
+
text = text.replace(/<tool_call>[\s\S]*?<\/tool_call>/g, "")
|
|
33
|
+
text = text.replace(/<invoke\b[^>]*>[\s\S]*?<\/invoke>/g, "")
|
|
34
|
+
text = text.replace(/<antml:function_calls>[\s\S]*?<\/antml:function_calls>/g, "")
|
|
35
|
+
|
|
36
|
+
// 2) Hapus pasangan <parameter ...>...</parameter> & <parameter>...</parameter>
|
|
37
|
+
text = text.replace(/<parameter\b[^>]*>[\s\S]*?<\/parameter>/g, "")
|
|
38
|
+
|
|
39
|
+
// 3) Hapus tag tool-call "dangling" (pembuka/penutup tanpa pasangan)
|
|
40
|
+
text = text.replace(/<\/?parameter\b[^>]*>/g, "")
|
|
41
|
+
text = text.replace(/<\/?invoke\b[^>]*>/g, "")
|
|
42
|
+
text = text.replace(/<\/?function_calls\b[^>]*>/g, "")
|
|
43
|
+
text = text.replace(/<\/?tool_call\b[^>]*>/g, "")
|
|
44
|
+
text = text.replace(/<\/?antml:\w+(?:\s[^>]*)?>/g, "")
|
|
45
|
+
text = text.replace(/<\|mask_start\|>[\s\S]*?<\|mask_end\|>/g, "")
|
|
46
|
+
|
|
47
|
+
// 4) Rapikan baris kosong berlebih & spasi sisa
|
|
48
|
+
text = text.replace(/[ \t]+\n/g, "\n").replace(/\n{3,}/g, "\n\n")
|
|
49
|
+
|
|
50
|
+
if (text !== before) output.text = text
|
|
51
|
+
},
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export default StripParameterPlugin
|
package/install.mjs
CHANGED
|
@@ -57,8 +57,32 @@ const work = path.join(__dirname, ".build")
|
|
|
57
57
|
fs.rmSync(work, { recursive: true, force: true })
|
|
58
58
|
fs.mkdirSync(work, { recursive: true })
|
|
59
59
|
|
|
60
|
+
async function fetchLatestAlpineVersion() {
|
|
61
|
+
try {
|
|
62
|
+
const res = await fetchWithRetry(fetch, "https://dl-cdn.alpinelinux.org/alpine/latest-stable/", {}, 3)
|
|
63
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
|
64
|
+
const text = await res.text()
|
|
65
|
+
const match = text.match(/href="(v\d+\.\d+)"/)
|
|
66
|
+
if (match) return match[1]
|
|
67
|
+
} catch {}
|
|
68
|
+
return "v3.21"
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function fetchAlpineReleaseVersion(version) {
|
|
72
|
+
try {
|
|
73
|
+
const res = await fetchWithRetry(fetch, `https://dl-cdn.alpinelinux.org/alpine/${version}/releases/${A}/`, {}, 3)
|
|
74
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
|
75
|
+
const text = await res.text()
|
|
76
|
+
const match = text.match(new RegExp(`alpine-minirootfs-(\\d+\\.\\d+\\.\\d+)-${A}\\.tar\\.gz`))
|
|
77
|
+
if (match) return match[1]
|
|
78
|
+
} catch {}
|
|
79
|
+
return "3.21.3"
|
|
80
|
+
}
|
|
81
|
+
|
|
60
82
|
try {
|
|
61
|
-
const AV =
|
|
83
|
+
const AV = process.env.OCX_ALPINE_VERSION || await fetchLatestAlpineVersion()
|
|
84
|
+
const AL = await fetchAlpineReleaseVersion(AV)
|
|
85
|
+
log(`Alpine version: ${AV} (release ${AL})`)
|
|
62
86
|
|
|
63
87
|
// Resolusi dinamis paket Alpine dari CDN (lihat lib/alpine.mjs)
|
|
64
88
|
const pkg = name => alpinePkg(fetch, `https://dl-cdn.alpinelinux.org/alpine/${AV}/main/${A}`, name)
|
package/lib/alpine.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolver paket Alpine dari CDN — anti-404 saat Alpine memperbarui paket
|
|
3
|
+
* dalam satu branch. Fallback ke versi terakhir yang diketahui hidup.
|
|
4
|
+
*/
|
|
5
|
+
export function cmpVer(a: string, b: string): number {
|
|
6
|
+
const pa = a.split(/[.\-r]/).filter(Boolean).map(Number)
|
|
7
|
+
const pb = b.split(/[.\-r]/).filter(Boolean).map(Number)
|
|
8
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
9
|
+
if ((pa[i] || 0) !== (pb[i] || 0)) return (pa[i] || 0) - (pb[i] || 0)
|
|
10
|
+
}
|
|
11
|
+
return 0
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface FetchFn {
|
|
15
|
+
(url: string, options?: RequestInit): Promise<{ text: () => Promise<string> }>
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function alpinePkg(
|
|
19
|
+
fetchFn: FetchFn,
|
|
20
|
+
cdnBase: string,
|
|
21
|
+
nameEncoded: string,
|
|
22
|
+
fallback = "14.2.0-r4"
|
|
23
|
+
): Promise<string> {
|
|
24
|
+
let latest: { file: string; v: string } | null = null
|
|
25
|
+
try {
|
|
26
|
+
const res = await fetchFn(`${cdnBase}/`)
|
|
27
|
+
const idx = await res.text()
|
|
28
|
+
const re = new RegExp(`${nameEncoded}-([0-9][0-9a-zA-Z.+]*)-r([0-9]+)\\.apk`, "g")
|
|
29
|
+
for (const m of idx.matchAll(re)) {
|
|
30
|
+
const v = `${m[1]}-r${m[2]}`
|
|
31
|
+
if (!latest || cmpVer(v, latest.v) > 0) latest = { file: m[0], v }
|
|
32
|
+
}
|
|
33
|
+
} catch {}
|
|
34
|
+
if (!latest) {
|
|
35
|
+
console.warn(`[opencode-termux] listing CDN gagal — fallback ${nameEncoded}-${fallback}.apk`)
|
|
36
|
+
latest = { file: `${nameEncoded}-${fallback}.apk`, v: fallback }
|
|
37
|
+
}
|
|
38
|
+
return latest.file
|
|
39
|
+
}
|