sarangai-cli 1.0.12 → 1.0.14
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/index.js +341 -60
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -35,7 +35,7 @@ var require_package = __commonJS({
|
|
|
35
35
|
"package.json"(exports2, module2) {
|
|
36
36
|
module2.exports = {
|
|
37
37
|
name: "sarangai-cli",
|
|
38
|
-
version: "1.0.
|
|
38
|
+
version: "1.0.14",
|
|
39
39
|
description: "CLI resmi SarangAI \u2014 Gateway multi-model AI langsung dari terminal Anda",
|
|
40
40
|
main: "dist/index.js",
|
|
41
41
|
bin: {
|
|
@@ -96,6 +96,8 @@ var import_chalk = __toESM(require("chalk"));
|
|
|
96
96
|
var import_readline = __toESM(require("readline"));
|
|
97
97
|
var import_child_process = require("child_process");
|
|
98
98
|
var import_crypto = __toESM(require("crypto"));
|
|
99
|
+
var import_fs2 = __toESM(require("fs"));
|
|
100
|
+
var import_path2 = __toESM(require("path"));
|
|
99
101
|
var import_clipboardy = __toESM(require("clipboardy"));
|
|
100
102
|
|
|
101
103
|
// src/config.ts
|
|
@@ -204,7 +206,25 @@ async function fetchUserMeta(baseUrl2, apiKey) {
|
|
|
204
206
|
}
|
|
205
207
|
} catch {
|
|
206
208
|
}
|
|
207
|
-
return
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
async function refreshUserMeta() {
|
|
212
|
+
const fresh = await fetchUserMeta(baseUrl, cfg?.apiKey || "");
|
|
213
|
+
if (!fresh) return;
|
|
214
|
+
const changed = !userMeta || fresh.balance !== userMeta.balance || fresh.tier !== userMeta.tier || fresh.accountId !== userMeta.accountId;
|
|
215
|
+
userMeta = fresh;
|
|
216
|
+
if (!changed) return;
|
|
217
|
+
if (inWorkspace) {
|
|
218
|
+
if (isExecuting) drawSessionScreen(lastTurn);
|
|
219
|
+
else if (sessionScreen) drawSessionScreen(lastTurn, { inputMode: "typing" });
|
|
220
|
+
else drawWorkspaceScreen(activeModel, sessionTimeLeft, currentTaskInput);
|
|
221
|
+
} else {
|
|
222
|
+
drawSelectionModal(activeModel, userMeta, expanded, cursorIdx, copiedNotice);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
function redrawWithFreshBalance() {
|
|
226
|
+
drawSelectionModal(activeModel, userMeta, expanded, cursorIdx, copiedNotice);
|
|
227
|
+
refreshUserMeta();
|
|
208
228
|
}
|
|
209
229
|
function getDisplayDir() {
|
|
210
230
|
const cwd = process.cwd();
|
|
@@ -257,7 +277,7 @@ function drawSelectionModal(activeModel2, userMeta2, expanded2, cursorIdx2, copi
|
|
|
257
277
|
if (!expanded2) {
|
|
258
278
|
lines.push(...renderCard(activeModel2, cursorIdx2 === 0, cardWidth, pad));
|
|
259
279
|
lines.push("");
|
|
260
|
-
const balanceStr = `${Number(userMeta2
|
|
280
|
+
const balanceStr = `${Number(userMeta2?.balance ?? 0).toLocaleString()} Credits`;
|
|
261
281
|
lines.push(
|
|
262
282
|
pad + import_chalk.default.bold.white(userMeta2.tier) + import_chalk.default.gray(" \u2022 ") + import_chalk.default.yellow.bold(balanceStr) + import_chalk.default.gray(" remaining \u2022 ") + import_chalk.default.hex("#a855f7")(userMeta2.accountId)
|
|
263
283
|
);
|
|
@@ -281,7 +301,7 @@ function drawSelectionModal(activeModel2, userMeta2, expanded2, cursorIdx2, copi
|
|
|
281
301
|
lines.push(...renderCard(m, cursorIdx2 === idx + 10, cardWidth, pad));
|
|
282
302
|
});
|
|
283
303
|
lines.push("");
|
|
284
|
-
const balanceStr = `${Number(userMeta2
|
|
304
|
+
const balanceStr = `${Number(userMeta2?.balance ?? 0).toLocaleString()} Credits`;
|
|
285
305
|
lines.push(
|
|
286
306
|
pad + import_chalk.default.bold.white(userMeta2.tier) + import_chalk.default.gray(" \u2022 ") + import_chalk.default.yellow.bold(balanceStr) + import_chalk.default.gray(" remaining \u2022 ") + import_chalk.default.hex("#a855f7")(userMeta2.accountId)
|
|
287
307
|
);
|
|
@@ -347,7 +367,7 @@ var expanded = false;
|
|
|
347
367
|
var cursorIdx = 0;
|
|
348
368
|
var copiedNotice = false;
|
|
349
369
|
var activeModel;
|
|
350
|
-
var userMeta;
|
|
370
|
+
var userMeta = null;
|
|
351
371
|
var sessionTimeLeft = 3600;
|
|
352
372
|
var timerInterval = null;
|
|
353
373
|
var currentTaskInput = "";
|
|
@@ -360,9 +380,13 @@ var respStreaming = false;
|
|
|
360
380
|
var respScroll = null;
|
|
361
381
|
var respTotalLines = 0;
|
|
362
382
|
var respViewRows = 12;
|
|
383
|
+
var conversationHistory = [];
|
|
384
|
+
var sessionScreen = false;
|
|
385
|
+
var respLastNotes = [];
|
|
386
|
+
var keyBuffer = "";
|
|
363
387
|
function cleanupAndExit() {
|
|
364
388
|
if (timerInterval) clearInterval(timerInterval);
|
|
365
|
-
process.stdout.write("\x1B[?1049l\x1B[?25h\n");
|
|
389
|
+
process.stdout.write("\x1B[?1000l\x1B[?1006l\x1B[?1049l\x1B[?25h\n");
|
|
366
390
|
process.exit(0);
|
|
367
391
|
}
|
|
368
392
|
function triggerCopyLink() {
|
|
@@ -373,22 +397,26 @@ function triggerCopyLink() {
|
|
|
373
397
|
function leaveWorkspace() {
|
|
374
398
|
inWorkspace = false;
|
|
375
399
|
isExecuting = false;
|
|
400
|
+
sessionScreen = false;
|
|
376
401
|
if (timerInterval) clearInterval(timerInterval);
|
|
377
402
|
currentTaskInput = "";
|
|
403
|
+
respScroll = null;
|
|
404
|
+
conversationHistory = [];
|
|
378
405
|
process.stdout.write("\x1B[2J\x1B[3J\x1B[H");
|
|
379
|
-
|
|
406
|
+
redrawWithFreshBalance();
|
|
380
407
|
}
|
|
381
408
|
function enterWorkspace() {
|
|
382
409
|
inWorkspace = true;
|
|
383
410
|
isExecuting = false;
|
|
384
411
|
currentTaskInput = "";
|
|
385
|
-
|
|
412
|
+
sessionScreen = true;
|
|
413
|
+
redrawInputScreen();
|
|
386
414
|
if (timerInterval) clearInterval(timerInterval);
|
|
387
415
|
timerInterval = setInterval(() => {
|
|
388
416
|
if (sessionTimeLeft > 0) {
|
|
389
417
|
sessionTimeLeft--;
|
|
390
418
|
if (inWorkspace && !isExecuting) {
|
|
391
|
-
|
|
419
|
+
redrawInputScreen();
|
|
392
420
|
}
|
|
393
421
|
}
|
|
394
422
|
}, 1e3);
|
|
@@ -432,7 +460,141 @@ function styleBodyLine(l) {
|
|
|
432
460
|
if (l.style === "error") return import_chalk.default.red(l.text);
|
|
433
461
|
return import_chalk.default.hex("#e2e8f0")(l.text);
|
|
434
462
|
}
|
|
435
|
-
|
|
463
|
+
var EXT_BY_LANG = {
|
|
464
|
+
python: "py",
|
|
465
|
+
py: "py",
|
|
466
|
+
javascript: "js",
|
|
467
|
+
js: "js",
|
|
468
|
+
typescript: "ts",
|
|
469
|
+
ts: "ts",
|
|
470
|
+
tsx: "tsx",
|
|
471
|
+
jsx: "jsx",
|
|
472
|
+
go: "go",
|
|
473
|
+
rust: "rs",
|
|
474
|
+
rs: "rs",
|
|
475
|
+
java: "java",
|
|
476
|
+
c: "c",
|
|
477
|
+
cpp: "cpp",
|
|
478
|
+
"c++": "cpp",
|
|
479
|
+
csharp: "cs",
|
|
480
|
+
"c#": "cs",
|
|
481
|
+
cs: "cs",
|
|
482
|
+
php: "php",
|
|
483
|
+
ruby: "rb",
|
|
484
|
+
rb: "rb",
|
|
485
|
+
swift: "swift",
|
|
486
|
+
kotlin: "kt",
|
|
487
|
+
kt: "kt",
|
|
488
|
+
bash: "sh",
|
|
489
|
+
sh: "sh",
|
|
490
|
+
shell: "sh",
|
|
491
|
+
zsh: "sh",
|
|
492
|
+
powershell: "ps1",
|
|
493
|
+
ps1: "ps1",
|
|
494
|
+
html: "html",
|
|
495
|
+
css: "css",
|
|
496
|
+
scss: "scss",
|
|
497
|
+
sass: "sass",
|
|
498
|
+
less: "less",
|
|
499
|
+
sql: "sql",
|
|
500
|
+
json: "json",
|
|
501
|
+
yaml: "yaml",
|
|
502
|
+
yml: "yml",
|
|
503
|
+
toml: "toml",
|
|
504
|
+
xml: "xml",
|
|
505
|
+
markdown: "md",
|
|
506
|
+
md: "md",
|
|
507
|
+
dockerfile: "",
|
|
508
|
+
makefile: "",
|
|
509
|
+
ini: "ini",
|
|
510
|
+
env: "",
|
|
511
|
+
txt: "txt",
|
|
512
|
+
text: "txt",
|
|
513
|
+
csv: "csv"
|
|
514
|
+
};
|
|
515
|
+
function extractFilesFromMarkdown(text) {
|
|
516
|
+
const files = [];
|
|
517
|
+
const fence = /^[ \t]*```[ \t]*([A-Za-z0-9_#+.-]*)(?::[ \t]*([^\n`]+))?[^\n]*\n([\s\S]*?)^[ \t]*```[ \t]*$/gm;
|
|
518
|
+
let m;
|
|
519
|
+
while ((m = fence.exec(text)) !== null) {
|
|
520
|
+
const lang = (m[1] || "").toLowerCase().trim();
|
|
521
|
+
const fname = (m[2] || "").trim().replace(/^["']|["']$/g, "");
|
|
522
|
+
if (!fname) continue;
|
|
523
|
+
files.push({ filename: fname, content: m[3], lang: lang || "text" });
|
|
524
|
+
}
|
|
525
|
+
return files;
|
|
526
|
+
}
|
|
527
|
+
function sanitizeFilename(name) {
|
|
528
|
+
const base = import_path2.default.basename(name.trim());
|
|
529
|
+
if (!base || base === "." || base === "..") return null;
|
|
530
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(base)) return null;
|
|
531
|
+
if (base.includes("..")) return null;
|
|
532
|
+
return base;
|
|
533
|
+
}
|
|
534
|
+
function defaultFilename(lang) {
|
|
535
|
+
if (lang === "dockerfile") return "Dockerfile";
|
|
536
|
+
if (lang === "makefile") return "Makefile";
|
|
537
|
+
const ext = EXT_BY_LANG[lang];
|
|
538
|
+
return ext === void 0 ? null : `snippet.${ext || "txt"}`;
|
|
539
|
+
}
|
|
540
|
+
function extractAllFiles(text) {
|
|
541
|
+
const extracted = extractFilesFromMarkdown(text);
|
|
542
|
+
if (extracted.length) return extracted;
|
|
543
|
+
const fence2 = /^[ \t]*```[ \t]*([A-Za-z0-9_#+.-]+)[ \t]*\n([\s\S]*?)^[ \t]*```[ \t]*$/gm;
|
|
544
|
+
let best = null;
|
|
545
|
+
let m;
|
|
546
|
+
while ((m = fence2.exec(text)) !== null) {
|
|
547
|
+
const lang = (m[1] || "").toLowerCase();
|
|
548
|
+
if (!EXT_BY_LANG.hasOwnProperty(lang) || lang === "text") continue;
|
|
549
|
+
const content = m[2];
|
|
550
|
+
if (content.trim().split("\n").length < 2) continue;
|
|
551
|
+
if (!best || content.length > best.content.length) {
|
|
552
|
+
best = { filename: defaultFilename(lang) || "snippet.txt", content, lang };
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
return best ? [best] : [];
|
|
556
|
+
}
|
|
557
|
+
function writeExtractedFiles(files) {
|
|
558
|
+
const notes = [];
|
|
559
|
+
for (const f of files) {
|
|
560
|
+
const safe = sanitizeFilename(f.filename);
|
|
561
|
+
if (!safe) {
|
|
562
|
+
notes.push(import_chalk.default.red(`\u2715 Skip nama file tidak valid: ${f.filename}`));
|
|
563
|
+
continue;
|
|
564
|
+
}
|
|
565
|
+
try {
|
|
566
|
+
const target = import_path2.default.join(process.cwd(), safe);
|
|
567
|
+
import_fs2.default.writeFileSync(target, f.content, "utf-8");
|
|
568
|
+
const bytes = Buffer.byteLength(f.content, "utf-8");
|
|
569
|
+
notes.push(import_chalk.default.green(`\u2714 File created: ${safe}`) + import_chalk.default.gray(` (${bytes} bytes)`));
|
|
570
|
+
} catch (err) {
|
|
571
|
+
notes.push(import_chalk.default.red(`\u2715 Gagal menulis ${safe}: ${err.message}`));
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
return notes;
|
|
575
|
+
}
|
|
576
|
+
var AGENT_SYSTEM_PROMPT = [
|
|
577
|
+
"Anda adalah asisten coding agent SarangAI yang berjalan di terminal pengguna.",
|
|
578
|
+
`Direktori kerja aktif: ${process.cwd()}.`,
|
|
579
|
+
"Jika permintaan pengguna melibatkan pembuatan atau perubahan file, SELALU output kode dalam fenced code block dengan format ````bahasa:nama-file` pada baris pembuka, contoh: ```python:binary_search.py",
|
|
580
|
+
"Gunakan SATU file per code block dan jangan menambah teks setelah baris penutup ``` milik block tersebut.",
|
|
581
|
+
"Untuk file konfigurasi tanpa ekstensi gunakan ```dockerfile:Dockerfile atau ```makefile:Makefile.",
|
|
582
|
+
"Jelaskan secara singkat sebelum/sesudah code block, tanpa membungkus nama file di backtick atau bold."
|
|
583
|
+
].join("\n");
|
|
584
|
+
var lastTurn = { query: "", text: "", streaming: false, notes: [] };
|
|
585
|
+
var activeAbort = null;
|
|
586
|
+
function buildTranscript(width) {
|
|
587
|
+
const out = [];
|
|
588
|
+
for (let i = 0; i + 1 < conversationHistory.length; i += 2) {
|
|
589
|
+
const q = conversationHistory[i]?.content ?? "";
|
|
590
|
+
const a = conversationHistory[i + 1]?.content ?? "";
|
|
591
|
+
out.push({ text: `\u203A Anda: ${q}`, style: "query" });
|
|
592
|
+
if (a && a !== "(error)") out.push(...wrapWithStyles(a, width));
|
|
593
|
+
out.push({ text: "", style: "plain" });
|
|
594
|
+
}
|
|
595
|
+
return out;
|
|
596
|
+
}
|
|
597
|
+
function drawSessionScreen(turn, opts = { inputMode: "idle" }) {
|
|
436
598
|
const boxWidth = 72;
|
|
437
599
|
const termRows = process.stdout.rows || 30;
|
|
438
600
|
const pad = getCenterPad(boxWidth);
|
|
@@ -441,22 +603,31 @@ function drawResponseScreen(query, text, opts) {
|
|
|
441
603
|
const lines = [];
|
|
442
604
|
SARANG_BANNER.forEach((l) => lines.push(pad + import_chalk.default.white.bold(l)));
|
|
443
605
|
lines.push("");
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
606
|
+
const mm = Math.floor(sessionTimeLeft / 60);
|
|
607
|
+
const ss = sessionTimeLeft % 60;
|
|
608
|
+
lines.push(pad + import_chalk.default.gray(`${conversationHistory.length} pesan \u2022 ${activeModel.name} \u2022 ${mm}m ${ss < 10 ? "0" : ""}${ss}s left`));
|
|
609
|
+
lines.push("");
|
|
610
|
+
const transcript = buildTranscript(contentW);
|
|
611
|
+
if (!turn.streaming) {
|
|
612
|
+
transcript.push({ text: `\u203A Anda: ${turn.query}`, style: "query" });
|
|
613
|
+
if (turn.error) {
|
|
614
|
+
transcript.push(...wrapWithStyles(turn.error, contentW).map((l) => ({ ...l, style: "error" })));
|
|
615
|
+
} else if (turn.text) {
|
|
616
|
+
transcript.push(...wrapWithStyles(turn.text, contentW));
|
|
617
|
+
}
|
|
618
|
+
for (const note of turn.notes) transcript.push({ text: note, style: "plain" });
|
|
452
619
|
}
|
|
453
|
-
respTotalLines =
|
|
620
|
+
respTotalLines = transcript.length;
|
|
621
|
+
const headerRows = lines.length;
|
|
622
|
+
const footerRows = opts.inputMode === "typing" ? 8 : 4;
|
|
623
|
+
const viewRows = Math.max(4, termRows - headerRows - footerRows);
|
|
454
624
|
const maxStart = Math.max(0, respTotalLines - viewRows);
|
|
455
|
-
const start = Math.min(respScroll ?? maxStart, maxStart);
|
|
456
|
-
const visible =
|
|
625
|
+
const start = turn.streaming ? maxStart : Math.min(respScroll ?? maxStart, maxStart);
|
|
626
|
+
const visible = transcript.slice(start, start + viewRows);
|
|
457
627
|
respViewRows = viewRows;
|
|
628
|
+
lines.push(pad + import_chalk.default.gray("\u250C" + "\u2500".repeat(innerW) + "\u2510"));
|
|
458
629
|
for (const l of visible) {
|
|
459
|
-
const styled = styleBodyLine(l);
|
|
630
|
+
const styled = l.style === "query" ? import_chalk.default.bold.hex("#c084fc")(l.text) : styleBodyLine(l);
|
|
460
631
|
const fill = Math.max(0, contentW - visibleLength(styled));
|
|
461
632
|
lines.push(pad + import_chalk.default.gray("\u2502 ") + styled + " ".repeat(fill) + import_chalk.default.gray(" \u2502"));
|
|
462
633
|
}
|
|
@@ -464,32 +635,46 @@ function drawResponseScreen(query, text, opts) {
|
|
|
464
635
|
lines.push(pad + import_chalk.default.gray("\u2502 ") + " ".repeat(contentW) + import_chalk.default.gray(" \u2502"));
|
|
465
636
|
}
|
|
466
637
|
lines.push(pad + import_chalk.default.gray("\u2514" + "\u2500".repeat(innerW) + "\u2518"));
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
638
|
+
if (opts.inputMode === "typing") {
|
|
639
|
+
lines.push("");
|
|
640
|
+
const scrollTag = start > 0 || start < maxStart ? import_chalk.default.gray(`[\u2191/\u2193/mouse scroll \u2022 ${start + 1}-${Math.min(respTotalLines, start + viewRows)}/${respTotalLines}] `) : "";
|
|
641
|
+
lines.push(pad + scrollTag + import_chalk.default.gray("(/new = sesi baru \u2022 exit = keluar \u2022 Esc = menu)"));
|
|
642
|
+
const textContent = currentTaskInput ? currentTaskInput : import_chalk.default.gray("Ketik follow-up\u2026");
|
|
643
|
+
const visibleLen = currentTaskInput ? currentTaskInput.length : 17;
|
|
644
|
+
const paddingRight = Math.max(0, innerW - 2 - visibleLen);
|
|
645
|
+
lines.push(pad + import_chalk.default.gray("\u250C" + "\u2500".repeat(innerW) + "\u2510"));
|
|
646
|
+
const inputRowNumber = lines.length + 1;
|
|
647
|
+
lines.push(pad + import_chalk.default.gray(`\u2502 ${textContent}${" ".repeat(paddingRight)}\u2502`));
|
|
648
|
+
lines.push(pad + import_chalk.default.gray("\u2514" + "\u2500".repeat(innerW) + "\u2518"));
|
|
649
|
+
process.stdout.write("\x1B[2J\x1B[3J\x1B[H" + lines.join("\n"));
|
|
650
|
+
const cursorCol = pad.length + 5 + currentTaskInput.length;
|
|
651
|
+
process.stdout.write(`\x1B[${inputRowNumber};${cursorCol}H\x1B[?25h`);
|
|
652
|
+
} else {
|
|
653
|
+
lines.push("");
|
|
654
|
+
const range = start > 0 || start < maxStart ? ` \u2022 ${start + 1}-${Math.min(respTotalLines, start + viewRows)}/${respTotalLines}` : ` \u2022 ${respTotalLines} baris`;
|
|
655
|
+
const status = turn.streaming ? import_chalk.default.gray(`[Streaming \u2022 ${activeModel.name}${range}] Esc = batalkan`) : turn.error ? import_chalk.default.red("\u2715 Error") + import_chalk.default.gray(`${range} \u2022 tekan tombol untuk lanjut`) : import_chalk.default.green("\u2714 Selesai") + import_chalk.default.gray(`${range} \u2022 ketik follow-up di bawah \u2022 Esc = menu`);
|
|
656
|
+
lines.push(pad + status);
|
|
657
|
+
process.stdout.write("\x1B[2J\x1B[3J\x1B[H" + lines.join("\n"));
|
|
658
|
+
}
|
|
473
659
|
}
|
|
474
|
-
function
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
});
|
|
660
|
+
function scrollSession(delta) {
|
|
661
|
+
const maxStart = Math.max(0, respTotalLines - respViewRows);
|
|
662
|
+
const current = respScroll ?? maxStart;
|
|
663
|
+
respScroll = Math.max(0, Math.min(maxStart, current + delta));
|
|
664
|
+
drawSessionScreen(lastTurn, { inputMode: "typing" });
|
|
665
|
+
}
|
|
666
|
+
function redrawInputScreen() {
|
|
667
|
+
if (sessionScreen) drawSessionScreen(lastTurn, { inputMode: "typing" });
|
|
668
|
+
else drawWorkspaceScreen(activeModel, sessionTimeLeft, currentTaskInput);
|
|
669
|
+
}
|
|
670
|
+
function finishTurnToInput() {
|
|
671
|
+
refreshUserMeta();
|
|
672
|
+
isExecuting = false;
|
|
673
|
+
sessionScreen = true;
|
|
674
|
+
currentTaskInput = keyBuffer;
|
|
675
|
+
keyBuffer = "";
|
|
676
|
+
respScroll = null;
|
|
677
|
+
drawSessionScreen(lastTurn, { inputMode: "typing" });
|
|
493
678
|
}
|
|
494
679
|
async function runPromptStream(query, isRetry = false) {
|
|
495
680
|
isExecuting = true;
|
|
@@ -498,15 +683,18 @@ async function runPromptStream(query, isRetry = false) {
|
|
|
498
683
|
respText = "";
|
|
499
684
|
respError = void 0;
|
|
500
685
|
respScroll = null;
|
|
686
|
+
activeAbort = new AbortController();
|
|
501
687
|
process.stdout.write("\x1B[?25l");
|
|
688
|
+
lastTurn = { query, text: "", streaming: true, notes: [] };
|
|
689
|
+
drawSessionScreen(lastTurn);
|
|
502
690
|
let lastPaint = 0;
|
|
503
691
|
const repaint = (streaming, force = false) => {
|
|
504
692
|
const now = Date.now();
|
|
505
693
|
if (!force && now - lastPaint < 80) return;
|
|
506
694
|
lastPaint = now;
|
|
507
|
-
|
|
695
|
+
lastTurn = { query, text: respText, streaming, error: respError, notes: lastTurn.notes };
|
|
696
|
+
drawSessionScreen(lastTurn);
|
|
508
697
|
};
|
|
509
|
-
repaint(true, true);
|
|
510
698
|
try {
|
|
511
699
|
const res = await fetch(`${baseUrl}/api/gateway/v1/chat/completions`, {
|
|
512
700
|
method: "POST",
|
|
@@ -514,9 +702,13 @@ async function runPromptStream(query, isRetry = false) {
|
|
|
514
702
|
"Content-Type": "application/json",
|
|
515
703
|
Authorization: `Bearer ${cfg.apiKey}`
|
|
516
704
|
},
|
|
705
|
+
signal: activeAbort.signal,
|
|
517
706
|
body: JSON.stringify({
|
|
518
707
|
model: activeModel.id,
|
|
519
|
-
messages: [
|
|
708
|
+
messages: [
|
|
709
|
+
{ role: "system", content: AGENT_SYSTEM_PROMPT },
|
|
710
|
+
...conversationHistory
|
|
711
|
+
],
|
|
520
712
|
stream: true
|
|
521
713
|
})
|
|
522
714
|
});
|
|
@@ -541,7 +733,7 @@ async function runPromptStream(query, isRetry = false) {
|
|
|
541
733
|
const newKey = await handleAutoAuth(baseUrl);
|
|
542
734
|
if (newKey) {
|
|
543
735
|
cfg = getConfig();
|
|
544
|
-
userMeta = await fetchUserMeta(baseUrl, cfg.apiKey || "");
|
|
736
|
+
userMeta = await fetchUserMeta(baseUrl, cfg.apiKey || "") ?? userMeta;
|
|
545
737
|
return runPromptStream(query, true);
|
|
546
738
|
}
|
|
547
739
|
respError = "Otorisasi ulang dibatalkan.";
|
|
@@ -576,11 +768,25 @@ async function runPromptStream(query, isRetry = false) {
|
|
|
576
768
|
}
|
|
577
769
|
}
|
|
578
770
|
} catch (err) {
|
|
579
|
-
|
|
580
|
-
|
|
771
|
+
if (err?.name !== "AbortError") {
|
|
772
|
+
respError = err.message;
|
|
773
|
+
repaint(false, true);
|
|
774
|
+
} else {
|
|
775
|
+
respText += "\n[ dibatalkan oleh pengguna ]";
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
let notes = [];
|
|
779
|
+
if (respText && !respError) {
|
|
780
|
+
notes = writeExtractedFiles(extractAllFiles(respText));
|
|
581
781
|
}
|
|
582
782
|
respStreaming = false;
|
|
583
|
-
|
|
783
|
+
lastTurn = { query, text: respText, streaming: false, error: respError, notes };
|
|
784
|
+
if (respError && !isRetry) {
|
|
785
|
+
conversationHistory.push({ role: "assistant", content: respText || "(error)" });
|
|
786
|
+
} else if (respText) {
|
|
787
|
+
conversationHistory.push({ role: "assistant", content: respText });
|
|
788
|
+
}
|
|
789
|
+
finishTurnToInput();
|
|
584
790
|
}
|
|
585
791
|
async function handleAutoAuth(baseUrl2) {
|
|
586
792
|
const sessionCode = "SA-" + import_crypto.default.randomBytes(4).toString("hex").toUpperCase();
|
|
@@ -630,7 +836,7 @@ async function startInteractiveSession() {
|
|
|
630
836
|
}
|
|
631
837
|
userMeta = await fetchUserMeta(baseUrl, cfg.apiKey || "");
|
|
632
838
|
activeModel = CODING_MODELS.find((m) => m.id === cfg.defaultModel) || CODING_MODELS[0];
|
|
633
|
-
process.stdout.write("\x1B[?1049h\x1B[2J\x1B[3J\x1B[H");
|
|
839
|
+
process.stdout.write("\x1B[?1049h\x1B[2J\x1B[3J\x1B[H\x1B[?1000h\x1B[?1006h");
|
|
634
840
|
import_readline.default.emitKeypressEvents(process.stdin);
|
|
635
841
|
if (process.stdin.isTTY) {
|
|
636
842
|
process.stdin.setRawMode(true);
|
|
@@ -639,14 +845,34 @@ async function startInteractiveSession() {
|
|
|
639
845
|
process.stdout.on("resize", () => {
|
|
640
846
|
if (inWorkspace) {
|
|
641
847
|
if (isExecuting) {
|
|
642
|
-
|
|
848
|
+
drawSessionScreen(lastTurn);
|
|
643
849
|
} else {
|
|
644
|
-
|
|
850
|
+
redrawInputScreen();
|
|
645
851
|
}
|
|
646
852
|
} else {
|
|
647
853
|
drawSelectionModal(activeModel, userMeta, expanded, cursorIdx, copiedNotice);
|
|
648
854
|
}
|
|
649
855
|
});
|
|
856
|
+
process.stdin.on("data", (buf) => {
|
|
857
|
+
const s = buf.toString("utf8");
|
|
858
|
+
if (!inWorkspace) return;
|
|
859
|
+
let delta = 0;
|
|
860
|
+
const m = s.match(/^\x1b\[(?:M([\s\S]{3})|<(?:0|64|65);(\d+);(\d+)[Mm])/);
|
|
861
|
+
if (m) {
|
|
862
|
+
if (m[1]) {
|
|
863
|
+
const b = m[1].charCodeAt(0) - 32;
|
|
864
|
+
delta = b === 64 ? -3 : b === 65 ? 3 : 0;
|
|
865
|
+
} else {
|
|
866
|
+
delta = s.includes("<64") ? -3 : s.includes("<65") ? 3 : 0;
|
|
867
|
+
}
|
|
868
|
+
} else if (s === "\x1B[A") delta = -3;
|
|
869
|
+
else if (s === "\x1B[B") delta = 3;
|
|
870
|
+
if (delta !== 0 && !isExecuting && sessionScreen) {
|
|
871
|
+
scrollSession(delta);
|
|
872
|
+
return;
|
|
873
|
+
}
|
|
874
|
+
if (delta !== 0) return;
|
|
875
|
+
});
|
|
650
876
|
process.stdin.on("keypress", (str, key) => {
|
|
651
877
|
if (key.ctrl && key.name === "c") {
|
|
652
878
|
cleanupAndExit();
|
|
@@ -654,7 +880,55 @@ async function startInteractiveSession() {
|
|
|
654
880
|
}
|
|
655
881
|
if (inWorkspace) {
|
|
656
882
|
if (isExecuting) {
|
|
657
|
-
if (key.name === "escape")
|
|
883
|
+
if (key.name === "escape" && activeAbort) {
|
|
884
|
+
activeAbort.abort();
|
|
885
|
+
return;
|
|
886
|
+
}
|
|
887
|
+
if (str && !key.ctrl && !key.meta && !str.startsWith("\x1B")) {
|
|
888
|
+
keyBuffer += str;
|
|
889
|
+
}
|
|
890
|
+
return;
|
|
891
|
+
}
|
|
892
|
+
if (sessionScreen) {
|
|
893
|
+
if (key.name === "escape") {
|
|
894
|
+
leaveWorkspace();
|
|
895
|
+
return;
|
|
896
|
+
}
|
|
897
|
+
if (key.name === "up" || key.name === "down") {
|
|
898
|
+
scrollSession(key.name === "up" ? -3 : 3);
|
|
899
|
+
return;
|
|
900
|
+
}
|
|
901
|
+
if (key.name === "return") {
|
|
902
|
+
const query = currentTaskInput.trim();
|
|
903
|
+
if (!query) return;
|
|
904
|
+
if (query === "/exit" || query === "exit" || query === "quit" || query === ":q") {
|
|
905
|
+
leaveWorkspace();
|
|
906
|
+
return;
|
|
907
|
+
}
|
|
908
|
+
if (query === "/model" || query === "/back") {
|
|
909
|
+
leaveWorkspace();
|
|
910
|
+
return;
|
|
911
|
+
}
|
|
912
|
+
if (query === "/new" || query === "/reset") {
|
|
913
|
+
conversationHistory = [];
|
|
914
|
+
respLastNotes = [];
|
|
915
|
+
enterWorkspace();
|
|
916
|
+
return;
|
|
917
|
+
}
|
|
918
|
+
conversationHistory.push({ role: "user", content: query });
|
|
919
|
+
runPromptStream(query);
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
922
|
+
if (key.name === "backspace") {
|
|
923
|
+
currentTaskInput = currentTaskInput.slice(0, -1);
|
|
924
|
+
redrawInputScreen();
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
927
|
+
if (str && !key.ctrl && !key.meta && !str.startsWith("\x1B")) {
|
|
928
|
+
currentTaskInput += str;
|
|
929
|
+
redrawInputScreen();
|
|
930
|
+
return;
|
|
931
|
+
}
|
|
658
932
|
return;
|
|
659
933
|
}
|
|
660
934
|
if (key.name === "escape") {
|
|
@@ -664,14 +938,21 @@ async function startInteractiveSession() {
|
|
|
664
938
|
if (key.name === "return") {
|
|
665
939
|
const query = currentTaskInput.trim();
|
|
666
940
|
if (!query) return;
|
|
667
|
-
if (query === "/exit" || query === "exit" || query === ":q") {
|
|
668
|
-
|
|
941
|
+
if (query === "/exit" || query === "exit" || query === "quit" || query === ":q") {
|
|
942
|
+
leaveWorkspace();
|
|
669
943
|
return;
|
|
670
944
|
}
|
|
671
945
|
if (query === "/model" || query === "/back") {
|
|
672
946
|
leaveWorkspace();
|
|
673
947
|
return;
|
|
674
948
|
}
|
|
949
|
+
if (query === "/new" || query === "/reset") {
|
|
950
|
+
conversationHistory = [];
|
|
951
|
+
respLastNotes = [];
|
|
952
|
+
enterWorkspace();
|
|
953
|
+
return;
|
|
954
|
+
}
|
|
955
|
+
conversationHistory.push({ role: "user", content: query });
|
|
675
956
|
runPromptStream(query);
|
|
676
957
|
return;
|
|
677
958
|
}
|