sarangai-cli 1.0.11 → 1.0.12
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 +154 -26
- 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.12",
|
|
39
39
|
description: "CLI resmi SarangAI \u2014 Gateway multi-model AI langsung dari terminal Anda",
|
|
40
40
|
main: "dist/index.js",
|
|
41
41
|
bin: {
|
|
@@ -124,6 +124,14 @@ function saveConfig(newConfig) {
|
|
|
124
124
|
|
|
125
125
|
// src/index.ts
|
|
126
126
|
var program = new import_commander.Command();
|
|
127
|
+
var SARANG_BANNER = [
|
|
128
|
+
"\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557",
|
|
129
|
+
"\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551",
|
|
130
|
+
"\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551",
|
|
131
|
+
"\u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551",
|
|
132
|
+
"\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551",
|
|
133
|
+
"\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D"
|
|
134
|
+
];
|
|
127
135
|
var borderActive = import_chalk.default.hex("#a855f7");
|
|
128
136
|
var borderMuted = import_chalk.default.hex("#334155");
|
|
129
137
|
var violetText = import_chalk.default.hex("#c084fc");
|
|
@@ -345,6 +353,13 @@ var timerInterval = null;
|
|
|
345
353
|
var currentTaskInput = "";
|
|
346
354
|
var cfg;
|
|
347
355
|
var baseUrl;
|
|
356
|
+
var respQuery = "";
|
|
357
|
+
var respText = "";
|
|
358
|
+
var respError;
|
|
359
|
+
var respStreaming = false;
|
|
360
|
+
var respScroll = null;
|
|
361
|
+
var respTotalLines = 0;
|
|
362
|
+
var respViewRows = 12;
|
|
348
363
|
function cleanupAndExit() {
|
|
349
364
|
if (timerInterval) clearInterval(timerInterval);
|
|
350
365
|
process.stdout.write("\x1B[?1049l\x1B[?25h\n");
|
|
@@ -378,13 +393,120 @@ function enterWorkspace() {
|
|
|
378
393
|
}
|
|
379
394
|
}, 1e3);
|
|
380
395
|
}
|
|
396
|
+
function visibleLength(s) {
|
|
397
|
+
return s.replace(/\x1b\[[0-9;]*m/g, "").length;
|
|
398
|
+
}
|
|
399
|
+
function wrapWithStyles(input, width) {
|
|
400
|
+
const out = [];
|
|
401
|
+
let inCode = false;
|
|
402
|
+
const text = input.replace(/\r\n?/g, "\n");
|
|
403
|
+
for (const raw of text.split("\n")) {
|
|
404
|
+
const trimmed = raw.trimStart();
|
|
405
|
+
let style = "plain";
|
|
406
|
+
if (trimmed.startsWith("```")) {
|
|
407
|
+
inCode = !inCode;
|
|
408
|
+
style = "code";
|
|
409
|
+
} else if (inCode) {
|
|
410
|
+
style = "code";
|
|
411
|
+
} else if (trimmed.startsWith("#")) {
|
|
412
|
+
style = "heading";
|
|
413
|
+
}
|
|
414
|
+
if (raw === "") {
|
|
415
|
+
out.push({ text: "", style });
|
|
416
|
+
continue;
|
|
417
|
+
}
|
|
418
|
+
let remaining = raw;
|
|
419
|
+
while (remaining.length > width) {
|
|
420
|
+
let cut = remaining.lastIndexOf(" ", width);
|
|
421
|
+
if (cut < Math.floor(width * 0.5)) cut = width;
|
|
422
|
+
out.push({ text: remaining.slice(0, cut), style });
|
|
423
|
+
remaining = remaining.slice(cut).replace(/^ /, "");
|
|
424
|
+
}
|
|
425
|
+
out.push({ text: remaining, style });
|
|
426
|
+
}
|
|
427
|
+
return out;
|
|
428
|
+
}
|
|
429
|
+
function styleBodyLine(l) {
|
|
430
|
+
if (l.style === "heading") return import_chalk.default.bold.hex("#c084fc")(l.text);
|
|
431
|
+
if (l.style === "code") return import_chalk.default.hex("#94a3b8")(l.text);
|
|
432
|
+
if (l.style === "error") return import_chalk.default.red(l.text);
|
|
433
|
+
return import_chalk.default.hex("#e2e8f0")(l.text);
|
|
434
|
+
}
|
|
435
|
+
function drawResponseScreen(query, text, opts) {
|
|
436
|
+
const boxWidth = 72;
|
|
437
|
+
const termRows = process.stdout.rows || 30;
|
|
438
|
+
const pad = getCenterPad(boxWidth);
|
|
439
|
+
const innerW = boxWidth - 2;
|
|
440
|
+
const contentW = innerW - 4;
|
|
441
|
+
const lines = [];
|
|
442
|
+
SARANG_BANNER.forEach((l) => lines.push(pad + import_chalk.default.white.bold(l)));
|
|
443
|
+
lines.push("");
|
|
444
|
+
lines.push(pad + import_chalk.default.bold.hex("#c084fc")(`\u203A Task: ${query}`));
|
|
445
|
+
lines.push(pad + import_chalk.default.gray("\u250C" + "\u2500".repeat(innerW) + "\u2510"));
|
|
446
|
+
const viewRows = Math.max(6, termRows - 13);
|
|
447
|
+
let body;
|
|
448
|
+
if (opts.error) {
|
|
449
|
+
body = wrapWithStyles(opts.error, contentW).map((l) => ({ ...l, style: "error" }));
|
|
450
|
+
} else {
|
|
451
|
+
body = wrapWithStyles(text, contentW);
|
|
452
|
+
}
|
|
453
|
+
respTotalLines = body.length;
|
|
454
|
+
const maxStart = Math.max(0, respTotalLines - viewRows);
|
|
455
|
+
const start = Math.min(respScroll ?? maxStart, maxStart);
|
|
456
|
+
const visible = body.slice(start, start + viewRows);
|
|
457
|
+
respViewRows = viewRows;
|
|
458
|
+
for (const l of visible) {
|
|
459
|
+
const styled = styleBodyLine(l);
|
|
460
|
+
const fill = Math.max(0, contentW - visibleLength(styled));
|
|
461
|
+
lines.push(pad + import_chalk.default.gray("\u2502 ") + styled + " ".repeat(fill) + import_chalk.default.gray(" \u2502"));
|
|
462
|
+
}
|
|
463
|
+
for (let i = visible.length; i < viewRows; i++) {
|
|
464
|
+
lines.push(pad + import_chalk.default.gray("\u2502 ") + " ".repeat(contentW) + import_chalk.default.gray(" \u2502"));
|
|
465
|
+
}
|
|
466
|
+
lines.push(pad + import_chalk.default.gray("\u2514" + "\u2500".repeat(innerW) + "\u2518"));
|
|
467
|
+
lines.push("");
|
|
468
|
+
const range = respTotalLines > viewRows ? ` \u2022 baris ${start + 1}-${Math.min(respTotalLines, start + viewRows)}/${respTotalLines}` : ` \u2022 ${respTotalLines} baris`;
|
|
469
|
+
const scrollHint = start > 0 || start < maxStart ? import_chalk.default.gray("[\u2191/\u2193 Scroll] ") : "";
|
|
470
|
+
const status = opts.streaming ? import_chalk.default.gray(`[Streaming \u2022 ${activeModel.name}]${range}`) : opts.error ? scrollHint + import_chalk.default.red("\u2715 Selesai dengan error \u2022 tekan sembarang tombol untuk kembali") : scrollHint + import_chalk.default.green("\u2714 Selesai") + import_chalk.default.gray(`${range} \u2022 tekan sembarang tombol untuk kembali`);
|
|
471
|
+
lines.push(pad + status);
|
|
472
|
+
process.stdout.write("\x1B[2J\x1B[3J\x1B[H" + lines.join("\n"));
|
|
473
|
+
}
|
|
474
|
+
function waitResponseDismiss() {
|
|
475
|
+
process.stdin.once("keypress", (str, key) => {
|
|
476
|
+
if (key?.ctrl && key?.name === "c") {
|
|
477
|
+
cleanupAndExit();
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
if (key?.name === "up" || key?.name === "down") {
|
|
481
|
+
const maxStart = Math.max(0, respTotalLines - respViewRows);
|
|
482
|
+
const current = respScroll ?? maxStart;
|
|
483
|
+
respScroll = key.name === "up" ? Math.max(0, current - 3) : Math.min(maxStart, current + 3);
|
|
484
|
+
drawResponseScreen(respQuery, respText, { streaming: false, error: respError });
|
|
485
|
+
waitResponseDismiss();
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
isExecuting = false;
|
|
489
|
+
currentTaskInput = "";
|
|
490
|
+
respScroll = null;
|
|
491
|
+
drawWorkspaceScreen(activeModel, sessionTimeLeft, currentTaskInput);
|
|
492
|
+
});
|
|
493
|
+
}
|
|
381
494
|
async function runPromptStream(query, isRetry = false) {
|
|
382
495
|
isExecuting = true;
|
|
496
|
+
respStreaming = true;
|
|
497
|
+
respQuery = query;
|
|
498
|
+
respText = "";
|
|
499
|
+
respError = void 0;
|
|
500
|
+
respScroll = null;
|
|
383
501
|
process.stdout.write("\x1B[?25l");
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
502
|
+
let lastPaint = 0;
|
|
503
|
+
const repaint = (streaming, force = false) => {
|
|
504
|
+
const now = Date.now();
|
|
505
|
+
if (!force && now - lastPaint < 80) return;
|
|
506
|
+
lastPaint = now;
|
|
507
|
+
drawResponseScreen(respQuery, respText, { streaming, error: respError });
|
|
508
|
+
};
|
|
509
|
+
repaint(true, true);
|
|
388
510
|
try {
|
|
389
511
|
const res = await fetch(`${baseUrl}/api/gateway/v1/chat/completions`, {
|
|
390
512
|
method: "POST",
|
|
@@ -406,11 +528,11 @@ async function runPromptStream(query, isRetry = false) {
|
|
|
406
528
|
if (msg) detail = typeof msg === "string" ? msg : JSON.stringify(msg);
|
|
407
529
|
} catch {
|
|
408
530
|
}
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
`));
|
|
531
|
+
respError = `(${res.status}) ${detail}`;
|
|
532
|
+
repaint(false, true);
|
|
412
533
|
if (res.status === 401 && !isRetry) {
|
|
413
|
-
|
|
534
|
+
respError = "API key tidak valid atau sudah di-revoke. Memulai ulang otorisasi...";
|
|
535
|
+
repaint(false, true);
|
|
414
536
|
try {
|
|
415
537
|
saveConfig({ apiKey: void 0 });
|
|
416
538
|
} catch {
|
|
@@ -422,42 +544,43 @@ Error (${res.status}): ${detail}
|
|
|
422
544
|
userMeta = await fetchUserMeta(baseUrl, cfg.apiKey || "");
|
|
423
545
|
return runPromptStream(query, true);
|
|
424
546
|
}
|
|
425
|
-
|
|
547
|
+
respError = "Otorisasi ulang dibatalkan.";
|
|
548
|
+
repaint(false, true);
|
|
426
549
|
}
|
|
427
550
|
} else {
|
|
428
551
|
const reader = res.body?.getReader();
|
|
429
552
|
const decoder = new TextDecoder();
|
|
430
553
|
if (reader) {
|
|
554
|
+
let sseBuffer = "";
|
|
431
555
|
while (true) {
|
|
432
556
|
const { done, value } = await reader.read();
|
|
433
557
|
if (done) break;
|
|
434
|
-
|
|
435
|
-
const
|
|
436
|
-
|
|
558
|
+
sseBuffer += decoder.decode(value, { stream: true });
|
|
559
|
+
const nlIdx = sseBuffer.lastIndexOf("\n");
|
|
560
|
+
if (nlIdx === -1) continue;
|
|
561
|
+
const complete = sseBuffer.slice(0, nlIdx);
|
|
562
|
+
sseBuffer = sseBuffer.slice(nlIdx + 1);
|
|
563
|
+
for (const l of complete.split("\n")) {
|
|
437
564
|
if (l.startsWith("data: ") && l !== "data: [DONE]") {
|
|
438
565
|
try {
|
|
439
566
|
const p = JSON.parse(l.slice(6));
|
|
440
567
|
const delta = p.choices?.[0]?.delta?.content;
|
|
441
|
-
if (delta)
|
|
568
|
+
if (delta) respText += delta;
|
|
442
569
|
} catch {
|
|
443
570
|
}
|
|
444
571
|
}
|
|
445
572
|
}
|
|
573
|
+
repaint(true);
|
|
446
574
|
}
|
|
447
|
-
|
|
575
|
+
repaint(false, true);
|
|
448
576
|
}
|
|
449
577
|
}
|
|
450
578
|
} catch (err) {
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
`));
|
|
579
|
+
respError = err.message;
|
|
580
|
+
repaint(false, true);
|
|
454
581
|
}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
isExecuting = false;
|
|
458
|
-
currentTaskInput = "";
|
|
459
|
-
drawWorkspaceScreen(activeModel, sessionTimeLeft, currentTaskInput);
|
|
460
|
-
});
|
|
582
|
+
respStreaming = false;
|
|
583
|
+
waitResponseDismiss();
|
|
461
584
|
}
|
|
462
585
|
async function handleAutoAuth(baseUrl2) {
|
|
463
586
|
const sessionCode = "SA-" + import_crypto.default.randomBytes(4).toString("hex").toUpperCase();
|
|
@@ -515,7 +638,9 @@ async function startInteractiveSession() {
|
|
|
515
638
|
drawSelectionModal(activeModel, userMeta, expanded, cursorIdx, copiedNotice);
|
|
516
639
|
process.stdout.on("resize", () => {
|
|
517
640
|
if (inWorkspace) {
|
|
518
|
-
if (
|
|
641
|
+
if (isExecuting) {
|
|
642
|
+
drawResponseScreen(respQuery, respText, { streaming: respStreaming, error: respError });
|
|
643
|
+
} else {
|
|
519
644
|
drawWorkspaceScreen(activeModel, sessionTimeLeft, currentTaskInput);
|
|
520
645
|
}
|
|
521
646
|
} else {
|
|
@@ -528,7 +653,10 @@ async function startInteractiveSession() {
|
|
|
528
653
|
return;
|
|
529
654
|
}
|
|
530
655
|
if (inWorkspace) {
|
|
531
|
-
if (isExecuting)
|
|
656
|
+
if (isExecuting) {
|
|
657
|
+
if (key.name === "escape") cleanupAndExit();
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
532
660
|
if (key.name === "escape") {
|
|
533
661
|
leaveWorkspace();
|
|
534
662
|
return;
|