@ryuu-reinzz/ryuu-cli 0.0.0-stage → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/CHANGELOG.md +560 -0
  2. package/LICENSE +21 -0
  3. package/README.md +1213 -2
  4. package/package.json +45 -4
  5. package/src/agent/agent.js +1194 -0
  6. package/src/agent/approvals.js +76 -0
  7. package/src/agent/context.js +106 -0
  8. package/src/agent/planner.js +237 -0
  9. package/src/agent/worklog.js +318 -0
  10. package/src/cli/app.js +463 -0
  11. package/src/cli/commands.js +1150 -0
  12. package/src/cli/input.js +287 -0
  13. package/src/cli/markdown.js +527 -0
  14. package/src/cli/prompts.js +255 -0
  15. package/src/cli/renderer.js +2155 -0
  16. package/src/cli/settings-editor.js +280 -0
  17. package/src/cli/settings-tui.js +669 -0
  18. package/src/cli/tui.js +1737 -0
  19. package/src/cli/width.js +48 -0
  20. package/src/config/load.js +132 -0
  21. package/src/config/scaffold.js +50 -0
  22. package/src/config/settings-registry.js +495 -0
  23. package/src/config/settings.js +304 -0
  24. package/src/config/validate.js +451 -0
  25. package/src/index.js +449 -0
  26. package/src/providers/catalog.js +282 -0
  27. package/src/providers/index.js +84 -0
  28. package/src/providers/nvidia.js +645 -0
  29. package/src/providers/registry.js +104 -0
  30. package/src/services/clipboard.js +38 -0
  31. package/src/services/diff.js +153 -0
  32. package/src/services/doctor.js +126 -0
  33. package/src/services/logger.js +190 -0
  34. package/src/services/preferences.js +249 -0
  35. package/src/services/processes.js +345 -0
  36. package/src/services/provider-settings.js +226 -0
  37. package/src/services/sessions.js +279 -0
  38. package/src/services/undo.js +133 -0
  39. package/src/services/ushell.js +146 -0
  40. package/src/services/websearch/manager.js +616 -0
  41. package/src/services/websearch/mcp.js +233 -0
  42. package/src/services/websearch/normalize.js +284 -0
  43. package/src/services/websearch/providers.js +178 -0
  44. package/src/services/websearch/serper.js +263 -0
  45. package/src/services/workspace.js +589 -0
  46. package/src/tools/filesystem.js +304 -0
  47. package/src/tools/git.js +114 -0
  48. package/src/tools/index.js +48 -0
  49. package/src/tools/project.js +160 -0
  50. package/src/tools/registry.js +118 -0
  51. package/src/tools/search.js +210 -0
  52. package/src/tools/shell.js +279 -0
  53. package/src/tools/url.js +542 -0
  54. package/src/tools/web.js +67 -0
  55. package/src/utils/errors.js +160 -0
  56. package/src/utils/network.js +129 -0
  57. package/src/utils/redaction.js +73 -0
  58. package/src/utils/validation.js +370 -0
  59. package/src/utils/version.js +24 -0
package/README.md CHANGED
@@ -1,3 +1,1214 @@
1
- # Temporary Holding Version
1
+ # Ryuu Agent CLI
2
+
3
+ Agen coding AI otonom yang setiap aksi tulis/eksekusi harus disetujui dulu, untuk
4
+ terminal Anda. Ryuu memeriksa workspace, menyusun rencana, memanggil tool,
5
+ menunjukkan apa yang sedang dilakukannya, dan melaporkan apa yang berubah —
6
+ didukung oleh beberapa provider AI melalui antarmuka Chat Completions yang
7
+ kompatibel dengan OpenAI: NVIDIA Integrate API dan Token Harbor sudah tersedia,
8
+ dan provider lain bisa Anda daftarkan sendiri lewat konfigurasi.
9
+
10
+ ```
11
+ ╭─ ◢ RYUU // AGENT OS ──────────────────────────────────────────── v0.3.1 ─╮
12
+ │ │
13
+ │ ██████╗ ██╗ ██╗██╗ ██╗██╗ ██╗ │
14
+ │ ██╔══██╗╚██╗ ██╔╝██║ ██╗██║ ██╗ │
15
+ │ ██████╔╝ ╚████╔╝ ██║ ██╗██║ ██╗ │
16
+ │ ██╔══██╗ ╚██╔╝ ██║ ██╝██║ ██╗ │
17
+ │ ██║ ██║ ██║ ╚██████╔╝╚██████╔╝ │
18
+ │ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ │
19
+ │ │
20
+ │ autonomous coding intelligence · NVIDIA │
21
+ │ │
22
+ ├─ ◆ TELEMETRY ────────────────────────────────────────────────────────────┤
23
+ │ model minimaxai/minimax-m3.1-flash │
24
+ │ workspace /path/to/your/project │
25
+ │ runtime Node.js v25.x · linux │
26
+ │ access ask · shell is not a sandbox │
27
+ │ log /path/to/your/project/.ryuu/logs/agent.log │
28
+ ╰──────────────────────────────────────────────────────────────────────────╯
29
+ click a block or /expand N !cmd shell ⌃C cancel /help commands /exit quit
30
+ ```
31
+
32
+ ![Contoh UI mode normal Ryuu](https://cdn.zass.in/JLdbvD233M.jpg)
33
+
34
+ ## Fitur
35
+
36
+ - **Loop agent yang eksplisit** — model → tool call tervalidasi → persetujuan →
37
+ eksekusi nyata → hasil tool → model, sampai ada jawaban akhir, pembatalan, atau
38
+ batas durasi tugas. Jumlah langkah tidak dibatasi secara bawaan;
39
+ `agent.maxSteps` menambahkan batas opsional.
40
+ - **Tool workspace yang nyata** — list, tree, read, search, file info, diff,
41
+ create, edit, write, move, delete, inspeksi Git, dan ringkasan project.
42
+ - **Manajer proses** — menjalankan command dengan stdout/stderr asli, exit code,
43
+ sinyal, durasi, timeout, buffer terbatas, server background, dan penghentian
44
+ yang bersih.
45
+ - **Gerbang persetujuan (approval gate)** — setiap aksi tulis, eksekusi, dan
46
+ destruktif diperiksa sebelum dijalankan. Tool read-only tidak pernah meminta
47
+ konfirmasi. Aksi yang ditolak dilaporkan kembali ke model.
48
+ - **HUD kaya telemetri** — banner gradien `RYUU // AGENT OS`, panel telemetri,
49
+ feed aktivitas, panel reasoning live, kartu persetujuan, dan dasbor ringkasan
50
+ run. Baris aktivitas yang selalu menyala melaporkan fase, waktu berjalan per
51
+ fase, **total waktu run, nomor langkah, tool mana yang sedang berjalan dan
52
+ berapa lagi mengantre (`writing edit_file src/x.js (2/5)`), jumlah token
53
+ prompt/completion live, dan latensi token pertama**.
54
+ Setiap pemanggilan model mencetak barisnya sendiri `◇ model 9.4s · 21.3k↑ 388↓ tok · first token 1.8s`,
55
+ command yang berjalan mengalirkan stdout/stderr ke wilayah live, setiap command
56
+ yang selesai mencetak kotak output `$ argv` bernomor (pratinjau lima baris;
57
+ klik bloknya, atau jalankan `/expand <id>`, untuk log utuh yang disimpan beserta
58
+ scrolling internal, dan `/logs <id>` sesudahnya), dan setiap perubahan file
59
+ mencetak baris `-`/`+` yang benar-benar berubah. Blok reasoning dinomori dengan
60
+ cara yang sama dan tetap bisa dibuka ulang selama sesi ini. Tempel panjang
61
+ (1000+ karakter) diringkas menjadi placeholder `> [Pasted text #N X chars]` di
62
+ composer, sementara model tetap menerima teks lengkapnya.
63
+ - **Tanpa batas retry buatan** — koneksi provider yang putus atau giliran model
64
+ kosong akan dicoba ulang dengan heartbeat di layar dan backoff eksponensial
65
+ sampai model menjawab, tenggat tugas lewat, atau Anda membatalkan. Giliran
66
+ kosong tidak pernah mengakhiri tugas dengan galat; `agent.maxRecoveryAttempts`
67
+ tetap bisa memasang batas keras.
68
+ - **Composer tetap bisa dipakai saat run** — prompt digambar di bawah wilayah
69
+ aktivitas live dan dicetak ulang di setiap baris yang dialirkan, jadi slash
70
+ command bisa diketik dan dijalankan sementara agent sedang berpikir,
71
+ menjalankan tool, atau mengalirkan jawabannya (`/status`, `/expand 4`,
72
+ `/model 2`, `/cancel`, …). Prompt tugas baru ditolak sampai run selesai,
73
+ sehingga dua run tidak bisa tumpang tindih.
74
+ - **Session worklog** — fakta terverifikasi (file yang dibaca beserta rentang
75
+ barisnya, file yang diubah, command dan exit code, temuan, rencana) disimpan
76
+ sepanjang sesi dan diteruskan ke tugas berikutnya, sehingga tindak lanjut
77
+ melanjutkan pekerjaan alih-alih mengulang analisis. File yang sudah dibaca dan
78
+ tidak berubah di disk tidak dibaca lagi; agent diberi tahu bahwa isinya sudah
79
+ diketahui.
80
+ - **Jawaban Markdown-native** — heading, bold/italic, inline code, link, list,
81
+ kutipan, code fence dengan penyorotan sintaks, dan tabel yang mengecil,
82
+ membungkus, atau berpindah ke tata letak vertikal agar sesuai lebar terminal.
83
+ - **Perlindungan workspace** — penolakan escape path/symlink, pemblokiran file
84
+ rahasia, pengecualian direktori hasil-generate, batas ukuran, penulisan
85
+ atomik, dan deteksi penulisan basi.
86
+ - **Memori lokal** — memori percakapan lintas tugas dalam satu sesi (lihat dengan
87
+ `/memory`, pulihkan yang tersimpan dengan `/resume <id>`), ID sesi dan
88
+ riwayat, transkrip yang disunting (redacted), cache ringkasan project,
89
+ pemadatan konteks, dan undo stack yang terbatas pada sesi.
90
+ - **Mode gagal yang aman** — konfigurasi hilang, input tidak valid, galat
91
+ provider, batas laju, dan tool calling yang tidak didukung menghasilkan pesan
92
+ yang bisa ditindaklanjuti alih-alih stack trace (pakai `--debug` untuk stack).
93
+ - **Mode layar penuh (`--tui`)** — antarmuka opsional bertipe retained-mode yang
94
+ menggambar ulang seluruh percakapan dari sebuah model, alih-alih menambahkan
95
+ baris ke scrollback: setiap reasoning, output `run_command`, dan jawaban punya
96
+ keadaan terbuka/tertutupnya sendiri, sehingga beberapa blok bisa dibuka
97
+ sekaligus dan yang lama tetap bisa dibuka sepanjang sesi. Composer adalah bagian
98
+ dari frame yang sama, dan transkrip menggul di dalam aplikasi
99
+ (`PgUp`/`PgDn`, roda mouse, klik untuk melipat/membuka) alih-alih sejarah milik
100
+ terminal. Blok dipisahkan baris kosong dan diberi warna (prosa putih, Markdown
101
+ dan tabel biru langit, nama tool kuning, reasoning hijau, kegagalan merah),
102
+ layar pembuka memakai banner ASCII yang sama dengan antarmuka baris, prompt
103
+ berupa gradien pink → blue `ryuu > `, dan setiap baris membungkus sesuai lebar
104
+ terminal. Tanpa flag ini tidak ada yang berubah.
105
+ - **Shell Anda sendiri (`/bash`, `!cmd`)** — jalankan sendiri sebuah command di
106
+ workspace tanpa melibatkan model: output lengkap dicetak segera, dan command,
107
+ exit code, serta outputnya disimpan sebagai giliran pengguna di sesi, sehingga
108
+ tugas berikutnya tahu apa yang Anda jalankan dan apa yang terjadi.
109
+ - **Mode plain** — dipakai otomatis ketika stdout bukan TTY, atau dengan
110
+ `--plain`.
111
+
112
+ ## Persyaratan
113
+
114
+ | Persyaratan | Detail |
115
+ | --- | --- |
116
+ | Node.js | **25.x dianjurkan dan terverifikasi.** Minimum yang didukung: 22.x (versi mayor lebih tua tidak diuji). |
117
+ | npm | 10+ (sudah menyertai Node 25). |
118
+ | OS | Linux, macOS, atau Windows. Perilaku pensinyalan proses berbeda di Windows; lihat [Perilaku proses](#perilaku-proses). |
119
+ | Jaringan | Dibutuhkan untuk panggilan model langsung. Git opsional dan hanya dipakai untuk `/diff` dan `inspect_git`. |
120
+ | Kunci provider | Dibutuhkan untuk panggilan langsung. Kunci disimpan di `config.json` di bawah `providers.<id>.apiKey` (`nvidia`, `tokenharbor`), diisi lewat `ryuu --settings`. |
121
+
122
+ Instal Node 25 dengan [nvm](https://github.com/nvm-sh/nvm):
123
+
124
+ ```bash
125
+ nvm install 25
126
+ nvm use 25
127
+ node --version # v25.x
128
+ ```
129
+
130
+ Di Windows, pakai nvm-windows atau installer resmi Node.js, lalu verifikasi dengan
131
+ `node --version` di PowerShell.
132
+
133
+ ## Instalasi dan konfigurasi
134
+
135
+ ```bash
136
+ # paket npm, versi terbaru
137
+ npm install -g @ryuu-reinzz/ryuu-cli@latest
138
+ ryuu --version
139
+
140
+ # atau tanpa instalasi global
141
+ npx @ryuu-reinzz/ryuu-cli@latest --version
142
+ ```
143
+
144
+ Dari source (untuk pengembangan):
145
+
146
+ ```bash
147
+ npm install
148
+ node src/index.js --version
149
+ ```
150
+
151
+ Setelah terpasang, simpan kunci provider di `config.json` (owner-only, di luar
152
+ repo) — file itu dibuat otomatis pada run pertama:
153
+
154
+ ```bash
155
+ ryuu --settings
156
+ # lalu isi baris providers.nvidia.apiKey dengan kunci asli
157
+ ```
158
+
159
+ Tidak ada file `.env` dan tidak ada variabel environment: **semua konfigurasi
160
+ berasal dari `config.json`**. Saat pertama dijalankan tanpa file itu, Ryuu
161
+ membuatnya otomatis berisi semua kunci yang bisa diatur dengan nilai kosong —
162
+ nilai kosong berarti memakai bawaan katalog, jadi Anda hanya perlu mengisi kunci
163
+ provider. File dibuat mode `0600`, berada di luar repo, dan tidak pernah dicetak
164
+ balik oleh CLI (API key selalu `••••••••`).
165
+
166
+ Ambil kunci NVIDIA di <https://build.nvidia.com/> dan kunci Token Harbor di
167
+ <https://tokenharbor.ai/>. Jangan pernah meng-commit kunci asli, menempelkannya
168
+ ke issue, atau memasukkannya ke log. CLI tidak pernah mencetak kunci: `/config`
169
+ menampilkan `set (redacted)`, dan log/sesi sudah melewati proses penyingkatan
170
+ (redaction). Kunci satu provider tidak pernah dikirim ke provider lain.
171
+
172
+ Pilih provider aktif secara eksplisit (opsional):
173
+
174
+ ```bash
175
+ # permanen: ubah baris provider / providerModel.<id> dengan --settings
176
+ ryuu --settings
177
+
178
+ # atau per run
179
+ ryuu --provider tokenharbor --model deepseek-v4.1-flash:free "tugas"
180
+ ```
181
+
182
+ Tanpa pilihan eksplisit, Ryuu memakai `nvidia` bila kuncinya ada; jika tidak,
183
+ provider pertama yang punya kunci. Provider yang dipilih dengan `/provider`
184
+ disimpan di `.ryuu/preferences.json` workspace itu dan menimpa `config.json`
185
+ untuk workspace tersebut.
186
+
187
+ ### Verifikasi instalasi
188
+
189
+ ```bash
190
+ ryuu --doctor # pemeriksaan offline, tanpa panggilan API
191
+ ryuu --doctor --live # satu permintaan nyata yang kecil (memakai kuota)
192
+ ```
193
+
194
+ Hasil offline yang diharapkan dengan kunci valid: runtime, config, endpoint,
195
+ workspace, direktori state, dan Git lolos; konektivitas berupa `SKIP` sampai Anda
196
+ memakai `--live`.
197
+
198
+ ## Menjalankan
199
+
200
+ ```bash
201
+ ryuu # antarmuka terminal interaktif (instalasi npm)
202
+ npm start # dari source
203
+ node src/index.js # sama saja
204
+ ```
205
+
206
+ Binary `ryuu` bisa dipakai dari direktori mana pun setelah instalasi global:
207
+
208
+ ```bash
209
+ cd /path/to/another/project
210
+ ryuu
211
+ ryuu "Summarize this project"
212
+ ```
213
+
214
+ Saat dijalankan di luar project ini, `ryuu` tetap memakai folder saat ini sebagai
215
+ workspace dan membaca `config.json` milik pengguna, sehingga kunci dan batasan
216
+ tidak perlu diulang di setiap direktori.
217
+
218
+ Di prompt interaktif, ketik tugas lalu tekan Enter sekali untuk mengirimnya. Untuk
219
+ tugas beberapa baris, akhiri baris dengan `\` lalu teruskan; baris berikutnya tanpa
220
+ `\` menyelesaikannya. Slash command (`/help`, `/model`, `/exit`, …) berjalan
221
+ segera.
222
+ Selama tugas berjalan, composer tetap aktif: slash command (beserta argumennya)
223
+ dieksekusi segera, dan hanya prompt tugas baru yang ditolak dengan
224
+ `A task is already running...` sampai run selesai atau dihentikan `/cancel`.
225
+ Berpikir, reasoning, tool call, persetujuan, dan jawaban yang dialirkan tetap
226
+ berjalan sementara output command dicetak di atas prompt.
227
+
228
+ Menjalankan satu tugas tanpa REPL:
229
+
230
+ ```bash
231
+ ryuu "List the files in src and summarize the architecture"
232
+ ryuu "Fix the failing test in tests/util.test.js, then run npm test"
233
+ echo "Explain the error in build.log" | ryuu
234
+ ```
235
+
236
+ Sesi non-interaktif tidak punya prompt untuk persetujuan, jadi penulisan dan
237
+ command **ditolak secara bawaan**. `--yes` menyetujui otomatis penulisan file
238
+ (command tetap butuh persetujuan interaktif). Pakai `--auto-approve` untuk
239
+ menyetujui otomatis **semua** aksi — termasuk command shell — agar agent bisa
240
+ menyelesaikan pekerjaan tanpa pengawasan:
241
+
242
+ ```bash
243
+ ryuu --auto-approve "Fix the failing test, then run npm test"
244
+ ```
245
+
246
+ Eksekusi shell tetap bukan sandbox; hanya pakai `--auto-approve` pada tugas yang
247
+ Anda percaya.
248
+
249
+ ### Opsi CLI
250
+
251
+ | Opsi | Fungsi |
252
+ | --- | --- |
253
+ | `-h`, `--help` | Menampilkan penggunaan. |
254
+ | `-v`, `--version` | Menampilkan versi. |
255
+ | `--task "..."` | Tugas sekali jalan eksplisit (tugas posisional juga bisa). |
256
+ | `--workspace <path>` | Override workspace untuk run ini. |
257
+ | `--provider <id>` | Override provider untuk run ini, mis. `tokenharbor`. |
258
+ | `--model <id>` | Override model untuk run ini. |
259
+ | `--max-steps <n>` | Batas langkah agent opsional (`0` = tanpa batas, bawaan). |
260
+ | `-y`, `--yes` | Menyetujui otomatis penulisan file untuk sesi ini. |
261
+ | `--read-only` | Menolak setiap aksi tulis, eksekusi, dan destruksi. |
262
+ | `--doctor` | Diagnostik offline. |
263
+ | `--live` | Bersama `--doctor`: satu permintaan konektivitas nyata. |
264
+ | `--settings` | Editor pengaturan global baris bernomor (tanpa menjalankan agent). |
265
+ | `--settings --tui` | Editor pengaturan layar penuh (butuh TTY interaktif). |
266
+ | `--settings --list` | Mencetak pengaturan yang bisa diedit lalu keluar. |
267
+ | `--tui` | UI layar penuh retained-mode (butuh TTY interaktif; diabaikan dengan `--plain`). |
268
+ | `--plain` | Output plain, tanpa spinner atau gaya ANSI. |
269
+ | `--no-color` | Menonaktifkan warna. |
270
+ | `--debug` | Log debug dan stack trace. |
271
+
272
+ ### Pengaturan global (`ryuu --settings`)
273
+
274
+ Semua pengaturan milik pengguna tinggal di satu file yang terbaca dari direktori
275
+ kerja mana pun — tidak ada `.env` dan tidak ada variabel environment:
276
+
277
+ ```bash
278
+ ryuu --settings # editor baris bernomor
279
+ ryuu --settings --tui # editor layar penuh (butuh TTY)
280
+ ryuu --settings --list # cetak daftar, tanpa masuk mode edit
281
+ ryuu --settings --help # bantuan khusus pengaturan
282
+ ```
283
+
284
+ Lokasi file mengikuti konvensi platform dan bisa dialihkan dengan
285
+ `RYUU_CONFIG_DIR` (berguna di mesin tanpa home tetap atau di test):
286
+
287
+ | Platform | Lokasi bawaan |
288
+ | --- | --- |
289
+ | Linux/BSD | `$XDG_CONFIG_HOME/ryuu/config.json` atau `~/.config/ryuu/config.json` |
290
+ | macOS | `~/Library/Application Support/ryuu/config.json` |
291
+ | Windows | `%APPDATA%\ryuu\config.json` |
292
+
293
+ **Editor baris.** Daftar bernomor menampilkan nilai saat ini, dengan API key
294
+ selalu disamarkan (`••••••••`). Perintahnya: `<nomor> <nilai baru>`, `/list`,
295
+ `/show <nomor>`, `/reset <nomor>` (meminta konfirmasi), `/help`, dan `/exit`.
296
+ Nilai boleh mengandung spasi; semuanya setelah nomor dipertahankan. Setiap
297
+ perubahan divalidasi lalu **langsung disimpan** (read-modify-write), jadi
298
+ terminal yang tertutup mendadak tidak menghilangkan perubahan yang sudah lolos.
299
+ Jika penulisan gagal, editor menyebut nilai itu `NOT saved` dan mempertahankan
300
+ nilai valid terakhir; satu kunci tidak pernah menghapus kunci lain atau properti
301
+ yang tidak dikenal. `/reset` adalah cara mengosongkan kunci rahasia — tidak
302
+ perlu memasukkan placeholder.
303
+
304
+ **Editor TUI.** `--settings --tui` membuka layar alternatif (bukan agent): daftar
305
+ di kiri/atas, panel deskripsi dan batasan di bawahnya, `↑↓`/`PgUp`/`PgDn` untuk
306
+ navigasi, `←/→` untuk memilih provider dan model katalog (mengetik tetap bisa
307
+ memasukkan id khusus), `Enter` membuka/menyimpan, `Esc` membatalkan edit (dan
308
+ `Esc` lagi keluar), `Ctrl+C` keluar. Raw mode, layar alternatif, dan listener
309
+ selalu dipulihkan saat keluar, termasuk setelah galat; tanpa TTY perintah gagal
310
+ dengan pesan jelas dan tidak pernah menjalankan agent.
311
+
312
+ **Presedensi** (terkuat lebih dulu): flag CLI (`--provider`, `--model`,
313
+ `--max-steps`) → `.ryuu/preferences.json` workspace → `config.json` → bawaan
314
+ katalog. Perubahan file berlaku pada run berikutnya. Satu-satunya variabel
315
+ environment yang masih dibaca adalah `RYUU_CONFIG_DIR`, yang menentukan letak
316
+ file ini.
317
+
318
+ **Dibuat otomatis.** Jika file belum ada, run pertama (termasuk `--doctor`)
319
+ langsung membuatnya berisi `version`, `provider`, `providers`,
320
+ `providerModel`, `extraProviders`, `agent`, dan `webSearch` dengan setiap kunci
321
+ bernilai kosong. Nilai kosong divalidasi sebagai bawaan, sehingga file yang baru
322
+ dibuat tidak mengubah perilaku apa pun; file yang sudah ada — termasuk yang JSON-
323
+ nya rusak — tidak pernah ditimpa.
324
+
325
+ **Provider dan model.** Baris kunci, base URL, dan model dibuat dari katalog
326
+ provider yang sama dengan runtime. Provider tambahan dari `extraProviders`
327
+ (lihat [Menambah provider sendiri](#menambah-provider-sendiri-extraproviders))
328
+ otomatis mendapat baris `providers.<id>.apiKey`, `providers.<id>.baseURL`, dan
329
+ `providerModel.<id>`.
330
+
331
+ **Keamanan, backup, dan pemulihan.** File ditulis atomik (file sementara di
332
+ direktori yang sama, fsync, lalu rename) dengan izin `0600` pada POSIX; path yang
333
+ berupa symlink ditolak. JSON yang rusak **tidak pernah ditimpa**: mode
334
+ `--settings` berhenti dengan pesan yang menyebut path-nya, sedangkan startup
335
+ normal hanya memberi peringatan lalu memakai bawaan. Untuk
336
+ memulihkan, perbaiki JSON-nya atau pindahkan file ke samping, lalu jalankan
337
+ `ryuu --settings` lagi. Hanya nilai yang Anda ubah yang ditulis; kunci yang
338
+ tidak ada memakai bawaan. Jangan meng-commit file ini — lokasinya di luar repo,
339
+ tetapi tetap berisi kredensial.
340
+
341
+ **Pemecahan masalah.** `Permission denied` berarti direktori tidak bisa ditulis:
342
+ arahkan `RYUU_CONFIG_DIR` ke lokasi yang bisa ditulis. Nilai yang ditolak
343
+ menyebut batasannya (mis. `agent.maxSteps must be between 0 and 100000`), dan
344
+ kesalahan format JSON menyebut path serta tidak mengubah file.
345
+
346
+ ### UI layar penuh (`--tui`)
347
+
348
+ ```bash
349
+ ryuu --tui
350
+ ```
351
+
352
+ ![Contoh UI mode TUI Ryuu](https://cdn.zass.in/5hjUEOV2xB.jpg)
353
+
354
+ Antarmuka bawaan menambahkan baris ke scrollback terminal dan melupakan setiap
355
+ barisnya. `--tui` justru menyimpan percakapan sebagai data: setiap item punya id
356
+ stabil, tanda terbuka/tertutup, dan teks lengkap di memori, dan setiap frame
357
+ digambar dari model tersebut. Di sinilah perilaku blok menjadi mungkin:
358
+
359
+ | Aksi | Hasil |
360
+ | --- | --- |
361
+ | Klik sebuah prompt (`▸ TASK … ⧉ copy`) | Menyalin prompt itu ke clipboard terminal. |
362
+ | Klik sebuah blok kode (header `js … ⧉ copy`) | Menyalin isi blok kode itu — hanya kodenya, tanpa penanda fence — ke clipboard terminal. Berlaku di UI baris maupun `--tui`. |
363
+ | Klik blok reasoning / `run_command` / shell | Menukar keadaan terbuka/tertutup di tempat; setiap blok menyimpan keadaan sendiri, sehingga beberapa bisa tetap terbuka. |
364
+ | `PgUp`, `PgDn`, roda mouse | Menggul transkrip di dalam aplikasi (scrollback terminal sendiri tidak dipakai). |
365
+ | `/expand <id>` | Membuka blok yang sudah tergulir keluar dari tampilan. `/expand` tanpa id mendaftarkannya. |
366
+ | `Esc` | Melipat setiap blok yang terbuka, atau melompat kembali ke bawah. |
367
+ | `Enter` | Mengirim baris; `↑`/`↓` memanggil riwayat, `←`/`→`, `Home`/`End` menyunting composer. |
368
+ | `Ctrl+Y` | Menyalin prompt yang terakhir Anda kirim. `/copy [n]` menyalin yang lebih lama (`n` = berapa prompt ke belakang). |
369
+ | `Ctrl+C` | Membatalkan tugas atau command shell yang berjalan; dua kali untuk keluar. |
370
+
371
+ #### Tampilan dan rasa
372
+
373
+ Setiap blok dibuka dengan header kecil (`▸ TASK`, `◆ response`, `⚙ Using tools`,
374
+ `◈ reasoning`, `◆ PLAN`, `▸ run_command`) dan blok dipisahkan baris kosong
375
+ serta garis pada gradien **pink → violet → biru langit**, sehingga perubahan jenis
376
+ terlihat sekilas. Tool call berurutan berbagi satu blok dan satu header
377
+ `⚙ Using tools` (dengan jumlah pemanggilan) tanpa pembatas di antaranya, karena
378
+ keduanya aktivitas sejenis. Warna membawa jenis baris:
379
+
380
+ | Baris | Warna |
381
+ | --- | --- |
382
+ | Header blok (`⚙ Using tools`, `◈ reasoning`, `◆ response`) | Gradien pink → blue (tool), putih tebal (response), glif hijau (reasoning) |
383
+ | Jawaban assistant dan prosa biasa | Putih |
384
+ | Heading, daftar, link, inline code, code fence, tabel Markdown | Biru langit |
385
+ | Header tugas, rencana, prompt persetujuan | Violet / amber |
386
+ | Isi reasoning (`◈ reasoning #N`) | Hijau, dengan baris status putih (`chars · live · partial`) |
387
+ | Nama tool (`✓ read_file`, `▸ run_command #N`) | Kuning |
388
+ | Nama tool yang gagal (`✕ run_command`) | Merah tebal, dengan outputnya merah-redup |
389
+ | Baris command `$` dan header shell | Biru langit |
390
+ | Galat runtime dan aksi yang ditolak | Merah / amber |
391
+ | Input composer | Kelabu, dengan `ryuu` pada gradien pink → blue dan ` > ` sesudahnya |
392
+
393
+ Pertanyaan (persetujuan, kelanjutan) dicetak di atas composer alih-alih
394
+ menggantikan prompt. Status bar mewarnai setiap segmen secara terpisah (fase,
395
+ langkah, tool, token, posisi gulir). Semuanya — telemetri banner, output command,
396
+ diff, pemberitahuan, dan composer — membungkus sesuai lebar terminal alih-alih
397
+ terpotong atau tergulir keluar dari tampilan. Palet naik ke truecolor ketika
398
+ terminal mendukungnya (`--no-color` tetap mematikannya).
399
+
400
+ Tombol salin (`⧉ copy` di setiap prompt dan blok kode, `Ctrl+Y`, atau `/copy [n]`) menulis teks
401
+ dengan **OSC 52**, sehingga terminal itulah yang memiliki clipboard: tanpa proses
402
+ anak `pbcopy`/`xclip`/`clip.exe`, dan bisa dipakai lewat SSH. Terminal yang tidak
403
+ menerapkan OSC 52 mengabaikan sekuensnya; status bar tetap melaporkan apa yang
404
+ dikirim beserta jumlah karakternya.
405
+
406
+ Versi yang tampil di banner, header TUI, dan `--version` dibaca dari
407
+ `package.json` saat runtime, sehingga sebuah rilis hanya perlu menaikkan file itu.
408
+
409
+ Ia butuh TTY sungguhan. Dengan `--plain`, output yang dipipe, atau sesi
410
+ non-interaktif, flag diabaikan dengan peringatan dan antarmuka baris normal
411
+ dipakai.
412
+
413
+ ### Menjalankan shell Anda sendiri (`/bash`, `!cmd`)
414
+
415
+ Baris yang dimulai dengan `!` adalah command shell, bukan prompt:
416
+
417
+ ```text
418
+ !git status
419
+ !npm test
420
+ /bash npm test # hal yang sama, ditulis panjang
421
+ ```
422
+
423
+ Command berjalan di workspace dengan izin Anda sendiri (tanpa prompt
424
+ persetujuan, tidak ada yang dikirim ke model), dan stdout/stderr yang tersimpan
425
+ dicetak lengkap — pratinjau `run_command` memang untuk tool call milik model,
426
+ bukan untuk shell Anda sendiri. Output yang sangat panjang menyimpan ekor
427
+ hasil tangkapan dan menyatakan bahwa ia terpotong.
428
+
429
+ Command, exit code, durasi, dan outputnya ditambahkan ke sesi sebagai giliran
430
+ pengguna, sehingga tugas berikutnya sudah tahu apa yang Anda jalankan, apa yang
431
+ dicataknya, dan apakah gagal. `/memory` menampilkan giliran itu; `/clear`
432
+ membuangnya.
433
+
434
+ `--read-only` menolak baris shell, dan `Ctrl+C` membatalkan command yang masih
435
+ berjalan (seluruh grup proses disinyalkan, sehingga `npm test` tidak selamat
436
+ darinya).
437
+
438
+ ## Di dalam CLI
439
+
440
+ Apa pun yang tidak dimulai dengan `/` dikirim ke agent sebagai tugas; apa pun
441
+ yang dimulai dengan `!` dijalankan sebagai command shell Anda sendiri.
442
+
443
+ | Command | Keterangan | Catatan |
444
+ | --- | --- | --- |
445
+ | `/help [command]` | Mendaftarkan command atau menampilkan detail. | |
446
+ | `/exit` | Keluar dan menghentikan proses anak. | Juga `Ctrl+C` dua kali. |
447
+ | `/clear` | Melupakan memori percakapan agar tugas berikutnya mulai bersih. | Sesi tersimpan tidak disentuh. |
448
+ | `/memory [clear]` | Menampilkan apa yang diingat agent antar-tugas. | Dibatasi `agent.maxContextChars`. |
449
+ | `/worklog` | Menampilkan fakta terverifikasi (file dibaca/diubah, command, temuan) yang akan dipakai agent alih-alih membaca ulang. | Dihapus oleh `/clear`. |
450
+ | `/resume <session-id>` | Memulihkan memori percakapan sebuah sesi tersimpan. | Daftar id dengan `/history`. |
451
+ | `/status` | Provider, model, endpoint, status kunci, workspace, batas, jumlah proses/undo, tugas terakhir. | |
452
+ | `/provider [user <name> \| add <name> <url> [apiKey] \| del <name>]` | Mendaftarkan provider beserta status kunci, berpindah provider, atau menambah/menghapus provider sendiri. | Riwayat percakapan dan worklog tetap. Pilihan disimpan di `.ryuu/preferences.json`; `--provider` menimpa per run. Provider sendiri disimpan di `config.json`. |
453
+ | `/model [n]` | Mendaftarkan model milik provider aktif secara bernomor, atau berpindah model menurut nomor. | Disimpan per provider di `.ryuu/preferences.json`; tetap ada setelah restart. `--model` menimpa per run. |
454
+ | `/addmodel <provider> <model-id>` | Menambahkan id model ke daftar `/model` milik satu provider. | Provider sendiri menyimpannya di `config.json`; provider bawaan di `.ryuu/preferences.json`. Id tidak diperiksa ke server: id salah akan gagal saat dipakai, dan itu risiko pengguna. |
455
+ | `/delmodel <provider> <model-id>` | Membuang id yang ditambahkan lewat `/addmodel` dari satu provider. | Model bawaan katalog tidak bisa dibuang. Membuang model saat ini tetap menyimpannya untuk sesi ini. |
456
+ | `/cmodel <name>` | Mengeset id model apa pun langsung, tanpa menambahkannya ke daftar. | Alias: `/custommodel`. Aturan tanpa-pemeriksaan sama seperti `/addmodel`. |
457
+ | `/workspace` | Menampilkan satu workspace yang boleh disentuh sesi ini. | Berpindah di tengah sesi sengaja tidak didukung. |
458
+ | `/files [path]` | Mendaftarkan sebuah direktori. | File rahasia/direktori hasil-generate disembunyikan. |
459
+ | `/tree [path]` | Pohon direktori dengan batas. | |
460
+ | `/tools` | Tool terdaftar, risiko, kebutuhan persetujuan. | |
461
+ | `/copy [n]` | Menyalin prompt yang sudah Anda kirim ke clipboard terminal. | `n` menghitung mundur dari yang terbaru (bawaan: yang terakhir). Bekerja di kedua antarmuka; memakai OSC 52. |
462
+ | `/bash <command>` | Menjalankan sendiri command shell di workspace. | Output lengkap; command dan output masuk ke konteks sesi. Sama dengan baris yang dimulai `!`. |
463
+ | `/web [status\|providers\|test\|enable <id>\|disable <id>]` | Status pencarian web multi-provider, daftar kapabilitas, uji koneksi, dan aktif/nonaktif sesi. | `test` hanya memeriksa koneksi dan penemuan tool; tidak menjalankan kueri pencarian. |
464
+ | `/processes` | Proses yang berjalan dan terbaru. | |
465
+ | `/logs [id]` | Output proses, atau lokasi file log. | |
466
+ | `/expand [block-id]` | Mendaftarkan blok reasoning/`run_command`, atau membuka satu secara penuh. | Bisa juga dengan mengklik blok; `Esc` atau klik lain menutup panel. Blok tetap tersedia sepanjang sesi. |
467
+ | `/cancel [process-id]` | Membatalkan tugas aktif atau menghentikan proses. | Bekerja saat tugas berjalan; command tetap tersedia sepanjang waktu. |
468
+ | `/history` | Sesi tersimpan terbaru. | Disunting (redacted), lokal saja. |
469
+ | `/sessions [list\|show <id>\|delete <id>\|clear]` | Mengelola riwayat sesi. | `clear` meminta konfirmasi di mode interaktif. |
470
+ | `/diff` | Diff perubahan agent, atau `git diff` sebagai cadangan. | |
471
+ | `/undo [confirm]` | Mengembalikan perubahan file terakhir oleh agent. | Lihat [Undo](#undo). |
472
+ | `/config` | Konfigurasi efektif, kunci disunting. | |
473
+ | `/doctor [live]` | Diagnostik; `live` membuat satu permintaan API. | |
474
+
475
+ ### Provider dan model (`/provider`, `/model`, `/addmodel`, `/delmodel`)
476
+
477
+ Provider adalah endpoint API beserta kuncinya; model adalah id yang dikirim ke
478
+ endpoint itu. Daftar keduanya dideklarasikan di katalog provider, bukan di loop
479
+ agent, sehingga menambah model atau provider tidak menyentuh agent core.
480
+
481
+ | Provider | Nama | Base URL | Kunci | Model bawaan | Kapabilitas yang diumumkan |
482
+ | --- | --- | --- | --- | --- | --- |
483
+ | `nvidia` | NVIDIA Integrate API | `https://integrate.api.nvidia.com/v1` | `providers.nvidia.apiKey` | `minimaxai/minimax-m3.1-flash` (ke-6 di `/model`) | streaming, tools, reasoning, multimodal |
484
+ | `tokenharbor` | Token Harbor | `https://tokenharbor.ai/v1` | `providers.tokenharbor.apiKey` | `deepseek-v4.1-flash:free` | streaming, tools, reasoning |
485
+
486
+ Memilih provider dan model:
487
+
488
+ ```bash
489
+ ryuu --provider tokenharbor # default model provider itu
490
+ ryuu --provider tokenharbor --model deepseek-v4.1-flash:free
491
+ ```
492
+
493
+ Di dalam sesi:
494
+
495
+ ```text
496
+ /provider # daftar provider, status kunci, model bawaan
497
+ /provider add river https://api.river.ai/v1 rv_… # tambah provider OpenAI-compatible
498
+ /addmodel river deepseek-ai/DeepSeek-V4.1-Flash-1017 # tambah model untuk provider itu
499
+ /provider user river # pindah provider; riwayat & worklog tetap
500
+ /provider del river # hapus provider yang Anda tambahkan
501
+ /model # daftar model provider aktif
502
+ /model 2 # pindah model menurut nomor
503
+ /addmodel nvidia model-baru # tambah id ke provider bawaan
504
+ /delmodel nvidia model-baru # buang id yang ditambahkan
505
+ ```
506
+
507
+ - `/provider` hanya menampilkan `key set (redacted)` atau
508
+ `no key (providers.<id>.apiKey)`; nilai kunci tidak pernah dicetak. Provider
509
+ tanpa kunci tidak bisa dipilih dan pesannya menyebut baris `config.json` yang
510
+ harus diisi.
511
+ - Berpindah provider tidak menghapus memori percakapan atau worklog. Pilihan
512
+ provider dan model per provider disimpan di `.ryuu/preferences.json` milik
513
+ workspace saat ini; `--provider`/`--model` hanya berlaku untuk satu run dan
514
+ tidak menulis pilihan tersimpan.
515
+ - Hanya format id yang divalidasi (huruf, angka, `. _ : / -`, sampai 200 karakter,
516
+ maksimal 100 id tambahan **per provider**). Ketersediaan, kuota, ukuran
517
+ konteks, dan dukungan tool calling model **tidak** diperiksa. Daftar model
518
+ adalah id yang ditawarkan CLI, bukan jaminan model itu aktif di server.
519
+ - `/provider add <name> <url> [apiKey]` menyimpan provider ke `extraProviders`
520
+ di `config.json` dan kuncinya ke `providers.<name>.apiKey`. Argumen ketiga
521
+ opsional; tanpa itu, isi kuncinya lewat `ryuu --settings`. Memanggil `add`
522
+ lagi dengan nama yang sama dan kunci baru akan memperbarui kuncinya.
523
+ - `/provider del <name>` hanya menghapus provider yang Anda tambahkan, beserta
524
+ kunci dan model tersimpannya. Provider bawaan (`nvidia`, `tokenharbor`) dan
525
+ provider yang sedang aktif tidak bisa dihapus; pindah dulu dengan
526
+ `/provider user <name>`.
527
+ - Provider baru boleh belum punya model. Tambahkan lewat
528
+ `/addmodel <name> <model-id>`; model pertama menjadi default provider itu.
529
+ - Entri katalog bawaan tidak bisa dihapus, dan id yang sudah ada tidak
530
+ ditambahkan dua kali.
531
+ - `/cmodel <name>` memilih id apa pun untuk provider aktif tanpa menambahkannya
532
+ ke daftar; `/help addmodel` dan `/help delmodel` menampilkan tutorial yang sama
533
+ di dalam CLI.
534
+
535
+ #### Menambah provider sendiri (`extraProviders`)
536
+
537
+ Provider tambahan didaftarkan sebagai array JSON di `config.json`. Semua provider
538
+ memakai protokol Chat Completions yang kompatibel OpenAI:
539
+
540
+ ```json
541
+ {
542
+ "extraProviders": [
543
+ {
544
+ "id": "myproxy",
545
+ "name": "My Proxy",
546
+ "baseURL": "https://proxy.example/v1",
547
+ "defaultModel": "model-b",
548
+ "models": ["model-a", { "id": "model-b", "name": "Model B" }],
549
+ "capabilities": { "tools": true, "streaming": true, "reasoning": false }
550
+ }
551
+ ],
552
+ "providers": { "myproxy": { "apiKey": "…" } }
553
+ }
554
+ ```
555
+
556
+ Cara termudah adalah dari dalam sesi — perintahnya menulis bentuk yang sama:
557
+
558
+ ```text
559
+ /provider add myproxy https://proxy.example/v1 sk-…
560
+ /addmodel myproxy model-a
561
+ /provider user myproxy
562
+ ```
563
+
564
+ Kuncinya disimpan seperti provider lain, yaitu `providers.myproxy.apiKey`, dan
565
+ bisa diisi lewat `ryuu --settings` setelah provider terdaftar.
566
+
567
+ - `id`, `name`, dan `baseURL` wajib. `models` boleh kosong saat baru dibuat dan
568
+ diisi lewat `/addmodel`; `id` tidak boleh menabrak provider bawaan.
569
+ - `capabilities` menentukan apakah agent mengirim tools (`tools`), meminta
570
+ stream (`streaming`), dan menampilkan reasoning bila provider mengirimnya
571
+ (`reasoning`). Jangan menyalakan `tools` untuk endpoint yang tidak mendukung
572
+ `tool_calls`: agent akan menampilkan keterbatasan dan tetap bisa menjawab,
573
+ tetapi tidak bisa membaca atau mengubah file.
574
+ - Setelah ditambahkan, provider muncul di `/provider` dan bisa dipilih dengan
575
+ `/provider user myproxy` (alias lama `/provider myproxy`) atau
576
+ `--provider myproxy`.
577
+ - Tidak ada fallback otomatis antar-provider AI. Kegagalan provider aktif
578
+ dilaporkan apa adanya (dengan retry terbatas untuk kegagalan sesaat) agar
579
+ kunci dan data tidak berpindah ke provider lain tanpa keputusan Anda.
580
+
581
+ ## Tool yang tersedia untuk model
582
+
583
+ Model hanya bisa memanggil tool yang terdaftar di allowlist lokal. Setiap
584
+ pemanggilan divalidasi skemanya sebelum dieksekusi.
585
+
586
+ | Tool | Risiko | Persetujuan | Tujuan |
587
+ | --- | --- | --- | --- |
588
+ | `list_files` | read | tidak | Daftar direktori terfilter dan berpaginasi. |
589
+ | `tree` | read | tidak | Pohon direktori dengan batas. |
590
+ | `read_file` | read | tidak | Teks UTF-8; rentang baris yang diminta dialirkan, jadi juga bekerja untuk file di atas batas baca. |
591
+ | `file_info` | read | tidak | Ukuran, jenis, timestamp, jumlah baris. |
592
+ | `search_files` | read | tidak | Pencarian nama/isi dengan pengecualian. |
593
+ | `diff_file` | read | tidak | Diff terpadu tanpa menulis apa pun. |
594
+ | `inspect_git` | read | tidak | Status/diff Git bila tersedia. |
595
+ | `project_summary` | read | tidak | Manifes dan struktur dari file sungguhan. |
596
+ | `web_search` | read | tidak | Hasil web dari provider MCP/REST terkonfigurasi, dengan fallback otomatis. Terdaftar ketika setidaknya satu provider tersedia (You.com dan Firecrawl bisa dipakai tanpa kunci). |
597
+ | `read_url` | read | tidak | Mengambil halaman http(s) publik dan mengekstrak teks yang bisa dibaca. Selalu tersedia. |
598
+ | `create_file` | tulis | ya | File baru; menimpa file lama hanya dengan `overwrite`. |
599
+ | `write_file` | tulis | ya | Penulisan seluruh file dengan penjaga mtime opsional. |
600
+ | `edit_file` | tulis | ya | Penggantian terarah unik, mengembalikan diff. |
601
+ | `move_file` | destruktif | ya | Memindah/mengganti nama di dalam workspace. |
602
+ | `delete_file` | destruktif | ya | Menghapus file atau direktori kosong; `confirm: true` wajib. |
603
+ | `run_command` | exec | ya | Menjalankan argv, menunggu, menangkap output/exit/durasi. |
604
+ | `start_process` | exec | ya | Server/watcher di background. |
605
+ | `list_processes` | read | tidak | Daftar proses. |
606
+ | `get_process_output` | read | tidak | Output terbatas untuk sebuah proses. |
607
+ | `stop_process` | exec | ya | Penghentian bersih, lalu pemutusan paksa. |
608
+
609
+ Katalog tool juga ditulis ke dalam system prompt (nama, argumen, gerbang
610
+ persetujuan, tujuan satu baris), bersama skrip npm milik project itu sendiri dan
611
+ tata letak modulnya, sehingga model memilih tool yang benar alih-alih keluar ke
612
+ `grep` untuk sesuatu yang sudah dikerjakan `search_files`.
613
+
614
+ ### Pencarian web (MCP multi-provider dengan fallback)
615
+
616
+ `web_search` tetap satu tool bagi model, tetapi didukung oleh `WebSearchManager`
617
+ yang memiliki beberapa provider dan mencobanya sesuai urutan yang bisa
618
+ dikonfigurasi. Setiap provider dinormalkan ke bentuk yang sama: `results`
619
+ (`title`, `url`, `snippet`, `date` opsional), `answer_box`, `people_also_ask`,
620
+ dan `related_searches`. `depth: \"deep\"` meminta dua kali lipat hasil (maksimal
621
+ 10).
622
+
623
+ | Provider | Transport | Kunci | Kapabilitas | Catatan |
624
+ | --- | --- | --- | --- | --- |
625
+ | You.com MCP | Streamable HTTP | opsional (`webSearch.youApiKey`) | pencarian (tanpa kunci); ekstraksi/konten dengan kunci | Tanpa kunci memakai `?profile=free`; penemuan langsung mengembalikan `you-search`, `you-discover`. |
626
+ | Firecrawl MCP | Streamable HTTP | opsional (`webSearch.firecrawlApiKey`) | pencarian + scrape | Tingkat tanpa kunci punya batas kredit harian; penemuan langsung mengembalikan `firecrawl_search`, `firecrawl_scrape`, `firecrawl_parse`. |
627
+ | DuckDuckGo MCP | stdio | tidak ada | pencarian + ekstrak (sebagaimana diumumkan server) | Server komunitas opsional (`uvx free-search-mcp`, MIT). **Nonaktif secara bawaan** karena menjalankan kode pihak ketiga; butuh `uv` terpasang. |
628
+ | Tavily MCP | Streamable HTTP | wajib (`webSearch.tavilyApiKey`) | pencarian + ekstrak | Endpoint remote butuh kunci; tanpa itu provider dilewati, tidak pernah dihubungi. |
629
+ | Serper REST | HTTPS | wajib (`webSearch.serperApiKey`) | pencarian | Backend asli; ikut dalam rantai fallback yang sama. |
630
+
631
+ Bagaimana ia berperilaku:
632
+
633
+ - **Koneksi malas.** Tidak ada yang menghubungkan pada saat start. Sebuah
634
+ provider dihubungkan saat pertama dipakai, tool-nya ditemukan dengan
635
+ `tools/list`, dan koneksinya dipakai ulang selama sisa sesi. `/web test`
636
+ hanya melakukan handshake + penemuan (tanpa kueri).
637
+ - **Pemetaan tool, bukan nama yang di-hard-code.** Tool pencarian/ekstraksi
638
+ dipilih menurut nama, deskripsi, dan skema input, sehingga sebuah server bisa
639
+ mengganti nama tool-nya tanpa membuat agent rusak. Argumen wajib diisi dari
640
+ skema tool itu sendiri (`query`, `q`, `url`, `urls`, `limit`, `formats`,
641
+ `objective`).
642
+ - **Fallback hanya untuk kegagalan sungguhan.** Timeout, jaringan/DNS, HTTP
643
+ 401/403/429/5xx, kegagalan handshake/penemuan, tool yang hilang, skema salah,
644
+ dan payload tidak valid semuanya pindah ke provider berikutnya. Hasil kosong
645
+ yang valid dicatat dan boleh juga mencoba provider berikutnya, tetapi tidak
646
+ dilaporkan sebagai kegagalan. Pembatalan tidak pernah memicu fallback.
647
+ - **Retry terbatas.** Maksimal `webSearch.mcpMaxRetries` retry per provider untuk
648
+ kegagalan *pemanggilan* sesaat (bukan handshake), dengan backoff eksponensial
649
+ dan jitter. `429` langsung diteruskan ke provider berikutnya alih-alih
650
+ membekukan agent. Dua kegagalan berturut-turut membuka circuit breaker 60
651
+ detik untuk provider itu.
652
+ - **Satu kegagalan bertipe.** Jika semua provider gagal, tool melempar
653
+ `SEARCH_ALL_PROVIDERS_FAILED` (atau `SEARCH_EXTRACT_FAILED`) dengan daftar
654
+ alasan singkat per provider; agent tetap berjalan tanpa pencarian web alih-alih
655
+ crash dan diberi tahu untuk tidak mengarang hasil.
656
+ - **Terminal menampilkan fallback**, bukan hanya pemenangnya: satu pohon `WEB
657
+ SEARCH` ringkas per pencarian dengan `✓`/`✕`/`!`/`○`, durasi, dan alasan
658
+ singkat. Stack trace tetap di log debug; kunci dan header otorisasi tidak
659
+ pernah dicetak.
660
+ - **Fallback ekstraksi halaman.** `read_url` tetap mengambil secara lokal lebih
661
+ dulu. Jika pengambilan lokal gagal (jaringan, timeout, 401/403, jenis tidak
662
+ didukung) atau tidak mengekstrak teks dari halaman yang dirender JavaScript,
663
+ sebuah provider berkemampuan ekstraksi (Firecrawl/Tavily) dicoba dan hasilnya
664
+ menyebutkan provider mana yang menghasilkan teksnya.
665
+ - **Keamanan.** Kueri dan URL keluar dari mesin; file tidak. Teks halaman adalah
666
+ data tak terpercaya, bukan instruksi. Server MCP stdio mendapat environment
667
+ minimal (PATH/HOME/TMP), bukan kunci NVIDIA atau pencarian Anda. Server MCP
668
+ tidak pernah diberi akses filesystem atau shell oleh agent ini.
669
+ - **Privasi dan kuota.** Firecrawl tanpa kunci punya batas kredit harian yang
670
+ kecil; manajer tidak pernah menguji dengan kueri sungguhan saat start. `/web
671
+ test` secara eksplisit menyatakan tidak ada kueri yang dijalankan. Nonaktifkan
672
+ semuanya dengan `webSearch.enabled: false` atau `webSearch.providers: []`.
673
+ - **Worklog:** setiap pencarian dicatat (`web_search \"...\" -> N result(s) ·
674
+ top: ...`), sehingga tugas lanjutan memakai temuan yang ada alih-alih membayar
675
+ kueri yang sama.
676
+
677
+ ### Membaca sebuah halaman (`read_url`)
678
+
679
+ `read_url` mengambil satu URL http(s) dan mengembalikan teks hasil ekstraksi,
680
+ judul, dan meta deskripsinya, sehingga model bisa memverifikasi snippet
681
+ pencarian terhadap halaman sumbernya.
682
+
683
+ - **Selalu tersedia.** Tidak butuh kunci, ia tool read-only, dan tidak pernah
684
+ meminta konfirmasi.
685
+ - **Terbatas.** Batas badan 2 MB, `max_chars` 1.000–50.000 (bawaan 20.000),
686
+ timeout 15 detik, dan maksimal 5 redirect.
687
+ - **Skema diperiksa.** Hanya http/https; URL dengan kredensial tertanam ditolak,
688
+ setiap hop redirect diperiksa ulang, dan penurunan https→http ditolak.
689
+ - **Dijaga SSRF.** Alamat loopback, privat, link-local, CGNAT, multicast, dan
690
+ metadata cloud ditolak, begitu pula `localhost`, `*.local`, `*.internal`, dan
691
+ hostname ber-label tunggal. Pengambilan sungguhan melakukan resolusi DNS lebih
692
+ dulu dan menolak bila ada alamat privat; setiap redirect diperiksa ulang. DNS
693
+ rebinding dikendalikan tetapi tidak dihilangkan sepenuhnya tanpa agen HTTP
694
+ ber-IP-pinned.
695
+ - **Teks saja.** `text/*`, HTML/XHTML, JSON, dan XML dibaca; gambar, PDF, dan
696
+ biner lain mengembalikan `READ_URL_UNSUPPORTED`.
697
+ - **Fallback ekstraksi MCP.** Jika pengambilan lokal tidak bisa membaca halaman
698
+ (galat jaringan, timeout, konten terblokir/tidak didukung) atau tidak
699
+ mengekstrak teks dari halaman yang dirender JavaScript, sebuah provider
700
+ pencarian web berkemampuan ekstraksi dicoba; hasilnya menyebutkan provider di
701
+ `provider` dan menetapkan `extraction: \"mcp\"`.
702
+ - **Tidak ada JavaScript secara lokal.** Halaman yang dirender sisi-klien bisa
703
+ saja tidak mengembalikan teks; hasilnya menyatakan hal itu alih-alih
704
+ berpura-pura halaman itu kosong, dan fallback MCP di atas bisa memulihkannya.
705
+ - **Konten tak terpercaya.** Prompt memperlakukan teks yang diambil sebagai
706
+ data, bukan instruksi, sehingga sebuah halaman tidak bisa mengalihkan
707
+ perilaku agent.
708
+ - **Privasi:** URL diminta dari mesin Anda, jadi situs itu melihat IP Anda.
709
+ - **Worklog:** setiap pengambilan dicatat (`read_url <url> -> HTTP 200 · N chars`).
710
+
711
+ ### Model prompt dan konteks
712
+
713
+ Protokol provider ini tidak punya keadaan (stateless): setiap permintaan
714
+ membawa seluruh percakapan. Ryuu membuat hal itu stabil dan murah alih-alih
715
+ berpura-pura sebaliknya.
716
+
717
+ - System prompt dirakit **sekali per sesi** dan dipakai ulang byte-per-byte. Ia
718
+ identik pada setiap langkah (sehingga prefix caching provider bisa kena) dan
719
+ tidak pernah tumbuh seiring berjalannya tugas.
720
+ - Setiap tugas menambahkan tepat satu pesan `TASK BRIEF`: tugas, sisa batas
721
+ langkah dan waktu, serta worklog sesi. Itulah satu-satunya blok instruksi
722
+ per-tugas; model tidak pernah diberi tahu ulang untuk \"mulai dengan rencana\".
723
+ - Hasil tool tetap berada di percakapan selama langkah-langkah yang
724
+ membutuhkannya. Worklog membawa fakta terverifikasi (file yang dibaca beserta
725
+ rentang baris, file yang diubah, command dan exit code, temuan, rencana) ke
726
+ tugas-tugas berikutnya, sehingga tindak lanjut melanjutkan pekerjaan alih-alih
727
+ memulai lagi. File yang sudah dibaca dan tidak berubah di disk tidak dibaca
728
+ lagi: agent menerima balasan `cached: true` yang memberi tahu bahwa isinya
729
+ sudah diketahui.
730
+ - `finish_reason: \"length\"` ditangani: pemanggilan tool yang terpotong pada
731
+ batas token output dibuang dan dilaporkan, tidak pernah dieksekusi sebagai
732
+ file yang tertulis setengah jalan.
733
+ - Pada 70% anggaran waktu, model diberi tahu untuk menyatu (konvergen), dan
734
+ aliran respons yang tidak mengirim byte selama
735
+ `agent.streamIdleTimeoutMs` dibatalkan dengan `STREAM_STALLED` alih-alih
736
+ menahan langkah tetap terbuka.
737
+
738
+ ## Model persetujuan dan izin
739
+
740
+ Kebijakannya diatur dengan `agent.approvalPolicy`:
741
+
742
+ | Risiko | `ask` (bawaan) | `auto` | `read-only` |
743
+ | --- | --- | --- | --- |
744
+ | read | izinkan | izinkan | izinkan |
745
+ | tulis | prompt | izinkan | tolak |
746
+ | exec | prompt | prompt | tolak |
747
+ | destruktif | prompt | prompt | tolak |
748
+
749
+ - `--yes` menyetujui otomatis **hanya penulisan**; eksekusi dan aksi destruktif
750
+ tetap memakai prompt.
751
+ - Di sesi non-interaktif tidak ada prompt, jadi aksi yang butuh persetujuan
752
+ ditolak dan penolakannya dilaporkan ke model.
753
+ - Persetujuan ditegakkan di loop agent, bukan di dalam tool masing-masing,
754
+ sehingga model tidak bisa melewatinya dengan memanggil tool secara langsung.
755
+ - Prompt persetujuan menampilkan tool, tingkat risiko, argumen, dan pratinjau
756
+ (diff atau command beserta direktori kerjanya) sebelum Anda menjawab.
757
+ - Penilaian risiko adalah sinyal UX best-effort, **bukan batas keamanan**.
758
+
759
+ ### Undo
760
+
761
+ `/undo` memakai snapshot before/after yang tepat, direkam oleh proses agent ini
762
+ untuk `create_file`, `write_file`, `edit_file`, `move_file`, dan `delete_file`. Ia
763
+ menolak mengembalikan perubahan jika file berubah setelah suntingan agent,
764
+ alih-alih menimpa pekerjaan Anda. Cakupannya mencakup perubahan yang dibuat pada
765
+ tugas/sesi saat ini dan bukan pengganti kontrol versi. Pakai `git` untuk sejarah
766
+ yang tahan lama.
767
+
768
+ ## Referensi konfigurasi
769
+
770
+ Semua pengaturan berasal dari satu file `config.json` (lihat
771
+ [Pengaturan global](#pengaturan-global-ryuu---settings)). Tidak ada `.env` dan
772
+ tidak ada variabel environment; flag CLI menimpa nilai file untuk satu run.
773
+ Workspace tetap ditentukan per run (cwd atau `--workspace`), bukan dari file
774
+ global ini. Nilai bawaan divalidasi saat start; nilai tidak valid membuat proses
775
+ gagal sebelum ada panggilan API.
776
+
777
+ | Kunci `config.json` | Bawaan | Makna |
778
+ | --- | --- | --- |
779
+ | *(kosong)* | bawaan katalog | Nilai kosong pada kunci mana pun berarti "pakai bawaan" di kolom Bawaan. |
780
+ | `provider` | — | Provider aktif (`nvidia`, `tokenharbor`, atau id dari `extraProviders`). Bila kosong: `nvidia` kalau kuncinya ada, jika tidak provider pertama yang punya kunci. |
781
+ | `providerModel.<id>` | model bawaan provider | Model untuk provider itu. Menang atas bawaan katalog. |
782
+ | `providers.<id>.apiKey` | — | Kunci provider. Tidak pernah dicetak, ditulis owner-only. |
783
+ | `providers.<id>.baseURL` | URL bawaan provider | Base URL kompatibel OpenAI. |
784
+ | `extraProviders` | — | Array provider tambahan yang kompatibel OpenAI (lihat [Menambah provider sendiri](#menambah-provider-sendiri-extraproviders)). |
785
+ | `agent.logLevel` | `info` | `debug`, `info`, `warn`, `error`, `silent`. |
786
+ | `agent.maxSteps` | `0` | Batas langkah agent opsional (0 = tanpa batas, 1–100000). |
787
+ | `agent.approvalPolicy` | `ask` | `ask`, `auto`, `read-only`. |
788
+ | `agent.maxOutputTokens` | `4096` | Batas token output (64–32768). Nilai tinggi bisa ditolak atau mahal. |
789
+ | `agent.requestTimeoutMs` | `120000` | Timeout per permintaan (1s–10menit). |
790
+ | `agent.streamIdleTimeoutMs` | `45000` | Membatalkan aliran respons yang tidak mengirim byte selama ini (0 menonaktifkan). |
791
+ | `agent.maxRetries` | `3` | Retry untuk kegagalan sesaat (0–10). |
792
+ | `agent.maxRecoveryAttempts` | `0` | Percobaan sambung ulang saat tautan provider putus. `0` = tanpa batas (coba sampai tenggat tugas atau `/cancel`); 1–1000 membatasinya. |
793
+ | `agent.recoveryBackoffMs` | `1000` | Jeda dasar antar-percobaan sambung ulang (100ms–60s). |
794
+ | `agent.maxFileBytes` | `262144` | Batas baca per file (1KB–5MB). |
795
+ | `agent.maxSearchResults` | `50` | Batas hasil pencarian (1–500). |
796
+ | `agent.maxToolOutputBytes` | `32768` | Batas hasil tool yang dikirim balik ke model. |
797
+ | `agent.maxTaskDurationMs` | `900000` | Batas waktu nyata tugas (10s–24jam). |
798
+ | `agent.maxContextChars` | `120000` | Ambang pemadatan konteks lokal. |
799
+ | `agent.allowShell` | `false` | Mengizinkan sintaks shell mentah (tetap bergerbang persetujuan). |
800
+ | `agent.debug` | `false` | Log debug dan stack trace. |
801
+ | `agent.historyDir` | `<workspace>/.ryuu/sessions` | Penyimpanan sesi, relatif ke workspace. |
802
+ | `agent.logDir` | `<workspace>/.ryuu/logs` | Penyimpanan log (`agent.log`, dirotasi pada 5MB), relatif ke workspace. |
803
+ | `webSearch.enabled` | `true` | Mendaftarkan `web_search` sama sekali. `false` membuangnya bersama fallback ekstraksi `read_url`. |
804
+ | `webSearch.fallback` | `true` | Mencoba provider berikutnya setelah kegagalan. `false` berhenti pada kandidat pertama. |
805
+ | `webSearch.providers` | `serper` (bila ada kunci) → `you` → `firecrawl` → `duckduckgo` → `tavily` | Urutan provider; `[]` atau `none` menonaktifkan semuanya. |
806
+ | `webSearch.disabled` | `[]` | Daftar id provider yang dilewati. `/web enable`/`disable` hanya mengubah sesi saat ini. |
807
+ | `webSearch.mcpTimeoutMs` | `6000` | Timeout handshake, penemuan, dan pemanggilan per percobaan (1s–30s). |
808
+ | `webSearch.mcpMaxRetries` | `1` | Retry per provider untuk kegagalan pemanggilan sesaat (0–5). |
809
+ | `webSearch.debug` | `false` | Diagnostik provider tambahan di log (tanpa kunci). |
810
+ | `webSearch.youApiKey` | — | Kunci You.com opsional; tanpa kunci memakai profil pencarian gratis. |
811
+ | `webSearch.firecrawlApiKey` | — | Kunci Firecrawl opsional; tanpa kunci memakai tingkat kredit gratis. |
812
+ | `webSearch.tavilyApiKey` | — | Wajib untuk Tavily; tanpa itu Tavily dilewati. |
813
+ | `webSearch.duckduckgoEnabled` | `false` | Menjalankan server stdio `uvx free-search-mcp` lokal saat pertama dipakai. |
814
+ | `webSearch.duckduckgoCommand` / `webSearch.duckduckgoArgs` | `uvx` / `free-search-mcp` | Command untuk provider stdio lokal. |
815
+ | `webSearch.serperApiKey` | — | Kunci Serper-kompatibel; menambahkan Serper ke rantai fallback. |
816
+ | `webSearch.endpoint` | `https://google.serper.dev/search` | Endpoint SERP (`http`/`https`). |
817
+ | `webSearch.authHeader` | `X-API-KEY` | `X-API-KEY` atau `Authorization` (Bearer). |
818
+ | `webSearch.results` | `5` | Hasil per kueri (1–10); `deep` menggandakan sampai batas. |
819
+ | `webSearch.timeoutMs` | `15000` | Timeout permintaan Serper (1s–60s). |
820
+
821
+ State lokal (`.ryuu/`) di-gitignore dan dikecualikan dari operasi file agent.
822
+
823
+ ## Arsitektur
824
+
825
+ ```
826
+ src/
827
+ index.js Entri CLI, penguraian argumen, validasi start
828
+ cli/
829
+ app.js menghubungkan UI, agent, tool, provider, proses, storage
830
+ commands.js slash command
831
+ renderer.js banner, feed aktivitas, baris telemetri, diff, ringkasan
832
+ tui.js layar retained-mode --tui: model transkrip, tata letak, input
833
+ markdown.js Markdown terminal, tabel sadar-lebar, blok kode
834
+ prompts.js sesi prompt readline dan penanganan Ctrl+C
835
+ agent/
836
+ agent.js loop pemanggilan-tool eksplisit dan ringkasan bukti
837
+ approvals.js penegakan kebijakan persetujuan
838
+ context.js pemadatan konteks dengan batas
839
+ planner.js prompt sesi, task brief, ekstraksi rencana, ringkasan
840
+ worklog.js fakta sesi terverifikasi yang dipakai ulang antar langkah dan tugas
841
+ providers/
842
+ catalog.js katalog provider + model, kapabilitas, validasi id, extraProviders
843
+ registry.js registry provider dinamis (factory per id)
844
+ index.js pembuatan provider aktif dan resolusi pilihan provider/model
845
+ nvidia.js klien SDK OpenAI (dipakai semua provider), normalisasi, retry, pemetaan galat
846
+ tools/
847
+ registry.js allowlist, skema, risiko, antarmuka plugin
848
+ filesystem.js baca/tulis/edit/pindah/hapus/diff file
849
+ search.js pencarian nama dan isi
850
+ git.js inspeksi git read-only
851
+ project.js ringkasan manifes/project
852
+ shell.js tool command dan proses background
853
+ url.js read_url: pengambilan halaman terbatas, ekstraksi teks, fallback MCP
854
+ web.js web_search: satu tool di atas manajer multi-provider
855
+ services/
856
+ websearch/
857
+ manager.js urutan provider, fallback, retry, circuit breaker, normalisasi
858
+ mcp.js klien MCP (Streamable HTTP + stdio), timeout, galat bertipe
859
+ providers.js preset provider dan deskriptor berbasis config
860
+ normalize.js normalisasi hasil pencarian/ekstraksi yang agnostik provider
861
+ serper.js provider REST Serper-kompatibel (backend yang sudah ada)
862
+ workspace.js keamanan path, penulisan atomik, pemblokiran rahasia
863
+ ushell.js /bash dan !cmd: tangkapan shell pengguna, group kill, env bersih
864
+ processes.js spawn, buffer streaming, timeout, stop, log
865
+ sessions.js penyimpanan sesi yang disunting dan cache ringkasan
866
+ undo.js snapshot before/after dengan pemeriksaan basi
867
+ diff.js diff terpadu dan statistik
868
+ logger.js log bertingkat, disunting, berotasi
869
+ doctor.js diagnostik tanpa rahasia
870
+ config/ pemuatan dan validasi env
871
+ utils/ galat, validasi skema, redaksi, penjaga jaringan (SSRF),
872
+ versi (dibaca dari package.json — satu-satunya sumber versi)
873
+ tests/
874
+ unit/ uji modul fokus
875
+ integration/ uji proses CLI sungguhan + aplikasi terhubung
876
+ ```
877
+
878
+ Tool call dieksekusi **secara berurutan**, sehingga dua pemanggilan tidak bisa
879
+ berlomba pada file yang sama. Setiap hasil tool ditambahkan ke percakapan dalam
880
+ format tool-call OpenAI dengan `tool_call_id`-nya yang sesuai.
881
+
882
+ ## Model keamanan
883
+
884
+ Apa yang dilakukan implementasinya:
885
+
886
+ - Meresolusi setiap path terhadap realpath workspace dan menolak traversal
887
+ serta escape symlink. Path absolut di luar workspace ditolak.
888
+ - Memblokir `.env`, `.env.local`, private key/sertifikat, penyimpan kredensial
889
+ umum (`.ssh`, `.aws`, `.gnupg`, `.kube`, `.docker`, `.npmrc`, `id_rsa*`, ...),
890
+ serta `*.pem`/`*.key`/`*.p12`/`*.pfx`/`*.jks`/`*.keystore` secara bawaan.
891
+ `.env.example`, `.env.sample`, `.env.template`, dan `.env.dist` tetap bisa
892
+ dibaca sebagai dokumentasi.
893
+ - Mengecualikan `node_modules`, `.git`, `dist`, `build`, `coverage`, dan
894
+ direktori hasil-generate sejenis dari daftar/pencarian secara bawaan; tool bisa
895
+ memasukkannya secara eksplisit.
896
+ - `read_url` hanya menerima http/https, menolak URL dengan kredensial tertanam,
897
+ memeriksa ulang setiap hop redirect (https→http ditolak), hanya membaca jenis
898
+ konten tekstual, dan membatasi badan hingga 2 MB.
899
+ - Menegakkan batas file, hasil pencarian, output tool, konteks, langkah,
900
+ serta durasi tugas.
901
+ - Memakai penulisan atomik file-temp + rename, mempertahankan CRLF vs LF, dan
902
+ bisa membatalkan ketika mtime file berubah sejak pemeriksaan.
903
+ - Menjalankan command dengan `spawn(command, args, { shell: false })` dan array
904
+ argumen. Sintaks shell mentah dinonaktifkan kecuali `agent.allowShell: true`,
905
+ dan selalu bergerbang persetujuan dengan command serta direktori kerjanya
906
+ ditampilkan persis.
907
+ - Membuang variabel yang tampak seperti kredensial (`*API_KEY*`, `*TOKEN*`,
908
+ `*SECRET*`, `*PASSWORD*`, ...) dari environment proses anak.
909
+ - Menyunting (redact) kunci API yang dikonfigurasi dan pola token dari log,
910
+ output command, file sesi, dan pesan galat.
911
+ - Membatasi isi sesi/transkrip yang dipersistensi dan menyimpannya di bawah
912
+ `.ryuu/` dengan izin khusus pemilik bila didukung.
913
+
914
+ Apa yang **tidak** dilakukannya:
915
+
916
+ - **Eksekusi shell bukan sandbox.** Command yang disetujui berjalan dengan izin
917
+ pengguna Anda dan bisa membaca atau mengubah apa pun yang bisa dilakukan
918
+ pengguna itu.
919
+ - Label risiko persetujuan hanyalah heuristik. Periksa command persisnya sebelum
920
+ menyetujui.
921
+ - Ia tidak memasang dependensi otomatis, mendorong commit, men-*deploy*, atau
922
+ meningkatkan hak istimewa. Command yang disetujui tetap bisa melakukan hal-hal
923
+ itu — itu keputusan Anda.
924
+ - Ia tidak mengindeks atau mengunggah seluruh workspace; hanya file yang dibaca
925
+ model yang dikirim ke provider.
926
+ - Ia tidak mengarang atau merekonstruksi rantai-pikir privat. Saat provider
927
+ mengalirkan delta `reasoning_content`, Ryuu menampilkan teks itu langsung di
928
+ kotak tertutup yang lebarnya sesuai dengan pratinjau lima baris. Setiap
929
+ giliran mendapat id stabil; klik sebuah blok (atau jalankan `/expand <id>`)
930
+ untuk membuka ulang teks lengkapnya, dan Esc atau klik lain menutup panel.
931
+ Teks lengkapnya juga ditulis ke `.ryuu/logs/reasoning/`. Satu baris aktivitas
932
+ yang selalu menyala berada di bagian bawah UI percakapan dan membawa seluruh
933
+ keadaan run: fase, waktu berjalan per fase, total waktu run, langkah, tool
934
+ yang sedang berjalan beserta posisi antriannya, jumlah token live, dan latensi
935
+ token pertama; output command yang berjalan mengalirkan ekornya tepat di atas
936
+ baris itu. Jawaban akhir tidak dikotak: ia dicetak langsung sebagaimana
937
+ provider menulisnya, dengan Markdown inline (bold, italic, code, link)
938
+ dirender live, header ATX (`###`) dirender tebal tanpa tanda pagarnya, dan
939
+ tabel pipe / blok kode bersal die render sebagai blok segera setelah
940
+ selesai dialirkan. Token kontrol chat-template yang bocor dibuang; jawaban
941
+ tidak dirender ulang setelahnya.
942
+
943
+ ## Pengujian dan pengembangan
944
+
945
+ ```bash
946
+ npm test # 300+ uji unit + integrasi (tanpa jaringan, tanpa kunci asli)
947
+ npm run test:unit
948
+ npm run test:integration
949
+ npm run check # pemeriksaan sintaks di seluruh src/, scripts/, tests/
950
+ npm run verify # check + test
951
+ ```
952
+
953
+ Uji memakai runner bawaan Node `node:test`, workspace sementara, provider palsu
954
+ terskrip, dan `fetch` yang disuntikkan untuk uji tingkat-SDK. Mereka tidak pernah
955
+ memanggil NVIDIA.
956
+
957
+ Pemeriksaan *smoke* paket opsional:
958
+
959
+ ```bash
960
+ npm pack --dry-run
961
+ ```
962
+
963
+ ## Pemecahan masalah
964
+
965
+ ### `Provider nvidia has no API key` / `Provider tokenharbor has no API key`
966
+
967
+ Pesan itu menyebut provider aktif dan path kunci yang diharapkan
968
+ (`providers.<id>.apiKey`). Isi lewat `ryuu --settings` (baris
969
+ `providers.nvidia.apiKey` atau `providers.tokenharbor.apiKey`). Jalankan
970
+ `ryuu --doctor` untuk melihat provider mana yang aktif dan path kunci mana yang
971
+ belum terisi. Provider tanpa kunci tidak bisa dipilih lewat `/provider`.
972
+
973
+ ### 401 / 403
974
+
975
+ `401` berarti kunci hilang, tidak valid, atau sudah dicabut. `403` biasanya
976
+ berarti kunci tidak punya akses ke model terpilih. Periksa path kunci yang
977
+ disebut pesan galat (`providers.nvidia.apiKey` atau
978
+ `providers.tokenharbor.apiKey`), verifikasi
979
+ akses model di dashboard provider, lalu jalankan `ryuu --doctor --live`.
980
+ Ryuu tidak mencoba kunci provider lain sebagai pengganti.
981
+
982
+ ### 404
983
+
984
+ Periksa model provider aktif (`--model`, `/model`, atau `providerModel.<id>`) dan
985
+ base URL-nya (`providers.<id>.baseURL`). Base URL harus berupa root yang
986
+ kompatibel dengan OpenAI, biasanya berakhiran `/v1`.
987
+
988
+ ### Model tidak tersedia / tidak ditemukan
989
+
990
+ Daftar model adalah id yang ditawarkan CLI, bukan jaminan model itu aktif di
991
+ server. Untuk Token Harbor, model `deepseek-v4.1-flash:free` bisa berubah atau
992
+ dibatasi ketersediaannya. Cek `/provider` dan `/model`, lalu coba model lain atau
993
+ hubungi provider. Ryuu menolak id dengan karakter tidak valid sebelum mengirim
994
+ apa pun, tetapi tidak melakukan pemeriksaan ketersediaan ke server.
995
+
996
+ ### 429 / batas laju
997
+
998
+ Kuota dan batas laju ditentukan provider dan tidak dijamin tanpa batas (termasuk
999
+ untuk model bertanda `:free`). Tunggu lalu coba lagi, perkecil ukuran
1000
+ konteks/tugas, atau pindah model/provider. Ryuu mencoba ulang kegagalan sesaat
1001
+ `429`/`5xx`/jaringan dengan backoff eksponensial terbatas beserta jitter, serta
1002
+ menghormati `Retry-After` bila ada.
1003
+
1004
+ ### Model bawaan sangat lambat atau habis waktu
1005
+
1006
+ Model bawaan sekarang `minimaxai/minimax-m3.1-flash` (entri ke-6 di `/model`).
1007
+ Endpoint `deepseek-ai/deepseek-v4.1-flash` dihapus dari bawaan karena sering timeout di sisi provider. Forum NVIDIA punya laporan terbuka tentang
1008
+ respons 3–5+ menit untuk model ini, jadi ini latensi sisi-provider, bukan
1009
+ kegagalan autentikasi. `GET /v1/models` dan
1010
+ `GET /v1/models/deepseek-ai/deepseek-v4.1-flash` tetap berhasil dengan kunci yang
1011
+ valid.
1012
+
1013
+ Pakai model berkemampuan-tool yang cepat sebagai solusi, tanpa mengubah bawaan
1014
+ project:
1015
+
1016
+ ```bash
1017
+ ryuu --model z-ai/glm-5.3 "your task"
1018
+ # atau set "providerModel": { "nvidia": "z-ai/glm-5.3" } di config.json
1019
+ ```
1020
+
1021
+ Jika Anda tetap memakai model deepseek, naikkan `agent.requestTimeoutMs` dan
1022
+ turunkan `agent.maxRetries` agar satu langkah lambat tidak berulang selama
1023
+ banyak menit. Periksa status terkini di <https://build.nvidia.com/> sebelum
1024
+ menganggap kunci rusak.
1025
+
1026
+ ### "Model may not support tool calling"
1027
+
1028
+ Ryuu butuh dukungan `tool_calls` bergaya OpenAI pada endpoint terpilih. Coba
1029
+ model berkemampuan-tool lewat `providerModel.nvidia` atau `/model`. Tanpa tool call,
1030
+ agent tetap bisa menjawab secara percakapan, tetapi tidak bisa memeriksa atau
1031
+ mengubah file.
1032
+
1033
+ ### Galat jaringan / timeout
1034
+
1035
+ Periksa DNS, proxy, dan akses firewall ke base URL. Naikkan
1036
+ `agent.requestTimeoutMs` untuk endpoint lambat. `--debug` menampilkan
1037
+ diagnostik yang sudah disunting.
1038
+
1039
+ ### Sebuah langkah menggantung tanpa output terlihat
1040
+
1041
+ Jika aliran berhenti mengirim byte, Ryuu membatalkannya setelah
1042
+ `agent.streamIdleTimeoutMs` (bawaan 45s) dan melaporkan `STREAM_STALLED`;
1043
+ langkah hanya dicoba ulang ketika belum ada apa pun yang dicetak. Baris aktivitas
1044
+ selalu menunjukkan waktu berjalan, langkah, dan jumlah token, sehingga
1045
+ penggantunganan terlihat berbeda dari respons lambat yang tetap berkembang.
1046
+ Naikkan nilainya untuk endpoint yang memang lambat, set `0` untuk menonaktifkan
1047
+ watchdog, lalu periksa `/status` untuk rata-rata per-pemanggilan.
1048
+
1049
+ ### Tautan terputus di tengah run (heartbeat)
1050
+
1051
+ Saat permintaan provider gagal dengan galat yang bisa dipulihkan (jaringan,
1052
+ timeout, `5xx`, `429`), agent tidak menyerah: ia menampilkan **heartbeat** di
1053
+ feed aktivitas (`♥ heartbeat · reconnecting · attempt 2 (unlimited) in 1.0s`)
1054
+ dan memutar ulang permintaan dengan backoff eksponensial (maksimal 30 detik
1055
+ antar-percobaan). Baris `♥ link restored` menandai saat provider menjawab lagi,
1056
+ dan run melanjutkan dari tempat ia berhenti. Baris aktivitas tetap berputar
1057
+ selama penantian, sehingga terminal tidak terlihat membeku.
1058
+
1059
+ `agent.maxRecoveryAttempts: 0` (bawaan) berarti **tanpa batas percobaan**: agent
1060
+ terus menyambung ulang sampai provider menjawab, tenggat tugas tercapai, atau
1061
+ Anda menekan `Ctrl+C`/mengirim `/cancel`. Set nilai positif (1–1000) bila Anda
1062
+ menginginkan penghentian keras. Hanya kegagalan yang tidak bisa dicoba ulang
1063
+ (seperti kunci API tidak valid atau galat validasi permintaan `400`) yang
1064
+ mengakhiri run seketika.
1065
+
1066
+ ### Model terus menjawab tanpa isi
1067
+
1068
+ Giliran model kosong diperlakukan sebagai percobaan ulang, bukan galat. Agent
1069
+ menambahkan dorongan ("your previous response was empty… call the next tool or
1070
+ give the final answer"), menampilkan
1071
+ `♥ heartbeat · empty response · attempt 3 (unlimited) in 2.0s`, menunggu dengan
1072
+ backoff yang dimulai dari `agent.recoveryBackoffMs` dan tumbuh hingga 15s,
1073
+ lalu bertanya lagi dengan nomor langkah yang sama — sehingga batas opsional
1074
+ `agent.maxSteps` tidak dikonsumsi oleh percobaan ulang. Tidak ada kegagalan
1075
+ `EMPTY_RESPONSE`: run hanya berakhir saat model menjawab, tenggat tugas lewat,
1076
+ atau Anda membatalkan. Jika ini terjadi terus-menerus, kemungkinan endpoint
1077
+ sedang mengembalikan aliran kosong (periksa `/status`, file log, atau coba model
1078
+ lain dengan `/model`).
1079
+
1080
+ Setiap percobaan ulang dicatat (`empty model response; retrying`) dengan nomor
1081
+ percobaan dan jedanya, dan jumlah percobaan hanya dibatasi oleh tenggat tugas.
1082
+
1083
+ ### "must be a valid JSON object string" (HTTP 400)
1084
+
1085
+ Provider memvalidasi setiap tool call di percakapan pada setiap permintaan, jadi
1086
+ dulu satu payload terpotong bisa menggagalkan permintaan *berikutnya* dan
1087
+ membunuh seluruh run. Ryuu kini memperbaiki payload terpotong (menutup
1088
+ string/array/objek yang masih terbuka) dan mengeksekusi pemanggilannya; jika
1089
+ payload tidak bisa diperbaiki, pemanggilan yang rusak diganti dengan `{}` dalam
1090
+ sejarah tersimpan dan model diberi tahu untuk mengirimnya ulang. Permintaan
1091
+ disederhanakan sekali lagi di sisi provider, sehingga payload rusak tidak lagi
1092
+ bisa sampai ke API.
1093
+
1094
+ ### Sebuah tool terus gagal
1095
+
1096
+ Mengulang pemanggilan gagal yang sama tidak bisa menghasilkan informasi baru.
1097
+ Setelah dua kegagalan identik, agent memblokir yang ketiga dan memberi tahu model
1098
+ untuk mengganti tool, argumen, atau melaporkan temuannya. Command yang keluar
1099
+ dengan `1` tanpa output juga dihitung kegagalan — itu biasanya berarti "tidak ada
1100
+ yang cocok", bukan "coba lagi".
1101
+
1102
+ ### Galat panjang konteks
1103
+
1104
+ Mulai sesi baru, persempit tugas, turunkan `agent.maxContextChars`, atau
1105
+ kecilkan `agent.maxToolOutputBytes`. Pemadatan lokal mempertahankan tugas,
1106
+ rencana, file yang berubah, command, dan masalah yang menggantung, tetapi tidak
1107
+ bisa melampaui konteks provider.
1108
+
1109
+ ### Ketidaksesuaian versi runtime
1110
+
1111
+ Node 25.x adalah target terverifikasi; Node 22+ diterima dengan peringatan di
1112
+ doctor. Versi lebih lama gagal saat start. Tingkatkan dengan
1113
+ `nvm install 25 && nvm use 25`.
1114
+
1115
+ ### Di mana lognya?
1116
+
1117
+ `.ryuu/logs/agent.log` di dalam workspace (dirotasi pada 5MB). Jalankan dengan
1118
+ `--debug` untuk stack trace dan metadata provider yang verbose. Log sudah
1119
+ disingkat, tetapi tinjau dulu sebelum dibagikan.
1120
+
1121
+ ### Perilaku proses
1122
+
1123
+ Di Linux/macOS, proses anak berjalan di grup prosesnya sendiri; menghentikannya
1124
+ menyinyalkan grup itu, lalu meningkat ke `SIGKILL` setelah timeout. Di Windows,
1125
+ grup proses dan semantik sinyal berbeda: Ryuu menghentikan PID anak dan
1126
+ mengandalkan perilaku bawaan Node, sehingga cucu proses yatim masih mungkin.
1127
+ Utamakan `start_process`/`stop_process` untuk dev server dan verifikasi pembersihan
1128
+ secara manual di Windows.
1129
+
1130
+ ## Keterbatasan yang diketahui
1131
+
1132
+ - **Pengujian API NVIDIA langsung membutuhkan kunci Anda.** Uji otomatis repositori
1133
+ ini sepenuhnya offline. Validasi langsung pada 2026-10-20 lolos dengan
1134
+ `z-ai/glm-5.3` (tool call sungguhan + jawaban akhir); model DeepSeek bawaan
1135
+ sedang habis waktu di sisi provider saat itu. Jalankan ulang
1136
+ `ryuu --doctor --live` untuk kunci dan region Anda.
1137
+ - Perilaku model, ukuran konteks, kuota gratis, batas laju, dan dukungan tool
1138
+ calling bergantung pada provider/model dan bisa berubah. Kapabilitas provider
1139
+ (`streaming`, `tools`, `reasoning`) hanya dipakai untuk memutuskan apa yang
1140
+ dikirim; reasoning tetap hanya ditampilkan bila provider mengirimnya.
1141
+ - Daftar model di katalog hanyalah id yang ditawarkan CLI. Ryuu tidak memeriksa
1142
+ ketersediaan model ke server, dan tidak melakukan fallback otomatis ke
1143
+ provider AI lain saat provider aktif gagal — itu mencegah kunci/data berpindah
1144
+ tanpa keputusan Anda, tetapi berarti Anda harus memilih provider lain dengan
1145
+ `/provider` atau `--provider`.
1146
+ - Token Harbor menyediakan model `deepseek-v4.1-flash:free`; Ryuu tidak menjamin
1147
+ layanan itu gratis tanpa batas. Kuota, ketersediaan, dan harga mengikuti
1148
+ ketentuan Token Harbor.
1149
+ - Respons dialirkan dan dikumpulkan sisi-klien: reasoning muncul langsung di
1150
+ kotaknya dan jawaban akhir dicetak langsung saat tiba (tanpa kotak, tanpa
1151
+ render ulang). Mode plain/non-TTY menyembunyikan aliran langsung dan hanya
1152
+ melaporkan jumlah karakter reasoning.
1153
+ - Tool call berjalan berurutan, bukan paralel. Prompt meminta model untuk
1154
+ menggabungkan pemanggilan independen ke dalam satu respons (beberapa baca
1155
+ sekaligus) untuk memotong bolak-balik.
1156
+ - Transport-nya stateless, jadi prompt dikirim ulang dengan setiap permintaan. Ia
1157
+ dirakit sekali dan identik byte-per-byte, tidak dibangun ulang; brief per-tugas
1158
+ adalah satu-satunya bagian yang bervariasi.
1159
+ - Worklog adalah lembar fakta ringkas, bukan isi file. Ia menghentikan pembacaan
1160
+ ulang dan analisis ulang yang redundan, tetapi file besar tetap harus dibaca
1161
+ sekali.
1162
+ - `--tui` menyimpan seluruh transkrip di memori karena setiap frame digambar ulang
1163
+ darinya. Tangkapan per-blok dibatasi, tetapi sesi yang sangat panjang tumbuh
1164
+ seiring jumlah blok; belum ada pencarian dalam aplikasi atau mode salin, dan
1165
+ pengubahan ukuran mengalirkan kembali frame dari model alih-alih mempertahankan
1166
+ tata letak piksel sebelumnya.
1167
+ - Penyalinan clipboard memakai OSC 52. Sebagian terminal menonaktifkannya, dan
1168
+ tmux perlu `set -g set-clipboard on`; ketika sekuensnya diabaikan, clipboard
1169
+ mempertahankan isi lamanya, sementara `/copy` tetap melaporkan jumlah karakter
1170
+ yang dikirim.
1171
+ - `/bash` dan `!cmd` sengaja melewati prompt persetujuan: Anda yang mengetik
1172
+ commandnya, jadi ia berjalan dengan izin Anda sendiri. `--read-only` adalah
1173
+ satu-satunya yang memblokirnya.
1174
+ - `/undo` terbatas pada sesi/tugas dan menolak saat file berubah di bawahnya.
1175
+ - Sesi disimpan lokal dan bukan log kejadian lengkap yang bisa diputar ulang.
1176
+ - Tidak ada sandbox atau isolasi kontainer bawaan.
1177
+ - Sintaks shell mentah harus diaktifkan secara eksplisit dan disetujui; penulisan
1178
+ atomik tidak melindungi dari proses eksternal yang crash di tengah penulisan.
1179
+ - Render Markdown mencakup sintaks umum; gambar, tabel bersarang, dan HTML
1180
+ kompleks ditampilkan sebagai teks.
1181
+ - `read_url` memakai ekstraktor HTML bawaan kecil: halaman yang dirender
1182
+ JavaScript, PDF, dan gambar tidak bisa dibaca, dan teks hasil ekstraksi adalah
1183
+ penyederhanaan dari halaman tersebut.
1184
+ - Ini project 0.3.1: harapkannya masih berkekakuan. Tidak ada klaim bebas bug.
1185
+
1186
+ ## Daftar periksa validasi API langsung
1187
+
1188
+ Dengan kunci asli yang dikonfigurasi:
1189
+
1190
+ 1. `ryuu --doctor` → semua pemeriksaan non-konektivitas lolos, kunci tidak
1191
+ dicetak.
1192
+ 2. `ryuu --doctor --live` → `provider.connectivity` lolos dan melaporkan
1193
+ latensi untuk provider aktif. Jika model bawaan habis waktu, konfirmasi dengan
1194
+ `--model z-ai/glm-5.3` sebelum menganggap kunci buruk (lihat bagian
1195
+ pemecahan masalah latensi).
1196
+ 3. Token Harbor (dengan `providers.tokenharbor.apiKey`): `ryuu --doctor --provider
1197
+ tokenharbor --live` → `provider.catalog` menandai `tokenharbor*` dan
1198
+ konektivitas lolos bila endpoint-nya dapat dijangkau. Bila provider tidak
1199
+ dapat dijangkau, itu dilaporkan sebagai kegagalan, bukan dianggap berhasil.
1200
+ 4. `npm link && ryuu` → banner menampilkan model, provider, workspace, versi
1201
+ Node. `/provider` menampilkan status kunci tanpa nilainya.
1202
+ 5. Tanya: `List the files in this project and summarize what it does.` → amati
1203
+ pemanggilan `list_files`/`read_file` sungguhan dan ringkasan akhir.
1204
+ 6. Tanya: `Add a comment to the top of <file> and show me the diff.` → setujui
1205
+ diff → konfirmasi file berubah dan ringkasannya menyebutkannya.
1206
+ 7. Tanya: `Run npm test and report the result.` → setujui command persisnya →
1207
+ verifikasi exit code asli, durasi, dan outputnya.
1208
+ 8. `/undo` bekerja untuk perubahan file; `/status`, `/diff`, `/sessions`,
1209
+ `/logs` memperlihatkan bukti.
1210
+
1211
+ ## Lisensi
1212
+
1213
+ MIT (lihat `package.json`).
2
1214
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.