sarangai-cli 1.0.7 → 1.0.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/index.js ADDED
@@ -0,0 +1,610 @@
1
+ #!/usr/bin/env node
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __commonJS = (cb, mod) => function __require() {
9
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
10
+ };
11
+ var __export = (target, all) => {
12
+ for (var name in all)
13
+ __defProp(target, name, { get: all[name], enumerable: true });
14
+ };
15
+ var __copyProps = (to, from, except, desc) => {
16
+ if (from && typeof from === "object" || typeof from === "function") {
17
+ for (let key of __getOwnPropNames(from))
18
+ if (!__hasOwnProp.call(to, key) && key !== except)
19
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
20
+ }
21
+ return to;
22
+ };
23
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
24
+ // If the importer is in node compatibility mode or this is not an ESM
25
+ // file that has been converted to a CommonJS file using a Babel-
26
+ // compatible transform (i.e. "__esModule" has not been set), then set
27
+ // "default" to the CommonJS "module.exports" for node compatibility.
28
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
29
+ mod
30
+ ));
31
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
32
+
33
+ // package.json
34
+ var require_package = __commonJS({
35
+ "package.json"(exports2, module2) {
36
+ module2.exports = {
37
+ name: "sarangai-cli",
38
+ version: "1.0.9",
39
+ description: "CLI resmi SarangAI \u2014 Gateway multi-model AI langsung dari terminal Anda",
40
+ main: "dist/index.js",
41
+ bin: {
42
+ sarang: "dist/index.js",
43
+ sarangai: "dist/index.cjs"
44
+ },
45
+ files: [
46
+ "dist"
47
+ ],
48
+ scripts: {
49
+ build: "tsup && chmod +x dist/index.js",
50
+ prepublishOnly: "npm run build"
51
+ },
52
+ keywords: [
53
+ "sarangai",
54
+ "cli",
55
+ "ai",
56
+ "llm",
57
+ "openai",
58
+ "deepseek",
59
+ "claude",
60
+ "gateway"
61
+ ],
62
+ author: "SarangAI Team",
63
+ license: "MIT",
64
+ dependencies: {
65
+ chalk: "^4.1.2",
66
+ clipboardy: "^5.3.2",
67
+ commander: "^12.1.0",
68
+ ink: "^6.8.0",
69
+ ora: "^5.4.1",
70
+ prompts: "^2.4.2",
71
+ react: "^19.3.0",
72
+ "react-dom": "^19.3.0",
73
+ "readline-sync": "^1.4.10"
74
+ },
75
+ devDependencies: {
76
+ "@types/clipboardy": "^1.1.0",
77
+ "@types/node": "^20.14.9",
78
+ "@types/prompts": "^2.4.9",
79
+ "@types/react": "^19.3.0",
80
+ "@types/readline-sync": "^1.4.8",
81
+ tsup: "^8.5.1",
82
+ typescript: "^5.5.2"
83
+ }
84
+ };
85
+ }
86
+ });
87
+
88
+ // src/index.ts
89
+ var index_exports = {};
90
+ __export(index_exports, {
91
+ CODING_MODELS: () => CODING_MODELS
92
+ });
93
+ module.exports = __toCommonJS(index_exports);
94
+ var import_commander = require("commander");
95
+ var import_chalk = __toESM(require("chalk"));
96
+ var import_readline = __toESM(require("readline"));
97
+ var import_child_process = require("child_process");
98
+ var import_crypto = __toESM(require("crypto"));
99
+ var import_clipboardy = __toESM(require("clipboardy"));
100
+
101
+ // src/config.ts
102
+ var import_fs = __toESM(require("fs"));
103
+ var import_path = __toESM(require("path"));
104
+ var import_os = __toESM(require("os"));
105
+ var CONFIG_FILE = import_path.default.join(import_os.default.homedir(), ".sarangairc");
106
+ function getConfig() {
107
+ try {
108
+ if (import_fs.default.existsSync(CONFIG_FILE)) {
109
+ const raw = import_fs.default.readFileSync(CONFIG_FILE, "utf-8");
110
+ return JSON.parse(raw);
111
+ }
112
+ } catch {
113
+ }
114
+ return {
115
+ baseUrl: "https://idshop.or.id",
116
+ defaultModel: "deepseek/deepseek-chat"
117
+ };
118
+ }
119
+ function saveConfig(newConfig) {
120
+ const current = getConfig();
121
+ const merged = { ...current, ...newConfig };
122
+ import_fs.default.writeFileSync(CONFIG_FILE, JSON.stringify(merged, null, 2), "utf-8");
123
+ }
124
+
125
+ // src/index.ts
126
+ var program = new import_commander.Command();
127
+ var borderActive = import_chalk.default.hex("#a855f7");
128
+ var borderMuted = import_chalk.default.hex("#334155");
129
+ var violetText = import_chalk.default.hex("#c084fc");
130
+ var greenDot = import_chalk.default.hex("#4ade80");
131
+ var grayMuted = import_chalk.default.hex("#94a3b8");
132
+ var CODING_MODELS = [
133
+ {
134
+ id: "anthropic/claude-3.7-sonnet",
135
+ name: "Claude 3.7 Sonnet",
136
+ desc: "Anthropic \u2022 Best coding & agentic workflow",
137
+ rate: "12,000 Credits/hr"
138
+ },
139
+ {
140
+ id: "openai/gpt-4o",
141
+ name: "GPT-4o",
142
+ desc: "OpenAI \u2022 Fast full-stack & complex logic",
143
+ rate: "5,000 Credits/hr"
144
+ },
145
+ {
146
+ id: "deepseek/deepseek-r1",
147
+ name: "DeepSeek-R1",
148
+ desc: "DeepSeek \u2022 Deep reasoning & hard algorithms",
149
+ rate: "1,500 Credits/hr"
150
+ },
151
+ {
152
+ id: "qwen/qwen-2.5-coder-32b-instruct",
153
+ name: "Qwen 2.5 Coder",
154
+ desc: "Alibaba \u2022 Multi-language syntax specialist",
155
+ rate: "700 Credits/hr"
156
+ },
157
+ {
158
+ id: "google/gemini-2.5-flash",
159
+ name: "Gemini 2.5 Flash",
160
+ desc: "Google \u2022 1M context & full repo analysis",
161
+ rate: "500 Credits/hr"
162
+ },
163
+ {
164
+ id: "meta-llama/llama-3.3-70b-instruct",
165
+ name: "Llama 3.3 70B",
166
+ desc: "Meta \u2022 Robust open-source coding engine",
167
+ rate: "600 Credits/hr"
168
+ }
169
+ ];
170
+ function resolveBaseUrl() {
171
+ const cfg2 = getConfig();
172
+ return process.env.SARANGAI_BASE_URL || cfg2.baseUrl || "https://idshop.or.id";
173
+ }
174
+ function copyToClipboard(text) {
175
+ try {
176
+ import_clipboardy.default.writeSync(text);
177
+ } catch {
178
+ try {
179
+ (0, import_child_process.execSync)(`echo -n "${text}" | xclip -selection clipboard 2>/dev/null || echo -n "${text}" | pbcopy 2>/dev/null || echo | set /p="${text}" | clip 2>/dev/null`);
180
+ } catch {
181
+ }
182
+ }
183
+ }
184
+ async function fetchUserMeta(baseUrl2, apiKey) {
185
+ try {
186
+ const res = await fetch(`${baseUrl2}/api/user/stats`, {
187
+ headers: { Authorization: `Bearer ${apiKey}` }
188
+ });
189
+ if (res.ok) {
190
+ const d = await res.json();
191
+ return {
192
+ balance: d.balance ?? 75313,
193
+ tier: d.tier || "DEVELOPER",
194
+ accountId: d.accountId || "SA-8WwREVSJ"
195
+ };
196
+ }
197
+ } catch {
198
+ }
199
+ return { balance: 75313, tier: "DEVELOPER", accountId: "SA-8WwREVSJ" };
200
+ }
201
+ function getDisplayDir() {
202
+ const cwd = process.cwd();
203
+ const home = process.env.HOME || "/root";
204
+ if (cwd.startsWith(home)) {
205
+ return "~" + cwd.slice(home.length);
206
+ }
207
+ return cwd;
208
+ }
209
+ function getCenterPad(contentWidth = 72) {
210
+ const termCols = process.stdout.columns || 80;
211
+ const padLen = Math.max(2, Math.floor((termCols - contentWidth) / 2));
212
+ return " ".repeat(padLen);
213
+ }
214
+ function renderCard(model, isFocused, cardWidth, pad) {
215
+ const borderFn = isFocused ? borderActive : borderMuted;
216
+ const prefix = isFocused ? import_chalk.default.cyan.bold("\u203A ") : " ";
217
+ const innerW = cardWidth - 2;
218
+ const rawL1 = ` ${model.name} \u2022 ${model.desc}`;
219
+ const padL1 = Math.max(0, innerW - rawL1.length);
220
+ const line1 = `${prefix}${import_chalk.default.bold.white(model.name)} \u2022 ${grayMuted(model.desc)}${" ".repeat(padL1)}`;
221
+ const rawL2 = ` ${model.rate}`;
222
+ const padL2 = Math.max(0, innerW - rawL2.length);
223
+ const line2 = ` ${violetText.bold(model.rate)}${" ".repeat(padL2)}`;
224
+ return [
225
+ pad + borderFn("\u250C" + "\u2500".repeat(innerW) + "\u2510"),
226
+ pad + borderFn("\u2502") + line1 + borderFn("\u2502"),
227
+ pad + borderFn("\u2502") + line2 + borderFn("\u2502"),
228
+ pad + borderFn("\u2514" + "\u2500".repeat(innerW) + "\u2518")
229
+ ];
230
+ }
231
+ function drawSelectionModal(activeModel2, userMeta2, expanded2, cursorIdx2, copiedNotice2 = false) {
232
+ const cardWidth = 72;
233
+ const pad = getCenterPad(cardWidth);
234
+ const lines = [];
235
+ const banner = [
236
+ "\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",
237
+ "\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",
238
+ "\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",
239
+ "\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",
240
+ "\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",
241
+ "\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"
242
+ ];
243
+ banner.forEach((l) => lines.push(pad + import_chalk.default.white.bold(l)));
244
+ lines.push("");
245
+ lines.push(
246
+ pad + import_chalk.default.bold.white("Start coding for SarangAI") + " " + greenDot("\u25CF\u25CF\u25CF\u25CF\u25CF\u25CB") + " ".repeat(30) + import_chalk.default.gray("\u2715")
247
+ );
248
+ lines.push("");
249
+ if (!expanded2) {
250
+ lines.push(...renderCard(activeModel2, cursorIdx2 === 0, cardWidth, pad));
251
+ lines.push("");
252
+ const balanceStr = `${Number(userMeta2.balance).toLocaleString()} Credits`;
253
+ lines.push(
254
+ 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)
255
+ );
256
+ lines.push("");
257
+ const isSeeAllActive = cursorIdx2 === 1;
258
+ const seeAllPrefix = isSeeAllActive ? import_chalk.default.cyan.bold("\u203A ") : " ";
259
+ const seeAllText = isSeeAllActive ? import_chalk.default.bold.cyan.underline(`\u2193 See all ${CODING_MODELS.length} models`) : import_chalk.default.hex("#a855f7")(`\u2193 See all ${CODING_MODELS.length} models`);
260
+ lines.push(pad + seeAllPrefix + seeAllText);
261
+ lines.push("");
262
+ const isStartActive = cursorIdx2 === 2;
263
+ lines.push(pad + (isStartActive ? import_chalk.default.green.bold("\u203A [\u{1F680} Start Coding Workspace \u21B5]") : import_chalk.default.white(" [\u{1F680} Start Coding Workspace \u21B5]")));
264
+ lines.push("");
265
+ lines.push(pad + import_chalk.default.gray("\u2726 Refer friends \u2192 manage credits:"));
266
+ lines.push("");
267
+ const isCopyActive = cursorIdx2 === 3;
268
+ const copyPrefix = isCopyActive ? import_chalk.default.cyan.bold("\u203A ") : " ";
269
+ const copyLabel = isCopyActive ? import_chalk.default.bold.cyan.underline("\u{1F4CB} Copy invite / dashboard link") : import_chalk.default.white("\u{1F4CB} Copy invite / dashboard link");
270
+ lines.push(pad + copyPrefix + copyLabel);
271
+ } else {
272
+ CODING_MODELS.forEach((m, idx) => {
273
+ lines.push(...renderCard(m, cursorIdx2 === idx + 10, cardWidth, pad));
274
+ });
275
+ lines.push("");
276
+ const balanceStr = `${Number(userMeta2.balance).toLocaleString()} Credits`;
277
+ lines.push(
278
+ 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)
279
+ );
280
+ lines.push("");
281
+ const isFewerActive = cursorIdx2 === 20;
282
+ const fewerPrefix = isFewerActive ? import_chalk.default.cyan.bold("\u203A ") : " ";
283
+ const fewerText = isFewerActive ? import_chalk.default.bold.cyan.underline("\u2191 Show fewer") : import_chalk.default.hex("#a855f7")("\u2191 Show fewer");
284
+ lines.push(pad + fewerPrefix + fewerText);
285
+ }
286
+ if (copiedNotice2) {
287
+ lines.push("");
288
+ lines.push(pad + import_chalk.default.green("\u2714 Dashboard link copied to clipboard: https://idshop.or.id/user/dashboard"));
289
+ }
290
+ lines.push("");
291
+ lines.push(pad + import_chalk.default.gray("\u2500".repeat(cardWidth)));
292
+ lines.push(pad + import_chalk.default.gray("Navigasi: [\u2191/\u2193 Panah] \u2022 [Enter/Space] Pilih \u2022 [c] Copy Link \u2022 [q] Keluar"));
293
+ process.stdout.write("\x1B[2J\x1B[3J\x1B[H\x1B[?25l" + lines.join("\n"));
294
+ }
295
+ function drawWorkspaceScreen(model, timeLeftSec, userInput) {
296
+ const boxWidth = 72;
297
+ const pad = getCenterPad(boxWidth);
298
+ const banner = [
299
+ "\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",
300
+ "\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",
301
+ "\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",
302
+ "\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",
303
+ "\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",
304
+ "\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"
305
+ ];
306
+ const lines = [];
307
+ banner.forEach((l) => lines.push(pad + import_chalk.default.white.bold(l)));
308
+ lines.push("");
309
+ lines.push(pad + import_chalk.default.white("SarangAI will run commands on your behalf to help you build."));
310
+ lines.push("");
311
+ lines.push(pad + import_chalk.default.bold.white("Directory ") + import_chalk.default.gray(getDisplayDir()));
312
+ lines.push("");
313
+ const m = Math.floor(timeLeftSec / 60);
314
+ const s = timeLeftSec % 60;
315
+ const timeFormatted = `${m}m ${s < 10 ? "0" : ""}${s}s left`;
316
+ const statusLeft = ` ${model.name} \u2022 ${timeFormatted}`;
317
+ const statusRight = `[Esc] End session `;
318
+ const spaceBetween = Math.max(2, boxWidth - statusLeft.length - statusRight.length);
319
+ const statusBar = import_chalk.default.bgHex("#1e293b").white.bold(statusLeft + " ".repeat(spaceBetween) + import_chalk.default.gray(statusRight));
320
+ lines.push(pad + statusBar);
321
+ const innerWidth = boxWidth - 2;
322
+ const borderTop = "\u250C" + "\u2500".repeat(innerWidth) + "\u2510";
323
+ const borderBottom = "\u2514" + "\u2500".repeat(innerWidth) + "\u2518";
324
+ const textContent = userInput ? userInput : import_chalk.default.gray("Enter a coding task or / for commands");
325
+ const visibleLen = userInput ? userInput.length : 37;
326
+ const paddingRight = Math.max(0, innerWidth - 2 - visibleLen);
327
+ const middleLine = `\u2502 ${textContent}${" ".repeat(paddingRight)}\u2502`;
328
+ lines.push(pad + import_chalk.default.gray(borderTop));
329
+ const inputRowNumber = lines.length + 1;
330
+ lines.push(pad + import_chalk.default.gray(middleLine));
331
+ lines.push(pad + import_chalk.default.gray(borderBottom));
332
+ process.stdout.write("\x1B[2J\x1B[3J\x1B[H" + lines.join("\n"));
333
+ const cursorCol = pad.length + 3 + userInput.length + 1;
334
+ process.stdout.write(`\x1B[${inputRowNumber};${cursorCol}H\x1B[?25h`);
335
+ }
336
+ var inWorkspace = false;
337
+ var isExecuting = false;
338
+ var expanded = false;
339
+ var cursorIdx = 0;
340
+ var copiedNotice = false;
341
+ var activeModel;
342
+ var userMeta;
343
+ var sessionTimeLeft = 3600;
344
+ var timerInterval = null;
345
+ var currentTaskInput = "";
346
+ var cfg;
347
+ var baseUrl;
348
+ function cleanupAndExit() {
349
+ if (timerInterval) clearInterval(timerInterval);
350
+ process.stdout.write("\x1B[?1049l\x1B[?25h\n");
351
+ process.exit(0);
352
+ }
353
+ function triggerCopyLink() {
354
+ copyToClipboard("https://idshop.or.id/user/dashboard");
355
+ copiedNotice = true;
356
+ drawSelectionModal(activeModel, userMeta, expanded, cursorIdx, copiedNotice);
357
+ }
358
+ function leaveWorkspace() {
359
+ inWorkspace = false;
360
+ isExecuting = false;
361
+ if (timerInterval) clearInterval(timerInterval);
362
+ currentTaskInput = "";
363
+ process.stdout.write("\x1B[2J\x1B[3J\x1B[H");
364
+ drawSelectionModal(activeModel, userMeta, expanded, cursorIdx, copiedNotice);
365
+ }
366
+ function enterWorkspace() {
367
+ inWorkspace = true;
368
+ isExecuting = false;
369
+ currentTaskInput = "";
370
+ drawWorkspaceScreen(activeModel, sessionTimeLeft, currentTaskInput);
371
+ if (timerInterval) clearInterval(timerInterval);
372
+ timerInterval = setInterval(() => {
373
+ if (sessionTimeLeft > 0) {
374
+ sessionTimeLeft--;
375
+ if (inWorkspace && !isExecuting) {
376
+ drawWorkspaceScreen(activeModel, sessionTimeLeft, currentTaskInput);
377
+ }
378
+ }
379
+ }, 1e3);
380
+ }
381
+ async function runPromptStream(query) {
382
+ isExecuting = true;
383
+ process.stdout.write("\x1B[?25l");
384
+ const pad = getCenterPad(72);
385
+ console.log("\n\n" + pad + import_chalk.default.bold.hex("#c084fc")(`\u203A Task: ${query}`));
386
+ console.log(pad + import_chalk.default.gray(`[Generating with ${activeModel.name}...]
387
+ `));
388
+ try {
389
+ const res = await fetch(`${baseUrl}/api/gateway/v1/chat/completions`, {
390
+ method: "POST",
391
+ headers: {
392
+ "Content-Type": "application/json",
393
+ Authorization: `Bearer ${cfg.apiKey}`
394
+ },
395
+ body: JSON.stringify({
396
+ model: activeModel.id,
397
+ messages: [{ role: "user", content: query }],
398
+ stream: true
399
+ })
400
+ });
401
+ if (!res.ok) {
402
+ console.log(pad + import_chalk.default.red(`
403
+ Error: ${res.statusText}
404
+ `));
405
+ } else {
406
+ const reader = res.body?.getReader();
407
+ const decoder = new TextDecoder();
408
+ if (reader) {
409
+ while (true) {
410
+ const { done, value } = await reader.read();
411
+ if (done) break;
412
+ const chunk = decoder.decode(value);
413
+ const linesArr = chunk.split("\n");
414
+ for (const l of linesArr) {
415
+ if (l.startsWith("data: ") && l !== "data: [DONE]") {
416
+ try {
417
+ const p = JSON.parse(l.slice(6));
418
+ const delta = p.choices?.[0]?.delta?.content;
419
+ if (delta) process.stdout.write(delta);
420
+ } catch {
421
+ }
422
+ }
423
+ }
424
+ }
425
+ console.log("\n");
426
+ }
427
+ }
428
+ } catch (err) {
429
+ console.log(pad + import_chalk.default.red(`
430
+ Error: ${err.message}
431
+ `));
432
+ }
433
+ console.log(pad + import_chalk.default.gray("\nTekan sembarang tombol untuk kembali ke workspace..."));
434
+ process.stdin.once("keypress", () => {
435
+ isExecuting = false;
436
+ currentTaskInput = "";
437
+ drawWorkspaceScreen(activeModel, sessionTimeLeft, currentTaskInput);
438
+ });
439
+ }
440
+ async function handleAutoAuth(baseUrl2) {
441
+ const sessionCode = "SA-" + import_crypto.default.randomBytes(4).toString("hex").toUpperCase();
442
+ const authUrl = `${baseUrl2}/auth/cli?code=${sessionCode}`;
443
+ console.clear();
444
+ const pad = getCenterPad(72);
445
+ console.log("\n" + pad + violetText.bold("\u{1F510} Sesi Otorisasi CLI Diperlukan\n"));
446
+ copyToClipboard(authUrl);
447
+ console.log(pad + import_chalk.default.white("Buka URL berikut untuk mengizinkan:"));
448
+ console.log(pad + violetText.underline(authUrl));
449
+ console.log(pad + import_chalk.default.green("\u2714 Link otomatis disalin ke clipboard!\n"));
450
+ console.log(pad + import_chalk.default.gray(`Menunggu verifikasi web browser (Kode: ${sessionCode})...
451
+ `));
452
+ for (let i = 0; i < 60; i++) {
453
+ await new Promise((r) => setTimeout(r, 2e3));
454
+ try {
455
+ const pollRes = await fetch(`${baseUrl2}/api/auth/cli/poll?code=${sessionCode}`);
456
+ if (pollRes.ok) {
457
+ const payload = await pollRes.json();
458
+ if (payload.apiKey) {
459
+ saveConfig({ apiKey: payload.apiKey });
460
+ return payload.apiKey;
461
+ }
462
+ }
463
+ } catch {
464
+ }
465
+ }
466
+ return null;
467
+ }
468
+ async function startInteractiveSession() {
469
+ cfg = getConfig();
470
+ baseUrl = resolveBaseUrl();
471
+ if (!cfg.apiKey) {
472
+ const key = await handleAutoAuth(baseUrl);
473
+ if (!key) {
474
+ console.log("Login dibatalkan.");
475
+ process.exit(1);
476
+ }
477
+ cfg = getConfig();
478
+ }
479
+ userMeta = await fetchUserMeta(baseUrl, cfg.apiKey || "");
480
+ activeModel = CODING_MODELS.find((m) => m.id === cfg.defaultModel) || CODING_MODELS[0];
481
+ process.stdout.write("\x1B[?1049h\x1B[2J\x1B[3J\x1B[H");
482
+ import_readline.default.emitKeypressEvents(process.stdin);
483
+ if (process.stdin.isTTY) {
484
+ process.stdin.setRawMode(true);
485
+ }
486
+ drawSelectionModal(activeModel, userMeta, expanded, cursorIdx, copiedNotice);
487
+ process.stdout.on("resize", () => {
488
+ if (inWorkspace) {
489
+ if (!isExecuting) {
490
+ drawWorkspaceScreen(activeModel, sessionTimeLeft, currentTaskInput);
491
+ }
492
+ } else {
493
+ drawSelectionModal(activeModel, userMeta, expanded, cursorIdx, copiedNotice);
494
+ }
495
+ });
496
+ process.stdin.on("keypress", (str, key) => {
497
+ if (key.ctrl && key.name === "c") {
498
+ cleanupAndExit();
499
+ return;
500
+ }
501
+ if (inWorkspace) {
502
+ if (isExecuting) return;
503
+ if (key.name === "escape") {
504
+ leaveWorkspace();
505
+ return;
506
+ }
507
+ if (key.name === "return") {
508
+ const query = currentTaskInput.trim();
509
+ if (!query) return;
510
+ if (query === "/exit" || query === "exit" || query === ":q") {
511
+ cleanupAndExit();
512
+ return;
513
+ }
514
+ if (query === "/model" || query === "/back") {
515
+ leaveWorkspace();
516
+ return;
517
+ }
518
+ runPromptStream(query);
519
+ return;
520
+ }
521
+ if (key.name === "backspace") {
522
+ currentTaskInput = currentTaskInput.slice(0, -1);
523
+ drawWorkspaceScreen(activeModel, sessionTimeLeft, currentTaskInput);
524
+ return;
525
+ }
526
+ if (str && !key.ctrl && !key.meta && !str.startsWith("\x1B")) {
527
+ currentTaskInput += str;
528
+ drawWorkspaceScreen(activeModel, sessionTimeLeft, currentTaskInput);
529
+ return;
530
+ }
531
+ return;
532
+ }
533
+ if (key.name === "escape" || str === "q") {
534
+ cleanupAndExit();
535
+ return;
536
+ }
537
+ copiedNotice = false;
538
+ if (key.name === "down") {
539
+ if (!expanded) {
540
+ cursorIdx = (cursorIdx + 1) % 4;
541
+ } else {
542
+ if (cursorIdx >= 10 && cursorIdx < 10 + CODING_MODELS.length - 1) {
543
+ cursorIdx++;
544
+ } else if (cursorIdx === 10 + CODING_MODELS.length - 1) {
545
+ cursorIdx = 20;
546
+ } else {
547
+ cursorIdx = 10;
548
+ }
549
+ }
550
+ drawSelectionModal(activeModel, userMeta, expanded, cursorIdx, copiedNotice);
551
+ return;
552
+ }
553
+ if (key.name === "up") {
554
+ if (!expanded) {
555
+ cursorIdx = (cursorIdx - 1 + 4) % 4;
556
+ } else {
557
+ if (cursorIdx === 20) {
558
+ cursorIdx = 10 + CODING_MODELS.length - 1;
559
+ } else if (cursorIdx > 10) {
560
+ cursorIdx--;
561
+ } else {
562
+ cursorIdx = 20;
563
+ }
564
+ }
565
+ drawSelectionModal(activeModel, userMeta, expanded, cursorIdx, copiedNotice);
566
+ return;
567
+ }
568
+ if (str === "c" || str === "C") {
569
+ cursorIdx = 3;
570
+ triggerCopyLink();
571
+ return;
572
+ }
573
+ if (key.name === "return" || key.name === "space") {
574
+ if (!expanded) {
575
+ if (cursorIdx === 0 || cursorIdx === 2) {
576
+ enterWorkspace();
577
+ } else if (cursorIdx === 1) {
578
+ expanded = true;
579
+ cursorIdx = 10;
580
+ drawSelectionModal(activeModel, userMeta, expanded, cursorIdx, copiedNotice);
581
+ } else if (cursorIdx === 3) {
582
+ triggerCopyLink();
583
+ }
584
+ } else {
585
+ if (cursorIdx === 20) {
586
+ expanded = false;
587
+ cursorIdx = 1;
588
+ drawSelectionModal(activeModel, userMeta, expanded, cursorIdx, copiedNotice);
589
+ } else if (cursorIdx >= 10) {
590
+ const selected = CODING_MODELS[cursorIdx - 10];
591
+ if (selected) {
592
+ activeModel = selected;
593
+ saveConfig({ defaultModel: activeModel.id });
594
+ expanded = false;
595
+ enterWorkspace();
596
+ }
597
+ }
598
+ }
599
+ return;
600
+ }
601
+ });
602
+ }
603
+ program.name("sarang").description("SarangAI CLI \u2014 Gateway AI Coding Workspace").version(require_package().version).action(() => {
604
+ startInteractiveSession();
605
+ });
606
+ program.parse(process.argv);
607
+ // Annotate the CommonJS export names for ESM import in node:
608
+ 0 && (module.exports = {
609
+ CODING_MODELS
610
+ });
package/package.json CHANGED
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "name": "sarangai-cli",
3
- "version": "1.0.7",
3
+ "version": "1.0.9",
4
4
  "description": "CLI resmi SarangAI — Gateway multi-model AI langsung dari terminal Anda",
5
- "main": "dist/index.cjs",
5
+ "main": "dist/index.js",
6
6
  "bin": {
7
- "sarang": "dist/index.cjs",
7
+ "sarang": "dist/index.js",
8
8
  "sarangai": "dist/index.cjs"
9
9
  },
10
10
  "files": [
11
11
  "dist"
12
12
  ],
13
13
  "scripts": {
14
- "build": "tsup",
14
+ "build": "tsup && chmod +x dist/index.js",
15
15
  "prepublishOnly": "npm run build"
16
16
  },
17
17
  "keywords": [
@@ -28,14 +28,20 @@
28
28
  "license": "MIT",
29
29
  "dependencies": {
30
30
  "chalk": "^4.1.2",
31
+ "clipboardy": "^5.3.2",
31
32
  "commander": "^12.1.0",
33
+ "ink": "^6.8.0",
32
34
  "ora": "^5.4.1",
33
35
  "prompts": "^2.4.2",
36
+ "react": "^19.3.0",
37
+ "react-dom": "^19.3.0",
34
38
  "readline-sync": "^1.4.10"
35
39
  },
36
40
  "devDependencies": {
41
+ "@types/clipboardy": "^1.1.0",
37
42
  "@types/node": "^20.14.9",
38
43
  "@types/prompts": "^2.4.9",
44
+ "@types/react": "^19.3.0",
39
45
  "@types/readline-sync": "^1.4.8",
40
46
  "tsup": "^8.5.1",
41
47
  "typescript": "^5.5.2"
package/dist/index.cjs DELETED
@@ -1,399 +0,0 @@
1
- #!/usr/bin/env node
2
- var __create = Object.create;
3
- var __defProp = Object.defineProperty;
4
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
- var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __copyProps = (to, from, except, desc) => {
9
- if (from && typeof from === "object" || typeof from === "function") {
10
- for (let key of __getOwnPropNames(from))
11
- if (!__hasOwnProp.call(to, key) && key !== except)
12
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
13
- }
14
- return to;
15
- };
16
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
17
- // If the importer is in node compatibility mode or this is not an ESM
18
- // file that has been converted to a CommonJS file using a Babel-
19
- // compatible transform (i.e. "__esModule" has not been set), then set
20
- // "default" to the CommonJS "module.exports" for node compatibility.
21
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
22
- mod
23
- ));
24
-
25
- // src/index.ts
26
- var import_commander = require("commander");
27
- var import_chalk = __toESM(require("chalk"));
28
- var import_prompts = __toESM(require("prompts"));
29
- var import_ora = __toESM(require("ora"));
30
- var import_readline = __toESM(require("readline"));
31
- var import_child_process = require("child_process");
32
- var import_path2 = __toESM(require("path"));
33
-
34
- // src/config.ts
35
- var import_fs = __toESM(require("fs"));
36
- var import_path = __toESM(require("path"));
37
- var import_os = __toESM(require("os"));
38
- var CONFIG_FILE = import_path.default.join(import_os.default.homedir(), ".sarangairc");
39
- function getConfig() {
40
- try {
41
- if (import_fs.default.existsSync(CONFIG_FILE)) {
42
- const raw = import_fs.default.readFileSync(CONFIG_FILE, "utf-8");
43
- return JSON.parse(raw);
44
- }
45
- } catch {
46
- }
47
- return {
48
- baseUrl: "https://idshop.or.id",
49
- defaultModel: "minimax/minimax-m2.7"
50
- };
51
- }
52
- function saveConfig(newConfig) {
53
- const current = getConfig();
54
- const merged = { ...current, ...newConfig };
55
- import_fs.default.writeFileSync(CONFIG_FILE, JSON.stringify(merged, null, 2), "utf-8");
56
- }
57
-
58
- // src/index.ts
59
- var program = new import_commander.Command();
60
- function resolveBaseUrl() {
61
- const cfg = getConfig();
62
- return process.env.SARANGAI_BASE_URL || cfg.baseUrl || "https://idshop.or.id";
63
- }
64
- function getGitBranch() {
65
- try {
66
- return (0, import_child_process.execSync)("git branch --show-current 2>/dev/null").toString().trim() || "no-git";
67
- } catch {
68
- return "no-git";
69
- }
70
- }
71
- function renderBanner(modelName) {
72
- console.clear();
73
- const banner = `
74
- \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
75
- \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
76
- \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
77
- \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
78
- \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
79
- \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
80
- `;
81
- console.log(import_chalk.default.cyanBright(banner));
82
- console.log(import_chalk.default.gray(" Tips for getting started:"));
83
- console.log(import_chalk.default.gray(" 1. Ketik pesan Anda langsung untuk chat streaming."));
84
- console.log(import_chalk.default.gray(" 2. Perintah: ") + import_chalk.default.yellow("/clear") + import_chalk.default.gray(", ") + import_chalk.default.yellow("/model <id>") + import_chalk.default.gray(", ") + import_chalk.default.yellow("/exit") + import_chalk.default.gray("."));
85
- console.log("");
86
- const cwd = import_path2.default.basename(process.cwd());
87
- const branch = getGitBranch();
88
- const leftStatus = import_chalk.default.gray(`\u{1F4C2} ~/${cwd} `) + import_chalk.default.magenta(`(${branch})`);
89
- const rightStatus = import_chalk.default.bgCyan.black(` ${modelName} `) + import_chalk.default.greenBright(" (ready)");
90
- console.log(` ${leftStatus} ${rightStatus}`);
91
- console.log(import_chalk.default.cyan(" " + "\u2500".repeat(65)));
92
- console.log("");
93
- }
94
- async function startInteractiveSession(initialModel) {
95
- const cfg = getConfig();
96
- if (!cfg.apiKey) {
97
- console.log(import_chalk.default.yellow("\nSilakan login terlebih dahulu: sarang login\n"));
98
- return;
99
- }
100
- let currentModel = initialModel || cfg.defaultModel || "minimax/minimax-m2.7";
101
- const baseUrl = resolveBaseUrl();
102
- const history = [];
103
- renderBanner(currentModel);
104
- const rl = import_readline.default.createInterface({
105
- input: process.stdin,
106
- output: process.stdout
107
- });
108
- const ask = () => {
109
- rl.question(import_chalk.default.cyanBright("\u276F "), async (input) => {
110
- var _a, _b, _c, _d, _e;
111
- const trimmed = input.trim();
112
- if (!trimmed) {
113
- ask();
114
- return;
115
- }
116
- if (trimmed === "/exit" || trimmed === "exit" || trimmed === ":q") {
117
- console.log(import_chalk.default.gray("\nSampai jumpa!\n"));
118
- rl.close();
119
- process.exit(0);
120
- }
121
- if (trimmed === "/clear" || trimmed === "clear") {
122
- renderBanner(currentModel);
123
- ask();
124
- return;
125
- }
126
- if (trimmed.startsWith("/model")) {
127
- const parts = trimmed.split(" ");
128
- if (parts[1]) {
129
- currentModel = parts[1].trim();
130
- saveConfig({ defaultModel: currentModel });
131
- console.log(import_chalk.default.green(`
132
- \u2714 Model dialihkan ke: ${currentModel}
133
- `));
134
- } else {
135
- console.log(import_chalk.default.yellow(`
136
- Format: /model <model-id>
137
- `));
138
- }
139
- ask();
140
- return;
141
- }
142
- history.push({ role: "user", content: trimmed });
143
- process.stdout.write(import_chalk.default.gray(`
144
- [${currentModel}]
145
- `));
146
- try {
147
- const res = await fetch(`${baseUrl}/api/gateway/v1/chat/completions`, {
148
- method: "POST",
149
- headers: {
150
- "Content-Type": "application/json",
151
- Authorization: `Bearer ${cfg.apiKey}`
152
- },
153
- body: JSON.stringify({
154
- model: currentModel,
155
- messages: history,
156
- stream: true
157
- })
158
- });
159
- if (!res.ok) {
160
- const errText = await res.text();
161
- let parsedErr;
162
- try {
163
- parsedErr = JSON.parse(errText);
164
- } catch {
165
- }
166
- throw new Error(((_a = parsedErr == null ? void 0 : parsedErr.error) == null ? void 0 : _a.message) || `HTTP ${res.status}: ${errText.slice(0, 80)}`);
167
- }
168
- const reader = (_b = res.body) == null ? void 0 : _b.getReader();
169
- const decoder = new TextDecoder();
170
- let assistantReply = "";
171
- if (reader) {
172
- while (true) {
173
- const { done, value } = await reader.read();
174
- if (done) break;
175
- const chunk = decoder.decode(value);
176
- const lines = chunk.split("\n");
177
- for (const line of lines) {
178
- if (line.startsWith("data: ") && line !== "data: [DONE]") {
179
- try {
180
- const parsed = JSON.parse(line.slice(6));
181
- const delta = (_e = (_d = (_c = parsed.choices) == null ? void 0 : _c[0]) == null ? void 0 : _d.delta) == null ? void 0 : _e.content;
182
- if (delta) {
183
- process.stdout.write(delta);
184
- assistantReply += delta;
185
- }
186
- } catch {
187
- }
188
- }
189
- }
190
- }
191
- console.log("\n");
192
- history.push({ role: "assistant", content: assistantReply });
193
- }
194
- } catch (err) {
195
- console.error(import_chalk.default.red(`
196
- Error: ${err.message}
197
- `));
198
- }
199
- ask();
200
- });
201
- };
202
- ask();
203
- }
204
- program.name("sarang").description(import_chalk.default.cyanBright("SarangAI CLI \u2014 Gateway ratusan model AI langsung di terminal")).version("1.0.3").action(() => {
205
- startInteractiveSession();
206
- });
207
- program.command("login").description("Hubungkan API Key SarangAI akun Anda").action(async () => {
208
- const baseUrl = resolveBaseUrl();
209
- console.log(import_chalk.default.cyanBright("\n\u{1F510} Autentikasi SarangAI CLI"));
210
- console.log(import_chalk.default.gray("Dapatkan API Key di: ") + import_chalk.default.underline.cyan(`${baseUrl}`));
211
- console.log(import_chalk.default.gray("--------------------------------------------------\n"));
212
- const res = await (0, import_prompts.default)({
213
- type: "password",
214
- name: "key",
215
- message: "Masukkan API Key:"
216
- });
217
- if (!res.key) {
218
- console.log(import_chalk.default.red("\nLogin dibatalkan.\n"));
219
- return;
220
- }
221
- saveConfig({ apiKey: res.key.trim() });
222
- console.log(import_chalk.default.green("\n\u2714 Berhasil! API Key tersimpan di ~/.sarangairc\n"));
223
- });
224
- program.command("set-url [url]").description("Atur Base URL Gateway SarangAI").action(async (url) => {
225
- let target = url;
226
- if (!target) {
227
- const res = await (0, import_prompts.default)({
228
- type: "text",
229
- name: "url",
230
- message: "Masukkan URL Gateway:"
231
- });
232
- target = res.url;
233
- }
234
- if (!target) return;
235
- saveConfig({ baseUrl: target.trim().replace(/\/+$/, "") });
236
- console.log(import_chalk.default.green(`\u2714 Base URL diatur ke: ${target.trim()}`));
237
- });
238
- program.command("balance").description("Cek sisa saldo kredit akun SarangAI Anda").action(async () => {
239
- const cfg = getConfig();
240
- if (!cfg.apiKey) {
241
- console.log(import_chalk.default.yellow("\nAnda belum login. Jalankan: sarang login\n"));
242
- return;
243
- }
244
- const baseUrl = resolveBaseUrl();
245
- const spinner = (0, import_ora.default)("Mengambil status saldo...").start();
246
- try {
247
- const res = await fetch(`${baseUrl}/api/gateway/v1/balance`, {
248
- headers: { Authorization: `Bearer ${cfg.apiKey}` }
249
- });
250
- const rawText = await res.text();
251
- let data = {};
252
- try {
253
- data = JSON.parse(rawText);
254
- } catch {
255
- throw new Error(`Respons server non-JSON (${res.status}): ${rawText.slice(0, 100)}`);
256
- }
257
- spinner.stop();
258
- if (!res.ok) throw new Error(data.error || `HTTP ${res.status}`);
259
- console.log(import_chalk.default.bold("\nStatus Akun SarangAI:"));
260
- console.log(`Credit Balance : ${import_chalk.default.greenBright(data.balance ?? 0)} CREDIT`);
261
- console.log(`Email : ${import_chalk.default.gray(data.email || "\u2014")}
262
- `);
263
- } catch (err) {
264
- spinner.fail(import_chalk.default.red(err.message));
265
- }
266
- });
267
- program.command("models").option("-s, --search <keyword>", "Filter nama model").description("Daftar model AI yang tersedia").action(async (cmd) => {
268
- var _a;
269
- const cfg = getConfig();
270
- if (!cfg.apiKey) {
271
- console.log(import_chalk.default.yellow("\nAnda belum login. Jalankan: sarang login\n"));
272
- return;
273
- }
274
- const baseUrl = resolveBaseUrl();
275
- const spinner = (0, import_ora.default)("Mengambil katalog model...").start();
276
- try {
277
- const res = await fetch(`${baseUrl}/api/gateway/v1/models`, {
278
- headers: { Authorization: `Bearer ${cfg.apiKey}` }
279
- });
280
- const rawText = await res.text();
281
- let data = {};
282
- try {
283
- data = JSON.parse(rawText);
284
- } catch {
285
- throw new Error(`Respons tidak valid: ${rawText.slice(0, 100)}`);
286
- }
287
- spinner.stop();
288
- if (!res.ok) throw new Error(((_a = data.error) == null ? void 0 : _a.message) || data.error || `HTTP ${res.status}`);
289
- let list = Array.isArray(data) ? data : data.data || [];
290
- if (list.length === 0) {
291
- console.log(import_chalk.default.yellow("Tidak ada model yang ditemukan."));
292
- return;
293
- }
294
- if (cmd.search) {
295
- const q = cmd.search.toLowerCase();
296
- list = list.filter((m) => (m.id || m.modelId || m.name || "").toLowerCase().includes(q));
297
- }
298
- console.log(import_chalk.default.bold(`
299
- Model Tersedia (${list.length}):`));
300
- console.log(import_chalk.default.gray("---------------------------------------------------------"));
301
- list.forEach((m) => {
302
- const id = m.id || m.modelId || m.name;
303
- const isCurrent = id === cfg.defaultModel;
304
- console.log(`${isCurrent ? import_chalk.default.green("\u2714 [Aktif] ") : " "}${import_chalk.default.cyan(id)}`);
305
- });
306
- console.log(import_chalk.default.gray("---------------------------------------------------------\n"));
307
- } catch (err) {
308
- spinner.fail(import_chalk.default.red(err.message));
309
- }
310
- });
311
- program.command("set-model [modelId]").description("Atur model default untuk chat").action(async (modelId) => {
312
- let target = modelId;
313
- if (!target) {
314
- const res = await (0, import_prompts.default)({
315
- type: "text",
316
- name: "model",
317
- message: "Masukkan Model ID default baru:"
318
- });
319
- target = res.model;
320
- }
321
- if (!target) return;
322
- saveConfig({ defaultModel: target.trim() });
323
- console.log(import_chalk.default.green(`\u2714 Default model berhasil diubah ke: ${target.trim()}`));
324
- });
325
- program.command("chat [prompt...]").option("-m, --model <modelId>", "Pilih model spesifik").description("Kirim instruksi / chat (Mendukung streaming teks atau Unix Pipe)").action(async (promptArr, cmd) => {
326
- var _a, _b, _c, _d, _e;
327
- let prompt = promptArr == null ? void 0 : promptArr.join(" ");
328
- if (!process.stdin.isTTY) {
329
- const chunks = [];
330
- for await (const chunk of process.stdin) {
331
- chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
332
- }
333
- const pipedData = Buffer.concat(chunks).toString("utf-8").trim();
334
- prompt = prompt ? `${prompt}
335
-
336
- ${pipedData}` : pipedData;
337
- }
338
- if (!prompt) {
339
- await startInteractiveSession(cmd.model);
340
- return;
341
- }
342
- const cfg = getConfig();
343
- if (!cfg.apiKey) {
344
- console.log(import_chalk.default.yellow("\nSilakan login terlebih dahulu: sarang login\n"));
345
- return;
346
- }
347
- const baseUrl = resolveBaseUrl();
348
- const selectedModel = cmd.model || cfg.defaultModel || "minimax/minimax-m2.7";
349
- console.log(import_chalk.default.gray(`
350
- [Model: ${selectedModel}]`));
351
- try {
352
- const res = await fetch(`${baseUrl}/api/gateway/v1/chat/completions`, {
353
- method: "POST",
354
- headers: {
355
- "Content-Type": "application/json",
356
- Authorization: `Bearer ${cfg.apiKey}`
357
- },
358
- body: JSON.stringify({
359
- model: selectedModel,
360
- messages: [{ role: "user", content: prompt }],
361
- stream: true
362
- })
363
- });
364
- if (!res.ok) {
365
- const errText = await res.text();
366
- let parsedErr;
367
- try {
368
- parsedErr = JSON.parse(errText);
369
- } catch {
370
- }
371
- throw new Error(((_a = parsedErr == null ? void 0 : parsedErr.error) == null ? void 0 : _a.message) || `HTTP ${res.status}: ${errText.slice(0, 80)}`);
372
- }
373
- const reader = (_b = res.body) == null ? void 0 : _b.getReader();
374
- const decoder = new TextDecoder();
375
- if (reader) {
376
- while (true) {
377
- const { done, value } = await reader.read();
378
- if (done) break;
379
- const chunk = decoder.decode(value);
380
- const lines = chunk.split("\n");
381
- for (const line of lines) {
382
- if (line.startsWith("data: ") && line !== "data: [DONE]") {
383
- try {
384
- const parsed = JSON.parse(line.slice(6));
385
- const content = (_e = (_d = (_c = parsed.choices) == null ? void 0 : _c[0]) == null ? void 0 : _d.delta) == null ? void 0 : _e.content;
386
- if (content) process.stdout.write(content);
387
- } catch {
388
- }
389
- }
390
- }
391
- }
392
- console.log("\n");
393
- }
394
- } catch (err) {
395
- console.error(import_chalk.default.red(`
396
- Error: ${err.message}`));
397
- }
398
- });
399
- program.parse(process.argv);