luang-cli 0.1.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 +335 -110
- package/bin/luang-cli.js +48 -17
- package/lib/agent.js +504 -79
- 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
|
@@ -1,171 +1,280 @@
|
|
|
1
|
-
# luang-cli
|
|
1
|
+
# ⚡ luang-cli
|
|
2
2
|
|
|
3
|
-
CLI Agent Orchestration
|
|
3
|
+
> **CLI Agent Orchestration Luang** — hubungkan harness agent coding favoritmu (Claude Code, OpenCode, Codex, Gemini CLI, apapun) ke dashboard **Luang**, kelola tugas dari terminal, jalankan daemon yang memantau progress di kanban, dan eksekusi tugas dengan izin yang dikontrol admin.
|
|
4
4
|
|
|
5
|
-
-
|
|
5
|
+
   
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm i -g luang-cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## ✨ Kenapa luang-cli?
|
|
14
|
+
|
|
15
|
+
| Kebutuhan | Solusi luang-cli |
|
|
16
|
+
|---|---|
|
|
17
|
+
| Admin menugaskan coding task dari kanban dashboard | CLI menerima tugas ber-status live (`To-Do → In Progress → Review → Done`) + catatan progress + analytics |
|
|
18
|
+
| Banyak harness berbeda (Claude Code / OpenCode / Codex / Gemini) | Layer **provider** — auto-detect dari config dir / binary di PATH, atau daftar manual |
|
|
19
|
+
| Login tanpa copy-paste token manual | **Device flow**: CLI membuka browser `luang.id` → kamu approve → key diterima CLI otomatis |
|
|
20
|
+
| Integrasi AI assistant lain (bukan lewat terminal) | Server **MCP** (`luang mcp-config`) — 5 tools siap dipakai Claude Code, OpenCode, Cursor, dll |
|
|
21
|
+
| Aman lintas-tenant | Key per-laptop, hash SHA-256 di server, bisa dicabut kapan pun dari dashboard |
|
|
22
|
+
|
|
23
|
+
- **Zero-dependency**: hanya bawaan Node.js (`fetch`, `node:fs`, `node:readline`, `child_process`) — install cepat, tanpa postinstall.
|
|
6
24
|
- **Node.js ≥ 18** (butuh global `fetch`).
|
|
7
25
|
- API default: `https://api.luang.id` · Dashboard: `https://app.luang.id`
|
|
8
26
|
|
|
9
27
|
---
|
|
10
28
|
|
|
11
|
-
##
|
|
29
|
+
## 🗺 Arsitektur
|
|
12
30
|
|
|
13
|
-
Dari folder repo ini (lokal):
|
|
14
|
-
|
|
15
|
-
```bash
|
|
16
|
-
npm i -g .
|
|
17
31
|
```
|
|
32
|
+
┌─────────────────────┐ device flow (user_code 6 digit) ┌──────────────────────┐
|
|
33
|
+
│ luang-cli (laptop) │ ──────────────────────────────────▶ │ app.luang.id │
|
|
34
|
+
│ bin/luang-cli.js │ ◀──── api_key (via poll, sekali) ── │ halaman /cli/verify │
|
|
35
|
+
└─────────┬───────────┘ └──────────────────────┘
|
|
36
|
+
│ X-CLI-Key ▲ JWT browser
|
|
37
|
+
│ heartbeat 30s · poll tugas 5s │
|
|
38
|
+
▼ ▼
|
|
39
|
+
┌────────────────────────────────────────────────────────────────────────────┐
|
|
40
|
+
│ api.luang.id → modul agent │
|
|
41
|
+
│ device_approvals · agent_keys · agent_providers │
|
|
42
|
+
│ tasks(agent_key_id/agent_status/agent_progress_note) · insights │
|
|
43
|
+
│ POST /api/v1/agent/mcp ← JSON-RPC 2.0 (MCP tools) │
|
|
44
|
+
└────────────────────────────────────────────────────────────────────────────┘
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
---
|
|
18
48
|
|
|
19
|
-
|
|
49
|
+
## 📦 Instalasi
|
|
20
50
|
|
|
21
51
|
```bash
|
|
52
|
+
# Dari registry npm (recommended, setelah publish)
|
|
22
53
|
npm i -g luang-cli
|
|
54
|
+
|
|
55
|
+
# Atau dari source repo ini
|
|
56
|
+
git clone https://github.com/yudono/Luang && cd Luang/luang-cli
|
|
57
|
+
npm i -g . # tanpa dependensi, instan
|
|
58
|
+
|
|
59
|
+
# Atau run tanpa install
|
|
60
|
+
npx luang-cli help
|
|
23
61
|
```
|
|
24
62
|
|
|
25
63
|
Cek instalasi:
|
|
26
64
|
|
|
27
65
|
```bash
|
|
28
66
|
luang-cli help
|
|
67
|
+
luang-cli --version
|
|
29
68
|
```
|
|
30
69
|
|
|
70
|
+
Upgrade: `npm i -g luang-cli@latest`
|
|
71
|
+
|
|
31
72
|
---
|
|
32
73
|
|
|
33
|
-
##
|
|
74
|
+
## 🚀 Quick start (±60 detik)
|
|
34
75
|
|
|
35
|
-
|
|
76
|
+
```bash
|
|
77
|
+
# 1. Login via browser (device flow)
|
|
78
|
+
luang-cli login --name "Laptop Kantor"
|
|
79
|
+
# → CLI membuka https://app.luang.id/cli/verify?code=123456
|
|
80
|
+
# → Login dashboard bila perlu → klik "Setujui"
|
|
81
|
+
# → CLI otomatis menerima key & menyimpannya
|
|
36
82
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
"api_url": "https://api.luang.id",
|
|
40
|
-
"app_url": "https://app.luang.id",
|
|
41
|
-
"key": null,
|
|
42
|
-
"default_provider": null,
|
|
43
|
-
"providers": {}
|
|
44
|
-
}
|
|
45
|
-
```
|
|
83
|
+
# 2. Verifikasi sesi
|
|
84
|
+
luang-cli whoami
|
|
46
85
|
|
|
47
|
-
|
|
86
|
+
# 3. (opsional) daftarkan harness agent di laptop-mu
|
|
87
|
+
luang-cli provider list # auto-detect
|
|
88
|
+
luang-cli provider default opencode
|
|
48
89
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
90
|
+
# 4. Jalanin daemon — lihat tugas update live di terminal
|
|
91
|
+
luang-cli agent
|
|
92
|
+
|
|
93
|
+
# 5. Dari dashboard: assign tugas ke agent-mu (halaman Agents)
|
|
94
|
+
# …terminal-mu langsung berisik: TGS-1 versi baru terdeteksi 👀
|
|
95
|
+
luang-cli task list
|
|
96
|
+
luang-cli task status TGS-1 in_progress --note "mulai refactor"
|
|
97
|
+
```
|
|
55
98
|
|
|
56
99
|
---
|
|
57
100
|
|
|
58
|
-
## Alur login → verify (device flow)
|
|
101
|
+
## 🔐 Alur login → verify (device flow)
|
|
59
102
|
|
|
60
|
-
|
|
103
|
+
```
|
|
104
|
+
CLI API Browser (kamu)
|
|
105
|
+
│ POST /agent/device/start │ │
|
|
106
|
+
│ ──────────────────────────▶ │ │
|
|
107
|
+
│ ◀─ device_code + user_code │ │
|
|
108
|
+
│ │ │
|
|
109
|
+
│ buka verify_url (6 digit) │ │
|
|
110
|
+
│ ──────────────────────────────────────────────────────────────────▶ │
|
|
111
|
+
│ │ GET /agent/device/lookup (JWT) │
|
|
112
|
+
│ │ ◀─────────────────────────────────── │
|
|
113
|
+
│ │ klik "Setujui" → agent_keys dibuat │
|
|
114
|
+
│ │ ◀─────────────────────────────────── │
|
|
115
|
+
│ GET /agent/device/poll (2s)│ │
|
|
116
|
+
│ ──────────────────────────▶ │ │
|
|
117
|
+
│ ◀─ {status:"approved", │ │
|
|
118
|
+
│ api_key:"lk_…"} │ ← plaintext key dikirim SEKALI, │
|
|
119
|
+
│ │ lalu dihapus dari server │
|
|
120
|
+
```
|
|
61
121
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
122
|
+
- `user_code` 6 digit, sesi kedaluwarsa **10 menit**. Sesi `denied`/`expired` → CLI keluar dengan pesan jelas.
|
|
123
|
+
- Nama sesi default `hostname-OS` — ganti dengan `--name "Laptop Kantor"`.
|
|
124
|
+
- Key dikirim sekali saja (`approved` tanpa `api_key` berarti sudah pernah diambil).
|
|
125
|
+
- Buka browser bisa dimatikan dengan env `LUANG_NO_OPEN=1` (URL tetap dicetak) — pas untuk CI/remote SSH.
|
|
65
126
|
|
|
66
|
-
|
|
67
|
-
Kode besar ditampilkan gaya banner ASCII dan browser dibuka ke `verify_url`
|
|
68
|
-
(bila browser gagal, URL tetap dicetak — cukup buka manual).
|
|
69
|
-
3. Masukkan `user_code` di dashboard, lalu **approve**.
|
|
70
|
-
4. CLI mem-poll `GET /api/v1/agent/device/poll` tiap 2 detik (batas 10 menit):
|
|
71
|
-
- `approved` + `api_key` → key tersimpan, CLI mencetak nama sesi, tenant, dan prefix key (`lk_xxxxxx…`).
|
|
72
|
-
- `approved` **tanpa** `api_key` → key sudah pernah diambil sebelumnya; pakai key lama atau buat sesi agent baru di dashboard.
|
|
73
|
-
- `denied` / `expired` → pesan jelas + exit 1.
|
|
74
|
-
5. Verifikasi:
|
|
127
|
+
### Keamanan
|
|
75
128
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
129
|
+
- Server hanya menyimpan **SHA-256** dari key; plaintext tak pernah persist selain window pickup.
|
|
130
|
+
- Key ber-lingkup **tenant + owner** (upload/assign lintas tenant mustahil — repo layer tenant-scoped).
|
|
131
|
+
- Revocation instan dari dashboard (**Agents → Revoke**) — request berikutnya langsung 401.
|
|
79
132
|
|
|
80
133
|
---
|
|
81
134
|
|
|
82
|
-
## Daftar command
|
|
135
|
+
## 🧭 Daftar command
|
|
83
136
|
|
|
84
137
|
| Command | Fungsi | Contoh |
|
|
85
138
|
|---|---|---|
|
|
139
|
+
| `help` / `--version` | Daftar command / versi | `luang-cli help agent` |
|
|
86
140
|
| `login` | Login device flow via browser | `luang-cli login --name "Laptop Kantor"` |
|
|
87
141
|
| `logout` | Hapus API key tersimpan (`-y` tanpa konfirmasi) | `luang-cli logout -y` |
|
|
88
|
-
| `whoami` | Info sesi, tenant, izin | `luang-cli whoami` |
|
|
89
|
-
| `
|
|
90
|
-
| `provider
|
|
142
|
+
| `whoami` | Info sesi: nama, tenant, izin, provider | `luang-cli whoami` |
|
|
143
|
+
| `status` | Ringkasan singkat CLI | `luang-cli status` |
|
|
144
|
+
| `provider list` | Deteksi otomatis + tabel (`--json`) | `luang-cli provider list` |
|
|
145
|
+
| `provider add` | Tambah / override provider config | `luang-cli provider add codex --exec_template 'codex exec "{task}"'` |
|
|
91
146
|
| `provider remove` | Hapus provider dari config | `luang-cli provider remove codex` |
|
|
92
|
-
| `provider default` | Set default
|
|
93
|
-
| `
|
|
94
|
-
| `
|
|
95
|
-
| `task
|
|
96
|
-
| `task
|
|
97
|
-
| `status` |
|
|
98
|
-
| `mcp-config` |
|
|
147
|
+
| `provider default` | Set provider default (dipakai `agent --exec`) | `luang-cli provider default opencode` |
|
|
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` |
|
|
150
|
+
| `task list` | Daftar tugas agent (filter status) | `luang-cli task list --status todo` |
|
|
151
|
+
| `task create` | Buat tugas (butuh izin admin) | `luang-cli task create --title "Refactor auth" --priority high` |
|
|
152
|
+
| `task status` | Perbarui status + catatan progress | `luang-cli task status TGS-12 in_progress --note "mulai"` |
|
|
153
|
+
| `mcp-config` | Cetak blok konfigurasi MCP siap-tempel | `luang-cli mcp-config` |
|
|
99
154
|
|
|
100
|
-
|
|
155
|
+
Dokumen bantuan per-com: `luang-cli <command> --help` (mis. `luang-cli agent --help`).
|
|
101
156
|
|
|
102
|
-
|
|
157
|
+
### Pemetaan nilai
|
|
103
158
|
|
|
104
|
-
|
|
159
|
+
**Status tugas** (CLI → server kanban):
|
|
105
160
|
|
|
106
|
-
|
|
107
|
-
|
|
161
|
+
| CLI token | Kanban dashboard |
|
|
162
|
+
|---|---|
|
|
163
|
+
| `todo` / `to_do` | `To-Do` |
|
|
164
|
+
| `in_progress` / `progress` | `In Progress` |
|
|
165
|
+
| `review` | `Review` |
|
|
166
|
+
| `done` / `completed` | `Done` |
|
|
108
167
|
|
|
109
|
-
|
|
110
|
-
|---|---|---|
|
|
111
|
-
| `task create` | Bisa-buat-tugas | `can_create_tasks` (HTTP 403 bila belum aktif) |
|
|
112
|
-
| `agent --exec` / `task status` | Bisa-update-tugas | `can_update_tasks` |
|
|
168
|
+
Selain status kanban, bisa kirim **`--agent-status`** (status mesin singkat, mis. `membaca repo`) dan **`--note`** (catatan progress yang tampil di dashboard).
|
|
113
169
|
|
|
114
|
-
|
|
170
|
+
**Prioritas** untuk `task create`: `high` → `Tinggi` · `medium` → `Sedang` · `low` → `Rendah`.
|
|
171
|
+
**Referensi tugas**: pakai kode `TGS-<n>` (mis. `TGS-7`) atau UUID id tugas.
|
|
115
172
|
|
|
116
173
|
---
|
|
117
174
|
|
|
118
|
-
## Integrasi provider (
|
|
175
|
+
## 🛠 Integrasi provider (multi-harness)
|
|
119
176
|
|
|
120
|
-
|
|
177
|
+
Auto-detect (`provider list`):
|
|
121
178
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
179
|
+
| Provider | Dari | exec_template default |
|
|
180
|
+
|---|---|---|
|
|
181
|
+
| `claude_code` | `~/.claude` / binary `claude` | `claude -p "{task}"` |
|
|
182
|
+
| `codex` | `~/.codex` / binary `codex` | `codex exec "{task}"` |
|
|
183
|
+
| `opencode` | `~/.config/opencode` / `~/.opencode` / binary `opencode` | `opencode run "{task}"` |
|
|
184
|
+
| `gemini_cli` | `~/.gemini` / binary `gemini` | `gemini -p "{task}"` |
|
|
127
185
|
|
|
128
|
-
|
|
186
|
+
Daftar manual (path khusus, alias brand, multi-provider):
|
|
129
187
|
|
|
130
188
|
```bash
|
|
131
|
-
luang-cli provider add claude_code --path /
|
|
132
|
-
--exec_template 'claude -p "{task}"'
|
|
133
|
-
luang-cli provider add
|
|
134
|
-
luang-cli provider add opencode --exec_template 'opencode run "{task}"'
|
|
189
|
+
luang-cli provider add claude_code --path /opt/homebrew/bin/claude \
|
|
190
|
+
--exec_template 'claude -p --model {model} "{task}"'
|
|
191
|
+
luang-cli provider add opencode --exec_template 'opencode run "{task}"' --model sonnet-4
|
|
135
192
|
luang-cli provider default opencode
|
|
136
193
|
```
|
|
137
194
|
|
|
138
|
-
`{task}` pada
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
`
|
|
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).
|
|
142
199
|
|
|
143
200
|
---
|
|
144
201
|
|
|
145
|
-
## Daemon agent
|
|
202
|
+
## 🖥 Daemon agent (`luang-cli agent`)
|
|
203
|
+
|
|
204
|
+
```bash
|
|
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)
|
|
208
|
+
```
|
|
209
|
+
|
|
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):
|
|
213
|
+
|
|
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.
|
|
216
|
+
3. Baris perubahan dicetak: `[HH:MM:SS] TGS-1 Judul In Progress agent=… note="…"`.
|
|
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 |
|
|
228
|
+
|
|
229
|
+
### Mode `--exec` (semi-otomatis, konfirmasi y/N)
|
|
230
|
+
|
|
231
|
+
Bila ada tugas `To-Do` yang belum pernah dieksekusi dan default provider punya `exec_template` berisi `{task}` (dan tidak sedang PAUSE):
|
|
232
|
+
|
|
233
|
+
1. Konfirmasi `y/N` di terminal — **tidak ada eksekusi diam-diam**.
|
|
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).
|
|
236
|
+
4. Hasil:
|
|
237
|
+
- exit 0 → `{status:"done", agent_status:"selesai", progress_note:"exec exit 0"}`;
|
|
238
|
+
- exit ≠ 0 → `{status:"review", agent_status:"gagal", progress_note:"exit code <n>"}`.
|
|
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:
|
|
146
256
|
|
|
147
257
|
```bash
|
|
148
|
-
luang-cli
|
|
149
|
-
|
|
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
|
|
150
263
|
```
|
|
151
264
|
|
|
152
|
-
|
|
153
|
-
- Polling `GET /api/v1/agent/tasks` tiap `--interval` detik (default 5).
|
|
154
|
-
- Perubahan tugas dicetak satu baris: `TGS-1 Judul In Progress [agent_status] note`.
|
|
155
|
-
- State daftar run disimpan di `~/.luang-cli/state.json` (terakhir `agent_updated_at`/`updated_at` per tugas + riwayat tugas yang sudah dieksekusi).
|
|
156
|
-
- `--exec`: tugas berstatus `To-Do` yang belum dijalankan, saat default provider punya `exec_template` berisi `{task}`:
|
|
157
|
-
1. konfirmasi `y/N` di terminal,
|
|
158
|
-
2. status dikirim `in_progress` (`agent_status: menjalankan`),
|
|
159
|
-
3. template dijalankan via `child_process.exec` setelah `{task}` diganti,
|
|
160
|
-
4. exit 0 → `done` ("selesai", note `exec exit 0`); gagal → `review` ("gagal", note `exit code N`).
|
|
161
|
-
- `Ctrl+C` menghentikan daemon dengan bersih (exit 0).
|
|
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).
|
|
162
266
|
|
|
163
267
|
---
|
|
164
268
|
|
|
165
|
-
##
|
|
269
|
+
## 🎛 Integrasi MCP (tanpa terminal)
|
|
166
270
|
|
|
167
|
-
|
|
168
|
-
|
|
271
|
+
Luang juga menyediakan server MCP (JSON-RPC 2.0) di `POST /api/v1/agent/mcp` dengan auth header `X-CLI-Key` — pas untuk Claude Code, OpenCode, Cursor, dll.
|
|
272
|
+
|
|
273
|
+
Cetak blok konfigurasi siap-tempel:
|
|
274
|
+
|
|
275
|
+
```bash
|
|
276
|
+
luang-cli mcp-config
|
|
277
|
+
```
|
|
169
278
|
|
|
170
279
|
```json
|
|
171
280
|
{
|
|
@@ -173,27 +282,143 @@ luang-cli agent --interval 10 --exec
|
|
|
173
282
|
"luang": {
|
|
174
283
|
"type": "http",
|
|
175
284
|
"url": "https://api.luang.id/api/v1/agent/mcp",
|
|
176
|
-
"headers": {
|
|
177
|
-
"X-CLI-Key": "lk_xxxxxxxx"
|
|
178
|
-
}
|
|
285
|
+
"headers": { "X-CLI-Key": "lk_xxxxxxxx…" }
|
|
179
286
|
}
|
|
180
287
|
}
|
|
181
288
|
}
|
|
182
289
|
```
|
|
183
290
|
|
|
184
|
-
|
|
185
|
-
otomatis).
|
|
291
|
+
Tools yang tersedia: `luang_list_tasks`, `luang_update_task_status`, `luang_create_task`, `luang_heartbeat`, `luang_me` — reuse service yang sama dengan CLI, jadi izin per key tetap berlaku.
|
|
186
292
|
|
|
187
293
|
---
|
|
188
294
|
|
|
189
|
-
##
|
|
295
|
+
## 🔑 Izin agent (dikontrol dari dashboard)
|
|
296
|
+
|
|
297
|
+
| Aksi CLI | Izin di dashboard | Field backend |
|
|
298
|
+
|---|---|---|
|
|
299
|
+
| `task status`, daemon update | **Bisa-update-tugas** (default ✅) | `can_update_tasks` |
|
|
300
|
+
| `task create` | **Bisa-buat-tugas** (default ❌) | `can_create_tasks` |
|
|
301
|
+
| kirim `telemetry.run` (usage/model) | **Kirim telemetri** (default ❌) | `telemetry_enabled` |
|
|
302
|
+
|
|
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`.
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
## ⚙️ Konfigurasi
|
|
308
|
+
|
|
309
|
+
File config auto-dibuat: `~/.luang-cli/config.json`
|
|
310
|
+
|
|
311
|
+
```json
|
|
312
|
+
{
|
|
313
|
+
"api_url": "https://api.luang.id",
|
|
314
|
+
"app_url": "https://app.luang.id",
|
|
315
|
+
"key": "lk_…", // hasil login
|
|
316
|
+
"name": null, // nama sesi terakhir
|
|
317
|
+
"telemetry": false, // konsent lokal telemetry (default OFF)
|
|
318
|
+
"default_provider": null,
|
|
319
|
+
"providers": { "opencode": { "path": "…", "exec_template": "…", "model": "sonnet-4" } }
|
|
320
|
+
}
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
State runtime (riwayat terlihat / eksekusi): `~/.luang-cli/state.json`.
|
|
324
|
+
|
|
325
|
+
| Env | Mempengaruhi |
|
|
326
|
+
|---|---|
|
|
327
|
+
| `LUANG_KEY` | Timpa key tanpa menyentuh config (pas untuk CI) |
|
|
328
|
+
| `LUANG_API_URL` | Timpa `api_url` |
|
|
329
|
+
| `LUANG_APP_URL` | Timpa `app_url` |
|
|
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) |
|
|
334
|
+
|
|
335
|
+
Precedence: **flag `--api <url>` → env → config file**.
|
|
336
|
+
|
|
337
|
+
---
|
|
338
|
+
|
|
339
|
+
## 🧯 Troubleshooting
|
|
190
340
|
|
|
191
341
|
| Gejala | Solusi |
|
|
192
342
|
|---|---|
|
|
193
|
-
| `❌ Belum login` |
|
|
194
|
-
| HTTP 401 di semua command | Key ditolak → `luang-cli logout` lalu `login` ulang |
|
|
195
|
-
| HTTP 403 saat `task create` |
|
|
196
|
-
|
|
|
197
|
-
|
|
|
198
|
-
|
|
|
199
|
-
|
|
|
343
|
+
| `❌ Belum login` | `luang-cli login`, atau set env `LUANG_KEY` |
|
|
344
|
+
| HTTP 401 di semua command | Key ditolak/dicabut → `luang-cli logout` lalu `login` ulang |
|
|
345
|
+
| HTTP 403 saat `task create` | Admin belum aktifkan izin **Bisa-buat-tugas** (lihat seksi Izin) |
|
|
346
|
+
| `Sesi login kedaluwarsa` | Sesi device flow lewat 10 menit → jalankan `login` lagi |
|
|
347
|
+
| `key sudah pernah diambil` | Browser approve dua kali / poll dijalankan 2× → buat sesi agent baru |
|
|
348
|
+
| Timeout 15 detik | Cek jaringan + `--api https://api.luang.id` |
|
|
349
|
+
| Tabel provider kosong | `provider add <type> --exec_template '…'` manual |
|
|
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 |
|
|
354
|
+
| Daemon boring (tak ada event) | Wajar — ia hanya print KETIKA ada perubahan; coba assign tugas dari dashboard |
|
|
355
|
+
| Mulai bersih total | Hapus `~/.luang-cli/` (config + state) |
|
|
356
|
+
|
|
357
|
+
---
|
|
358
|
+
|
|
359
|
+
## 💡 Mengapa dashboard perlu agent ini?
|
|
360
|
+
|
|
361
|
+
Dengan pasangan **Luang dashboard + luang-cli**, satu workspace bisa:
|
|
362
|
+
|
|
363
|
+
1. **Orchestrate** banyak user × banyak mesin × banyak harness, dengan kontrol izin per key.
|
|
364
|
+
2. **Kanban + analytics live**: `To-Do / In Progress / Review / Done` per agent + provider count + 5 tugas terakhir (modul **Agents**).
|
|
365
|
+
3. **Fleksibilitas dua jalur**: operasionalisasi lewat CLI, atau tools MCP bagi AI assistantmu.
|
|
366
|
+
|
|
367
|
+
Baca arsitektur & kontrak API lengkapnya di [`docs/agent-orchestration.md`](../docs/agent-orchestration.md).
|
|
368
|
+
|
|
369
|
+
---
|
|
370
|
+
|
|
371
|
+
## 🧪 Development
|
|
372
|
+
|
|
373
|
+
Struktur repo (folder `luang-cli/` di monorepo [yudono/Luang](https://github.com/yudono/Luang)):
|
|
374
|
+
|
|
375
|
+
```
|
|
376
|
+
luang-cli/
|
|
377
|
+
├── bin/luang-cli.js # entrypoint + dispatcher
|
|
378
|
+
├── lib/
|
|
379
|
+
│ ├── core.js # config, env, HTTP, provider detect, key util
|
|
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)
|
|
388
|
+
└── package.json # bin: luang-cli → bin/luang-cli.js
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
Jalankan dari source:
|
|
392
|
+
|
|
393
|
+
```bash
|
|
394
|
+
node bin/luang-cli.js help
|
|
395
|
+
node --check bin/luang-cli.js # sanity sintaks
|
|
396
|
+
node test/run-e2e.js # E2E offline penuh (tanpa jaringan/provider asli)
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
Publish (owner):
|
|
400
|
+
|
|
401
|
+
```bash
|
|
402
|
+
cd luang-cli
|
|
403
|
+
npm publish --access public --auth-type=legacy --otp=<kode2FA>
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
> Versi di `package.json` adalah sumber kebenaran — bump sebelum publish (`npm version patch`).
|
|
407
|
+
|
|
408
|
+
---
|
|
409
|
+
|
|
410
|
+
## 🗺 Roadmap
|
|
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
|
|
414
|
+
- [ ] MCP tools lanjutan: komentar tugas, subtasks, timesheet
|
|
415
|
+
- [ ] `agent --exec` multi-provider (eksekusi paralel beberapa harness)
|
|
416
|
+
- [ ] Skill installer (Claude Code skill / OpenCode skill yang membungkus luang-cli)
|
|
417
|
+
- [ ] `luang-cli setup` wizard pertama kali (login + provider + tes)
|
|
418
|
+
|
|
419
|
+
---
|
|
420
|
+
|
|
421
|
+
## 📄 Lisensi
|
|
422
|
+
|
|
423
|
+
**MIT** — © 2026 Yudono / Luang.
|
|
424
|
+
Link utama: [app.luang.id](https://app.luang.id) · [api.luang.id](https://api.luang.id/health) · [Luang repo](https://github.com/yudono/Luang)
|
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,37 +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
|
-
TGS-1 Judul In Progress [agent_status] 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 ("menjalankan"), lalu done ("selesai") bila exit 0,
|
|
106
|
-
review ("gagal") bila gagal.
|
|
107
|
-
• 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).`,
|
|
108
137
|
|
|
109
138
|
task: `luang-cli task — kelola tugas
|
|
110
139
|
|
|
@@ -146,7 +175,7 @@ config bila sudah login, selain itu placeholder.`,
|
|
|
146
175
|
|
|
147
176
|
const VALUE_FLAGS = new Set([
|
|
148
177
|
'name', 'path', 'exec_template', 'interval', 'status', 'title', 'desc',
|
|
149
|
-
'description', 'priority', 'due', 'note', 'agent-status', 'api',
|
|
178
|
+
'description', 'priority', 'due', 'note', 'agent-status', 'api', 'model',
|
|
150
179
|
]);
|
|
151
180
|
|
|
152
181
|
function parseArgs(argv) {
|
|
@@ -223,6 +252,8 @@ async function main() {
|
|
|
223
252
|
return c.cmdWhoami(rt, args);
|
|
224
253
|
case 'provider':
|
|
225
254
|
return c.cmdProvider(rt, args);
|
|
255
|
+
case 'telemetry':
|
|
256
|
+
return c.cmdTelemetry(rt, args);
|
|
226
257
|
case 'agent':
|
|
227
258
|
return ag.cmdAgent(rt, args);
|
|
228
259
|
case 'task':
|