luang-cli 0.2.0 → 0.3.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/README.md +78 -21
- package/bin/luang-cli.js +48 -18
- package/lib/agent.js +497 -73
- package/lib/commands.js +69 -3
- package/lib/core.js +8 -0
- package/lib/ws.js +552 -0
- package/package.json +4 -1
- package/test/bin/opencode +5 -0
- package/test/http-stub.js +39 -0
- package/test/run-e2e.js +458 -0
- package/test/ws-stub.js +202 -0
package/README.md
CHANGED
|
@@ -145,7 +145,8 @@ CLI API Browser (kamu)
|
|
|
145
145
|
| `provider add` | Tambah / override provider config | `luang-cli provider add codex --exec_template 'codex exec "{task}"'` |
|
|
146
146
|
| `provider remove` | Hapus provider dari config | `luang-cli provider remove codex` |
|
|
147
147
|
| `provider default` | Set provider default (dipakai `agent --exec`) | `luang-cli provider default opencode` |
|
|
148
|
-
| `
|
|
148
|
+
| `telemetry` | Konsent kirim data usage/model (`on\|off\|status`) | `luang-cli telemetry status` |
|
|
149
|
+
| `agent` | Daemon **realtime WS** + heartbeat + polling (fallback) | `luang-cli agent --exec` |
|
|
149
150
|
| `task list` | Daftar tugas agent (filter status) | `luang-cli task list --status todo` |
|
|
150
151
|
| `task create` | Buat tugas (butuh izin admin) | `luang-cli task create --title "Refactor auth" --priority high` |
|
|
151
152
|
| `task status` | Perbarui status + catatan progress | `luang-cli task status TGS-12 in_progress --note "mulai"` |
|
|
@@ -186,42 +187,82 @@ Daftar manual (path khusus, alias brand, multi-provider):
|
|
|
186
187
|
|
|
187
188
|
```bash
|
|
188
189
|
luang-cli provider add claude_code --path /opt/homebrew/bin/claude \
|
|
189
|
-
--exec_template 'claude -p "{task}"'
|
|
190
|
-
luang-cli provider add opencode --exec_template 'opencode run "{task}"'
|
|
191
|
-
luang-cli provider default
|
|
190
|
+
--exec_template 'claude -p --model {model} "{task}"'
|
|
191
|
+
luang-cli provider add opencode --exec_template 'opencode run "{task}"' --model sonnet-4
|
|
192
|
+
luang-cli provider default opencode
|
|
192
193
|
```
|
|
193
194
|
|
|
194
|
-
- `{task}` pada template otomatis diganti **judul + deskripsi** tugas; template tanpa `{task}` tidak dipakai mode `--exec
|
|
195
|
-
-
|
|
195
|
+
- `{task}` pada template otomatis diganti **judul + deskripsi** tugas; template tanpa `{task}` tidak dipakai mode `--exec` (notifikasi saja).
|
|
196
|
+
- `{model}` diganti model aktif provider — urutan: `agent.command set_model` (dashboard) → `--model` / field `model` di config → env `LUANG_MODEL`. Bila template memuat `{model}` tapi tak ada model, tugas ditunda dengan notifikasi (bukan error diam-diam).
|
|
197
|
+
- Provider tipe yang dikenal tapi tidak punya `exec_template` di config memakai template default pada tabel di atas.
|
|
198
|
+
- Daftar provider tersimpan di `~/.luang-cli/config.json` dan disinkron ke server lewat heartbeat / frame WS `provider.update` (`provider_count` + model tampil di dashboard Agents).
|
|
196
199
|
|
|
197
200
|
---
|
|
198
201
|
|
|
199
202
|
## 🖥 Daemon agent (`luang-cli agent`)
|
|
200
203
|
|
|
201
204
|
```bash
|
|
202
|
-
luang-cli agent #
|
|
203
|
-
luang-cli agent --interval 10 # poll tiap 10 detik
|
|
204
|
-
luang-cli agent --exec #
|
|
205
|
+
luang-cli agent # realtime WS (heartbeat + push tugas); fallback polling bila WS gagal
|
|
206
|
+
luang-cli agent --interval 10 # (mode fallback) poll tiap 10 detik
|
|
207
|
+
luang-cli agent --exec # eksekusi tugas To-Do (konfirmasi y/N)
|
|
205
208
|
```
|
|
206
209
|
|
|
207
|
-
|
|
210
|
+
**Mode utama: realtime WebSocket** (`/api/v1/agent/ws`, auth `X-CLI-Key`) — tugas di-assign dari dashboard langsung ter-push ke terminal tanpa polling. **Fallback**: bila WS gagal (jaringan/proxy/Node lama), otomatis turun ke polling HTTP seperti versi lama — daemon tetap hidup, tidak crash.
|
|
211
|
+
|
|
212
|
+
Perilaku (kedua mode):
|
|
208
213
|
|
|
209
|
-
1. **Heartbeat**
|
|
210
|
-
2. **
|
|
214
|
+
1. **Heartbeat** — `hello` sekali saat connect + heartbeat berkala (nama + provider tersinkron; dashboard tampil "Terakhir aktif …" + badge **Live**).
|
|
215
|
+
2. **Perubahan tugas terdeteksi** (push WS / fingerprint polling): status, agent_status, catatan, judul.
|
|
211
216
|
3. Baris perubahan dicetak: `[HH:MM:SS] TGS-1 Judul In Progress agent=… note="…"`.
|
|
212
|
-
4. Tugas yang dipindah status di dashboard → tampil live; tugas yang
|
|
217
|
+
4. Tugas yang dipindah status di dashboard → tampil live; tugas yang di-assign ke agent-mu → event `task.assigned`.
|
|
218
|
+
|
|
219
|
+
### Perintah dari dashboard (realtime)
|
|
220
|
+
|
|
221
|
+
Server bisa mengirim `agent.command` — CLI menanganinya langsung lalu membalas `command.ack`:
|
|
222
|
+
|
|
223
|
+
| Perintah | Efek di CLI |
|
|
224
|
+
|---|---|
|
|
225
|
+
| `set_model` | Simpan model ke provider config (tampil `🎛 set_model → opencode = …`), dipakai placeholder `{model}` |
|
|
226
|
+
| `pause` | Eksekusi `--exec` ditunda (heartbeat tetap jalan) |
|
|
227
|
+
| `resume` | Eksekusi dilanjutkan |
|
|
213
228
|
|
|
214
229
|
### Mode `--exec` (semi-otomatis, konfirmasi y/N)
|
|
215
230
|
|
|
216
|
-
Bila ada tugas `To-Do` yang belum pernah dieksekusi dan default provider punya `exec_template` berisi `{task}
|
|
231
|
+
Bila ada tugas `To-Do` yang belum pernah dieksekusi dan default provider punya `exec_template` berisi `{task}` (dan tidak sedang PAUSE):
|
|
217
232
|
|
|
218
233
|
1. Konfirmasi `y/N` di terminal — **tidak ada eksekusi diam-diam**.
|
|
219
|
-
2. CLI kirim `{status:"in_progress", agent_status:"menjalankan"}`
|
|
220
|
-
3. Template dipanggil via `child_process.
|
|
234
|
+
2. CLI kirim `{status:"in_progress", agent_status:"menjalankan"}` (via WS bila realtime, HTTP bila fallback).
|
|
235
|
+
3. Template dipanggil via `child_process.spawn` (shell mode, `stdio:inherit` → output provider tampil live, timeout **15 menit**, `windowsHide` on).
|
|
221
236
|
4. Hasil:
|
|
222
237
|
- exit 0 → `{status:"done", agent_status:"selesai", progress_note:"exec exit 0"}`;
|
|
223
238
|
- exit ≠ 0 → `{status:"review", agent_status:"gagal", progress_note:"exit code <n>"}`.
|
|
224
|
-
5.
|
|
239
|
+
5. Setelah selesai, event **`telemetry.run`** (started/completed/failed + provider/model/durasi/exit_code) dikirim **hanya bila konsent telemetry aktif** (lihat bawah).
|
|
240
|
+
6. Riwayat eksekusi disimpan `~/.luang-cli/state.json` — tugas yang sama tidak ditawari dua kali. `Ctrl+C` keluar bersih.
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## 📡 Telemetry (ops-in, konsent ganda)
|
|
245
|
+
|
|
246
|
+
Daemon bisa mengirim **data ringan** setiap kali eksekusi berjalan (`telemetry.run`): provider, model, durasi, exit_code, jumlah token (bila tersedia). **Tanpa isi kode / deskripsi tugas** — hanya metadata usage. Ini yang membuat dashboard bisa menampilkan usage & kontrol model secara realtime.
|
|
247
|
+
|
|
248
|
+
**Konsent ganda — keduanya harus ON, kalau tidak CLI tidak mengirim apa pun:**
|
|
249
|
+
|
|
250
|
+
| Level | Cara mengubah | Field |
|
|
251
|
+
|---|---|---|
|
|
252
|
+
| Lokal (mesin ini) | `luang-cli telemetry on` / `off` | `config.json → telemetry` (default `false`) |
|
|
253
|
+
| Server (per key) | Dashboard **Agents → toggle "Kirim telemetri"** | `agent_keys.telemetry_enabled` |
|
|
254
|
+
|
|
255
|
+
Cek status efektif:
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
luang-cli telemetry status
|
|
259
|
+
# 📊 Telemetry — konsent ganda (opt-in, bukan opt-out)
|
|
260
|
+
# Lokal : config.telemetry = false ❌ (…)
|
|
261
|
+
# Server : telemetry_enabled = false ❌ (Agents → toggle Telemetry di dashboard)
|
|
262
|
+
# Efektif: lokal ❌ · server ❌ → efektif ❌ tidak dikirim
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
Bila hanya satu yang ON → tetap tidak dikirim (dan server pun men-drop bila konsent server mati). Flag server diambil dari `/api/v1/agent/me` (atau `hello.ok` saat realtime).
|
|
225
266
|
|
|
226
267
|
---
|
|
227
268
|
|
|
@@ -257,6 +298,7 @@ Tools yang tersedia: `luang_list_tasks`, `luang_update_task_status`, `luang_crea
|
|
|
257
298
|
|---|---|---|
|
|
258
299
|
| `task status`, daemon update | **Bisa-update-tugas** (default ✅) | `can_update_tasks` |
|
|
259
300
|
| `task create` | **Bisa-buat-tugas** (default ❌) | `can_create_tasks` |
|
|
301
|
+
| kirim `telemetry.run` (usage/model) | **Kirim telemetri** (default ❌) | `telemetry_enabled` |
|
|
260
302
|
|
|
261
303
|
Bila `task create` gagal HTTP **403**, minta owner buka **app.luang.id/agents → pilih key → nyalakan \"Bisa-buat-tugas\"**, lalu cek ulang via `luang-cli whoami`.
|
|
262
304
|
|
|
@@ -272,8 +314,9 @@ File config auto-dibuat: `~/.luang-cli/config.json`
|
|
|
272
314
|
"app_url": "https://app.luang.id",
|
|
273
315
|
"key": "lk_…", // hasil login
|
|
274
316
|
"name": null, // nama sesi terakhir
|
|
317
|
+
"telemetry": false, // konsent lokal telemetry (default OFF)
|
|
275
318
|
"default_provider": null,
|
|
276
|
-
"providers": { "opencode": { "path": "…", "exec_template": "…" } }
|
|
319
|
+
"providers": { "opencode": { "path": "…", "exec_template": "…", "model": "sonnet-4" } }
|
|
277
320
|
}
|
|
278
321
|
```
|
|
279
322
|
|
|
@@ -285,6 +328,9 @@ State runtime (riwayat terlihat / eksekusi): `~/.luang-cli/state.json`.
|
|
|
285
328
|
| `LUANG_API_URL` | Timpa `api_url` |
|
|
286
329
|
| `LUANG_APP_URL` | Timpa `app_url` |
|
|
287
330
|
| `LUANG_NO_OPEN` | `login` tidak membuka browser otomatis (cetak URL saja) |
|
|
331
|
+
| `LUANG_MODEL` | Model cadangan untuk placeholder `{model}` |
|
|
332
|
+
| `LUANG_NO_WS` | `1`/`true` → matikan mode realtime, paksa polling HTTP |
|
|
333
|
+
| `LUANG_HEARTBEAT_SEC` | Interval heartbeat (detik; default internal) |
|
|
288
334
|
|
|
289
335
|
Precedence: **flag `--api <url>` → env → config file**.
|
|
290
336
|
|
|
@@ -302,6 +348,9 @@ Precedence: **flag `--api <url>` → env → config file**.
|
|
|
302
348
|
| Timeout 15 detik | Cek jaringan + `--api https://api.luang.id` |
|
|
303
349
|
| Tabel provider kosong | `provider add <type> --exec_template '…'` manual |
|
|
304
350
|
| `--exec` tak berjalan | Set `provider default <type>` + template harus berisi `{task}` |
|
|
351
|
+
| Menunggu `{model}` (tugas ditunda) | `provider add <type> --model <m>` · env `LUANG_MODEL` · atau set_model dari dashboard |
|
|
352
|
+
| Daemon tak realtime (polling) | Wajar bila WS gagal — cek `LUANG_NO_WS`, proxy/firewall port WS; polling tetap jalan |
|
|
353
|
+
| Telemetry tak terkirim | Butuh konsent **ganda**: `luang-cli telemetry on` + toggle server di dashboard Agents |
|
|
305
354
|
| Daemon boring (tak ada event) | Wajar — ia hanya print KETIKA ada perubahan; coba assign tugas dari dashboard |
|
|
306
355
|
| Mulai bersih total | Hapus `~/.luang-cli/` (config + state) |
|
|
307
356
|
|
|
@@ -328,8 +377,14 @@ luang-cli/
|
|
|
328
377
|
├── bin/luang-cli.js # entrypoint + dispatcher
|
|
329
378
|
├── lib/
|
|
330
379
|
│ ├── core.js # config, env, HTTP, provider detect, key util
|
|
331
|
-
│ ├──
|
|
332
|
-
│
|
|
380
|
+
│ ├── ws.js # klien WebSocket RFC 6455 (zero-dep) utk mode realtime
|
|
381
|
+
│ ├── commands.js # login/logout/whoami/telemetry/provider
|
|
382
|
+
│ └── agent.js # daemon realtime+fallback, task CRUD, status, mcp-config
|
|
383
|
+
├── test/
|
|
384
|
+
│ ├── run-e2e.js # E2E offline (61 assertion): realtime, fallback, konsent, model
|
|
385
|
+
│ ├── ws-stub.js # stub server WS (RFC 6455) utk uji offline
|
|
386
|
+
│ ├── http-stub.js # stub HTTP /agent/me (di-fork, IPC) utk uji konsent
|
|
387
|
+
│ └── bin/opencode # stub binary provider (tidak pernah panggil claude/gemini asli)
|
|
333
388
|
└── package.json # bin: luang-cli → bin/luang-cli.js
|
|
334
389
|
```
|
|
335
390
|
|
|
@@ -338,6 +393,7 @@ Jalankan dari source:
|
|
|
338
393
|
```bash
|
|
339
394
|
node bin/luang-cli.js help
|
|
340
395
|
node --check bin/luang-cli.js # sanity sintaks
|
|
396
|
+
node test/run-e2e.js # E2E offline penuh (tanpa jaringan/provider asli)
|
|
341
397
|
```
|
|
342
398
|
|
|
343
399
|
Publish (owner):
|
|
@@ -353,11 +409,12 @@ npm publish --access public --auth-type=legacy --otp=<kode2FA>
|
|
|
353
409
|
|
|
354
410
|
## 🗺 Roadmap
|
|
355
411
|
|
|
412
|
+
- [x] Realtime WebSocket (daemon + push tugas + perintah `set_model`/`pause`/`resume`) ✅ v0.3.0
|
|
413
|
+
- [x] Telemetry ops-in konsent ganda + `provider --model` / placeholder `{model}` ✅ v0.3.0
|
|
356
414
|
- [ ] MCP tools lanjutan: komentar tugas, subtasks, timesheet
|
|
357
415
|
- [ ] `agent --exec` multi-provider (eksekusi paralel beberapa harness)
|
|
358
416
|
- [ ] Skill installer (Claude Code skill / OpenCode skill yang membungkus luang-cli)
|
|
359
417
|
- [ ] `luang-cli setup` wizard pertama kali (login + provider + tes)
|
|
360
|
-
- [ ] Webhook/SSE realtime (pengganti polling)
|
|
361
418
|
|
|
362
419
|
---
|
|
363
420
|
|
package/bin/luang-cli.js
CHANGED
|
@@ -17,7 +17,8 @@ Command:
|
|
|
17
17
|
logout Hapus API key tersimpan luang-cli logout [-y]
|
|
18
18
|
whoami Info sesi agent, tenant & izin luang-cli whoami
|
|
19
19
|
provider Kelola provider CLI coding luang-cli provider list
|
|
20
|
-
|
|
20
|
+
telemetry Konsent telemetry on|off|status luang-cli telemetry status
|
|
21
|
+
agent Daemon realtime WS + polling tugas luang-cli agent [--exec]
|
|
21
22
|
task Kelola tugas (list|create|status) luang-cli task list
|
|
22
23
|
status Ringkasan CLI (key, tenant, izin) luang-cli status
|
|
23
24
|
mcp-config Snippet konfigurasi MCP klien luang-cli mcp-config
|
|
@@ -74,38 +75,65 @@ Penggunaan:
|
|
|
74
75
|
~/.config/opencode atau ~/.opencode (opencode), ~/.gemini (gemini_cli),
|
|
75
76
|
plus which(1) untuk claude, codex, opencode, gemini.
|
|
76
77
|
Bila key ada, hasil langsung disinkron via POST /api/v1/agent/heartbeat.
|
|
77
|
-
luang-cli provider add <type> [--path P] [--exec_template T]
|
|
78
|
+
luang-cli provider add <type> [--path P] [--exec_template T] [--model M]
|
|
78
79
|
luang-cli provider remove <type>
|
|
79
80
|
luang-cli provider default <type>
|
|
80
81
|
|
|
81
82
|
Param exec_template:
|
|
82
|
-
{task}
|
|
83
|
-
|
|
83
|
+
{task} diganti judul + deskripsi tugas saat mode --exec.
|
|
84
|
+
{model} diganti nama model (provider add --model, agent.command set_model dari
|
|
85
|
+
server, atau env LUANG_MODEL). Bila {model} ada tapi tidak ada model
|
|
86
|
+
yang bisa dipakai → eksekusi dilewati dengan notifikasi (bukan crash).
|
|
87
|
+
Contoh: 'opencode run --model "{model}" "{task}"'
|
|
84
88
|
|
|
85
89
|
Contoh:
|
|
86
|
-
luang-cli provider add opencode --exec_template 'opencode run "{task}"'
|
|
90
|
+
luang-cli provider add opencode --exec_template 'opencode run --model "{model}" "{task}"' --model opencode/gpt-5.1-codex
|
|
87
91
|
luang-cli provider default opencode`,
|
|
88
92
|
|
|
89
|
-
|
|
93
|
+
telemetry: `luang-cli telemetry — konsent telemetry (opt-in, default OFF)
|
|
94
|
+
|
|
95
|
+
Penggunaan:
|
|
96
|
+
luang-cli telemetry on|off|status
|
|
97
|
+
|
|
98
|
+
Perilaku:
|
|
99
|
+
• on/off mengubah config.telemetry (~/.luang-cli/config.json).
|
|
100
|
+
• status menampilkan DUA flag yang harus ON bersamaan:
|
|
101
|
+
lokal : config.telemetry (di sini)
|
|
102
|
+
server : agent_keys.telemetry_enabled — nyalakan di dashboard (Agents → Telemetry)
|
|
103
|
+
• Efektif kirim HANYA bila dua-duanya ON. Archetype frame: telemetry.run via WS
|
|
104
|
+
realtime (started/completed/failed saat exec) — pakai header X-CLI-Key, bukan JWT.
|
|
105
|
+
• Satu flag mati → CLI tidak pernah mengirim data run (server juga men-drop).`,
|
|
106
|
+
|
|
107
|
+
agent: `luang-cli agent — daemon realtime (WebSocket) + fallback polling
|
|
90
108
|
|
|
91
109
|
Penggunaan:
|
|
92
110
|
luang-cli agent [--interval N] [--exec]
|
|
93
111
|
|
|
112
|
+
Mode:
|
|
113
|
+
1) Realtime WebSocket (utama): wss://<api>/api/v1/agent/ws, auth header
|
|
114
|
+
X-CLI-Key. Kirim hello {mode:"cli", version, providers}, lalu:
|
|
115
|
+
• heartbeat frame tiap ≤30s (interval 25s)
|
|
116
|
+
• menerima task.snapshot / task.assigned / task.updated → tampil live
|
|
117
|
+
• exec --exec + konfirmasi y/N seperti biasa; status tugas
|
|
118
|
+
(task.status) & telemetry.run dikirim via WS
|
|
119
|
+
• menerima agent.command: set_model (menyimpan model provider +
|
|
120
|
+
command.ack), pause/resume (menghentikan eksekusi; heartbeat tetap)
|
|
121
|
+
2) Fallback polling HTTP (lama): bila WS gagal (kunci ditolak, server lama,
|
|
122
|
+
jaringan) atau env LUANG_NO_WS=1 → perilaku polling lama tetap jalan.
|
|
123
|
+
Telemetry tidak dikirim di mode ini (tidak ada kanal realtime).
|
|
124
|
+
|
|
94
125
|
Opsi:
|
|
95
|
-
--interval N Jeda polling
|
|
126
|
+
--interval N Jeda polling fallback HTTP (detik, default 5)
|
|
96
127
|
--exec Eksekusi otomatis tugas "To-Do" yang belum dijalankan
|
|
128
|
+
Sebelum menjalankan: konfirmasi y/N; template dipakai dengan
|
|
129
|
+
{task} (judul+deskripsi) dan {model} (model provider).
|
|
130
|
+
exit 0 → done/"selesai"; gagal → review/"gagal exit N".
|
|
131
|
+
Tiap run juga mengirim telemetry.run started/completed/failed
|
|
132
|
+
(consent: telemetry on + flag server ON).
|
|
97
133
|
|
|
98
134
|
Perilaku:
|
|
99
|
-
•
|
|
100
|
-
•
|
|
101
|
-
[HH:MM:SS] TGS-1 Judul In Progress agent=… note="…"
|
|
102
|
-
• State: ~/.luang-cli/state.json
|
|
103
|
-
• --exec: tugas To-Do + default provider punya exec_template berisi {task}
|
|
104
|
-
perlu konfirmasi y/N lalu dijalankan; sebelum eksekusi status di-set
|
|
105
|
-
in_progress, lalu done ("selesai", progress_note "exec selesai") bila
|
|
106
|
-
exit 0; review ("gagal") bila gagal. exec_template kosong/tanpa {task}
|
|
107
|
-
→ cukup notifikasi satu baris.
|
|
108
|
-
• Ctrl+C menghentikan daemon dengan bersih.`,
|
|
135
|
+
• State: ~/.luang-cli/state.json · Ctrl+C keluar bersih.
|
|
136
|
+
• Heartbeat HTTP fallback tiap 30 detik (override: LUANG_HEARTBEAT_SEC).`,
|
|
109
137
|
|
|
110
138
|
task: `luang-cli task — kelola tugas
|
|
111
139
|
|
|
@@ -147,7 +175,7 @@ config bila sudah login, selain itu placeholder.`,
|
|
|
147
175
|
|
|
148
176
|
const VALUE_FLAGS = new Set([
|
|
149
177
|
'name', 'path', 'exec_template', 'interval', 'status', 'title', 'desc',
|
|
150
|
-
'description', 'priority', 'due', 'note', 'agent-status', 'api',
|
|
178
|
+
'description', 'priority', 'due', 'note', 'agent-status', 'api', 'model',
|
|
151
179
|
]);
|
|
152
180
|
|
|
153
181
|
function parseArgs(argv) {
|
|
@@ -224,6 +252,8 @@ async function main() {
|
|
|
224
252
|
return c.cmdWhoami(rt, args);
|
|
225
253
|
case 'provider':
|
|
226
254
|
return c.cmdProvider(rt, args);
|
|
255
|
+
case 'telemetry':
|
|
256
|
+
return c.cmdTelemetry(rt, args);
|
|
227
257
|
case 'agent':
|
|
228
258
|
return ag.cmdAgent(rt, args);
|
|
229
259
|
case 'task':
|