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

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 (63) hide show
  1. package/CHANGELOG.md +592 -0
  2. package/LICENSE +21 -0
  3. package/README.md +1269 -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 +471 -0
  11. package/src/cli/commands.js +1155 -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 +137 -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 +461 -0
  25. package/src/index.js +449 -0
  26. package/src/providers/catalog.js +407 -0
  27. package/src/providers/ids.js +24 -0
  28. package/src/providers/index.js +103 -0
  29. package/src/providers/nvidia.js +671 -0
  30. package/src/providers/registry.js +104 -0
  31. package/src/providers/river.js +198 -0
  32. package/src/providers/tokenharbor.js +38 -0
  33. package/src/providers/vendor-extras.js +82 -0
  34. package/src/services/clipboard.js +38 -0
  35. package/src/services/diff.js +153 -0
  36. package/src/services/doctor.js +126 -0
  37. package/src/services/logger.js +190 -0
  38. package/src/services/preferences.js +249 -0
  39. package/src/services/processes.js +345 -0
  40. package/src/services/provider-settings.js +226 -0
  41. package/src/services/sessions.js +279 -0
  42. package/src/services/undo.js +133 -0
  43. package/src/services/ushell.js +146 -0
  44. package/src/services/websearch/manager.js +616 -0
  45. package/src/services/websearch/mcp.js +233 -0
  46. package/src/services/websearch/normalize.js +284 -0
  47. package/src/services/websearch/providers.js +178 -0
  48. package/src/services/websearch/serper.js +263 -0
  49. package/src/services/workspace.js +589 -0
  50. package/src/tools/filesystem.js +304 -0
  51. package/src/tools/git.js +114 -0
  52. package/src/tools/index.js +48 -0
  53. package/src/tools/project.js +160 -0
  54. package/src/tools/registry.js +118 -0
  55. package/src/tools/search.js +210 -0
  56. package/src/tools/shell.js +279 -0
  57. package/src/tools/url.js +542 -0
  58. package/src/tools/web.js +67 -0
  59. package/src/utils/errors.js +160 -0
  60. package/src/utils/network.js +129 -0
  61. package/src/utils/redaction.js +73 -0
  62. package/src/utils/validation.js +370 -0
  63. package/src/utils/version.js +24 -0
package/README.md CHANGED
@@ -1,3 +1,1270 @@
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 (`nvidia`,
569
+ `tokenharbor`, `river`).
570
+ - `capabilities` menentukan apakah agent mengirim tools (`tools`), meminta
571
+ stream (`streaming`), dan menampilkan reasoning bila provider mengirimnya
572
+ (`reasoning`). Jangan menyalakan `tools` untuk endpoint yang tidak mendukung
573
+ `tool_calls`: agent akan menampilkan keterbatasan dan tetap bisa menjawab,
574
+ tetapi tidak bisa membaca atau mengubah file.
575
+ - Setelah ditambahkan, provider muncul di `/provider` dan bisa dipilih dengan
576
+ `/provider user myproxy` (alias lama `/provider myproxy`) atau
577
+ `--provider myproxy`.
578
+ - Tidak ada fallback otomatis antar-provider AI. Kegagalan provider aktif
579
+ dilaporkan apa adanya (dengan retry terbatas untuk kegagalan sesaat) agar
580
+ kunci dan data tidak berpindah ke provider lain tanpa keputusan Anda.
581
+
582
+ #### Provider bawaan dan modul klien per vendor (`src/providers/`)
583
+
584
+ Tiga provider dikenali tanpa entri `extraProviders`: `nvidia`, `tokenharbor`,
585
+ dan `river`. Masing-masing punya modul sendiri di `src/providers/`:
586
+
587
+ | Modul | Provider | Isi |
588
+ | --- | --- | --- |
589
+ | `nvidia.js` | `nvidia` | Klien Chat Completions bersama (dipakai semua provider OpenAI-compatible), retry, streaming, ekstraksi reasoning. |
590
+ | `tokenharbor.js` | `tokenharbor` | Default provider + pass-through `extraBody`/`requestParams`. |
591
+ | `river.js` | `river` | Menambahkan `reasoning_effort` (lihat di bawah) dan profil reasoning per model. |
592
+ | `vendor-extras.js` | — | Tabel field request per provider; klien bersama bertanya ke sini sebelum setiap request. |
593
+
594
+ Karena ketiganya bawaan, `extraProviders` **tidak boleh** memakai id yang sama.
595
+ Entri lama untuk `river` yang menunjuk base URL yang sama tetap diterima: entri
596
+ itu diabaikan dengan peringatan, bukan menggagalkan startup, dan descriptor
597
+ bawaan yang dipakai (daftar model, kapabilitas, field vendor). Provider kustom
598
+ lainnya tetap memakai template `nvidia.js` tanpa perubahan kode; field request
599
+ tambahan bisa diberikan lewat `extraBody`/`requestParams` di entrinya:
600
+
601
+ ```json
602
+ { "id": "myproxy", "baseURL": "https://proxy.example/v1",
603
+ "extraBody": { "top_k": 40 }, "requestParams": { "reasoning_effort": "high" } }
604
+ ```
605
+
606
+ #### Reasoning DeepSeek di River AI
607
+
608
+ River mematikan thinking secara default untuk `DeepSeek-V4.1-Flash-1017` dan
609
+ `DeepSeek-V4.1-Flash`: tanpa `reasoning_effort` di body request, respons hanya
610
+ berisi `content`, dan tidak ada teks reasoning untuk ditampilkan. `river.js`
611
+ mengirim field itu otomatis (dokumentasi: <https://docs.river.ai/guides/inference-controls/>):
612
+
613
+ - Default: `high` untuk kedua model DeepSeek, sehingga reasoning langsung muncul
614
+ di UI dan di `.ryuu/logs/reasoning/`.
615
+ - Model River lain (`GLM-5.3`, `GLM-5.3-Flash`, `Qwen3.8-27B-FP8`,
616
+ `Nemotron-3.5-Super-VL-120B`) sudah reasoning secara default dan tidak dipaksa
617
+ — effort hanya dikirim bila Anda memintanya.
618
+ - Urutan prioritas: env `RYUU_RIVER_REASONING_EFFORT` → `reasoningEffort` di
619
+ `config.json` → env `RYUU_REASONING_EFFORT` → `capabilities.reasoningEffort`
620
+ di entri provider.
621
+ - Nilai yang diterima: `none|low|medium|high|xhigh|max` (plus `off`/`on`).
622
+ Level yang tidak didukung model akan diturunkan ke level aman model itu dan
623
+ dicatat di log, jadi request tidak gagal.
624
+
625
+ Contoh mematikan reasoning River lewat `config.json`:
626
+
627
+ ```json
628
+ { "provider": "river", "reasoningEffort": "none" }
629
+ ```
630
+
631
+ `capabilities.reasoning` hanya label tampilan di `/provider`; reasoning tetap
632
+ ditampilkan selama provider benar-benar mengirim `reasoning_content`.
633
+
634
+ ## Tool yang tersedia untuk model
635
+
636
+ Model hanya bisa memanggil tool yang terdaftar di allowlist lokal. Setiap
637
+ pemanggilan divalidasi skemanya sebelum dieksekusi.
638
+
639
+ | Tool | Risiko | Persetujuan | Tujuan |
640
+ | --- | --- | --- | --- |
641
+ | `list_files` | read | tidak | Daftar direktori terfilter dan berpaginasi. |
642
+ | `tree` | read | tidak | Pohon direktori dengan batas. |
643
+ | `read_file` | read | tidak | Teks UTF-8; rentang baris yang diminta dialirkan, jadi juga bekerja untuk file di atas batas baca. |
644
+ | `file_info` | read | tidak | Ukuran, jenis, timestamp, jumlah baris. |
645
+ | `search_files` | read | tidak | Pencarian nama/isi dengan pengecualian. |
646
+ | `diff_file` | read | tidak | Diff terpadu tanpa menulis apa pun. |
647
+ | `inspect_git` | read | tidak | Status/diff Git bila tersedia. |
648
+ | `project_summary` | read | tidak | Manifes dan struktur dari file sungguhan. |
649
+ | `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). |
650
+ | `read_url` | read | tidak | Mengambil halaman http(s) publik dan mengekstrak teks yang bisa dibaca. Selalu tersedia. |
651
+ | `create_file` | tulis | ya | File baru; menimpa file lama hanya dengan `overwrite`. |
652
+ | `write_file` | tulis | ya | Penulisan seluruh file dengan penjaga mtime opsional. |
653
+ | `edit_file` | tulis | ya | Penggantian terarah unik, mengembalikan diff. |
654
+ | `move_file` | destruktif | ya | Memindah/mengganti nama di dalam workspace. |
655
+ | `delete_file` | destruktif | ya | Menghapus file atau direktori kosong; `confirm: true` wajib. |
656
+ | `run_command` | exec | ya | Menjalankan argv, menunggu, menangkap output/exit/durasi. |
657
+ | `start_process` | exec | ya | Server/watcher di background. |
658
+ | `list_processes` | read | tidak | Daftar proses. |
659
+ | `get_process_output` | read | tidak | Output terbatas untuk sebuah proses. |
660
+ | `stop_process` | exec | ya | Penghentian bersih, lalu pemutusan paksa. |
661
+
662
+ Katalog tool juga ditulis ke dalam system prompt (nama, argumen, gerbang
663
+ persetujuan, tujuan satu baris), bersama skrip npm milik project itu sendiri dan
664
+ tata letak modulnya, sehingga model memilih tool yang benar alih-alih keluar ke
665
+ `grep` untuk sesuatu yang sudah dikerjakan `search_files`.
666
+
667
+ ### Pencarian web (MCP multi-provider dengan fallback)
668
+
669
+ `web_search` tetap satu tool bagi model, tetapi didukung oleh `WebSearchManager`
670
+ yang memiliki beberapa provider dan mencobanya sesuai urutan yang bisa
671
+ dikonfigurasi. Setiap provider dinormalkan ke bentuk yang sama: `results`
672
+ (`title`, `url`, `snippet`, `date` opsional), `answer_box`, `people_also_ask`,
673
+ dan `related_searches`. `depth: \"deep\"` meminta dua kali lipat hasil (maksimal
674
+ 10).
675
+
676
+ | Provider | Transport | Kunci | Kapabilitas | Catatan |
677
+ | --- | --- | --- | --- | --- |
678
+ | 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`. |
679
+ | 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`. |
680
+ | 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. |
681
+ | Tavily MCP | Streamable HTTP | wajib (`webSearch.tavilyApiKey`) | pencarian + ekstrak | Endpoint remote butuh kunci; tanpa itu provider dilewati, tidak pernah dihubungi. |
682
+ | Serper REST | HTTPS | wajib (`webSearch.serperApiKey`) | pencarian | Backend asli; ikut dalam rantai fallback yang sama. |
683
+
684
+ Bagaimana ia berperilaku:
685
+
686
+ - **Koneksi malas.** Tidak ada yang menghubungkan pada saat start. Sebuah
687
+ provider dihubungkan saat pertama dipakai, tool-nya ditemukan dengan
688
+ `tools/list`, dan koneksinya dipakai ulang selama sisa sesi. `/web test`
689
+ hanya melakukan handshake + penemuan (tanpa kueri).
690
+ - **Pemetaan tool, bukan nama yang di-hard-code.** Tool pencarian/ekstraksi
691
+ dipilih menurut nama, deskripsi, dan skema input, sehingga sebuah server bisa
692
+ mengganti nama tool-nya tanpa membuat agent rusak. Argumen wajib diisi dari
693
+ skema tool itu sendiri (`query`, `q`, `url`, `urls`, `limit`, `formats`,
694
+ `objective`).
695
+ - **Fallback hanya untuk kegagalan sungguhan.** Timeout, jaringan/DNS, HTTP
696
+ 401/403/429/5xx, kegagalan handshake/penemuan, tool yang hilang, skema salah,
697
+ dan payload tidak valid semuanya pindah ke provider berikutnya. Hasil kosong
698
+ yang valid dicatat dan boleh juga mencoba provider berikutnya, tetapi tidak
699
+ dilaporkan sebagai kegagalan. Pembatalan tidak pernah memicu fallback.
700
+ - **Retry terbatas.** Maksimal `webSearch.mcpMaxRetries` retry per provider untuk
701
+ kegagalan *pemanggilan* sesaat (bukan handshake), dengan backoff eksponensial
702
+ dan jitter. `429` langsung diteruskan ke provider berikutnya alih-alih
703
+ membekukan agent. Dua kegagalan berturut-turut membuka circuit breaker 60
704
+ detik untuk provider itu.
705
+ - **Satu kegagalan bertipe.** Jika semua provider gagal, tool melempar
706
+ `SEARCH_ALL_PROVIDERS_FAILED` (atau `SEARCH_EXTRACT_FAILED`) dengan daftar
707
+ alasan singkat per provider; agent tetap berjalan tanpa pencarian web alih-alih
708
+ crash dan diberi tahu untuk tidak mengarang hasil.
709
+ - **Terminal menampilkan fallback**, bukan hanya pemenangnya: satu pohon `WEB
710
+ SEARCH` ringkas per pencarian dengan `✓`/`✕`/`!`/`○`, durasi, dan alasan
711
+ singkat. Stack trace tetap di log debug; kunci dan header otorisasi tidak
712
+ pernah dicetak.
713
+ - **Fallback ekstraksi halaman.** `read_url` tetap mengambil secara lokal lebih
714
+ dulu. Jika pengambilan lokal gagal (jaringan, timeout, 401/403, jenis tidak
715
+ didukung) atau tidak mengekstrak teks dari halaman yang dirender JavaScript,
716
+ sebuah provider berkemampuan ekstraksi (Firecrawl/Tavily) dicoba dan hasilnya
717
+ menyebutkan provider mana yang menghasilkan teksnya.
718
+ - **Keamanan.** Kueri dan URL keluar dari mesin; file tidak. Teks halaman adalah
719
+ data tak terpercaya, bukan instruksi. Server MCP stdio mendapat environment
720
+ minimal (PATH/HOME/TMP), bukan kunci NVIDIA atau pencarian Anda. Server MCP
721
+ tidak pernah diberi akses filesystem atau shell oleh agent ini.
722
+ - **Privasi dan kuota.** Firecrawl tanpa kunci punya batas kredit harian yang
723
+ kecil; manajer tidak pernah menguji dengan kueri sungguhan saat start. `/web
724
+ test` secara eksplisit menyatakan tidak ada kueri yang dijalankan. Nonaktifkan
725
+ semuanya dengan `webSearch.enabled: false` atau `webSearch.providers: []`.
726
+ - **Worklog:** setiap pencarian dicatat (`web_search \"...\" -> N result(s) ·
727
+ top: ...`), sehingga tugas lanjutan memakai temuan yang ada alih-alih membayar
728
+ kueri yang sama.
729
+
730
+ ### Membaca sebuah halaman (`read_url`)
731
+
732
+ `read_url` mengambil satu URL http(s) dan mengembalikan teks hasil ekstraksi,
733
+ judul, dan meta deskripsinya, sehingga model bisa memverifikasi snippet
734
+ pencarian terhadap halaman sumbernya.
735
+
736
+ - **Selalu tersedia.** Tidak butuh kunci, ia tool read-only, dan tidak pernah
737
+ meminta konfirmasi.
738
+ - **Terbatas.** Batas badan 2 MB, `max_chars` 1.000–50.000 (bawaan 20.000),
739
+ timeout 15 detik, dan maksimal 5 redirect.
740
+ - **Skema diperiksa.** Hanya http/https; URL dengan kredensial tertanam ditolak,
741
+ setiap hop redirect diperiksa ulang, dan penurunan https→http ditolak.
742
+ - **Dijaga SSRF.** Alamat loopback, privat, link-local, CGNAT, multicast, dan
743
+ metadata cloud ditolak, begitu pula `localhost`, `*.local`, `*.internal`, dan
744
+ hostname ber-label tunggal. Pengambilan sungguhan melakukan resolusi DNS lebih
745
+ dulu dan menolak bila ada alamat privat; setiap redirect diperiksa ulang. DNS
746
+ rebinding dikendalikan tetapi tidak dihilangkan sepenuhnya tanpa agen HTTP
747
+ ber-IP-pinned.
748
+ - **Teks saja.** `text/*`, HTML/XHTML, JSON, dan XML dibaca; gambar, PDF, dan
749
+ biner lain mengembalikan `READ_URL_UNSUPPORTED`.
750
+ - **Fallback ekstraksi MCP.** Jika pengambilan lokal tidak bisa membaca halaman
751
+ (galat jaringan, timeout, konten terblokir/tidak didukung) atau tidak
752
+ mengekstrak teks dari halaman yang dirender JavaScript, sebuah provider
753
+ pencarian web berkemampuan ekstraksi dicoba; hasilnya menyebutkan provider di
754
+ `provider` dan menetapkan `extraction: \"mcp\"`.
755
+ - **Tidak ada JavaScript secara lokal.** Halaman yang dirender sisi-klien bisa
756
+ saja tidak mengembalikan teks; hasilnya menyatakan hal itu alih-alih
757
+ berpura-pura halaman itu kosong, dan fallback MCP di atas bisa memulihkannya.
758
+ - **Konten tak terpercaya.** Prompt memperlakukan teks yang diambil sebagai
759
+ data, bukan instruksi, sehingga sebuah halaman tidak bisa mengalihkan
760
+ perilaku agent.
761
+ - **Privasi:** URL diminta dari mesin Anda, jadi situs itu melihat IP Anda.
762
+ - **Worklog:** setiap pengambilan dicatat (`read_url <url> -> HTTP 200 · N chars`).
763
+
764
+ ### Model prompt dan konteks
765
+
766
+ Protokol provider ini tidak punya keadaan (stateless): setiap permintaan
767
+ membawa seluruh percakapan. Ryuu membuat hal itu stabil dan murah alih-alih
768
+ berpura-pura sebaliknya.
769
+
770
+ - System prompt dirakit **sekali per sesi** dan dipakai ulang byte-per-byte. Ia
771
+ identik pada setiap langkah (sehingga prefix caching provider bisa kena) dan
772
+ tidak pernah tumbuh seiring berjalannya tugas.
773
+ - Setiap tugas menambahkan tepat satu pesan `TASK BRIEF`: tugas, sisa batas
774
+ langkah dan waktu, serta worklog sesi. Itulah satu-satunya blok instruksi
775
+ per-tugas; model tidak pernah diberi tahu ulang untuk \"mulai dengan rencana\".
776
+ - Hasil tool tetap berada di percakapan selama langkah-langkah yang
777
+ membutuhkannya. Worklog membawa fakta terverifikasi (file yang dibaca beserta
778
+ rentang baris, file yang diubah, command dan exit code, temuan, rencana) ke
779
+ tugas-tugas berikutnya, sehingga tindak lanjut melanjutkan pekerjaan alih-alih
780
+ memulai lagi. File yang sudah dibaca dan tidak berubah di disk tidak dibaca
781
+ lagi: agent menerima balasan `cached: true` yang memberi tahu bahwa isinya
782
+ sudah diketahui.
783
+ - `finish_reason: \"length\"` ditangani: pemanggilan tool yang terpotong pada
784
+ batas token output dibuang dan dilaporkan, tidak pernah dieksekusi sebagai
785
+ file yang tertulis setengah jalan.
786
+ - Pada 70% anggaran waktu, model diberi tahu untuk menyatu (konvergen), dan
787
+ aliran respons yang tidak mengirim byte selama
788
+ `agent.streamIdleTimeoutMs` dibatalkan dengan `STREAM_STALLED` alih-alih
789
+ menahan langkah tetap terbuka.
790
+
791
+ ## Model persetujuan dan izin
792
+
793
+ Kebijakannya diatur dengan `agent.approvalPolicy`:
794
+
795
+ | Risiko | `ask` (bawaan) | `auto` | `read-only` |
796
+ | --- | --- | --- | --- |
797
+ | read | izinkan | izinkan | izinkan |
798
+ | tulis | prompt | izinkan | tolak |
799
+ | exec | prompt | prompt | tolak |
800
+ | destruktif | prompt | prompt | tolak |
801
+
802
+ - `--yes` menyetujui otomatis **hanya penulisan**; eksekusi dan aksi destruktif
803
+ tetap memakai prompt.
804
+ - Di sesi non-interaktif tidak ada prompt, jadi aksi yang butuh persetujuan
805
+ ditolak dan penolakannya dilaporkan ke model.
806
+ - Persetujuan ditegakkan di loop agent, bukan di dalam tool masing-masing,
807
+ sehingga model tidak bisa melewatinya dengan memanggil tool secara langsung.
808
+ - Prompt persetujuan menampilkan tool, tingkat risiko, argumen, dan pratinjau
809
+ (diff atau command beserta direktori kerjanya) sebelum Anda menjawab.
810
+ - Penilaian risiko adalah sinyal UX best-effort, **bukan batas keamanan**.
811
+
812
+ ### Undo
813
+
814
+ `/undo` memakai snapshot before/after yang tepat, direkam oleh proses agent ini
815
+ untuk `create_file`, `write_file`, `edit_file`, `move_file`, dan `delete_file`. Ia
816
+ menolak mengembalikan perubahan jika file berubah setelah suntingan agent,
817
+ alih-alih menimpa pekerjaan Anda. Cakupannya mencakup perubahan yang dibuat pada
818
+ tugas/sesi saat ini dan bukan pengganti kontrol versi. Pakai `git` untuk sejarah
819
+ yang tahan lama.
820
+
821
+ ## Referensi konfigurasi
822
+
823
+ Semua pengaturan berasal dari satu file `config.json` (lihat
824
+ [Pengaturan global](#pengaturan-global-ryuu---settings)). Tidak ada `.env` dan
825
+ tidak ada variabel environment; flag CLI menimpa nilai file untuk satu run.
826
+ Workspace tetap ditentukan per run (cwd atau `--workspace`), bukan dari file
827
+ global ini. Nilai bawaan divalidasi saat start; nilai tidak valid membuat proses
828
+ gagal sebelum ada panggilan API.
829
+
830
+ | Kunci `config.json` | Bawaan | Makna |
831
+ | --- | --- | --- |
832
+ | *(kosong)* | bawaan katalog | Nilai kosong pada kunci mana pun berarti "pakai bawaan" di kolom Bawaan. |
833
+ | `provider` | — | Provider aktif (`nvidia`, `tokenharbor`, atau id dari `extraProviders`). Bila kosong: `nvidia` kalau kuncinya ada, jika tidak provider pertama yang punya kunci. |
834
+ | `providerModel.<id>` | model bawaan provider | Model untuk provider itu. Menang atas bawaan katalog. |
835
+ | `providers.<id>.apiKey` | — | Kunci provider. Tidak pernah dicetak, ditulis owner-only. |
836
+ | `providers.<id>.baseURL` | URL bawaan provider | Base URL kompatibel OpenAI. |
837
+ | `extraProviders` | — | Array provider tambahan yang kompatibel OpenAI (lihat [Menambah provider sendiri](#menambah-provider-sendiri-extraproviders)). |
838
+ | `agent.logLevel` | `info` | `debug`, `info`, `warn`, `error`, `silent`. |
839
+ | `agent.maxSteps` | `0` | Batas langkah agent opsional (0 = tanpa batas, 1–100000). |
840
+ | `agent.approvalPolicy` | `ask` | `ask`, `auto`, `read-only`. |
841
+ | `agent.maxOutputTokens` | `4096` | Batas token output (64–32768). Nilai tinggi bisa ditolak atau mahal. |
842
+ | `agent.requestTimeoutMs` | `120000` | Timeout per permintaan (1s–10menit). |
843
+ | `agent.streamIdleTimeoutMs` | `45000` | Membatalkan aliran respons yang tidak mengirim byte selama ini (0 menonaktifkan). |
844
+ | `agent.maxRetries` | `3` | Retry untuk kegagalan sesaat (0–10). |
845
+ | `agent.maxRecoveryAttempts` | `0` | Percobaan sambung ulang saat tautan provider putus. `0` = tanpa batas (coba sampai tenggat tugas atau `/cancel`); 1–1000 membatasinya. |
846
+ | `agent.recoveryBackoffMs` | `1000` | Jeda dasar antar-percobaan sambung ulang (100ms–60s). |
847
+ | `agent.maxFileBytes` | `262144` | Batas baca per file (1KB–5MB). |
848
+ | `agent.maxSearchResults` | `50` | Batas hasil pencarian (1–500). |
849
+ | `agent.maxToolOutputBytes` | `32768` | Batas hasil tool yang dikirim balik ke model. |
850
+ | `agent.maxTaskDurationMs` | `900000` | Batas waktu nyata tugas (10s–24jam). |
851
+ | `agent.maxContextChars` | `120000` | Ambang pemadatan konteks lokal. |
852
+ | `agent.allowShell` | `false` | Mengizinkan sintaks shell mentah (tetap bergerbang persetujuan). |
853
+ | `agent.debug` | `false` | Log debug dan stack trace. |
854
+ | `agent.historyDir` | `<workspace>/.ryuu/sessions` | Penyimpanan sesi, relatif ke workspace. |
855
+ | `agent.logDir` | `<workspace>/.ryuu/logs` | Penyimpanan log (`agent.log`, dirotasi pada 5MB), relatif ke workspace. |
856
+ | `webSearch.enabled` | `true` | Mendaftarkan `web_search` sama sekali. `false` membuangnya bersama fallback ekstraksi `read_url`. |
857
+ | `webSearch.fallback` | `true` | Mencoba provider berikutnya setelah kegagalan. `false` berhenti pada kandidat pertama. |
858
+ | `webSearch.providers` | `serper` (bila ada kunci) → `you` → `firecrawl` → `duckduckgo` → `tavily` | Urutan provider; `[]` atau `none` menonaktifkan semuanya. |
859
+ | `webSearch.disabled` | `[]` | Daftar id provider yang dilewati. `/web enable`/`disable` hanya mengubah sesi saat ini. |
860
+ | `webSearch.mcpTimeoutMs` | `6000` | Timeout handshake, penemuan, dan pemanggilan per percobaan (1s–30s). |
861
+ | `webSearch.mcpMaxRetries` | `1` | Retry per provider untuk kegagalan pemanggilan sesaat (0–5). |
862
+ | `webSearch.debug` | `false` | Diagnostik provider tambahan di log (tanpa kunci). |
863
+ | `webSearch.youApiKey` | — | Kunci You.com opsional; tanpa kunci memakai profil pencarian gratis. |
864
+ | `webSearch.firecrawlApiKey` | — | Kunci Firecrawl opsional; tanpa kunci memakai tingkat kredit gratis. |
865
+ | `webSearch.tavilyApiKey` | — | Wajib untuk Tavily; tanpa itu Tavily dilewati. |
866
+ | `webSearch.duckduckgoEnabled` | `false` | Menjalankan server stdio `uvx free-search-mcp` lokal saat pertama dipakai. |
867
+ | `webSearch.duckduckgoCommand` / `webSearch.duckduckgoArgs` | `uvx` / `free-search-mcp` | Command untuk provider stdio lokal. |
868
+ | `webSearch.serperApiKey` | — | Kunci Serper-kompatibel; menambahkan Serper ke rantai fallback. |
869
+ | `webSearch.endpoint` | `https://google.serper.dev/search` | Endpoint SERP (`http`/`https`). |
870
+ | `webSearch.authHeader` | `X-API-KEY` | `X-API-KEY` atau `Authorization` (Bearer). |
871
+ | `webSearch.results` | `5` | Hasil per kueri (1–10); `deep` menggandakan sampai batas. |
872
+ | `webSearch.timeoutMs` | `15000` | Timeout permintaan Serper (1s–60s). |
873
+
874
+ State lokal (`.ryuu/`) di-gitignore dan dikecualikan dari operasi file agent.
875
+
876
+ ## Arsitektur
877
+
878
+ ```
879
+ src/
880
+ index.js Entri CLI, penguraian argumen, validasi start
881
+ cli/
882
+ app.js menghubungkan UI, agent, tool, provider, proses, storage
883
+ commands.js slash command
884
+ renderer.js banner, feed aktivitas, baris telemetri, diff, ringkasan
885
+ tui.js layar retained-mode --tui: model transkrip, tata letak, input
886
+ markdown.js Markdown terminal, tabel sadar-lebar, blok kode
887
+ prompts.js sesi prompt readline dan penanganan Ctrl+C
888
+ agent/
889
+ agent.js loop pemanggilan-tool eksplisit dan ringkasan bukti
890
+ approvals.js penegakan kebijakan persetujuan
891
+ context.js pemadatan konteks dengan batas
892
+ planner.js prompt sesi, task brief, ekstraksi rencana, ringkasan
893
+ worklog.js fakta sesi terverifikasi yang dipakai ulang antar langkah dan tugas
894
+ providers/
895
+ catalog.js katalog provider + model (nvidia, tokenharbor, river), kapabilitas, extraProviders
896
+ registry.js registry provider dinamis (factory per id)
897
+ index.js pembuatan provider aktif dan resolusi pilihan provider/model
898
+ nvidia.js klien SDK OpenAI (dipakai semua provider), normalisasi, retry, pemetaan galat
899
+ tokenharbor.js provider Token Harbor: default dan pass-through field request
900
+ river.js provider River AI: reasoning_effort untuk DeepSeek + profil reasoning per model
901
+ vendor-extras.js tabel field request per provider yang dibaca klien bersama
902
+ tools/
903
+ registry.js allowlist, skema, risiko, antarmuka plugin
904
+ filesystem.js baca/tulis/edit/pindah/hapus/diff file
905
+ search.js pencarian nama dan isi
906
+ git.js inspeksi git read-only
907
+ project.js ringkasan manifes/project
908
+ shell.js tool command dan proses background
909
+ url.js read_url: pengambilan halaman terbatas, ekstraksi teks, fallback MCP
910
+ web.js web_search: satu tool di atas manajer multi-provider
911
+ services/
912
+ websearch/
913
+ manager.js urutan provider, fallback, retry, circuit breaker, normalisasi
914
+ mcp.js klien MCP (Streamable HTTP + stdio), timeout, galat bertipe
915
+ providers.js preset provider dan deskriptor berbasis config
916
+ normalize.js normalisasi hasil pencarian/ekstraksi yang agnostik provider
917
+ serper.js provider REST Serper-kompatibel (backend yang sudah ada)
918
+ workspace.js keamanan path, penulisan atomik, pemblokiran rahasia
919
+ ushell.js /bash dan !cmd: tangkapan shell pengguna, group kill, env bersih
920
+ processes.js spawn, buffer streaming, timeout, stop, log
921
+ sessions.js penyimpanan sesi yang disunting dan cache ringkasan
922
+ undo.js snapshot before/after dengan pemeriksaan basi
923
+ diff.js diff terpadu dan statistik
924
+ logger.js log bertingkat, disunting, berotasi
925
+ doctor.js diagnostik tanpa rahasia
926
+ config/ pemuatan dan validasi env
927
+ utils/ galat, validasi skema, redaksi, penjaga jaringan (SSRF),
928
+ versi (dibaca dari package.json — satu-satunya sumber versi)
929
+ tests/
930
+ unit/ uji modul fokus
931
+ integration/ uji proses CLI sungguhan + aplikasi terhubung
932
+ ```
933
+
934
+ Tool call dieksekusi **secara berurutan**, sehingga dua pemanggilan tidak bisa
935
+ berlomba pada file yang sama. Setiap hasil tool ditambahkan ke percakapan dalam
936
+ format tool-call OpenAI dengan `tool_call_id`-nya yang sesuai.
937
+
938
+ ## Model keamanan
939
+
940
+ Apa yang dilakukan implementasinya:
941
+
942
+ - Meresolusi setiap path terhadap realpath workspace dan menolak traversal
943
+ serta escape symlink. Path absolut di luar workspace ditolak.
944
+ - Memblokir `.env`, `.env.local`, private key/sertifikat, penyimpan kredensial
945
+ umum (`.ssh`, `.aws`, `.gnupg`, `.kube`, `.docker`, `.npmrc`, `id_rsa*`, ...),
946
+ serta `*.pem`/`*.key`/`*.p12`/`*.pfx`/`*.jks`/`*.keystore` secara bawaan.
947
+ `.env.example`, `.env.sample`, `.env.template`, dan `.env.dist` tetap bisa
948
+ dibaca sebagai dokumentasi.
949
+ - Mengecualikan `node_modules`, `.git`, `dist`, `build`, `coverage`, dan
950
+ direktori hasil-generate sejenis dari daftar/pencarian secara bawaan; tool bisa
951
+ memasukkannya secara eksplisit.
952
+ - `read_url` hanya menerima http/https, menolak URL dengan kredensial tertanam,
953
+ memeriksa ulang setiap hop redirect (https→http ditolak), hanya membaca jenis
954
+ konten tekstual, dan membatasi badan hingga 2 MB.
955
+ - Menegakkan batas file, hasil pencarian, output tool, konteks, langkah,
956
+ serta durasi tugas.
957
+ - Memakai penulisan atomik file-temp + rename, mempertahankan CRLF vs LF, dan
958
+ bisa membatalkan ketika mtime file berubah sejak pemeriksaan.
959
+ - Menjalankan command dengan `spawn(command, args, { shell: false })` dan array
960
+ argumen. Sintaks shell mentah dinonaktifkan kecuali `agent.allowShell: true`,
961
+ dan selalu bergerbang persetujuan dengan command serta direktori kerjanya
962
+ ditampilkan persis.
963
+ - Membuang variabel yang tampak seperti kredensial (`*API_KEY*`, `*TOKEN*`,
964
+ `*SECRET*`, `*PASSWORD*`, ...) dari environment proses anak.
965
+ - Menyunting (redact) kunci API yang dikonfigurasi dan pola token dari log,
966
+ output command, file sesi, dan pesan galat.
967
+ - Membatasi isi sesi/transkrip yang dipersistensi dan menyimpannya di bawah
968
+ `.ryuu/` dengan izin khusus pemilik bila didukung.
969
+
970
+ Apa yang **tidak** dilakukannya:
971
+
972
+ - **Eksekusi shell bukan sandbox.** Command yang disetujui berjalan dengan izin
973
+ pengguna Anda dan bisa membaca atau mengubah apa pun yang bisa dilakukan
974
+ pengguna itu.
975
+ - Label risiko persetujuan hanyalah heuristik. Periksa command persisnya sebelum
976
+ menyetujui.
977
+ - Ia tidak memasang dependensi otomatis, mendorong commit, men-*deploy*, atau
978
+ meningkatkan hak istimewa. Command yang disetujui tetap bisa melakukan hal-hal
979
+ itu — itu keputusan Anda.
980
+ - Ia tidak mengindeks atau mengunggah seluruh workspace; hanya file yang dibaca
981
+ model yang dikirim ke provider.
982
+ - Ia tidak mengarang atau merekonstruksi rantai-pikir privat. Saat provider
983
+ mengalirkan delta `reasoning_content`, Ryuu menampilkan teks itu langsung di
984
+ kotak tertutup yang lebarnya sesuai dengan pratinjau lima baris. Setiap
985
+ giliran mendapat id stabil; klik sebuah blok (atau jalankan `/expand <id>`)
986
+ untuk membuka ulang teks lengkapnya, dan Esc atau klik lain menutup panel.
987
+ Teks lengkapnya juga ditulis ke `.ryuu/logs/reasoning/`. Satu baris aktivitas
988
+ yang selalu menyala berada di bagian bawah UI percakapan dan membawa seluruh
989
+ keadaan run: fase, waktu berjalan per fase, total waktu run, langkah, tool
990
+ yang sedang berjalan beserta posisi antriannya, jumlah token live, dan latensi
991
+ token pertama; output command yang berjalan mengalirkan ekornya tepat di atas
992
+ baris itu. Jawaban akhir tidak dikotak: ia dicetak langsung sebagaimana
993
+ provider menulisnya, dengan Markdown inline (bold, italic, code, link)
994
+ dirender live, header ATX (`###`) dirender tebal tanpa tanda pagarnya, dan
995
+ tabel pipe / blok kode bersal die render sebagai blok segera setelah
996
+ selesai dialirkan. Token kontrol chat-template yang bocor dibuang; jawaban
997
+ tidak dirender ulang setelahnya.
998
+
999
+ ## Pengujian dan pengembangan
1000
+
1001
+ ```bash
1002
+ npm test # 300+ uji unit + integrasi (tanpa jaringan, tanpa kunci asli)
1003
+ npm run test:unit
1004
+ npm run test:integration
1005
+ npm run check # pemeriksaan sintaks di seluruh src/, scripts/, tests/
1006
+ npm run verify # check + test
1007
+ ```
1008
+
1009
+ Uji memakai runner bawaan Node `node:test`, workspace sementara, provider palsu
1010
+ terskrip, dan `fetch` yang disuntikkan untuk uji tingkat-SDK. Mereka tidak pernah
1011
+ memanggil NVIDIA.
1012
+
1013
+ Pemeriksaan *smoke* paket opsional:
1014
+
1015
+ ```bash
1016
+ npm pack --dry-run
1017
+ ```
1018
+
1019
+ ## Pemecahan masalah
1020
+
1021
+ ### `Provider nvidia has no API key` / `Provider tokenharbor has no API key`
1022
+
1023
+ Pesan itu menyebut provider aktif dan path kunci yang diharapkan
1024
+ (`providers.<id>.apiKey`). Isi lewat `ryuu --settings` (baris
1025
+ `providers.nvidia.apiKey` atau `providers.tokenharbor.apiKey`). Jalankan
1026
+ `ryuu --doctor` untuk melihat provider mana yang aktif dan path kunci mana yang
1027
+ belum terisi. Provider tanpa kunci tidak bisa dipilih lewat `/provider`.
1028
+
1029
+ ### 401 / 403
1030
+
1031
+ `401` berarti kunci hilang, tidak valid, atau sudah dicabut. `403` biasanya
1032
+ berarti kunci tidak punya akses ke model terpilih. Periksa path kunci yang
1033
+ disebut pesan galat (`providers.nvidia.apiKey` atau
1034
+ `providers.tokenharbor.apiKey`), verifikasi
1035
+ akses model di dashboard provider, lalu jalankan `ryuu --doctor --live`.
1036
+ Ryuu tidak mencoba kunci provider lain sebagai pengganti.
1037
+
1038
+ ### 404
1039
+
1040
+ Periksa model provider aktif (`--model`, `/model`, atau `providerModel.<id>`) dan
1041
+ base URL-nya (`providers.<id>.baseURL`). Base URL harus berupa root yang
1042
+ kompatibel dengan OpenAI, biasanya berakhiran `/v1`.
1043
+
1044
+ ### Model tidak tersedia / tidak ditemukan
1045
+
1046
+ Daftar model adalah id yang ditawarkan CLI, bukan jaminan model itu aktif di
1047
+ server. Untuk Token Harbor, model `deepseek-v4.1-flash:free` bisa berubah atau
1048
+ dibatasi ketersediaannya. Cek `/provider` dan `/model`, lalu coba model lain atau
1049
+ hubungi provider. Ryuu menolak id dengan karakter tidak valid sebelum mengirim
1050
+ apa pun, tetapi tidak melakukan pemeriksaan ketersediaan ke server.
1051
+
1052
+ ### 429 / batas laju
1053
+
1054
+ Kuota dan batas laju ditentukan provider dan tidak dijamin tanpa batas (termasuk
1055
+ untuk model bertanda `:free`). Tunggu lalu coba lagi, perkecil ukuran
1056
+ konteks/tugas, atau pindah model/provider. Ryuu mencoba ulang kegagalan sesaat
1057
+ `429`/`5xx`/jaringan dengan backoff eksponensial terbatas beserta jitter, serta
1058
+ menghormati `Retry-After` bila ada.
1059
+
1060
+ ### Model bawaan sangat lambat atau habis waktu
1061
+
1062
+ Model bawaan sekarang `minimaxai/minimax-m3.1-flash` (entri ke-6 di `/model`).
1063
+ Endpoint `deepseek-ai/deepseek-v4.1-flash` dihapus dari bawaan karena sering timeout di sisi provider. Forum NVIDIA punya laporan terbuka tentang
1064
+ respons 3–5+ menit untuk model ini, jadi ini latensi sisi-provider, bukan
1065
+ kegagalan autentikasi. `GET /v1/models` dan
1066
+ `GET /v1/models/deepseek-ai/deepseek-v4.1-flash` tetap berhasil dengan kunci yang
1067
+ valid.
1068
+
1069
+ Pakai model berkemampuan-tool yang cepat sebagai solusi, tanpa mengubah bawaan
1070
+ project:
1071
+
1072
+ ```bash
1073
+ ryuu --model z-ai/glm-5.3 "your task"
1074
+ # atau set "providerModel": { "nvidia": "z-ai/glm-5.3" } di config.json
1075
+ ```
1076
+
1077
+ Jika Anda tetap memakai model deepseek, naikkan `agent.requestTimeoutMs` dan
1078
+ turunkan `agent.maxRetries` agar satu langkah lambat tidak berulang selama
1079
+ banyak menit. Periksa status terkini di <https://build.nvidia.com/> sebelum
1080
+ menganggap kunci rusak.
1081
+
1082
+ ### "Model may not support tool calling"
1083
+
1084
+ Ryuu butuh dukungan `tool_calls` bergaya OpenAI pada endpoint terpilih. Coba
1085
+ model berkemampuan-tool lewat `providerModel.nvidia` atau `/model`. Tanpa tool call,
1086
+ agent tetap bisa menjawab secara percakapan, tetapi tidak bisa memeriksa atau
1087
+ mengubah file.
1088
+
1089
+ ### Galat jaringan / timeout
1090
+
1091
+ Periksa DNS, proxy, dan akses firewall ke base URL. Naikkan
1092
+ `agent.requestTimeoutMs` untuk endpoint lambat. `--debug` menampilkan
1093
+ diagnostik yang sudah disunting.
1094
+
1095
+ ### Sebuah langkah menggantung tanpa output terlihat
1096
+
1097
+ Jika aliran berhenti mengirim byte, Ryuu membatalkannya setelah
1098
+ `agent.streamIdleTimeoutMs` (bawaan 45s) dan melaporkan `STREAM_STALLED`;
1099
+ langkah hanya dicoba ulang ketika belum ada apa pun yang dicetak. Baris aktivitas
1100
+ selalu menunjukkan waktu berjalan, langkah, dan jumlah token, sehingga
1101
+ penggantunganan terlihat berbeda dari respons lambat yang tetap berkembang.
1102
+ Naikkan nilainya untuk endpoint yang memang lambat, set `0` untuk menonaktifkan
1103
+ watchdog, lalu periksa `/status` untuk rata-rata per-pemanggilan.
1104
+
1105
+ ### Tautan terputus di tengah run (heartbeat)
1106
+
1107
+ Saat permintaan provider gagal dengan galat yang bisa dipulihkan (jaringan,
1108
+ timeout, `5xx`, `429`), agent tidak menyerah: ia menampilkan **heartbeat** di
1109
+ feed aktivitas (`♥ heartbeat · reconnecting · attempt 2 (unlimited) in 1.0s`)
1110
+ dan memutar ulang permintaan dengan backoff eksponensial (maksimal 30 detik
1111
+ antar-percobaan). Baris `♥ link restored` menandai saat provider menjawab lagi,
1112
+ dan run melanjutkan dari tempat ia berhenti. Baris aktivitas tetap berputar
1113
+ selama penantian, sehingga terminal tidak terlihat membeku.
1114
+
1115
+ `agent.maxRecoveryAttempts: 0` (bawaan) berarti **tanpa batas percobaan**: agent
1116
+ terus menyambung ulang sampai provider menjawab, tenggat tugas tercapai, atau
1117
+ Anda menekan `Ctrl+C`/mengirim `/cancel`. Set nilai positif (1–1000) bila Anda
1118
+ menginginkan penghentian keras. Hanya kegagalan yang tidak bisa dicoba ulang
1119
+ (seperti kunci API tidak valid atau galat validasi permintaan `400`) yang
1120
+ mengakhiri run seketika.
1121
+
1122
+ ### Model terus menjawab tanpa isi
1123
+
1124
+ Giliran model kosong diperlakukan sebagai percobaan ulang, bukan galat. Agent
1125
+ menambahkan dorongan ("your previous response was empty… call the next tool or
1126
+ give the final answer"), menampilkan
1127
+ `♥ heartbeat · empty response · attempt 3 (unlimited) in 2.0s`, menunggu dengan
1128
+ backoff yang dimulai dari `agent.recoveryBackoffMs` dan tumbuh hingga 15s,
1129
+ lalu bertanya lagi dengan nomor langkah yang sama — sehingga batas opsional
1130
+ `agent.maxSteps` tidak dikonsumsi oleh percobaan ulang. Tidak ada kegagalan
1131
+ `EMPTY_RESPONSE`: run hanya berakhir saat model menjawab, tenggat tugas lewat,
1132
+ atau Anda membatalkan. Jika ini terjadi terus-menerus, kemungkinan endpoint
1133
+ sedang mengembalikan aliran kosong (periksa `/status`, file log, atau coba model
1134
+ lain dengan `/model`).
1135
+
1136
+ Setiap percobaan ulang dicatat (`empty model response; retrying`) dengan nomor
1137
+ percobaan dan jedanya, dan jumlah percobaan hanya dibatasi oleh tenggat tugas.
1138
+
1139
+ ### "must be a valid JSON object string" (HTTP 400)
1140
+
1141
+ Provider memvalidasi setiap tool call di percakapan pada setiap permintaan, jadi
1142
+ dulu satu payload terpotong bisa menggagalkan permintaan *berikutnya* dan
1143
+ membunuh seluruh run. Ryuu kini memperbaiki payload terpotong (menutup
1144
+ string/array/objek yang masih terbuka) dan mengeksekusi pemanggilannya; jika
1145
+ payload tidak bisa diperbaiki, pemanggilan yang rusak diganti dengan `{}` dalam
1146
+ sejarah tersimpan dan model diberi tahu untuk mengirimnya ulang. Permintaan
1147
+ disederhanakan sekali lagi di sisi provider, sehingga payload rusak tidak lagi
1148
+ bisa sampai ke API.
1149
+
1150
+ ### Sebuah tool terus gagal
1151
+
1152
+ Mengulang pemanggilan gagal yang sama tidak bisa menghasilkan informasi baru.
1153
+ Setelah dua kegagalan identik, agent memblokir yang ketiga dan memberi tahu model
1154
+ untuk mengganti tool, argumen, atau melaporkan temuannya. Command yang keluar
1155
+ dengan `1` tanpa output juga dihitung kegagalan — itu biasanya berarti "tidak ada
1156
+ yang cocok", bukan "coba lagi".
1157
+
1158
+ ### Galat panjang konteks
1159
+
1160
+ Mulai sesi baru, persempit tugas, turunkan `agent.maxContextChars`, atau
1161
+ kecilkan `agent.maxToolOutputBytes`. Pemadatan lokal mempertahankan tugas,
1162
+ rencana, file yang berubah, command, dan masalah yang menggantung, tetapi tidak
1163
+ bisa melampaui konteks provider.
1164
+
1165
+ ### Ketidaksesuaian versi runtime
1166
+
1167
+ Node 25.x adalah target terverifikasi; Node 22+ diterima dengan peringatan di
1168
+ doctor. Versi lebih lama gagal saat start. Tingkatkan dengan
1169
+ `nvm install 25 && nvm use 25`.
1170
+
1171
+ ### Di mana lognya?
1172
+
1173
+ `.ryuu/logs/agent.log` di dalam workspace (dirotasi pada 5MB). Jalankan dengan
1174
+ `--debug` untuk stack trace dan metadata provider yang verbose. Log sudah
1175
+ disingkat, tetapi tinjau dulu sebelum dibagikan.
1176
+
1177
+ ### Perilaku proses
1178
+
1179
+ Di Linux/macOS, proses anak berjalan di grup prosesnya sendiri; menghentikannya
1180
+ menyinyalkan grup itu, lalu meningkat ke `SIGKILL` setelah timeout. Di Windows,
1181
+ grup proses dan semantik sinyal berbeda: Ryuu menghentikan PID anak dan
1182
+ mengandalkan perilaku bawaan Node, sehingga cucu proses yatim masih mungkin.
1183
+ Utamakan `start_process`/`stop_process` untuk dev server dan verifikasi pembersihan
1184
+ secara manual di Windows.
1185
+
1186
+ ## Keterbatasan yang diketahui
1187
+
1188
+ - **Pengujian API NVIDIA langsung membutuhkan kunci Anda.** Uji otomatis repositori
1189
+ ini sepenuhnya offline. Validasi langsung pada 2026-10-20 lolos dengan
1190
+ `z-ai/glm-5.3` (tool call sungguhan + jawaban akhir); model DeepSeek bawaan
1191
+ sedang habis waktu di sisi provider saat itu. Jalankan ulang
1192
+ `ryuu --doctor --live` untuk kunci dan region Anda.
1193
+ - Perilaku model, ukuran konteks, kuota gratis, batas laju, dan dukungan tool
1194
+ calling bergantung pada provider/model dan bisa berubah. Kapabilitas provider
1195
+ (`streaming`, `tools`, `reasoning`) hanya dipakai untuk memutuskan apa yang
1196
+ dikirim; reasoning tetap hanya ditampilkan bila provider mengirimnya.
1197
+ - Daftar model di katalog hanyalah id yang ditawarkan CLI. Ryuu tidak memeriksa
1198
+ ketersediaan model ke server, dan tidak melakukan fallback otomatis ke
1199
+ provider AI lain saat provider aktif gagal — itu mencegah kunci/data berpindah
1200
+ tanpa keputusan Anda, tetapi berarti Anda harus memilih provider lain dengan
1201
+ `/provider` atau `--provider`.
1202
+ - Token Harbor menyediakan model `deepseek-v4.1-flash:free`; Ryuu tidak menjamin
1203
+ layanan itu gratis tanpa batas. Kuota, ketersediaan, dan harga mengikuti
1204
+ ketentuan Token Harbor.
1205
+ - Respons dialirkan dan dikumpulkan sisi-klien: reasoning muncul langsung di
1206
+ kotaknya dan jawaban akhir dicetak langsung saat tiba (tanpa kotak, tanpa
1207
+ render ulang). Mode plain/non-TTY menyembunyikan aliran langsung dan hanya
1208
+ melaporkan jumlah karakter reasoning.
1209
+ - Tool call berjalan berurutan, bukan paralel. Prompt meminta model untuk
1210
+ menggabungkan pemanggilan independen ke dalam satu respons (beberapa baca
1211
+ sekaligus) untuk memotong bolak-balik.
1212
+ - Transport-nya stateless, jadi prompt dikirim ulang dengan setiap permintaan. Ia
1213
+ dirakit sekali dan identik byte-per-byte, tidak dibangun ulang; brief per-tugas
1214
+ adalah satu-satunya bagian yang bervariasi.
1215
+ - Worklog adalah lembar fakta ringkas, bukan isi file. Ia menghentikan pembacaan
1216
+ ulang dan analisis ulang yang redundan, tetapi file besar tetap harus dibaca
1217
+ sekali.
1218
+ - `--tui` menyimpan seluruh transkrip di memori karena setiap frame digambar ulang
1219
+ darinya. Tangkapan per-blok dibatasi, tetapi sesi yang sangat panjang tumbuh
1220
+ seiring jumlah blok; belum ada pencarian dalam aplikasi atau mode salin, dan
1221
+ pengubahan ukuran mengalirkan kembali frame dari model alih-alih mempertahankan
1222
+ tata letak piksel sebelumnya.
1223
+ - Penyalinan clipboard memakai OSC 52. Sebagian terminal menonaktifkannya, dan
1224
+ tmux perlu `set -g set-clipboard on`; ketika sekuensnya diabaikan, clipboard
1225
+ mempertahankan isi lamanya, sementara `/copy` tetap melaporkan jumlah karakter
1226
+ yang dikirim.
1227
+ - `/bash` dan `!cmd` sengaja melewati prompt persetujuan: Anda yang mengetik
1228
+ commandnya, jadi ia berjalan dengan izin Anda sendiri. `--read-only` adalah
1229
+ satu-satunya yang memblokirnya.
1230
+ - `/undo` terbatas pada sesi/tugas dan menolak saat file berubah di bawahnya.
1231
+ - Sesi disimpan lokal dan bukan log kejadian lengkap yang bisa diputar ulang.
1232
+ - Tidak ada sandbox atau isolasi kontainer bawaan.
1233
+ - Sintaks shell mentah harus diaktifkan secara eksplisit dan disetujui; penulisan
1234
+ atomik tidak melindungi dari proses eksternal yang crash di tengah penulisan.
1235
+ - Render Markdown mencakup sintaks umum; gambar, tabel bersarang, dan HTML
1236
+ kompleks ditampilkan sebagai teks.
1237
+ - `read_url` memakai ekstraktor HTML bawaan kecil: halaman yang dirender
1238
+ JavaScript, PDF, dan gambar tidak bisa dibaca, dan teks hasil ekstraksi adalah
1239
+ penyederhanaan dari halaman tersebut.
1240
+ - Ini project 0.3.1: harapkannya masih berkekakuan. Tidak ada klaim bebas bug.
1241
+
1242
+ ## Daftar periksa validasi API langsung
1243
+
1244
+ Dengan kunci asli yang dikonfigurasi:
1245
+
1246
+ 1. `ryuu --doctor` → semua pemeriksaan non-konektivitas lolos, kunci tidak
1247
+ dicetak.
1248
+ 2. `ryuu --doctor --live` → `provider.connectivity` lolos dan melaporkan
1249
+ latensi untuk provider aktif. Jika model bawaan habis waktu, konfirmasi dengan
1250
+ `--model z-ai/glm-5.3` sebelum menganggap kunci buruk (lihat bagian
1251
+ pemecahan masalah latensi).
1252
+ 3. Token Harbor (dengan `providers.tokenharbor.apiKey`): `ryuu --doctor --provider
1253
+ tokenharbor --live` → `provider.catalog` menandai `tokenharbor*` dan
1254
+ konektivitas lolos bila endpoint-nya dapat dijangkau. Bila provider tidak
1255
+ dapat dijangkau, itu dilaporkan sebagai kegagalan, bukan dianggap berhasil.
1256
+ 4. `npm link && ryuu` → banner menampilkan model, provider, workspace, versi
1257
+ Node. `/provider` menampilkan status kunci tanpa nilainya.
1258
+ 5. Tanya: `List the files in this project and summarize what it does.` → amati
1259
+ pemanggilan `list_files`/`read_file` sungguhan dan ringkasan akhir.
1260
+ 6. Tanya: `Add a comment to the top of <file> and show me the diff.` → setujui
1261
+ diff → konfirmasi file berubah dan ringkasannya menyebutkannya.
1262
+ 7. Tanya: `Run npm test and report the result.` → setujui command persisnya →
1263
+ verifikasi exit code asli, durasi, dan outputnya.
1264
+ 8. `/undo` bekerja untuk perubahan file; `/status`, `/diff`, `/sessions`,
1265
+ `/logs` memperlihatkan bukti.
1266
+
1267
+ ## Lisensi
1268
+
1269
+ MIT (lihat `package.json`).
2
1270
 
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.