@ryuu-reinzz/ryuu-cli 0.4.2 → 0.5.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.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,108 @@
3
3
  Semua perubahan penting pada Ryuu Agent CLI didokumentasikan di sini.
4
4
  Project ini mengikuti [Semantic Versioning](https://semver.org/).
5
5
 
6
+ ## [0.5.0] - 2026-11-01
7
+
8
+ Rilis ini menambahkan sistem plugin dan integrasi Google Drive, sekaligus
9
+ memindahkan seluruh kewenangan OAuth ke proyek API eksternal: CLI tidak lagi
10
+ menyimpan client id maupun client secret Google.
11
+
12
+ ### Ditambahkan
13
+
14
+ - **Sistem plugin.** Integrasi opsional kini hidup di `plugins/`, terpisah dari
15
+ `src/services/`, dan didaftarkan lewat `loadPlugins`/`syncPluginTools`.
16
+ Plugin hanya boleh menambah tool, command, dan konfigurasi sendiri; tool-nya
17
+ masuk registry yang sama sehingga approval, undo, dan audit tetap berlaku.
18
+ - **Integrasi Google Drive (`plugins/google-drive/`)** dengan 16 tool:
19
+ `drive_search`, `drive_list`, `drive_get_metadata`, `drive_download`,
20
+ `drive_upload`, `drive_create_folder`, `drive_move`, `drive_rename`,
21
+ `drive_copy`, `drive_delete`, `drive_backup`, `drive_restore`,
22
+ `drive_list_backups`, `drive_delete_backup`, `drive_storage_info`,
23
+ `drive_get_account`.
24
+ - **Command `/drive`**: `login`, `logout`, `account`, `enable`, `disable`,
25
+ `status`, `config [show]`, `config set <key> <value>`, `test`, `tools`.
26
+ - **Backup & restore proyek ke Drive** berbasis ZIP (writer/reader sendiri,
27
+ tanpa dependensi tambahan), dengan aturan exclusion dan hard-exclude untuk
28
+ berkas rahasia (`.env`, kunci privat, `node_modules`).
29
+ - **OAuth Web Application lewat backend API.** CLI meminta URL otorisasi ke
30
+ backend, membuka browser, lalu menerima hasil login tanpa pernah melihat
31
+ client id/secret. PKCE S256 dan penukaran `code` → token seluruhnya di server.
32
+ - **Kunci login khusus Drive** (header `x-ryuu-drive-auth`) yang tertanam,
33
+ terpisah dari root key admin, hanya aktif selama siklus login, dan tidak bisa
34
+ diubah lewat command, config, atau environment variable.
35
+ - **Tool plugin tampil beda di terminal**: nama tool dari plugin diberi warna
36
+ biru langit `#38bdf8` + bold di `--tui` maupun mode baris, sementara tool
37
+ bawaan tetap kuning (TUI) / biru datar (baris). Kegagalan tetap merah bold.
38
+ - **Blok kode dengan tombol copy** (`╭─ label ─… ⧉ copy ╮`) dipakai untuk URL
39
+ login Drive dan jawaban model yang mengandung blok kode. Satu blok = satu
40
+ bingkai utuh; klik barisnya menyalin teks asli (bukan potongan hasil wrap).
41
+ - **Dokumentasi** `docs/drive.md`: arsitektur plugin, alur OAuth, penyimpanan
42
+ credential terenkripsi AES-256-GCM, format backup, dan batasan scope
43
+ `drive.file`.
44
+
45
+ ### Diubah
46
+
47
+ - **Nama tool plugin** ikut membawa asal-usulnya (`plugin`) pada event
48
+ `toolStart`/`toolEnd`, sehingga renderer bisa membedakan tool bawaan dan tool
49
+ plugin tanpa lookup tambahan.
50
+ - **Bingkai blok kode dirapikan**: baris isi dipad dan ditutup `│`, pojok
51
+ kanan `╮`/`╯` dilengkapi, dan lebar dibagi rata agar tidak "bergerigi".
52
+ - **Satu perintah = satu blok transcript**: baris berurutan digabung sehingga
53
+ tidak ada garis pemisah di tengah output `/drive login`, dan notifikasi
54
+ sukses tidak lagi tercerai-berai.
55
+ - **`package.json`**: `plugins/` dan `docs/` ikut dipublikasikan; dependensi
56
+ baru `googleapis`.
57
+
58
+ ### Keamanan
59
+
60
+ - Client id/secret Google **tidak ada** di CLI: validasi menolak key
61
+ `clientId`, `clientSecret`, `scopes`, `oauthBackend`, `oauthCliPath`,
62
+ `rootKey`, `driveKey`, dan `secret`, dan normalizer membuang key asing
63
+ sehingga config tulisan tangan pun tidak bisa menyelipkan kredensial.
64
+ - Root key admin tidak pernah dikirim atau disimpan oleh plugin Drive;
65
+ refresh/revoke hanya memakai `grantId` sekali-terbit.
66
+ - Credential Drive disimpan terenkripsi (AES-256-GCM) dengan kunci terpisah
67
+ (`RYUU_DRIVE_KEY` dapat menggantikannya untuk secret manager).
68
+ - Jalur ekstraksi ZIP membatasi total ukuran (8 GB) dan menolak path traversal
69
+ serta symlink.
70
+
71
+ ### Diperbaiki
72
+
73
+ - Blok kode tanpa pojok kanan dan garis batas berantakan pada mode `--tui`.
74
+ - Notifikasi login Drive yang terpecah oleh rule gradien antarbaris.
75
+
76
+ ## [0.4.3] - 2026-10-31
77
+
78
+ ### Diperbaiki
79
+
80
+ - **Jawaban yang hanya mengumumkan langkah berikutnya tidak lagi menutup tugas
81
+ sebagai `completed`.** Gejalanya: edit terakhir berhasil, model menulis
82
+ "Edit applied. Let me verify the syntax…" tanpa satu pun tool call, lalu run
83
+ berhenti `◆ completed` padahal verifikasi yang dijanjikan tidak pernah jalan
84
+ dan tugasnya belum selesai. Kini jawaban yang kalimat penutupnya hanya
85
+ merencanakan aksi (`let me…`, `now I'll…`, `saya akan…`,
86
+ `langkah selanjutnya…`) dimasukkan ke riwayat dan model diminta mengirim tool
87
+ call-nya (heartbeat `UNFINISHED_ANSWER`, wording berputar, tidak memakan
88
+ langkah, dan **tanpa batas percobaan** — hanya `/cancel` atau tenggat tugas
89
+ yang menghentikannya, sama seperti giliran kosong). Kalimat yang
90
+ bertanya/menawarkan lanjutan ke pengguna ("let me know", "mau saya") tetap
91
+ dianggap jawaban akhir.
92
+ - **Batas percobaan pada respons terpotong dihapus.** Status `truncated` tidak
93
+ lagi dipakai untuk mengakhiri tugas lebih awal: respons yang kena
94
+ `agent.maxOutputTokens` terus diminta ulang dengan panggilan lebih kecil
95
+ (backoff eksponensial tetap, wording berputar) sampai tugas benar-benar
96
+ selesai, dibatalkan, atau tenggat waktu habis. Tidak ada lagi tugas yang
97
+ berhenti sendiri karena jumlah percobaan recovery.
98
+ - **Tugas yang berakhir tanpa jawaban tidak lagi disimpan sebagai jawaban
99
+ asisten.** Tugas `cancelled`/`failed`/`truncated` sebelumnya masuk memori
100
+ percakapan dengan teks `Task cancelled. No further tool calls were executed.`
101
+ sehingga tugas sesudahnya bisa memantulkannya kembali sebagai jawaban → run
102
+ `completed · 1 step · changed: none`. Kini hanya status `completed` dan
103
+ `step-limit` yang disimpan sebagai jawaban; sisanya menjadi catatan runtime
104
+ internal yang tidak boleh dikutip. Kontrak operasional prompt juga memuat
105
+ aturan baru: respons tanpa tool call mengakhiri tugas, jadi jangan pernah
106
+ menutup turn dengan mengumumkan aksi berikutnya.
107
+
6
108
  ## [0.4.2] - 2026-10-31
7
109
 
8
110
  ### Diperbaiki
package/README.md CHANGED
@@ -783,17 +783,64 @@ berpura-pura sebaliknya.
783
783
  - `finish_reason: \"length\"` ditangani: pemanggilan tool yang terpotong pada
784
784
  batas token output dibuang dan dilaporkan, tidak pernah dieksekusi sebagai
785
785
  file yang tertulis setengah jalan. Respons yang terpotong **tanpa** tool call
786
- yang utuh juga tidak pernah dianggap jawaban akhir: model diminta mengulang
787
- dengan panggilan yang lebih kecil (maksimum 3 kali, terlihat sebagai
788
- heartbeat `TRUNCATED_RESPONSE`), dan bila tetap terpotong tugas dihentikan
789
- dengan status `truncated` — bukan `completed` yang palsu. Sisa teks yang
790
- terpotong tetap masuk riwayat percakapan supaya model melanjutkan, bukan
791
- mengulang analisis.
786
+ yang utuh juga tidak pernah dianggap jawaban akhir: sisa teksnya tetap masuk
787
+ riwayat supaya model melanjutkan (bukan mengulang analisis), lalu model diminta
788
+ mengulang dengan panggilan yang lebih kecil (heartbeat `TRUNCATED_RESPONSE`,
789
+ backoff eksponensial). Pengulangan itu **tanpa batas percobaan** — sama seperti
790
+ giliran kosong, hanya `/cancel` atau tenggat tugas yang menghentikannya.
791
+ - Jawaban yang hanya **mengumumkan** langkah berikutnya ("Edit applied. Let me
792
+ verify the syntax…") juga tidak mengakhiri tugas: pengumuman itu masuk riwayat
793
+ dan model diminta mengirim tool call yang dijanjikannya sendiri (heartbeat
794
+ `UNFINISHED_ANSWER`, wording berputar, tanpa batas percobaan). Jadi run tidak
795
+ bisa berhenti sebagai `completed` dengan pekerjaan yang belum dijalankan.
796
+ Kalimat yang bertanya atau menawarkan lanjutan ke pengguna ("let me know",
797
+ "mau saya") tetap diterima sebagai jawaban akhir.
798
+ - Tugas yang berakhir `cancelled`/`failed` tidak disimpan sebagai jawaban
799
+ asisten, hanya sebagai catatan runtime internal, sehingga tugas berikutnya
800
+ tidak memantulkannya sebagai jawabannya sendiri.
801
+ - Respons tanpa tool call memang mengakhiri tugas, jadi jawaban yang kalimat
802
+ penutupnya hanya merencanakan aksi (`let me…`, `now I'll…`, `saya akan…`,
803
+ `langkah selanjutnya…`) tidak diterima sebagai hasil: model diminta mengirim
804
+ tool call-nya sendiri (heartbeat `UNFINISHED_ANSWER`, maksimum 2 kali, tanpa
805
+ memakan langkah), dan bila tetap mengumumkan tugas berhenti dengan status
806
+ `incomplete` (`✕ ended without finishing`) alih-alih `completed`. Kalimat yang
807
+ bertanya atau menawarkan lanjutan kepada Anda tetap dianggap jawaban akhir.
808
+ - Hanya tugas yang benar-benar menjawab (`completed`/`step-limit`) yang disimpan
809
+ sebagai jawaban di memori percakapan. Tugas `cancelled`, `failed`,
810
+ `truncated`, atau `incomplete` dicatat sebagai catatan runtime internal, jadi
811
+ tugas berikutnya tidak bisa memantulkan kembali "Task cancelled. No further
812
+ tool calls were executed." sebagai jawabannya sendiri.
792
813
  - Pada 70% anggaran waktu, model diberi tahu untuk menyatu (konvergen), dan
793
814
  aliran respons yang tidak mengirim byte selama
794
815
  `agent.streamIdleTimeoutMs` dibatalkan dengan `STREAM_STALLED` alih-alih
795
816
  menahan langkah tetap terbuka.
796
817
 
818
+ ## Plugin: Google Drive
819
+
820
+ Integrasi opsional yang tinggal di `agents/ryuu/plugins/` (bukan di `src/services/`) dan
821
+ mendaftarkan toolnya ke registry yang sudah ada hanya ketika aktif **dan** sudah login.
822
+
823
+ ```bash
824
+ /drive login # OAuth Web Application via API backend (CLI tanpa client id/secret)
825
+ /drive enable # daftarkan 16 tool Drive untuk AI Agent
826
+ /drive status # status integrasi, akun, scope, kuota
827
+ /drive test # diagnostik konfigurasi + credential + API
828
+ ```
829
+
830
+ Tool yang tersedia setelah aktif: `drive_search`, `drive_list`, `drive_get_metadata`,
831
+ `drive_download`, `drive_upload`, `drive_create_folder`, `drive_move`, `drive_rename`,
832
+ `drive_copy`, `drive_delete`, `drive_backup`, `drive_restore`, `drive_list_backups`,
833
+ `drive_delete_backup`, `drive_storage_info`, `drive_get_account`.
834
+
835
+ Client ID dan Client Secret Google **tidak ada di CLI**: keduanya hanya tersimpan di
836
+ proyek API (`website/api/lib/config.js`) yang melayani login lewat
837
+ `/admin/tools/drive-cli`. CLI memakai kunci khusus login Drive yang tertanam di
838
+ `plugins/google-drive/secret.js` (header `x-ryuu-drive-auth`, tidak dapat diubah dan
839
+ tidak pernah ditampilkan); root key admin tidak dipakai dan tidak dikirim ke CLI.
840
+
841
+ Endpoint backend dan redirect URI bersifat hardcode dan tidak bisa diubah lewat
842
+ command apa pun. Dokumentasi lengkap: **[docs/drive.md](docs/drive.md)**.
843
+
797
844
  ## Model persetujuan dan izin
798
845
 
799
846
  Kebijakannya diatur dengan `agent.approvalPolicy`:
package/docs/drive.md ADDED
@@ -0,0 +1,484 @@
1
+ # Google Drive Integration (plugin)
2
+
3
+ Integrasi Google Drive untuk Ryuu CLI. Plugin ini tinggal di `agents/ryuu/plugins/google-drive/`
4
+ dan terhubung ke arsitektur yang sudah ada: tool registry (`src/tools/registry.js`),
5
+ command parser (`src/cli/commands.js`), dan approval manager (`src/agent/approvals.js`).
6
+
7
+ Tidak ada sistem plugin, command parser, atau registry baru. Tidak ada backend OAuth eksternal.
8
+
9
+ ---
10
+
11
+ ## 1. Pengenalan
12
+
13
+ Setelah `/drive login` dan `/drive enable`, AI Agent dapat mencari file, upload, download,
14
+ mengelola folder, membuat backup ZIP project, dan melakukan restore — semuanya lewat
15
+ function tools yang sudah dikenal provider AI Ryuu.
16
+
17
+ Nama internal: `google-drive` · Command: `/drive` · Dependency: `googleapis`
18
+
19
+ ---
20
+
21
+ ## 2. Fitur
22
+
23
+ | Kelompok | Fitur |
24
+ | --- | --- |
25
+ | Autentikasi | OAuth 2.0 Web Application lewat backend API, PKCE S256 di server, logout + revocation |
26
+ | File | search, list, metadata, download, upload streaming, create folder, move, rename, copy, delete |
27
+ | Backup | ZIP streaming, exclusion pattern, upload streaming, structured result |
28
+ | Restore | download, validasi ZIP, ekstraksi anti Zip Slip, overwrite opt-in |
29
+ | Info | storage quota, akun terhubung |
30
+ | Lainnya | enable/disable, status, diagnostik, config manager |
31
+
32
+ ---
33
+
34
+ ## 3. Arsitektur
35
+
36
+ ```
37
+ plugins/
38
+ ├── index.js host: loadPlugins, syncPluginTools, unregisterPluginTools
39
+ └── google-drive/
40
+ ├── index.js deskriptor plugin + factory
41
+ ├── manager.js lifecycle (GoogleDrivePlugin), login/logout, status, diagnose
42
+ ├── config.js konfigurasi + validasi key (Client ID dikunci)
43
+ ├── credentials.js penyimpanan token terenkripsi AES-256-GCM
44
+ ├── auth.js PKCE, callback server, authorization URL, revocation
45
+ ├── drive-api.js client Drive API v3 (streaming, error mapping)
46
+ ├── backup.js engine backup/restore, exclusion, temp cleanup
47
+ ├── zip-writer.js penulis ZIP streaming (CRC32, data descriptor)
48
+ ├── zip-reader.js pembaca ZIP streaming + proteksi Zip Slip
49
+ ├── tools.js gabungan seluruh tool
50
+ ├── tools-files.js tool file & folder
51
+ ├── tools-backup.js tool backup/restore/info
52
+ ├── tool-helpers.js helper path lokal & format ukuran
53
+ └── command.js handler `/drive`
54
+ ```
55
+
56
+ Alur registrasi tool:
57
+
58
+ 1. `src/cli/app.js` memanggil `loadPlugins()` setelah registry dibangun.
59
+ 2. `syncPluginTools()` mendaftarkan tool **hanya bila** `enabled === true` **dan** sudah login.
60
+ 3. `/drive disable` dan `/drive logout` memanggil `unregisterPluginTools()`.
61
+ 4. Setiap handler tool memanggil `plugin.requireClient()`, yang memeriksa ulang status
62
+ dari disk — model yang masih memegang daftar tool lama tidak bisa mengeksekusi apa pun.
63
+
64
+ ---
65
+
66
+ ## 4. Persyaratan
67
+
68
+ - Node.js 18+ (proyek memakai ESM dan `node:test`).
69
+ - `googleapis` (sudah ditambahkan ke `package.json`).
70
+ - Project Google Cloud dengan **Google Drive API** aktif.
71
+ - OAuth Client bertipe **Web application** (redirect URI dikelola proyek API).
72
+
73
+ ---
74
+
75
+ ## 5. OAuth lewat backend API (credential bukan milik CLI)
76
+
77
+ Ryuu CLI **tidak memegang Client ID maupun Client Secret Google**. Keduanya hanya
78
+ ada di proyek root `website/api` (`lib/config.js`), dan seluruh langkah OAuth
79
+ dikerjakan di sana:
80
+
81
+ | Langkah | Dikerjakan oleh |
82
+ | --- | --- |
83
+ | `state` + PKCE verifier (S256) | backend API |
84
+ | authorization URL | backend API |
85
+ | penukaran `code` → token (memakai client secret) | backend API |
86
+ | refresh token & revocation | backend API (lewat `grantId`) |
87
+ | menyimpan token terenkripsi + memanggil Drive API | CLI |
88
+
89
+ Kontraknya: `POST /admin/tools/drive-cli` untuk
90
+ start/poll/refresh/revoke, dan `GET /admin/tools/drive-callback` untuk redirect
91
+ Google. Keduanya berada di folder `api/admin/tools/` proyek API.
92
+
93
+ ```
94
+ start { action: 'start' } → { state, url, redirectUri } [kunci login]
95
+ poll { action: 'poll', state } → { found, accessToken, grantId, … } [kunci login]
96
+ refresh { action: 'refresh', grantId } → { accessToken baru, grantId } [grant]
97
+ revoke { action: 'revoke', grantId } → { revoked } [grant]
98
+ ```
99
+
100
+ Refresh dan revoke tidak lagi mengirim refresh token maupun kunci aplikasi:
101
+ CLI memegang `grantId` (terbit sekali saat login, berlaku 90 hari, terikat satu
102
+ akun) dan backend menyimpan refresh token untuk grant tersebut.
103
+
104
+ Alur `/drive login`:
105
+
106
+ 1. CLI meminta `start`; backend membuat `state`, verifier PKCE, dan URL Google.
107
+ 2. CLI mencetak URL itu (dan membuka browser bila tersedia).
108
+ 3. Setelah pengguna setuju, Google mengarahkan ke
109
+ `https://restapi.ryuu-dev.my.id/admin/tools/drive-callback`.
110
+ 4. Backend menukar `code` + verifier + client secret menjadi token, lalu
111
+ menyimpannya maksimal 10 menit untuk diambil **sekali**.
112
+ 5. CLI mem-poll tiap 2 detik, menerima token, menyimpannya terenkripsi.
113
+
114
+ Endpoint backend dan redirect URI bersifat **hardcode**: tidak ada command,
115
+ config, maupun environment variable yang dapat mengubahnya.
116
+
117
+ ### Kunci login Drive (bukan root key)
118
+
119
+ Route CLI dilindungi kunci **khusus login Drive**: header `x-ryuu-drive-auth`,
120
+ nilai turunan HMAC-SHA256 dari 32 byte yang tertanam di
121
+ `website/api/lib/drive-login-key.js` (server) dan
122
+ `plugins/google-drive/secret.js` (CLI). Bahan disimpan sebagai empat potongan
123
+ ter-XOR, dan yang dikirim ke jaringan adalah turunannya — bukan bahan itu.
124
+
125
+ Tidak ada yang perlu diisi pengguna dan tidak ada command untuk mengubahnya.
126
+ Root key admin tidak dipakai di sini dan tidak pernah dikirim ke CLI, jadi
127
+ kebocoran sisi CLI tidak membuka endpoint admin lain. Kunci hanya terpakai pada
128
+ `start` dan `poll`; setelah login selesai CLI memakai `grantId` untuk
129
+ refresh/revoke, sehingga kunci aplikasi tidak tersimpan di mesin pengguna.
130
+
131
+ ---
132
+
133
+ ## 6. Yang tidak bisa diubah dari command
134
+
135
+ `validateConfigEntry` menolak key `clientId`, `clientSecret`, `scopes`,
136
+ `oauthBackend`, `oauthCliPath`, `rootKey`, `driveKey`, dan `secret`. Berkas
137
+ config yang ditulis tangan pun dibersihkan: hanya key yang dikenal plugin yang
138
+ dipertahankan, jadi kunci login Drive tidak bisa ditanamkan lewat berkas.
139
+
140
+ ---
141
+
142
+ ## 7. Google Cloud setup
143
+
144
+ 1. Buka Google Cloud Console → buat project.
145
+ 2. **APIs & Services → Library** → aktifkan **Google Drive API**.
146
+ 3. **OAuth consent screen** → tipe *External*, tambahkan scope `.../auth/drive.file`,
147
+ tambahkan akun Anda sebagai *test user* selama aplikasi belum dipublikasikan.
148
+ 4. **Credentials → Create credentials → OAuth client ID → Web application**.
149
+ 5. Tambahkan **Authorized redirect URI**:
150
+ `https://restapi.ryuu-dev.my.id/admin/tools/drive-callback`.
151
+ 6. Simpan Client ID + Client Secret di `website/api/lib/config.js`
152
+ (`global.driveOAuthClientId` / `global.driveOAuthClientSecret`). Tidak ada
153
+ tempat lain yang boleh menyimpannya.
154
+
155
+ ---
156
+
157
+ ## 8. Aktivasi Google Drive API
158
+
159
+ Tanpa langkah 7.2, login berhasil tetapi setiap panggilan API gagal dengan 403/`accessNotConfigured`.
160
+ Pesan error plugin akan menyebut scope `drive.file` dan menyarankan pemeriksaan ini.
161
+
162
+ ---
163
+
164
+ ## 9. Cara login
165
+
166
+ ```
167
+ /drive login
168
+ ```
169
+
170
+ Alur: minta URL otorisasi ke backend API (membawa kunci login Drive) → buka browser →
171
+ tunggu persetujuan → backend menukar code + PKCE verifier dengan client secret →
172
+ CLI mem-poll dan menerima token → simpan token terenkripsi → verifikasi akses dengan
173
+ `about.get` → aktifkan integrasi.
174
+
175
+ Contoh output:
176
+
177
+ ```
178
+ Google Drive Authentication
179
+
180
+ Browser tidak tersedia di mesin ini — buka tautan di bawah secara manual.
181
+ Login dengan akun Google lewat tautan berikut; blok di bawah bisa diklik untuk menyalin.
182
+
183
+ ╭─ login-url ────────────────────────────────────────────── ⧉ copy ╮
184
+ │ https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&… │
185
+ ╰──────────────────────────────────────────────────────────────────╯
186
+
187
+ Callback : https://restapi.ryuu-dev.my.id/admin/tools/drive-callback
188
+ Otorisasi : backend API https://restapi.ryuu-dev.my.id
189
+ Kredensial Google (client id/secret) tidak disimpan di CLI.
190
+
191
+ Menunggu persetujuan pengguna…
192
+ [✓] Autentikasi berhasil!
193
+
194
+ Akun : user@gmail.com
195
+ Permission : https://www.googleapis.com/auth/drive.file
196
+ Status : Connected
197
+
198
+ Google Drive aktif. Tools Drive sekarang tersedia untuk AI Agent.
199
+ ```
200
+
201
+ URL selalu dicetak sebagai blok berbingkai dengan tombol `⧉ copy`: di `--tui`
202
+ blok itu bisa diklik (dan menyalin URL utuh, bukan potongan baris hasil wrap),
203
+ sedangkan pada mode baris perintah peramban terminal sudah menyalinnya.
204
+
205
+ > Satu blok per perintah. Semua baris yang dicetak satu perintah tetap satu
206
+ > blok di `--tui`, jadi tidak ada garis pemisah yang menyelip di tengah
207
+ > notifikasi autentikasi.
208
+
209
+ Jika akun sudah terhubung, CLI menanyakan konfirmasi sebelum menimpa kredensial lama.
210
+
211
+ ---
212
+
213
+ ## 10. Login dari container, VPS, atau laptop
214
+
215
+ Tidak ada callback server lokal: port dalam container bukan lagi masalah, dan SSH
216
+ port forwarding tidak diperlukan.
217
+
218
+ 1. Jalankan `/drive login`. URL Google selalu dicetak lengkap.
219
+ 2. Buka URL itu di browser perangkat mana pun (boleh mesin berbeda) dan setujui.
220
+ 3. Google mengarahkan ke `https://restapi.ryuu-dev.my.id/admin/tools/drive-callback`.
221
+ 4. Backend menyimpan token sebentar; CLI mengambilnya lewat polling (2 detik) dan
222
+ langsung menyimpannya terenkripsi.
223
+
224
+ Batas waktu 5 menit; setelah itu jalankan `/drive login` lagi. Token di backend
225
+ dihapus setelah diambil atau setelah 10 menit.
226
+
227
+ Verifikasi backend tanpa menyentuh Google (fetch Google distub):
228
+
229
+ ```
230
+ cd website/api && node tools/test-drive-oauth.mjs
231
+ ```
232
+
233
+ ---
234
+
235
+ ## 11. Cara logout
236
+
237
+ ```
238
+ /drive logout
239
+ ```
240
+
241
+ Tool dilepas, token di memori dan di disk dihapus, encryption key ikut dihapus
242
+ (kecuali berasal dari `RYUU_DRIVE_KEY`), revocation OAuth dicoba. Konfigurasi
243
+ non-sensitif dan file pengguna di Google Drive **tidak** dihapus.
244
+
245
+ ---
246
+
247
+ ## 12. Enable / disable
248
+
249
+ | Perintah | Efek |
250
+ | --- | --- |
251
+ | `/drive enable` | Butuh login. Menulis `enabled=true` dan mendaftarkan seluruh tool. |
252
+ | `/drive disable` | Melepas tool dari registry, memblokir eksekusi, token & config tetap. |
253
+
254
+ Matriks lifecycle:
255
+
256
+ | enabled | authenticated | Tool tersedia |
257
+ | --- | --- | --- |
258
+ | ya | ya | ya |
259
+ | ya | tidak | tidak (arahkan `/drive login`) |
260
+ | tidak | ya | tidak |
261
+ | tidak | tidak | tidak |
262
+
263
+ ---
264
+
265
+ ## 13. Status
266
+
267
+ ```
268
+ /drive status
269
+ ```
270
+
271
+ Menampilkan status integrasi, autentikasi, akun, scope, status token, jumlah tool aktif,
272
+ folder backup, dan waktu pemeriksaan koneksi terakhir. Token tidak pernah ditampilkan.
273
+
274
+ ---
275
+
276
+ ## 14. Seluruh command
277
+
278
+ ```
279
+ /drive bantuan
280
+ /drive login mulai OAuth
281
+ /drive logout putuskan koneksi
282
+ /drive account akun + quota
283
+ /drive enable aktifkan integrasi
284
+ /drive disable nonaktifkan integrasi
285
+ /drive status ringkasan status
286
+ /drive config sama dengan config show
287
+ /drive config show tampilkan konfigurasi
288
+ /drive config set <k> <v> ubah satu konfigurasi
289
+ /drive test diagnostik lengkap
290
+ /drive tools daftar tool yang terdaftar
291
+ ```
292
+
293
+ Key konfigurasi: `enabled`, `defaultBackupFolder`, `backupDirectory`, `excludePatterns`,
294
+ `maxUploadSize`, `confirmDestructive`, `autoRefresh`.
295
+
296
+ ---
297
+
298
+ ## 15. Seluruh tool AI
299
+
300
+ | Tool | Risk | Keterangan |
301
+ | --- | --- | --- |
302
+ | `drive_search` | read | Cari by nama, MIME, folder, query mentah, pagination |
303
+ | `drive_list` | read | Isi folder |
304
+ | `drive_get_metadata` | read | Metadata lengkap satu item |
305
+ | `drive_download` | write | Unduh ke path lokal workspace, tidak menimpa tanpa izin |
306
+ | `drive_upload` | write | Upload streaming, dibatasi `maxUploadSize` |
307
+ | `drive_create_folder` | write | Buat folder |
308
+ | `drive_move` | write | Pindahkan item |
309
+ | `drive_rename` | write | Ubah nama |
310
+ | `drive_copy` | write | Salin file |
311
+ | `drive_delete` | destructive | Hapus permanen + approval |
312
+ | `drive_backup` | write | ZIP streaming + upload |
313
+ | `drive_restore` | write | Unduh + validasi + ekstraksi |
314
+ | `drive_list_backups` | read | Daftar arsip ZIP |
315
+ | `drive_delete_backup` | destructive | Hapus arsip backup hanya untuk `.zip` |
316
+ | `drive_storage_info` | read | Kuota penyimpanan |
317
+ | `drive_get_account` | read | Akun terhubung |
318
+
319
+ Operasi `destructive` melewati `ApprovalManager` yang sudah ada, sehingga
320
+ `--auto-approve` dan policy `read-only` tetap berlaku seperti tool bawaan.
321
+
322
+ ---
323
+
324
+ ## 16. Contoh backup
325
+
326
+ Perintah ke AI:
327
+
328
+ ```
329
+ Backup seluruh project ini ke Google Drive, kecuali node_modules, .git, .env dan cache.
330
+ ```
331
+
332
+ AI memanggil `drive_backup` dengan `sourcePath: "."` dan `excludePatterns` tambahan.
333
+ Default `excludePatterns` sudah memuat `node_modules`, `.git`, `.env`, `*.pem`, `*.key`,
334
+ `credentials.json`, `cache`, `tmp`, `temp`, `*.log`. Secret (`HARD_EXCLUDES`) tidak pernah
335
+ ikut meskipun pengguna memintanya.
336
+
337
+ Contoh hasil:
338
+
339
+ ```
340
+ Backup berhasil!
341
+ Project : Lyoraa
342
+ Arsip : lyoraa-backup-20261101-101500.zip
343
+ Ukuran : 42.8 MB
344
+ Lokasi : Google Drive / Backups
345
+ Dikecualikan: node_modules, .git, .env, cache, …
346
+ Status : berhasil
347
+ ```
348
+
349
+ ---
350
+
351
+ ## 17. Contoh restore
352
+
353
+ ```
354
+ Pulihkan backup Lyoraa terakhir ke folder ./restore.
355
+ ```
356
+
357
+ AI memanggil `drive_list_backups`, memilih arsip, lalu `drive_restore` dengan
358
+ `fileId` dan `destinationPath: "restore"`. Tanpa `overwrite=true`, restore berhenti
359
+ dan melaporkan file yang sudah ada.
360
+
361
+ ---
362
+
363
+ ## 18. Personal Drive
364
+
365
+ `folderId` yang dihilangkan berarti My Drive root (`root`). Semua panggilan memakai
366
+ `supportsAllDrives` dan `includeItemsFromAllDrives` agar item Shared Drive yang
367
+ terlihat oleh akun tetap terbaca.
368
+
369
+ ---
370
+
371
+ ## 19. Shared Folder
372
+
373
+ Gunakan folder ID sebagai identitas:
374
+
375
+ ```
376
+ /drive config set defaultBackupFolder <folder-id>
377
+ ```
378
+
379
+ Folder divalidasi lewat `assertFolder()` sebelum dipakai (harus ada dan bertipe folder).
380
+ Perlu dicatat: scope `drive.file` hanya memberi akses ke file yang dibuat atau dibuka
381
+ oleh aplikasi ini, jadi folder Shared Drive yang tidak pernah dibuka Ryuu akan ditolak
382
+ Google dengan 403. Pesan error menyebutkan hal ini, bukan menampilkan stack trace.
383
+
384
+ ---
385
+
386
+ ## 20. Lokasi credential per OS
387
+
388
+ | OS | Direktori |
389
+ | --- | --- |
390
+ | Linux | `$XDG_CONFIG_HOME/ryuu` atau `~/.config/ryuu` |
391
+ | macOS | `~/Library/Application Support/ryuu` |
392
+ | Windows | `%APPDATA%\ryuu` |
393
+
394
+ ```
395
+ .config/ryuu/
396
+ ├── config.json
397
+ ├── .keys/google-drive.key ← 0600, terpisah dari ciphertext
398
+ └── integrations/google-drive/
399
+ ├── config.json
400
+ └── credentials.enc ← AES-256-GCM
401
+ ```
402
+
403
+ `RYUU_CONFIG_DIR` (override yang sudah dipakai Ryuu) tetap berlaku.
404
+
405
+ ---
406
+
407
+ ## 21. Keamanan
408
+
409
+ - Token tidak pernah masuk ke context AI, log, atau output tool; `describeCredentials()`
410
+ hanya mengembalikan metadata.
411
+ - Ciphertext dan encryption key berada di direktori berbeda; file 0600, direktori 0700.
412
+ - `RYUU_DRIVE_KEY` (hex 64 karakter atau base64 32 byte) dapat menggantikan file kunci,
413
+ cocok untuk secret manager/VPS.
414
+ - Path lokal dari model divalidasi `Workspace.resolve()`: path absolut ditolak,
415
+ traversal dan symlink escape ditolak, file secret diblokir.
416
+ - ZIP: nama entri absolut dan `..` ditolak, symlink tujuan ditolak, CRC32 diverifikasi,
417
+ total ekstraksi dibatasi 8 GB.
418
+ - Backup tidak pernah menyertakan secret secara diam-diam, dan symlink tidak diikuti.
419
+
420
+ ---
421
+
422
+ ## 22. Error handling
423
+
424
+ Semua pesan berbahasa Indonesia dan tanpa stack trace secara default:
425
+
426
+ | Kondisi | Pesan |
427
+ | --- | --- |
428
+ | Belum login | `Jalankan /drive login terlebih dahulu` |
429
+ | Integrasi nonaktif | `Integrasi Google Drive sedang nonaktif. Jalankan /drive enable` |
430
+ | Refresh token invalid | `refresh token sudah tidak berlaku. Jalankan /drive login` |
431
+ | 403 permission | Menyebut scope `drive.file` |
432
+ | 403/429 kuota | `Kuota Google Drive habis atau rate limit API tercapai` |
433
+ | 404 | `File atau folder Google Drive tidak ditemukan` |
434
+ | Network | `Tidak dapat menghubungi Google Drive (masalah jaringan)` |
435
+ | Disk penuh | `Disk penuh: tidak ada ruang untuk menulis file` |
436
+ | ZIP rusak | `CRC arsip tidak cocok …` / `Arsip bukan ZIP yang valid` |
437
+ | Zip Slip | `Arsip berisi path traversal dan ditolak` |
438
+
439
+ ---
440
+
441
+ ## 23. Troubleshooting
442
+
443
+ | Gejala | Penyebab | Tindakan |
444
+ | --- | --- | --- |
445
+ | `redirect_uri_mismatch` | Redirect URI belum terdaftar | Tambahkan `https://restapi.ryuu-dev.my.id/admin/tools/drive-callback` di OAuth client Web application |
446
+ | Login sukses, tool tidak muncul | `enabled=false` | `/drive enable` |
447
+ | Tool hilang setelah restart | Belum pernah `/drive enable` | Jalankan sekali, nilainya tersimpan |
448
+ | `accessNotConfigured` | Drive API belum aktif | Aktifkan di Cloud Console |
449
+ | 403 pada file tertentu | Di luar scope `drive.file` | Buka file lewat Ryuu setelah upload |
450
+ | `Kredensial tidak dapat didekripsi` | Key berbeda/rusak | `/drive login` ulang |
451
+ | Restore berhenti | File tujuan sudah ada | Kirim `overwrite=true` |
452
+ | Backup melebihi batas | `maxUploadSize` | Naikkan atau tambah exclusion |
453
+
454
+ ---
455
+
456
+ ## 24. Batasan OAuth scope
457
+
458
+ Scope default `https://www.googleapis.com/auth/drive.file` hanya memberi akses ke file
459
+ yang dibuat atau dibuka oleh aplikasi ini. Konsekuensinya:
460
+
461
+ - File pribadi lain di Drive tidak terlihat oleh `drive_search`/`drive_list`.
462
+ - Shared Drive yang belum pernah dibuka Ryuu akan ditolak.
463
+ - `drive.file` **tidak** dapat dinaikkan menjadi `drive` dari command; itu keputusan
464
+ keamanan, bukan bug. Untuk akses penuh, ubah `DEFAULT_GOOGLE_DRIVE_CONFIG.scopes`
465
+ di source setelah meninjau risikonya.
466
+ - Google Picker tidak diimplementasikan, sehingga "membuka" file untuk aplikasi harus
467
+ dilakukan lewat upload Ryuu sendiri.
468
+
469
+ ---
470
+
471
+ ## Testing
472
+
473
+ `tests/unit/google-drive.test.js` (7 test, seluruhnya lulus):
474
+
475
+ - default config, Client ID terkunci, validasi key
476
+ - penyimpanan kredensial terenkripsi + tidak ada token di metadata
477
+ - PKCE S256, state acak, URL otorisasi tanpa client secret
478
+ - roundtrip ZIP streaming, penolakan overwrite, penolakan Zip Slip
479
+ - exclusion pattern + secret hard-exclude
480
+ - lifecycle: disabled/unauthenticated tidak pernah mendaftarkan tool, risk level benar
481
+ - schema seluruh tool valid dan unik
482
+
483
+ Tidak ada klaim autentikasi sungguhan: belum ada kredensial OAuth nyata yang dipakai
484
+ saat pengujian, jadi alur login hanya diverifikasi secara struktural (URL, PKCE, state).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ryuu-reinzz/ryuu-cli",
3
- "version": "0.4.2",
3
+ "version": "0.5.0",
4
4
  "description": "Ryuu Agent CLI - an autonomous, approval-gated AI coding agent for your terminal",
5
5
  "type": "module",
6
6
  "bin": {
@@ -12,6 +12,8 @@
12
12
  },
13
13
  "files": [
14
14
  "src",
15
+ "plugins",
16
+ "docs",
15
17
  "README.md",
16
18
  "CHANGELOG.md"
17
19
  ],
@@ -41,6 +43,7 @@
41
43
  "dependencies": {
42
44
  "@modelcontextprotocol/sdk": "^1.32.1",
43
45
  "chalk": "^6.0.1",
46
+ "googleapis": "^189.0.0",
44
47
  "openai": "^7.39.0",
45
48
  "ora": "^9.4.1"
46
49
  }