luang-cli 0.0.0-stage → 0.2.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
@@ -1,3 +1,367 @@
1
- # Temporary Holding Version
1
+ # ⚡ luang-cli
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
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
+
5
+ ![npm](https://img.shields.io/npm/v/luang-cli) ![node](https://img.shields.io/node/v/luang-cli) ![license](https://img.shields.io/npm/l/luang-cli) ![deps](https://img.shields.io/badge/dependencies-0-brightgreen)
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.
24
+ - **Node.js ≥ 18** (butuh global `fetch`).
25
+ - API default: `https://api.luang.id` · Dashboard: `https://app.luang.id`
26
+
27
+ ---
28
+
29
+ ## 🗺 Arsitektur
30
+
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
+ ---
48
+
49
+ ## 📦 Instalasi
50
+
51
+ ```bash
52
+ # Dari registry npm (recommended, setelah publish)
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
61
+ ```
62
+
63
+ Cek instalasi:
64
+
65
+ ```bash
66
+ luang-cli help
67
+ luang-cli --version
68
+ ```
69
+
70
+ Upgrade: `npm i -g luang-cli@latest`
71
+
72
+ ---
73
+
74
+ ## 🚀 Quick start (±60 detik)
75
+
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
82
+
83
+ # 2. Verifikasi sesi
84
+ luang-cli whoami
85
+
86
+ # 3. (opsional) daftarkan harness agent di laptop-mu
87
+ luang-cli provider list # auto-detect
88
+ luang-cli provider default opencode
89
+
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
+ ```
98
+
99
+ ---
100
+
101
+ ## 🔐 Alur login → verify (device flow)
102
+
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
+ ```
121
+
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.
126
+
127
+ ### Keamanan
128
+
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.
132
+
133
+ ---
134
+
135
+ ## 🧭 Daftar command
136
+
137
+ | Command | Fungsi | Contoh |
138
+ |---|---|---|
139
+ | `help` / `--version` | Daftar command / versi | `luang-cli help agent` |
140
+ | `login` | Login device flow via browser | `luang-cli login --name "Laptop Kantor"` |
141
+ | `logout` | Hapus API key tersimpan (`-y` tanpa konfirmasi) | `luang-cli logout -y` |
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}"'` |
146
+ | `provider remove` | Hapus provider dari config | `luang-cli provider remove codex` |
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` |
149
+ | `task list` | Daftar tugas agent (filter status) | `luang-cli task list --status todo` |
150
+ | `task create` | Buat tugas (butuh izin admin) | `luang-cli task create --title "Refactor auth" --priority high` |
151
+ | `task status` | Perbarui status + catatan progress | `luang-cli task status TGS-12 in_progress --note "mulai"` |
152
+ | `mcp-config` | Cetak blok konfigurasi MCP siap-tempel | `luang-cli mcp-config` |
153
+
154
+ Dokumen bantuan per-com: `luang-cli <command> --help` (mis. `luang-cli agent --help`).
155
+
156
+ ### Pemetaan nilai
157
+
158
+ **Status tugas** (CLI → server kanban):
159
+
160
+ | CLI token | Kanban dashboard |
161
+ |---|---|
162
+ | `todo` / `to_do` | `To-Do` |
163
+ | `in_progress` / `progress` | `In Progress` |
164
+ | `review` | `Review` |
165
+ | `done` / `completed` | `Done` |
166
+
167
+ Selain status kanban, bisa kirim **`--agent-status`** (status mesin singkat, mis. `membaca repo`) dan **`--note`** (catatan progress yang tampil di dashboard).
168
+
169
+ **Prioritas** untuk `task create`: `high` → `Tinggi` · `medium` → `Sedang` · `low` → `Rendah`.
170
+ **Referensi tugas**: pakai kode `TGS-<n>` (mis. `TGS-7`) atau UUID id tugas.
171
+
172
+ ---
173
+
174
+ ## 🛠 Integrasi provider (multi-harness)
175
+
176
+ Auto-detect (`provider list`):
177
+
178
+ | Provider | Dari | exec_template default |
179
+ |---|---|---|
180
+ | `claude_code` | `~/.claude` / binary `claude` | `claude -p "{task}"` |
181
+ | `codex` | `~/.codex` / binary `codex` | `codex exec "{task}"` |
182
+ | `opencode` | `~/.config/opencode` / `~/.opencode` / binary `opencode` | `opencode run "{task}"` |
183
+ | `gemini_cli` | `~/.gemini` / binary `gemini` | `gemini -p "{task}"` |
184
+
185
+ Daftar manual (path khusus, alias brand, multi-provider):
186
+
187
+ ```bash
188
+ 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
192
+ ```
193
+
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).
196
+
197
+ ---
198
+
199
+ ## 🖥 Daemon agent (`luang-cli agent`)
200
+
201
+ ```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
+ ```
206
+
207
+ Perilaku:
208
+
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.
211
+ 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.
213
+
214
+ ### Mode `--exec` (semi-otomatis, konfirmasi y/N)
215
+
216
+ Bila ada tugas `To-Do` yang belum pernah dieksekusi dan default provider punya `exec_template` berisi `{task}`:
217
+
218
+ 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).
221
+ 4. Hasil:
222
+ - exit 0 → `{status:"done", agent_status:"selesai", progress_note:"exec exit 0"}`;
223
+ - 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.
225
+
226
+ ---
227
+
228
+ ## 🎛 Integrasi MCP (tanpa terminal)
229
+
230
+ 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.
231
+
232
+ Cetak blok konfigurasi siap-tempel:
233
+
234
+ ```bash
235
+ luang-cli mcp-config
236
+ ```
237
+
238
+ ```json
239
+ {
240
+ "mcpServers": {
241
+ "luang": {
242
+ "type": "http",
243
+ "url": "https://api.luang.id/api/v1/agent/mcp",
244
+ "headers": { "X-CLI-Key": "lk_xxxxxxxx…" }
245
+ }
246
+ }
247
+ }
248
+ ```
249
+
250
+ 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.
251
+
252
+ ---
253
+
254
+ ## 🔑 Izin agent (dikontrol dari dashboard)
255
+
256
+ | Aksi CLI | Izin di dashboard | Field backend |
257
+ |---|---|---|
258
+ | `task status`, daemon update | **Bisa-update-tugas** (default ✅) | `can_update_tasks` |
259
+ | `task create` | **Bisa-buat-tugas** (default ❌) | `can_create_tasks` |
260
+
261
+ 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
+
263
+ ---
264
+
265
+ ## ⚙️ Konfigurasi
266
+
267
+ File config auto-dibuat: `~/.luang-cli/config.json`
268
+
269
+ ```json
270
+ {
271
+ "api_url": "https://api.luang.id",
272
+ "app_url": "https://app.luang.id",
273
+ "key": "lk_…", // hasil login
274
+ "name": null, // nama sesi terakhir
275
+ "default_provider": null,
276
+ "providers": { "opencode": { "path": "…", "exec_template": "…" } }
277
+ }
278
+ ```
279
+
280
+ State runtime (riwayat terlihat / eksekusi): `~/.luang-cli/state.json`.
281
+
282
+ | Env | Mempengaruhi |
283
+ |---|---|
284
+ | `LUANG_KEY` | Timpa key tanpa menyentuh config (pas untuk CI) |
285
+ | `LUANG_API_URL` | Timpa `api_url` |
286
+ | `LUANG_APP_URL` | Timpa `app_url` |
287
+ | `LUANG_NO_OPEN` | `login` tidak membuka browser otomatis (cetak URL saja) |
288
+
289
+ Precedence: **flag `--api <url>` → env → config file**.
290
+
291
+ ---
292
+
293
+ ## 🧯 Troubleshooting
294
+
295
+ | Gejala | Solusi |
296
+ |---|---|
297
+ | `❌ Belum login` | `luang-cli login`, atau set env `LUANG_KEY` |
298
+ | HTTP 401 di semua command | Key ditolak/dicabut → `luang-cli logout` lalu `login` ulang |
299
+ | HTTP 403 saat `task create` | Admin belum aktifkan izin **Bisa-buat-tugas** (lihat seksi Izin) |
300
+ | `Sesi login kedaluwarsa` | Sesi device flow lewat 10 menit → jalankan `login` lagi |
301
+ | `key sudah pernah diambil` | Browser approve dua kali / poll dijalankan 2× → buat sesi agent baru |
302
+ | Timeout 15 detik | Cek jaringan + `--api https://api.luang.id` |
303
+ | Tabel provider kosong | `provider add <type> --exec_template '…'` manual |
304
+ | `--exec` tak berjalan | Set `provider default <type>` + template harus berisi `{task}` |
305
+ | Daemon boring (tak ada event) | Wajar — ia hanya print KETIKA ada perubahan; coba assign tugas dari dashboard |
306
+ | Mulai bersih total | Hapus `~/.luang-cli/` (config + state) |
307
+
308
+ ---
309
+
310
+ ## 💡 Mengapa dashboard perlu agent ini?
311
+
312
+ Dengan pasangan **Luang dashboard + luang-cli**, satu workspace bisa:
313
+
314
+ 1. **Orchestrate** banyak user × banyak mesin × banyak harness, dengan kontrol izin per key.
315
+ 2. **Kanban + analytics live**: `To-Do / In Progress / Review / Done` per agent + provider count + 5 tugas terakhir (modul **Agents**).
316
+ 3. **Fleksibilitas dua jalur**: operasionalisasi lewat CLI, atau tools MCP bagi AI assistantmu.
317
+
318
+ Baca arsitektur & kontrak API lengkapnya di [`docs/agent-orchestration.md`](../docs/agent-orchestration.md).
319
+
320
+ ---
321
+
322
+ ## 🧪 Development
323
+
324
+ Struktur repo (folder `luang-cli/` di monorepo [yudono/Luang](https://github.com/yudono/Luang)):
325
+
326
+ ```
327
+ luang-cli/
328
+ ├── bin/luang-cli.js # entrypoint + dispatcher
329
+ ├── lib/
330
+ │ ├── 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
333
+ └── package.json # bin: luang-cli → bin/luang-cli.js
334
+ ```
335
+
336
+ Jalankan dari source:
337
+
338
+ ```bash
339
+ node bin/luang-cli.js help
340
+ node --check bin/luang-cli.js # sanity sintaks
341
+ ```
342
+
343
+ Publish (owner):
344
+
345
+ ```bash
346
+ cd luang-cli
347
+ npm publish --access public --auth-type=legacy --otp=<kode2FA>
348
+ ```
349
+
350
+ > Versi di `package.json` adalah sumber kebenaran — bump sebelum publish (`npm version patch`).
351
+
352
+ ---
353
+
354
+ ## 🗺 Roadmap
355
+
356
+ - [ ] MCP tools lanjutan: komentar tugas, subtasks, timesheet
357
+ - [ ] `agent --exec` multi-provider (eksekusi paralel beberapa harness)
358
+ - [ ] Skill installer (Claude Code skill / OpenCode skill yang membungkus luang-cli)
359
+ - [ ] `luang-cli setup` wizard pertama kali (login + provider + tes)
360
+ - [ ] Webhook/SSE realtime (pengganti polling)
361
+
362
+ ---
363
+
364
+ ## 📄 Lisensi
365
+
366
+ **MIT** — © 2026 Yudono / Luang.
367
+ Link utama: [app.luang.id](https://app.luang.id) · [api.luang.id](https://api.luang.id/health) · [Luang repo](https://github.com/yudono/Luang)
@@ -0,0 +1,248 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ // bin/luang-cli.js — entry point CLI Luang: parsing argumen, dispatch, help.
5
+
6
+ const pkg = require('../package.json');
7
+ const core = require('../lib/core');
8
+ const c = require('../lib/commands');
9
+ const ag = require('../lib/agent');
10
+
11
+ const OVERVIEW = `luang-cli v${pkg.version} — CLI Agent Orchestration Luang
12
+
13
+ Penggunaan: luang-cli <command> [sub] [opsi]
14
+
15
+ Command:
16
+ login Login device flow via browser luang-cli login --name "Laptop"
17
+ logout Hapus API key tersimpan luang-cli logout [-y]
18
+ whoami Info sesi agent, tenant & izin luang-cli whoami
19
+ provider Kelola provider CLI coding luang-cli provider list
20
+ agent Daemon heartbeat + polling tugas luang-cli agent [--interval 5] [--exec]
21
+ task Kelola tugas (list|create|status) luang-cli task list
22
+ status Ringkasan CLI (key, tenant, izin) luang-cli status
23
+ mcp-config Snippet konfigurasi MCP klien luang-cli mcp-config
24
+
25
+ Opsi global:
26
+ --api <url> Override URL API (default https://api.luang.id)
27
+ -h, --help Bantuan (juga: luang-cli help <command>) · --version
28
+
29
+ Detail: luang-cli help <command>`;
30
+
31
+ const TOPICS = {
32
+ login: `luang-cli login — login via device flow
33
+
34
+ Penggunaan:
35
+ luang-cli login [--name <label>]
36
+
37
+ Opsi:
38
+ --name <label> Nama sesi/agent (default: hostname — CLI)
39
+
40
+ Alur:
41
+ 1. CLI meminta device_code + user_code dari API
42
+ 2. Browser terbuka ke halaman verify (bila gagal, URL cukup dicetak)
43
+ 3. Masukkan user_code di dashboard, klik approve
44
+ 4. API key tersimpan di ~/.luang-cli/config.json
45
+ "approved" tanpa api_key berarti key sudah pernah diambil sebelumnya.
46
+
47
+ Contoh:
48
+ luang-cli login
49
+ luang-cli login --name "Laptop Kantor"`,
50
+
51
+ logout: `luang-cli logout — hapus API key dari ~/.luang-cli/config.json
52
+
53
+ Penggunaan:
54
+ luang-cli logout [-y]
55
+
56
+ Opsi:
57
+ -y, --yes Langsung hapus tanpa konfirmasi
58
+
59
+ Catatan: env LUANG_KEY (bila ter-set) tetap override.`,
60
+
61
+ whoami: `luang-cli whoami — info sesi agent dari GET /api/v1/agent/me
62
+
63
+ Penggunaan:
64
+ luang-cli whoami
65
+
66
+ Menampilkan: nama sesi, status, key id, tenant, last seen,
67
+ izin can_update_tasks / can_create_tasks, dan provider terdaftar.`,
68
+
69
+ provider: `luang-cli provider — kelola provider CLI coding
70
+
71
+ Penggunaan:
72
+ luang-cli provider list [--json]
73
+ Deteksi otomatis: ~/.claude (claude_code), ~/.codex (codex),
74
+ ~/.config/opencode atau ~/.opencode (opencode), ~/.gemini (gemini_cli),
75
+ plus which(1) untuk claude, codex, opencode, gemini.
76
+ 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 remove <type>
79
+ luang-cli provider default <type>
80
+
81
+ Param exec_template:
82
+ {task} diganti judul + deskripsi tugas saat mode --exec.
83
+ Contoh: 'claude -p "{task}"', 'opencode run "{task}"', 'codex exec "{task}"'
84
+
85
+ Contoh:
86
+ luang-cli provider add opencode --exec_template 'opencode run "{task}"'
87
+ luang-cli provider default opencode`,
88
+
89
+ agent: `luang-cli agent — daemon agent (heartbeat + polling tugas)
90
+
91
+ Penggunaan:
92
+ luang-cli agent [--interval N] [--exec]
93
+
94
+ Opsi:
95
+ --interval N Jeda polling GET /api/v1/agent/tasks (detik, default 5)
96
+ --exec Eksekusi otomatis tugas "To-Do" yang belum dijalankan
97
+
98
+ 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.`,
109
+
110
+ task: `luang-cli task — kelola tugas
111
+
112
+ Penggunaan:
113
+ luang-cli task list [--status todo|in_progress|review|done]
114
+ luang-cli task create --title "Judul" [--desc "…"] [--priority high|medium|low] [--due 2026-01-02]
115
+ luang-cli task status <TGS-12|uuid> <todo|in_progress|review|done> [--note "…"] [--agent-status "…"]
116
+
117
+ Catatan:
118
+ • task create butuh izin admin "Bisa-buat-tugas" (can_create_tasks)
119
+ • task status butuh izin "Bisa-update-tugas" (can_update_tasks)
120
+
121
+ Contoh:
122
+ luang-cli task list --status todo
123
+ luang-cli task create --title "Refactor auth" --priority high --due 2026-11-01
124
+ luang-cli task status TGS-12 in_progress --note "mulai analisis"`,
125
+
126
+ status: `luang-cli status — ringkasan CLI
127
+
128
+ Penggunaan:
129
+ luang-cli status
130
+
131
+ Menampilkan: API/App URL, key prefix, tenant, izin,
132
+ default provider & provider lokal, lokasi config/state.`,
133
+
134
+ 'mcp-config': `luang-cli mcp-config — snippet konfigurasi MCP
135
+
136
+ Penggunaan:
137
+ luang-cli mcp-config
138
+
139
+ Mencetak blok JSON mcpServers untuk klien MCP: endpoint
140
+ POST /api/v1/agent/mcp dengan header X-CLI-Key. Key dipakai dari
141
+ config bila sudah login, selain itu placeholder.`,
142
+
143
+ help: OVERVIEW,
144
+ };
145
+
146
+ // ── Parsing argumen ──────────────────────────────────────────────────────────
147
+
148
+ const VALUE_FLAGS = new Set([
149
+ 'name', 'path', 'exec_template', 'interval', 'status', 'title', 'desc',
150
+ 'description', 'priority', 'due', 'note', 'agent-status', 'api',
151
+ ]);
152
+
153
+ function parseArgs(argv) {
154
+ const pos = [];
155
+ const flags = {};
156
+ for (let i = 0; i < argv.length; i++) {
157
+ const a = argv[i];
158
+ if (a === '--') {
159
+ for (const x of argv.slice(i + 1)) pos.push(x);
160
+ break;
161
+ }
162
+ if (a.startsWith('--')) {
163
+ const eq = a.indexOf('=');
164
+ if (eq > 2) {
165
+ flags[a.slice(2, eq)] = a.slice(eq + 1);
166
+ continue;
167
+ }
168
+ const name = a.slice(2);
169
+ const next = argv[i + 1];
170
+ if (VALUE_FLAGS.has(name) && next !== undefined && !next.startsWith('-')) {
171
+ flags[name] = next;
172
+ i++;
173
+ } else {
174
+ flags[name] = true;
175
+ }
176
+ } else if (a.startsWith('-') && a.length >= 2) {
177
+ const name = a.slice(1);
178
+ if (name === 'h') flags.help = true;
179
+ else flags[name] = true;
180
+ } else {
181
+ pos.push(a);
182
+ }
183
+ }
184
+ return { pos, flags };
185
+ }
186
+
187
+ function printHelp(topic) {
188
+ const t = TOPICS[topic || 'help'];
189
+ if (!t) {
190
+ console.error(`❌ Command tidak dikenal: ${topic}`);
191
+ console.error('');
192
+ console.log(OVERVIEW);
193
+ return 1;
194
+ }
195
+ console.log(t);
196
+ return 0;
197
+ }
198
+
199
+ // ── Dispatch ─────────────────────────────────────────────────────────────────
200
+
201
+ async function main() {
202
+ const args = parseArgs(process.argv.slice(2));
203
+ const flags = args.flags;
204
+ const cmd = args.pos[0];
205
+
206
+ if (flags.version === true) {
207
+ console.log(`luang-cli v${pkg.version}`);
208
+ return 0;
209
+ }
210
+ if (!cmd || flags.help === true) {
211
+ return printHelp(cmd || 'help');
212
+ }
213
+ if (cmd === 'help') {
214
+ return printHelp(args.pos[1] || 'help');
215
+ }
216
+
217
+ const rt = core.resolveRuntime(core.loadConfig(), flags);
218
+ switch (cmd) {
219
+ case 'login':
220
+ return c.cmdLogin(rt, args);
221
+ case 'logout':
222
+ return c.cmdLogout(rt, args);
223
+ case 'whoami':
224
+ return c.cmdWhoami(rt, args);
225
+ case 'provider':
226
+ return c.cmdProvider(rt, args);
227
+ case 'agent':
228
+ return ag.cmdAgent(rt, args);
229
+ case 'task':
230
+ return ag.cmdTask(rt, args);
231
+ case 'status':
232
+ return ag.cmdStatus(rt, args);
233
+ case 'mcp-config':
234
+ return ag.cmdMcpConfig(rt, args);
235
+ default: {
236
+ console.error(`❌ Command tidak dikenal: ${cmd}`);
237
+ console.error(' Jalankan `luang-cli help` untuk daftar command.');
238
+ return 1;
239
+ }
240
+ }
241
+ }
242
+
243
+ main()
244
+ .then((code) => process.exit(code === undefined ? 0 : code))
245
+ .catch((e) => {
246
+ console.error(`❌ ${e && e.message ? e.message : e}`);
247
+ process.exit(1);
248
+ });