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 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
- | `agent` | Daemon: heartbeat + poll tugas live | `luang-cli agent --interval 5 --exec` |
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 claude_code
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
- - Daftar provider tersimpan di `~/.luang-cli/config.json` dan disinkron ke server lewat heartbeat (`provider_count` tampil di dashboard Agents).
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 # heartbeat tiap 30 detik, poll tugas tiap 5 detik
203
- luang-cli agent --interval 10 # poll tiap 10 detik
204
- luang-cli agent --exec # tertarik juga mengeksekusi tugas To-Do
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
- Perilaku:
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** `POST /api/v1/agent/heartbeat` setiap 30 detik (nama + provider list tersinkron; dashboard tampil "Terakhir aktif …").
210
- 2. **Polling** `GET /api/v1/agent/tasks` tiap `--interval` detik — perubahan terdeteksi via fingerprint (status, agent_status, catatan, judul, timestamp) per id tugas.
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 ditugaskan ke agent-mu → masuk daftar.
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"}` ke server sebelum menjalankan.
220
- 3. Template dipanggil via `child_process.exec` (shell-safe escaping, timeout **15 menit**, `windowsHide` on).
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. Riwayat eksekusi disimpan `~/.luang-cli/state.json` — tugas yang sama tidak ditawari dua kali. `Ctrl+C` keluar bersih.
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
- │ ├── commands.js # login/logout/whoami/provider
332
- │ └── agent.js # daemon, task CRUD, status, mcp-config
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
- agent Daemon heartbeat + polling tugas luang-cli agent [--interval 5] [--exec]
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} diganti judul + deskripsi tugas saat mode --exec.
83
- Contoh: 'claude -p "{task}"', 'opencode run "{task}"', 'codex exec "{task}"'
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
- agent: `luang-cli agent — daemon agent (heartbeat + polling tugas)
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 GET /api/v1/agent/tasks (detik, default 5)
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
- • POST /api/v1/agent/heartbeat tiap 30 detik
100
- • Polling GET /api/v1/agent/tasks tiap N detik; baris berubah dicetak:
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':