kiosapi 0.1.6 → 0.1.8
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 +310 -16
- package/dist/agent/schemas.js +56 -4
- package/dist/agent/team.js +38 -1
- package/dist/agent/tools.js +186 -15
- package/dist/api.js +54 -18
- package/dist/commands.js +256 -11
- 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 +2 -5
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
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kiosapi",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "CLI Kiosapi.id berbahasa Indonesia — bangun aplikasimu pakai API key Kiosapi (agen + multimodal).",
|
|
6
6
|
"keywords": [
|
|
@@ -28,10 +28,7 @@
|
|
|
28
28
|
"bin": {
|
|
29
29
|
"kiosapi": "./dist/index.js"
|
|
30
30
|
},
|
|
31
|
-
"files": [
|
|
32
|
-
"dist",
|
|
33
|
-
"README.md"
|
|
34
|
-
],
|
|
31
|
+
"files": ["dist", "README.md"],
|
|
35
32
|
"engines": {
|
|
36
33
|
"node": ">=20"
|
|
37
34
|
},
|