sarangai-cli 1.0.13 → 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.
Files changed (2) hide show
  1. package/dist/index.js +109 -41
  2. 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.13",
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: {
@@ -206,7 +206,25 @@ async function fetchUserMeta(baseUrl2, apiKey) {
206
206
  }
207
207
  } catch {
208
208
  }
209
- return { balance: 0, tier: "DEVELOPER", accountId: "SA-\u2014" };
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();
210
228
  }
211
229
  function getDisplayDir() {
212
230
  const cwd = process.cwd();
@@ -259,7 +277,7 @@ function drawSelectionModal(activeModel2, userMeta2, expanded2, cursorIdx2, copi
259
277
  if (!expanded2) {
260
278
  lines.push(...renderCard(activeModel2, cursorIdx2 === 0, cardWidth, pad));
261
279
  lines.push("");
262
- const balanceStr = `${Number(userMeta2.balance).toLocaleString()} Credits`;
280
+ const balanceStr = `${Number(userMeta2?.balance ?? 0).toLocaleString()} Credits`;
263
281
  lines.push(
264
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)
265
283
  );
@@ -283,7 +301,7 @@ function drawSelectionModal(activeModel2, userMeta2, expanded2, cursorIdx2, copi
283
301
  lines.push(...renderCard(m, cursorIdx2 === idx + 10, cardWidth, pad));
284
302
  });
285
303
  lines.push("");
286
- const balanceStr = `${Number(userMeta2.balance).toLocaleString()} Credits`;
304
+ const balanceStr = `${Number(userMeta2?.balance ?? 0).toLocaleString()} Credits`;
287
305
  lines.push(
288
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)
289
307
  );
@@ -349,7 +367,7 @@ var expanded = false;
349
367
  var cursorIdx = 0;
350
368
  var copiedNotice = false;
351
369
  var activeModel;
352
- var userMeta;
370
+ var userMeta = null;
353
371
  var sessionTimeLeft = 3600;
354
372
  var timerInterval = null;
355
373
  var currentTaskInput = "";
@@ -361,12 +379,14 @@ var respError;
361
379
  var respStreaming = false;
362
380
  var respScroll = null;
363
381
  var respTotalLines = 0;
382
+ var respViewRows = 12;
364
383
  var conversationHistory = [];
365
384
  var sessionScreen = false;
366
385
  var respLastNotes = [];
386
+ var keyBuffer = "";
367
387
  function cleanupAndExit() {
368
388
  if (timerInterval) clearInterval(timerInterval);
369
- process.stdout.write("\x1B[?1049l\x1B[?25h\n");
389
+ process.stdout.write("\x1B[?1000l\x1B[?1006l\x1B[?1049l\x1B[?25h\n");
370
390
  process.exit(0);
371
391
  }
372
392
  function triggerCopyLink() {
@@ -380,21 +400,23 @@ function leaveWorkspace() {
380
400
  sessionScreen = false;
381
401
  if (timerInterval) clearInterval(timerInterval);
382
402
  currentTaskInput = "";
403
+ respScroll = null;
383
404
  conversationHistory = [];
384
405
  process.stdout.write("\x1B[2J\x1B[3J\x1B[H");
385
- drawSelectionModal(activeModel, userMeta, expanded, cursorIdx, copiedNotice);
406
+ redrawWithFreshBalance();
386
407
  }
387
408
  function enterWorkspace() {
388
409
  inWorkspace = true;
389
410
  isExecuting = false;
390
411
  currentTaskInput = "";
391
- drawWorkspaceScreen(activeModel, sessionTimeLeft, currentTaskInput);
412
+ sessionScreen = true;
413
+ redrawInputScreen();
392
414
  if (timerInterval) clearInterval(timerInterval);
393
415
  timerInterval = setInterval(() => {
394
416
  if (sessionTimeLeft > 0) {
395
417
  sessionTimeLeft--;
396
418
  if (inWorkspace && !isExecuting) {
397
- drawWorkspaceScreen(activeModel, sessionTimeLeft, currentTaskInput);
419
+ redrawInputScreen();
398
420
  }
399
421
  }
400
422
  }, 1e3);
@@ -561,6 +583,17 @@ var AGENT_SYSTEM_PROMPT = [
561
583
  ].join("\n");
562
584
  var lastTurn = { query: "", text: "", streaming: false, notes: [] };
563
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
+ }
564
597
  function drawSessionScreen(turn, opts = { inputMode: "idle" }) {
565
598
  const boxWidth = 72;
566
599
  const termRows = process.stdout.rows || 30;
@@ -574,17 +607,27 @@ function drawSessionScreen(turn, opts = { inputMode: "idle" }) {
574
607
  const ss = sessionTimeLeft % 60;
575
608
  lines.push(pad + import_chalk.default.gray(`${conversationHistory.length} pesan \u2022 ${activeModel.name} \u2022 ${mm}m ${ss < 10 ? "0" : ""}${ss}s left`));
576
609
  lines.push("");
577
- const qLabel = turn.query.length > 64 ? turn.query.slice(0, 61) + "\u2026" : turn.query;
578
- lines.push(pad + import_chalk.default.bold.hex("#c084fc")(`\u203A Task: ${qLabel}`));
579
- const headerRows = lines.length + 1;
580
- const footerRows = opts.inputMode === "typing" ? 7 : 3;
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" });
619
+ }
620
+ respTotalLines = transcript.length;
621
+ const headerRows = lines.length;
622
+ const footerRows = opts.inputMode === "typing" ? 8 : 4;
581
623
  const viewRows = Math.max(4, termRows - headerRows - footerRows);
582
- const body = turn.error ? wrapWithStyles(turn.error, contentW).map((l) => ({ ...l, style: "error" })) : wrapWithStyles(turn.text, contentW);
583
- respTotalLines = body.length;
584
- const visible = turn.streaming ? body.slice(0, viewRows) : body.slice(Math.max(0, body.length - viewRows));
624
+ const maxStart = Math.max(0, respTotalLines - viewRows);
625
+ const start = turn.streaming ? maxStart : Math.min(respScroll ?? maxStart, maxStart);
626
+ const visible = transcript.slice(start, start + viewRows);
627
+ respViewRows = viewRows;
585
628
  lines.push(pad + import_chalk.default.gray("\u250C" + "\u2500".repeat(innerW) + "\u2510"));
586
629
  for (const l of visible) {
587
- const styled = styleBodyLine(l);
630
+ const styled = l.style === "query" ? import_chalk.default.bold.hex("#c084fc")(l.text) : styleBodyLine(l);
588
631
  const fill = Math.max(0, contentW - visibleLength(styled));
589
632
  lines.push(pad + import_chalk.default.gray("\u2502 ") + styled + " ".repeat(fill) + import_chalk.default.gray(" \u2502"));
590
633
  }
@@ -592,11 +635,12 @@ function drawSessionScreen(turn, opts = { inputMode: "idle" }) {
592
635
  lines.push(pad + import_chalk.default.gray("\u2502 ") + " ".repeat(contentW) + import_chalk.default.gray(" \u2502"));
593
636
  }
594
637
  lines.push(pad + import_chalk.default.gray("\u2514" + "\u2500".repeat(innerW) + "\u2518"));
595
- for (const note of turn.notes) lines.push(pad + " " + note);
596
638
  if (opts.inputMode === "typing") {
597
639
  lines.push("");
598
- const textContent = currentTaskInput ? currentTaskInput : import_chalk.default.gray("Ketik follow-up\u2026 (/new = sesi baru, exit = keluar)");
599
- const visibleLen = currentTaskInput ? currentTaskInput.length : 55;
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;
600
644
  const paddingRight = Math.max(0, innerW - 2 - visibleLen);
601
645
  lines.push(pad + import_chalk.default.gray("\u250C" + "\u2500".repeat(innerW) + "\u2510"));
602
646
  const inputRowNumber = lines.length + 1;
@@ -607,33 +651,30 @@ function drawSessionScreen(turn, opts = { inputMode: "idle" }) {
607
651
  process.stdout.write(`\x1B[${inputRowNumber};${cursorCol}H\x1B[?25h`);
608
652
  } else {
609
653
  lines.push("");
610
- const status = turn.streaming ? import_chalk.default.gray(`[Streaming \u2022 ${activeModel.name} \u2022 ${respTotalLines} baris] Esc = batalkan`) : turn.error ? import_chalk.default.red("\u2715 Error") + import_chalk.default.gray(" \u2022 tekan tombol untuk lanjut") : import_chalk.default.green("\u2714 Selesai") + import_chalk.default.gray(` \u2022 ${respTotalLines} baris \u2022 ketik follow-up di bawah \u2022 Esc = menu`);
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`);
611
656
  lines.push(pad + status);
612
657
  process.stdout.write("\x1B[2J\x1B[3J\x1B[H" + lines.join("\n"));
613
658
  }
614
659
  }
615
- function enterSessionInput() {
616
- currentTaskInput = "";
617
- sessionScreen = true;
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));
618
664
  drawSessionScreen(lastTurn, { inputMode: "typing" });
619
665
  }
620
666
  function redrawInputScreen() {
621
667
  if (sessionScreen) drawSessionScreen(lastTurn, { inputMode: "typing" });
622
668
  else drawWorkspaceScreen(activeModel, sessionTimeLeft, currentTaskInput);
623
669
  }
624
- function waitSessionKeypress() {
625
- process.stdin.once("keypress", (str, key) => {
626
- if (key?.ctrl && key?.name === "c") {
627
- cleanupAndExit();
628
- return;
629
- }
630
- isExecuting = false;
631
- if (key?.name === "escape") {
632
- leaveWorkspace();
633
- return;
634
- }
635
- enterSessionInput();
636
- });
670
+ function finishTurnToInput() {
671
+ refreshUserMeta();
672
+ isExecuting = false;
673
+ sessionScreen = true;
674
+ currentTaskInput = keyBuffer;
675
+ keyBuffer = "";
676
+ respScroll = null;
677
+ drawSessionScreen(lastTurn, { inputMode: "typing" });
637
678
  }
638
679
  async function runPromptStream(query, isRetry = false) {
639
680
  isExecuting = true;
@@ -692,7 +733,7 @@ async function runPromptStream(query, isRetry = false) {
692
733
  const newKey = await handleAutoAuth(baseUrl);
693
734
  if (newKey) {
694
735
  cfg = getConfig();
695
- userMeta = await fetchUserMeta(baseUrl, cfg.apiKey || "");
736
+ userMeta = await fetchUserMeta(baseUrl, cfg.apiKey || "") ?? userMeta;
696
737
  return runPromptStream(query, true);
697
738
  }
698
739
  respError = "Otorisasi ulang dibatalkan.";
@@ -740,13 +781,12 @@ async function runPromptStream(query, isRetry = false) {
740
781
  }
741
782
  respStreaming = false;
742
783
  lastTurn = { query, text: respText, streaming: false, error: respError, notes };
743
- drawSessionScreen(lastTurn);
744
784
  if (respError && !isRetry) {
745
785
  conversationHistory.push({ role: "assistant", content: respText || "(error)" });
746
786
  } else if (respText) {
747
787
  conversationHistory.push({ role: "assistant", content: respText });
748
788
  }
749
- waitSessionKeypress();
789
+ finishTurnToInput();
750
790
  }
751
791
  async function handleAutoAuth(baseUrl2) {
752
792
  const sessionCode = "SA-" + import_crypto.default.randomBytes(4).toString("hex").toUpperCase();
@@ -796,7 +836,7 @@ async function startInteractiveSession() {
796
836
  }
797
837
  userMeta = await fetchUserMeta(baseUrl, cfg.apiKey || "");
798
838
  activeModel = CODING_MODELS.find((m) => m.id === cfg.defaultModel) || CODING_MODELS[0];
799
- 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");
800
840
  import_readline.default.emitKeypressEvents(process.stdin);
801
841
  if (process.stdin.isTTY) {
802
842
  process.stdin.setRawMode(true);
@@ -813,6 +853,26 @@ async function startInteractiveSession() {
813
853
  drawSelectionModal(activeModel, userMeta, expanded, cursorIdx, copiedNotice);
814
854
  }
815
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
+ });
816
876
  process.stdin.on("keypress", (str, key) => {
817
877
  if (key.ctrl && key.name === "c") {
818
878
  cleanupAndExit();
@@ -822,6 +882,10 @@ async function startInteractiveSession() {
822
882
  if (isExecuting) {
823
883
  if (key.name === "escape" && activeAbort) {
824
884
  activeAbort.abort();
885
+ return;
886
+ }
887
+ if (str && !key.ctrl && !key.meta && !str.startsWith("\x1B")) {
888
+ keyBuffer += str;
825
889
  }
826
890
  return;
827
891
  }
@@ -830,6 +894,10 @@ async function startInteractiveSession() {
830
894
  leaveWorkspace();
831
895
  return;
832
896
  }
897
+ if (key.name === "up" || key.name === "down") {
898
+ scrollSession(key.name === "up" ? -3 : 3);
899
+ return;
900
+ }
833
901
  if (key.name === "return") {
834
902
  const query = currentTaskInput.trim();
835
903
  if (!query) return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sarangai-cli",
3
- "version": "1.0.13",
3
+ "version": "1.0.14",
4
4
  "description": "CLI resmi SarangAI — Gateway multi-model AI langsung dari terminal Anda",
5
5
  "main": "dist/index.js",
6
6
  "bin": {