kiosapi 0.1.7 → 0.1.9
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/dist/agent/run.js +338 -16
- package/dist/agent/schemas.js +58 -4
- package/dist/agent/team.js +38 -1
- package/dist/agent/tools.js +186 -15
- package/dist/api.js +15 -2
- package/dist/commands.js +254 -8
- package/dist/config.js +61 -3
- package/dist/help.js +46 -28
- package/dist/index.js +18 -2
- package/dist/session.js +393 -55
- package/dist/ui.js +5 -0
- package/package.json +1 -1
package/dist/help.js
CHANGED
|
@@ -19,43 +19,61 @@ export function printVersion() {
|
|
|
19
19
|
console.log(`kiosapi ${VERSION}`);
|
|
20
20
|
}
|
|
21
21
|
export function printHelp() {
|
|
22
|
-
console.log(`${bold('kiosapi')} — CLI Kiosapi.id
|
|
22
|
+
console.log(`${bold('kiosapi')} v${VERSION} — CLI Kiosapi.id
|
|
23
23
|
|
|
24
24
|
${bold('Penggunaan:')} kiosapi [perintah] [opsi]
|
|
25
25
|
Tanpa perintah → sesi interaktif (ketik tugas; perintah meta diawali "/").
|
|
26
26
|
|
|
27
|
-
${bold('
|
|
28
|
-
masuk
|
|
29
|
-
keluar
|
|
30
|
-
periksa
|
|
31
|
-
perbarui
|
|
32
|
-
model [--cari q]
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
27
|
+
${bold('Akun & setelan:')}
|
|
28
|
+
masuk Simpan API key (kios_live_…); tampilkan saldo
|
|
29
|
+
keluar Hapus API key tersimpan
|
|
30
|
+
periksa Cek konektivitas, key, versi, tim & checkpoint
|
|
31
|
+
perbarui Update CLI ke versi npm terbaru
|
|
32
|
+
model [--cari q] Daftar model (--tools = hanya yang ada 🔧)
|
|
33
|
+
setel <k> <v> Atur setelan (model | base-url)
|
|
34
|
+
saldo Lihat saldo & pengeluaran bulan ini
|
|
35
|
+
pakai [--hari N] Ringkasan pemakaian N hari terakhir
|
|
36
|
+
isi <jumlah> Buat tagihan top-up (min 10.000)
|
|
37
|
+
|
|
38
|
+
${bold('Chat & tanya:')}
|
|
39
|
+
tanya "…" Tanya sekali (streaming); dukung pipe stdin & @file
|
|
40
|
+
ngobrol Mode percakapan interaktif (REPL)
|
|
41
|
+
|
|
42
|
+
${bold('Agen (butuh model 🔧):')}
|
|
43
|
+
rencana "…" Telusuri kode & susun rencana (read-only)
|
|
44
|
+
edit "…" Lakukan perubahan kode (tulis/edit file, tampilkan diff)
|
|
45
|
+
buat "…" Agen otonom: bangun proyek (tulis + jalankan)
|
|
46
|
+
lanjut [instruksi] Lanjutkan sesi agen yang terputus dari checkpoint
|
|
47
|
+
init Buat kiosapi.json (config per-project: model, mode, otomatis)
|
|
48
|
+
${dim('Opsi: -m <model>, --otomatis (lewati konfirmasi). Mendukung pipe stdin.')}
|
|
49
|
+
|
|
50
|
+
${bold('Tim multi-agen:')}
|
|
51
|
+
tim "…" Jalankan tim bawaan (perencana → pengkode → peninjau)
|
|
52
|
+
tim --pakai <nama> "…" Jalankan tim kustom tersimpan
|
|
53
|
+
tim --buat <nama> Buat tim kustom baru (wizard interaktif)
|
|
54
|
+
tim --daftar Lihat semua tim tersimpan
|
|
55
|
+
sambung <tool> Pakai Kiosapi di agen lain (aider, opencode, cursor…)
|
|
47
56
|
|
|
48
57
|
${bold('Multimodal:')}
|
|
49
|
-
gambar "…"
|
|
50
|
-
video "…"
|
|
51
|
-
lihat <file> "…"
|
|
58
|
+
gambar "…" Buat gambar → simpan file (-o file, --rasio, --opsi)
|
|
59
|
+
video "…" Buat video (async) → simpan file (--detik N)
|
|
60
|
+
lihat <file> "…" Tanya model vision tentang sebuah gambar
|
|
61
|
+
|
|
62
|
+
${bold('Shell completion:')}
|
|
63
|
+
completion bash|zsh|fish|pwsh Output skrip completion (lalu source-kan)
|
|
52
64
|
|
|
53
65
|
${bold('Contoh:')}
|
|
54
66
|
kiosapi masuk
|
|
55
|
-
kiosapi
|
|
56
|
-
kiosapi tanya
|
|
57
|
-
|
|
67
|
+
kiosapi tanya @src/app.ts "jelaskan fungsi utama file ini"
|
|
68
|
+
cat error.log | kiosapi tanya "apa penyebabnya?"
|
|
69
|
+
kiosapi init
|
|
70
|
+
kiosapi buat "tambah endpoint POST /users"
|
|
71
|
+
npm test 2>&1 | kiosapi buat "perbaiki semua tes yang gagal"
|
|
72
|
+
kiosapi lanjut
|
|
73
|
+
kiosapi tim --buat fullstack-team
|
|
74
|
+
kiosapi tim --pakai fullstack-team "bikin halaman login"
|
|
75
|
+
kiosapi completion bash >> ~/.bashrc
|
|
58
76
|
|
|
59
|
-
${dim('
|
|
77
|
+
${dim('Sesi interaktif: /undo batalkan giliran terakhir · /ringkas padatkan riwayat · /model ganti model.')}
|
|
60
78
|
${dim('Env: KIOSAPI_API_KEY, KIOSAPI_BASE_URL, NO_COLOR.')}`);
|
|
61
79
|
}
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { cmdBuat, cmdEdit, cmdGambar, cmdIsi, cmdKeluar, cmdLihat, cmdMasuk, cmdModel, cmdNgobrol, cmdPakai, cmdPerbarui, cmdPeriksa, cmdRencana, cmdSaldo, cmdSambung, cmdSetel, cmdTanya, cmdTim, cmdVideo, } from './commands.js';
|
|
2
|
+
import { cmdBuat, cmdCompletion, cmdEdit, cmdGambar, cmdInit, cmdIsi, cmdKeluar, cmdLihat, cmdMasuk, cmdModel, cmdNgobrol, cmdPakai, cmdPerbarui, cmdPeriksa, cmdRencana, cmdSaldo, cmdSambung, cmdSetel, cmdTanya, cmdTim, cmdVideo, maybeNotifyUpdate, } from './commands.js';
|
|
3
3
|
import { printHelp, printVersion } from './help.js';
|
|
4
|
-
import { startSession } from './session.js';
|
|
4
|
+
import { resumeFromCheckpoint, startSession } from './session.js';
|
|
5
5
|
import { red } from './ui.js';
|
|
6
6
|
/** Indonesian command names with English aliases. */
|
|
7
7
|
const COMMANDS = {
|
|
@@ -41,6 +41,10 @@ const COMMANDS = {
|
|
|
41
41
|
video: cmdVideo,
|
|
42
42
|
lihat: cmdLihat,
|
|
43
43
|
vision: cmdLihat,
|
|
44
|
+
lanjut: resumeFromCheckpoint,
|
|
45
|
+
resume: resumeFromCheckpoint,
|
|
46
|
+
init: cmdInit,
|
|
47
|
+
completion: cmdCompletion,
|
|
44
48
|
};
|
|
45
49
|
async function main() {
|
|
46
50
|
const [cmd, ...rest] = process.argv.slice(2);
|
|
@@ -71,6 +75,18 @@ async function main() {
|
|
|
71
75
|
return;
|
|
72
76
|
}
|
|
73
77
|
await handler(rest);
|
|
78
|
+
// After every non-session command on a TTY, nudge if there's a newer version.
|
|
79
|
+
// Skip for `perbarui`/`update` itself (user just updated) and `keluar`/`logout` (no point).
|
|
80
|
+
const skipUpdateCheck = new Set([
|
|
81
|
+
'perbarui',
|
|
82
|
+
'update',
|
|
83
|
+
'keluar',
|
|
84
|
+
'logout',
|
|
85
|
+
'completion',
|
|
86
|
+
'versi',
|
|
87
|
+
]);
|
|
88
|
+
if (!skipUpdateCheck.has(cmd))
|
|
89
|
+
await maybeNotifyUpdate();
|
|
74
90
|
}
|
|
75
91
|
main().catch((err) => {
|
|
76
92
|
console.error(red(err instanceof Error ? err.message : String(err)));
|
package/dist/session.js
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { clearCheckpoint, loadCheckpoint, newSession, resetSession, runTurn, undoLastTurn, } from './agent/run.js';
|
|
3
|
+
import { runCustomTeam, runTeam } from './agent/team.js';
|
|
4
|
+
import { expandAtMentions } from './agent/tools.js';
|
|
5
|
+
import { resolveModel, streamChat } from './api.js';
|
|
6
|
+
import { cmdGambar, cmdIsi, cmdLihat, cmdMasuk, cmdPakai, cmdPerbarui, cmdSaldo, cmdVideo, maybeNotifyUpdate, pickModel, warnIfNoTools, } from './commands.js';
|
|
7
|
+
import { listTeamConfigs, loadConfig, loadProjectConfig, loadTeamConfig, saveTeamConfig, } from './config.js';
|
|
8
|
+
import { bold, cyan, dim, green, idn, prompt, red, thinking, yellow } from './ui.js';
|
|
7
9
|
const MODES = ['rencana', 'edit', 'buat'];
|
|
8
|
-
/** The prompt indicator shows the active mode
|
|
10
|
+
/** The prompt indicator shows the active mode, turn count, accumulated tokens, and ⚡ for auto. */
|
|
9
11
|
function label(s) {
|
|
10
|
-
|
|
12
|
+
const turns = s.messages.filter((m) => m.role === 'user').length;
|
|
13
|
+
const ctx = turns > 0 ? dim(` [${turns}]`) : '';
|
|
14
|
+
const tok = s.totalTokens > 0 ? dim(` ${idn(s.totalTokens)}tok`) : '';
|
|
15
|
+
return `${cyan(`${s.mode}${s.otomatis ? ' ⚡' : ''}`) + ctx + tok} › `;
|
|
11
16
|
}
|
|
12
17
|
function banner(s) {
|
|
13
18
|
console.log(`${bold('Kiosapi CLI')} — sesi interaktif
|
|
@@ -18,20 +23,32 @@ ${dim('Ketik tugasmu langsung. Perintah meta diawali "/". /bantuan untuk daftar,
|
|
|
18
23
|
}
|
|
19
24
|
function slashHelp() {
|
|
20
25
|
console.log(`${bold('Perintah sesi:')}
|
|
21
|
-
/tim <tugas>
|
|
22
|
-
/
|
|
23
|
-
/
|
|
24
|
-
/
|
|
25
|
-
/
|
|
26
|
-
/
|
|
27
|
-
/
|
|
28
|
-
/
|
|
29
|
-
/
|
|
30
|
-
/
|
|
31
|
-
/
|
|
32
|
-
/
|
|
33
|
-
/
|
|
34
|
-
/
|
|
26
|
+
/tim <tugas> Multi-agen bawaan (perencana→pengkode→peninjau)
|
|
27
|
+
/tim --pakai <nama> <tugas> Jalankan tim kustom tersimpan
|
|
28
|
+
/peran [peran] [model] Atur model per peran tim (atau lihat/reset)
|
|
29
|
+
/simpan-tim <nama> Simpan konfigurasi /peran saat ini sebagai tim kustom
|
|
30
|
+
/tim-daftar Lihat semua tim kustom tersimpan
|
|
31
|
+
/gambar <prompt> Buat gambar → file
|
|
32
|
+
/video <prompt> Buat video → file
|
|
33
|
+
/lihat <file> <pertanyaan> Tanya model vision tentang gambar
|
|
34
|
+
/mode [rencana|edit|buat] Lihat/ubah mode agen
|
|
35
|
+
/model [id|filter] Pilih model (tanpa argumen = daftar bernomor)
|
|
36
|
+
/otomatis Hidup/matikan auto-approve (lewati konfirmasi)
|
|
37
|
+
/commit [--all] Buat pesan commit (model) dari git diff → konfirmasi → commit
|
|
38
|
+
/diff [--staged|<ref>] Tampilkan git diff langsung (tanpa kirim ke model)
|
|
39
|
+
/shell <perintah> Jalankan perintah langsung tanpa agent (tidak konsumsi token)
|
|
40
|
+
/undo Batalkan giliran terakhir (hapus dari riwayat)
|
|
41
|
+
/ringkas Padatkan riwayat sesi menjadi ringkasan singkat
|
|
42
|
+
/bersih Bersihkan riwayat percakapan & checkpoint
|
|
43
|
+
/lanjut Lanjutkan sesi dari checkpoint terakhir
|
|
44
|
+
/perbarui Update CLI ke versi terbaru (tanpa keluar sesi)
|
|
45
|
+
/saldo Lihat saldo
|
|
46
|
+
/pakai [--hari N] Ringkasan pemakaian
|
|
47
|
+
/isi <jumlah> Buat tagihan top-up
|
|
48
|
+
/bantuan Tampilkan bantuan ini
|
|
49
|
+
/keluar Keluar dari sesi
|
|
50
|
+
|
|
51
|
+
${dim('Tips: @path/file.ts menyertakan file · @git:diff / @git:status / @git:log menyertakan output git.')}`);
|
|
35
52
|
}
|
|
36
53
|
/** Handle a /slash command. Returns true when the session should end. */
|
|
37
54
|
async function runSlash(line, s) {
|
|
@@ -83,7 +100,165 @@ async function runSlash(line, s) {
|
|
|
83
100
|
return false;
|
|
84
101
|
case 'bersih':
|
|
85
102
|
resetSession(s);
|
|
86
|
-
|
|
103
|
+
clearCheckpoint();
|
|
104
|
+
console.log(dim('Riwayat dan checkpoint dibersihkan.'));
|
|
105
|
+
return false;
|
|
106
|
+
case 'commit': {
|
|
107
|
+
const allFlag = rest[0] === '--all' || rest[0] === '-a';
|
|
108
|
+
const cwd = process.cwd();
|
|
109
|
+
if (allFlag)
|
|
110
|
+
spawnSync('git', ['add', '-A'], { cwd, encoding: 'utf8' });
|
|
111
|
+
const stagedOut = spawnSync('git', ['diff', '--staged'], { cwd, encoding: 'utf8' });
|
|
112
|
+
let diff = (stagedOut.stdout ?? '').trim();
|
|
113
|
+
if (!diff) {
|
|
114
|
+
const statusOut = spawnSync('git', ['status', '--short'], { cwd, encoding: 'utf8' });
|
|
115
|
+
const changes = (statusOut.stdout ?? '').trim();
|
|
116
|
+
if (!changes) {
|
|
117
|
+
console.log(dim('Tidak ada perubahan untuk di-commit.'));
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
console.log(yellow('Tidak ada yang di-stage. Gunakan /commit --all untuk stage semua.'));
|
|
121
|
+
console.log(dim(changes));
|
|
122
|
+
}
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
if (diff.length > 8000)
|
|
126
|
+
diff = `${diff.slice(0, 8000)}\n…[dipotong]`;
|
|
127
|
+
console.log(dim('Menganalisa diff…'));
|
|
128
|
+
const stop = thinking();
|
|
129
|
+
let commitMsg = '';
|
|
130
|
+
let commitPrinted = false;
|
|
131
|
+
try {
|
|
132
|
+
for await (const piece of streamChat(s.model, [
|
|
133
|
+
{
|
|
134
|
+
role: 'system',
|
|
135
|
+
content: 'Write ONLY a git commit message (no backticks, no preamble). Subject ≤72 chars, imperative English ("add", "fix", "update"). Optional body after blank line for complex changes.',
|
|
136
|
+
},
|
|
137
|
+
{ role: 'user', content: `Write a commit message for this diff:\n\n${diff}` },
|
|
138
|
+
])) {
|
|
139
|
+
if (!commitPrinted) {
|
|
140
|
+
stop();
|
|
141
|
+
commitPrinted = true;
|
|
142
|
+
}
|
|
143
|
+
process.stdout.write(piece);
|
|
144
|
+
commitMsg += piece;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
finally {
|
|
148
|
+
if (!commitPrinted)
|
|
149
|
+
stop();
|
|
150
|
+
}
|
|
151
|
+
if (commitMsg)
|
|
152
|
+
process.stdout.write('\n\n');
|
|
153
|
+
commitMsg = commitMsg
|
|
154
|
+
.trim()
|
|
155
|
+
.replace(/^```[\w]*\n?/gm, '')
|
|
156
|
+
.replace(/^```$/gm, '')
|
|
157
|
+
.trim();
|
|
158
|
+
if (!commitMsg) {
|
|
159
|
+
console.log(red('Gagal generate pesan commit.'));
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
const ans = (await prompt('Commit? [Enter=ya · e=edit · t=tidak] ')).trim().toLowerCase();
|
|
163
|
+
if (ans === 't' || ans === 'tidak') {
|
|
164
|
+
console.log(dim('Dibatalkan.'));
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
if (ans === 'e' || ans === 'edit') {
|
|
168
|
+
const manual = (await prompt('Pesan commit: ')).trim();
|
|
169
|
+
if (!manual) {
|
|
170
|
+
console.log(dim('Dibatalkan.'));
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
commitMsg = manual;
|
|
174
|
+
}
|
|
175
|
+
const res = spawnSync('git', ['commit', '-m', commitMsg], { cwd, encoding: 'utf8' });
|
|
176
|
+
if (res.status === 0) {
|
|
177
|
+
console.log(green(`✓ ${(res.stdout ?? '').trim()}`));
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
console.log(red(`Git error: ${((res.stderr ?? '') || (res.stdout ?? '')).trim()}`));
|
|
181
|
+
}
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
case 'undo':
|
|
185
|
+
case 'batalkan': {
|
|
186
|
+
if (undoLastTurn(s)) {
|
|
187
|
+
clearCheckpoint();
|
|
188
|
+
const turns = s.messages.filter((m) => m.role === 'user').length;
|
|
189
|
+
console.log(dim(`Giliran terakhir dibatalkan. Sisa: ${turns} giliran.`));
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
console.log(dim('Tidak ada giliran untuk dibatalkan.'));
|
|
193
|
+
}
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
case 'ringkas': {
|
|
197
|
+
const textMsgs = s.messages.filter((m) => (m.role === 'user' || m.role === 'assistant') &&
|
|
198
|
+
typeof m.content === 'string' &&
|
|
199
|
+
m.content.trim().length > 0);
|
|
200
|
+
if (textMsgs.length === 0) {
|
|
201
|
+
console.log(dim('Belum ada percakapan untuk diringkas.'));
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
console.log(dim('Meringkas konteks sesi…'));
|
|
205
|
+
const stop = thinking();
|
|
206
|
+
let summary = '';
|
|
207
|
+
let printed = false;
|
|
208
|
+
try {
|
|
209
|
+
for await (const piece of streamChat(s.model, [
|
|
210
|
+
...textMsgs,
|
|
211
|
+
{
|
|
212
|
+
role: 'user',
|
|
213
|
+
content: 'Buat ringkasan singkat (max 5 poin) dari semua yang sudah dikerjakan. Fokus pada file yang diubah dan keputusan penting. Format: daftar bullet Bahasa Indonesia.',
|
|
214
|
+
},
|
|
215
|
+
])) {
|
|
216
|
+
if (!printed) {
|
|
217
|
+
stop();
|
|
218
|
+
printed = true;
|
|
219
|
+
}
|
|
220
|
+
process.stdout.write(piece);
|
|
221
|
+
summary += piece;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
finally {
|
|
225
|
+
if (!printed)
|
|
226
|
+
stop();
|
|
227
|
+
}
|
|
228
|
+
if (summary)
|
|
229
|
+
process.stdout.write('\n');
|
|
230
|
+
if (!summary) {
|
|
231
|
+
console.log(red('Gagal membuat ringkasan.'));
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
// Replace the conversation body with the condensed summary
|
|
235
|
+
const systemMsg = s.messages[0];
|
|
236
|
+
s.messages = [
|
|
237
|
+
systemMsg,
|
|
238
|
+
{ role: 'user', content: `Ringkasan sesi sejauh ini:\n${summary}` },
|
|
239
|
+
{ role: 'assistant', content: 'Mengerti, saya akan melanjutkan dari titik ini.' },
|
|
240
|
+
];
|
|
241
|
+
clearCheckpoint();
|
|
242
|
+
console.log(dim('\n✓ Konteks dipadatkan menjadi 3 pesan. Lanjutkan dengan instruksi berikutnya.'));
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
case 'lanjut':
|
|
246
|
+
case 'resume': {
|
|
247
|
+
const cp = loadCheckpoint();
|
|
248
|
+
if (!cp) {
|
|
249
|
+
console.log(dim('Tidak ada checkpoint tersimpan.'));
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
const when = new Date(cp.savedAt).toLocaleString('id-ID');
|
|
253
|
+
const turns = cp.messages.filter((m) => m.role === 'user').length;
|
|
254
|
+
console.log(dim(`Checkpoint: ${cp.mode} · ${turns} giliran · ${when}`));
|
|
255
|
+
console.log(dim('Untuk melanjutkan, keluar sesi lalu jalankan: kiosapi lanjut'));
|
|
256
|
+
}
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
case 'perbarui':
|
|
260
|
+
case 'update':
|
|
261
|
+
await cmdPerbarui();
|
|
87
262
|
return false;
|
|
88
263
|
case 'saldo':
|
|
89
264
|
await cmdSaldo();
|
|
@@ -96,18 +271,87 @@ async function runSlash(line, s) {
|
|
|
96
271
|
return false;
|
|
97
272
|
case 'tim':
|
|
98
273
|
case 'team': {
|
|
99
|
-
|
|
100
|
-
if (
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
274
|
+
// /tim --pakai <nama> <tugas> or /tim <tugas>
|
|
275
|
+
if (rest[0] === '--pakai') {
|
|
276
|
+
const configName = rest[1]?.trim();
|
|
277
|
+
const task = rest.slice(2).join(' ').trim();
|
|
278
|
+
if (!configName) {
|
|
279
|
+
console.log(red('Beri nama tim. Contoh: /tim --pakai backend-team "tambah endpoint"'));
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
if (!task) {
|
|
283
|
+
console.log(red('Beri tugas. Contoh: /tim --pakai backend-team "tambah endpoint"'));
|
|
284
|
+
return false;
|
|
285
|
+
}
|
|
286
|
+
const config = loadTeamConfig(configName);
|
|
287
|
+
if (!config) {
|
|
288
|
+
console.log(red(`Tim "${configName}" tidak ditemukan. Lihat: /tim-daftar`));
|
|
289
|
+
return false;
|
|
290
|
+
}
|
|
291
|
+
await runCustomTeam(task, { config, otomatis: s.otomatis });
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
const task = rest.join(' ').trim();
|
|
295
|
+
if (!task)
|
|
296
|
+
console.log(red('Beri tugas. Contoh: /tim bikin endpoint /health + tes'));
|
|
297
|
+
else
|
|
298
|
+
await runTeam(task, {
|
|
299
|
+
models: {
|
|
300
|
+
perencana: s.teamModels.perencana ?? s.model,
|
|
301
|
+
pengkode: s.teamModels.pengkode ?? s.model,
|
|
302
|
+
peninjau: s.teamModels.peninjau ?? s.model,
|
|
303
|
+
},
|
|
304
|
+
otomatis: s.otomatis,
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
case 'simpan-tim': {
|
|
310
|
+
const nama = rest[0]?.trim();
|
|
311
|
+
if (!nama) {
|
|
312
|
+
console.log(red('Beri nama. Contoh: /simpan-tim backend-team'));
|
|
313
|
+
return false;
|
|
314
|
+
}
|
|
315
|
+
const config = {
|
|
316
|
+
nama,
|
|
317
|
+
peran: {
|
|
318
|
+
perencana: {
|
|
319
|
+
model: s.teamModels.perencana ?? s.model,
|
|
320
|
+
mode: 'rencana',
|
|
321
|
+
brief: 'Peranmu: PERENCANA. Telusuri kode dan susun rencana langkah yang jelas. JANGAN menulis kode.',
|
|
108
322
|
},
|
|
109
|
-
|
|
110
|
-
|
|
323
|
+
pengkode: {
|
|
324
|
+
model: s.teamModels.pengkode ?? s.model,
|
|
325
|
+
mode: 'buat',
|
|
326
|
+
brief: 'Peranmu: PENGKODE. Implementasikan tugas mengikuti rencana. Panggil selesai jika sudah.',
|
|
327
|
+
},
|
|
328
|
+
peninjau: {
|
|
329
|
+
model: s.teamModels.peninjau ?? s.model,
|
|
330
|
+
mode: 'rencana',
|
|
331
|
+
brief: 'Peranmu: PENINJAU. Tinjau hasil implementasi: sebutkan masalah dan saran perbaikan.',
|
|
332
|
+
},
|
|
333
|
+
},
|
|
334
|
+
alur: ['perencana', 'pengkode', 'peninjau'],
|
|
335
|
+
};
|
|
336
|
+
saveTeamConfig(config);
|
|
337
|
+
console.log(green(`✓ Tim "${nama}" tersimpan.`));
|
|
338
|
+
console.log(dim(` Edit di: ~/.kiosapi/tim/${nama}.json`));
|
|
339
|
+
console.log(dim(` Gunakan: /tim --pakai ${nama} "tugas" atau kiosapi tim --pakai ${nama} "tugas"`));
|
|
340
|
+
return false;
|
|
341
|
+
}
|
|
342
|
+
case 'tim-daftar': {
|
|
343
|
+
const teams = listTeamConfigs();
|
|
344
|
+
if (teams.length === 0) {
|
|
345
|
+
console.log(dim('Belum ada tim tersimpan. Buat dengan: /simpan-tim <nama>'));
|
|
346
|
+
}
|
|
347
|
+
else {
|
|
348
|
+
console.log(bold('Tim tersimpan:'));
|
|
349
|
+
for (const t of teams) {
|
|
350
|
+
const cfg = loadTeamConfig(t);
|
|
351
|
+
const alur = cfg?.alur.join(' → ') ?? '?';
|
|
352
|
+
console.log(` ${cyan(t)} ${dim(`(${alur})`)}`);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
111
355
|
return false;
|
|
112
356
|
}
|
|
113
357
|
case 'peran': {
|
|
@@ -147,6 +391,30 @@ async function runSlash(line, s) {
|
|
|
147
391
|
case 'lihat':
|
|
148
392
|
await cmdLihat(rest);
|
|
149
393
|
return false;
|
|
394
|
+
case 'diff': {
|
|
395
|
+
const diffArgs = rest.length > 0 ? rest : ['HEAD'];
|
|
396
|
+
spawnSync('git', ['diff', '--color=always', ...diffArgs], {
|
|
397
|
+
cwd: process.cwd(),
|
|
398
|
+
stdio: 'inherit',
|
|
399
|
+
timeout: 10_000,
|
|
400
|
+
});
|
|
401
|
+
return false;
|
|
402
|
+
}
|
|
403
|
+
case 'shell':
|
|
404
|
+
case 'sh': {
|
|
405
|
+
const shellCmd = rest.join(' ').trim();
|
|
406
|
+
if (!shellCmd) {
|
|
407
|
+
console.log(red('Beri perintah. Contoh: /shell npm test'));
|
|
408
|
+
return false;
|
|
409
|
+
}
|
|
410
|
+
spawnSync(shellCmd, {
|
|
411
|
+
cwd: process.cwd(),
|
|
412
|
+
shell: true,
|
|
413
|
+
stdio: 'inherit',
|
|
414
|
+
timeout: 120_000,
|
|
415
|
+
});
|
|
416
|
+
return false;
|
|
417
|
+
}
|
|
150
418
|
default:
|
|
151
419
|
console.log(red(`Perintah tidak dikenal: /${cmd} (coba /bantuan)`));
|
|
152
420
|
return false;
|
|
@@ -157,26 +425,8 @@ async function runSlash(line, s) {
|
|
|
157
425
|
return false;
|
|
158
426
|
}
|
|
159
427
|
}
|
|
160
|
-
/**
|
|
161
|
-
|
|
162
|
-
* prompt to the agent (with persistent context); `/command` runs a meta command. Logs in first if
|
|
163
|
-
* needed.
|
|
164
|
-
*/
|
|
165
|
-
export async function startSession() {
|
|
166
|
-
if (!loadConfig().apiKey) {
|
|
167
|
-
console.log(dim('Belum masuk — masukkan API key dulu.'));
|
|
168
|
-
await cmdMasuk();
|
|
169
|
-
if (!loadConfig().apiKey)
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
172
|
-
await maybeNotifyUpdate();
|
|
173
|
-
const model = await resolveModel(undefined);
|
|
174
|
-
const s = newSession(model, 'buat', false);
|
|
175
|
-
banner(s);
|
|
176
|
-
await warnIfNoTools(model);
|
|
177
|
-
// One readline at a time: each turn uses prompt() (create+close), and slash commands that need
|
|
178
|
-
// their own input (e.g. /model picker, masuk) also use prompt(). A persistent interface here would
|
|
179
|
-
// collide with those → double-echoed input and a stray close that exits the session.
|
|
428
|
+
/** Shared interactive loop — called by both startSession and resumeFromCheckpoint. */
|
|
429
|
+
async function runSessionLoop(s) {
|
|
180
430
|
while (true) {
|
|
181
431
|
let line;
|
|
182
432
|
try {
|
|
@@ -193,7 +443,7 @@ export async function startSession() {
|
|
|
193
443
|
continue;
|
|
194
444
|
}
|
|
195
445
|
try {
|
|
196
|
-
await runTurn(s, line);
|
|
446
|
+
await runTurn(s, expandAtMentions(line));
|
|
197
447
|
}
|
|
198
448
|
catch (err) {
|
|
199
449
|
console.error(red(err instanceof Error ? err.message : String(err)));
|
|
@@ -201,3 +451,91 @@ export async function startSession() {
|
|
|
201
451
|
}
|
|
202
452
|
console.log(green('Sampai jumpa.'));
|
|
203
453
|
}
|
|
454
|
+
/**
|
|
455
|
+
* Interactive session (Claude-Code-style): run `kiosapi` with no args to enter. Plain text is a
|
|
456
|
+
* prompt to the agent (with persistent context); `/command` runs a meta command. Logs in first if
|
|
457
|
+
* needed.
|
|
458
|
+
*/
|
|
459
|
+
export async function startSession() {
|
|
460
|
+
if (!loadConfig().apiKey) {
|
|
461
|
+
console.log(dim('Belum masuk — masukkan API key dulu.'));
|
|
462
|
+
await cmdMasuk();
|
|
463
|
+
if (!loadConfig().apiKey)
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
await maybeNotifyUpdate();
|
|
467
|
+
const proj = loadProjectConfig();
|
|
468
|
+
if (proj) {
|
|
469
|
+
const parts = [
|
|
470
|
+
proj.model && `model=${proj.model}`,
|
|
471
|
+
proj.mode && `mode=${proj.mode}`,
|
|
472
|
+
proj.otomatis && 'otomatis',
|
|
473
|
+
].filter(Boolean);
|
|
474
|
+
console.log(dim(`(kiosapi.json: ${parts.join(' · ')})`));
|
|
475
|
+
}
|
|
476
|
+
const model = await resolveModel(proj?.model ?? undefined);
|
|
477
|
+
const s = newSession(model, proj?.mode ?? 'buat', proj?.otomatis ?? false);
|
|
478
|
+
if (proj?.maxSteps)
|
|
479
|
+
s.maxSteps = proj.maxSteps;
|
|
480
|
+
if (proj?.projectContext) {
|
|
481
|
+
const sys = s.messages[0];
|
|
482
|
+
sys.content += `\n\n## Konteks Proyek\n${proj.projectContext}`;
|
|
483
|
+
}
|
|
484
|
+
banner(s);
|
|
485
|
+
await warnIfNoTools(model);
|
|
486
|
+
await runSessionLoop(s);
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Resume a checkpointed session interactively: load the saved state, show a preamble, ask for a
|
|
490
|
+
* continuation instruction, then drop into the full session loop (unlike cmdLanjut which only runs
|
|
491
|
+
* one turn and exits).
|
|
492
|
+
*/
|
|
493
|
+
export async function resumeFromCheckpoint(args) {
|
|
494
|
+
await maybeNotifyUpdate();
|
|
495
|
+
const cp = loadCheckpoint();
|
|
496
|
+
if (!cp) {
|
|
497
|
+
throw new Error('Tidak ada sesi tersimpan. Mulai dengan: kiosapi buat "tugas"\n' +
|
|
498
|
+
'Checkpoint disimpan otomatis setiap langkah agen.');
|
|
499
|
+
}
|
|
500
|
+
if (!loadConfig().apiKey) {
|
|
501
|
+
console.log(dim('Belum masuk — masukkan API key dulu.'));
|
|
502
|
+
await cmdMasuk();
|
|
503
|
+
if (!loadConfig().apiKey)
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
console.log(bold('Melanjutkan sesi tersimpan'));
|
|
507
|
+
console.log(` Model : ${cp.model}`);
|
|
508
|
+
console.log(` Mode : ${cp.mode}`);
|
|
509
|
+
console.log(` Disimpan : ${new Date(cp.savedAt).toLocaleString('id-ID')}`);
|
|
510
|
+
console.log(` Direktori: ${cp.cwd}`);
|
|
511
|
+
if (cp.cwd !== process.cwd()) {
|
|
512
|
+
console.log(yellow('\n⚠ Sesi ini disimpan di direktori berbeda:'));
|
|
513
|
+
console.log(` ${cp.cwd}`);
|
|
514
|
+
const ans = (await prompt('Lanjutkan di direktori saat ini? (y/t) ')).trim().toLowerCase();
|
|
515
|
+
if (ans !== 'y' && ans !== 'ya')
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
// Show last few turns as context
|
|
519
|
+
const history = cp.messages.filter((m) => m.role === 'user' || m.role === 'assistant');
|
|
520
|
+
const recent = history.slice(-4);
|
|
521
|
+
if (recent.length > 0) {
|
|
522
|
+
console.log(dim('\nKonteks terakhir:'));
|
|
523
|
+
for (const m of recent) {
|
|
524
|
+
const content = typeof m.content === 'string' ? m.content : '(tool calls)';
|
|
525
|
+
const snippet = content.trim().slice(0, 120).replace(/\n/g, ' ');
|
|
526
|
+
console.log(dim(` [${m.role}] ${snippet}${content.length > 120 ? '…' : ''}`));
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
console.log('');
|
|
530
|
+
const firstInstruction = args.join(' ').trim() ||
|
|
531
|
+
(await prompt('Instruksi lanjutan (Enter = lanjutkan): ')).trim() ||
|
|
532
|
+
'Lanjutkan dari titik terakhir. Tinjau apa yang sudah dikerjakan dan selesaikan jika belum.';
|
|
533
|
+
// Run first turn on the restored session, then enter the full interactive loop
|
|
534
|
+
try {
|
|
535
|
+
await runTurn(cp, firstInstruction);
|
|
536
|
+
}
|
|
537
|
+
catch (err) {
|
|
538
|
+
console.error(red(err instanceof Error ? err.message : String(err)));
|
|
539
|
+
}
|
|
540
|
+
await runSessionLoop(cp);
|
|
541
|
+
}
|
package/dist/ui.js
CHANGED
|
@@ -25,6 +25,11 @@ export const THINKING_TIPS = [
|
|
|
25
25
|
'kiosapi sambung aider — pakai Kiosapi di tool agen lain.',
|
|
26
26
|
'.kiosapiignore melindungi file (mis. rahasia) dari agen.',
|
|
27
27
|
'/saldo & /pakai untuk cek saldo dan pemakaian.',
|
|
28
|
+
'Ketik @path/file.ts dalam pesan untuk menyertakan isi file ke konteks.',
|
|
29
|
+
'/simpan-tim <nama> menyimpan konfigurasi peran tim saat ini.',
|
|
30
|
+
'kiosapi lanjut melanjutkan sesi agen yang terputus.',
|
|
31
|
+
'kiosapi tim --pakai <nama> "tugas" menjalankan tim kustom tersimpan.',
|
|
32
|
+
'kiosapi tim --daftar menampilkan semua tim tersimpan.',
|
|
28
33
|
];
|
|
29
34
|
const SPINNER = ['|', '/', '-', '\\'];
|
|
30
35
|
/**
|