@ryuu-reinzz/ryuu-cli 0.4.3 → 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 +70 -0
- package/README.md +26 -0
- package/docs/drive.md +484 -0
- package/package.json +4 -1
- package/plugins/google-drive/auth.js +57 -0
- package/plugins/google-drive/backup.js +282 -0
- package/plugins/google-drive/command.js +266 -0
- package/plugins/google-drive/config.js +252 -0
- package/plugins/google-drive/credentials.js +171 -0
- package/plugins/google-drive/drive-api.js +304 -0
- package/plugins/google-drive/index.js +26 -0
- package/plugins/google-drive/manager.js +507 -0
- package/plugins/google-drive/oauth-backend.js +161 -0
- package/plugins/google-drive/secret.js +50 -0
- package/plugins/google-drive/token-client.js +80 -0
- package/plugins/google-drive/tool-helpers.js +46 -0
- package/plugins/google-drive/tools-backup.js +204 -0
- package/plugins/google-drive/tools-files.js +315 -0
- package/plugins/google-drive/tools.js +13 -0
- package/plugins/google-drive/zip-reader.js +217 -0
- package/plugins/google-drive/zip-writer.js +204 -0
- package/plugins/index.js +56 -0
- package/src/agent/agent.js +12 -2
- package/src/cli/app.js +16 -0
- package/src/cli/commands.js +19 -0
- package/src/cli/markdown.js +10 -5
- package/src/cli/renderer.js +38 -5
- package/src/cli/tui.js +80 -9
- package/src/tools/registry.js +13 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,76 @@
|
|
|
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
|
+
|
|
6
76
|
## [0.4.3] - 2026-10-31
|
|
7
77
|
|
|
8
78
|
### Diperbaiki
|
package/README.md
CHANGED
|
@@ -815,6 +815,32 @@ berpura-pura sebaliknya.
|
|
|
815
815
|
`agent.streamIdleTimeoutMs` dibatalkan dengan `STREAM_STALLED` alih-alih
|
|
816
816
|
menahan langkah tetap terbuka.
|
|
817
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
|
+
|
|
818
844
|
## Model persetujuan dan izin
|
|
819
845
|
|
|
820
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.
|
|
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
|
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Helper autentikasi sisi CLI — TANPA kredensial.
|
|
5
|
+
*
|
|
6
|
+
* Semua langkah OAuth (authorization URL, PKCE verifier, penukaran code,
|
|
7
|
+
* refresh token, revoke) dikerjakan backend `website/api`, yang satu-satunya
|
|
8
|
+
* memegang Client ID & Client Secret. Modul ini hanya menyisakan hal yang
|
|
9
|
+
* memang milik CLI: membuka browser dan menerjemahkan pesan kegagalan.
|
|
10
|
+
*
|
|
11
|
+
* Tidak ada loopback callback server di sini: redirect Google menuju route
|
|
12
|
+
* publik backend (`/admin/tools/drive-callback`).
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/** Best-effort browser launch; returns false when no opener is available. */
|
|
16
|
+
export function openBrowser(url) {
|
|
17
|
+
const target = String(url);
|
|
18
|
+
const attempts =
|
|
19
|
+
process.platform === 'darwin'
|
|
20
|
+
? [['open', [target]]]
|
|
21
|
+
: process.platform === 'win32'
|
|
22
|
+
? [['cmd', ['/c', 'start', '', target]]]
|
|
23
|
+
: [['xdg-open', [target]], ['sensible-browser', [target]], ['gio', ['open', target]]];
|
|
24
|
+
return attempts.some(([command, args]) => {
|
|
25
|
+
try {
|
|
26
|
+
const child = spawn(command, args, { stdio: 'ignore', detached: true });
|
|
27
|
+
child.on('error', () => {});
|
|
28
|
+
child.unref();
|
|
29
|
+
return true;
|
|
30
|
+
} catch {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Turn a Google/network failure into an Indonesian hint. The message never
|
|
38
|
+
* contains a token: only the OAuth error code and a plain-language reason.
|
|
39
|
+
*/
|
|
40
|
+
export function describeOAuthFailure(error) {
|
|
41
|
+
const data = error?.response?.data || {};
|
|
42
|
+
const reason = String(data.error || error?.code || 'unknown');
|
|
43
|
+
const detail = String(data.error_description || error?.message || '').slice(0, 200);
|
|
44
|
+
if (reason === 'invalid_grant' || /invalid_grant/.test(detail)) {
|
|
45
|
+
return 'refresh token sudah tidak berlaku. Jalankan /drive login untuk menghubungkan ulang.';
|
|
46
|
+
}
|
|
47
|
+
if (reason === 'invalid_client' || /invalid_client/.test(detail)) {
|
|
48
|
+
return 'backend OAuth menolak kredensial Google. Hubungi admin layanan API.';
|
|
49
|
+
}
|
|
50
|
+
if (reason === 'access_denied') return 'akses ditolak oleh pengguna di halaman persetujuan Google.';
|
|
51
|
+
if (reason === 'invalid_request') return `permintaan OAuth tidak valid${detail ? ` (${detail})` : ''}.`;
|
|
52
|
+
if (/backend OAuth/i.test(detail)) return detail;
|
|
53
|
+
if (error?.code === 'ENOTFOUND' || error?.code === 'ECONNREFUSED' || error?.code === 'ETIMEDOUT') {
|
|
54
|
+
return 'tidak dapat menghubungi backend OAuth (masalah jaringan).';
|
|
55
|
+
}
|
|
56
|
+
return detail || reason;
|
|
57
|
+
}
|