sarangai-cli 1.0.0
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/README.md +104 -0
- package/dist/index.cjs +392 -0
- package/package.json +43 -0
package/README.md
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# SarangAI CLI
|
|
2
|
+
|
|
3
|
+
Akses 400+ model AI unggulan (DeepSeek, Claude, GPT, Qwen, Minimax, dsb.) langsung dari terminal Anda melalui SarangAI Gateway.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Fitur Utama
|
|
8
|
+
|
|
9
|
+
- **Streaming Teks Cepat**: Respons AI mengalir secara real-time langsung di terminal.
|
|
10
|
+
- **Katalog 400+ Model**: Jelajahi dan gunakan ratusan varian model AI populer.
|
|
11
|
+
- **Pencarian Cepat**: Filter model berdasarkan nama, provider, atau model gratis.
|
|
12
|
+
- **Dukungan Unix Piping**: Teruskan output perintah bash/kode program langsung ke AI.
|
|
13
|
+
- **Manajemen Saldo**: Cek status kredit akun langsung lewat terminal.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Instalasi
|
|
18
|
+
|
|
19
|
+
Instal paket secara global menggunakan npm:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install -g sarangai-cli
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Panduan Penggunaan
|
|
28
|
+
|
|
29
|
+
### 1. Autentikasi
|
|
30
|
+
Dapatkan API Key dari dashboard akun SarangAI Anda, lalu hubungkan ke CLI:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
sarang login
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
API Key akan disimpan di berkas konfigurasi lokal `~/.sarangairc`.
|
|
37
|
+
|
|
38
|
+
### 2. Cek Saldo Kredit
|
|
39
|
+
Lihat sisa saldo kredit dan status akun:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
sarang balance
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### 3. Jelajahi dan Filter Model
|
|
46
|
+
Melihat seluruh model yang tersedia:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
sarang models
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Menyaring model berdasarkan kata kunci:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
sarang models -s deepseek
|
|
56
|
+
sarang models -s claude
|
|
57
|
+
sarang models -s free
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### 4. Ganti Default Model
|
|
61
|
+
Tentukan model yang selalu digunakan secara otomatis:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
sarang set-model minimax/minimax-m2.7
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### 5. Chat & Streaming
|
|
68
|
+
Mengirim prompt langsung:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
sarang chat "Jelaskan konsep index di database PostgreSQL."
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Menggunakan model tertentu tanpa mengubah default:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
sarang chat -m anthropic/claude-sonnet-4 "Buatkan regex validasi nomor telepon Indonesia."
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### 6. Integrasi Unix Piping
|
|
81
|
+
Membaca input langsung dari terminal atau berkas kode:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
cat main.py | sarang chat "Review potensi bug dan tingkatkan performa kode ini."
|
|
85
|
+
git diff | sarang chat "Buatkan pesan commit yang ringkas berdasarkan perubahan ini."
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Konfigurasi Gateway
|
|
91
|
+
|
|
92
|
+
Secara default, CLI membaca konfigurasi dari `~/.sarangairc` atau environment variable `SARANGAI_BASE_URL`.
|
|
93
|
+
|
|
94
|
+
Untuk gateway self-hosted atau pengujian lokal:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
export SARANGAI_BASE_URL="http://localhost:3001"
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Lisensi
|
|
103
|
+
|
|
104
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,392 @@
|
|
|
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_PATH = import_path.default.join(import_os.default.homedir(), ".sarangairc");
|
|
39
|
+
function getConfig() {
|
|
40
|
+
try {
|
|
41
|
+
if (import_fs.default.existsSync(CONFIG_PATH)) {
|
|
42
|
+
return JSON.parse(import_fs.default.readFileSync(CONFIG_PATH, "utf-8"));
|
|
43
|
+
}
|
|
44
|
+
} catch {
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
baseUrl: "https://panel.idshop.or.id",
|
|
48
|
+
defaultModel: "openai/gpt-5-pro"
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function saveConfig(cfg) {
|
|
52
|
+
const current = getConfig();
|
|
53
|
+
const next = { ...current, ...cfg };
|
|
54
|
+
import_fs.default.writeFileSync(CONFIG_PATH, JSON.stringify(next, null, 2), "utf-8");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// src/index.ts
|
|
58
|
+
var program = new import_commander.Command();
|
|
59
|
+
function resolveBaseUrl() {
|
|
60
|
+
const cfg = getConfig();
|
|
61
|
+
return process.env.SARANGAI_BASE_URL || cfg.baseUrl || "https://api.sarang.ai";
|
|
62
|
+
}
|
|
63
|
+
function getGitBranch() {
|
|
64
|
+
try {
|
|
65
|
+
return (0, import_child_process.execSync)("git branch --show-current 2>/dev/null").toString().trim() || "no-git";
|
|
66
|
+
} catch {
|
|
67
|
+
return "no-git";
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function renderBanner(modelName) {
|
|
71
|
+
console.clear();
|
|
72
|
+
const banner = `
|
|
73
|
+
\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
|
|
74
|
+
\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
|
|
75
|
+
\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
|
|
76
|
+
\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
|
|
77
|
+
\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
|
|
78
|
+
\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
|
|
79
|
+
`;
|
|
80
|
+
console.log(import_chalk.default.cyanBright(banner));
|
|
81
|
+
console.log(import_chalk.default.gray(" Tips for getting started:"));
|
|
82
|
+
console.log(import_chalk.default.gray(" 1. Ketik pesan Anda langsung untuk chat streaming."));
|
|
83
|
+
console.log(import_chalk.default.gray(" 2. Perintah khusus: ") + import_chalk.default.yellow("/clear") + import_chalk.default.gray(" (bersihkan layar), ") + import_chalk.default.yellow("/model <id>") + import_chalk.default.gray(", ") + import_chalk.default.yellow("/exit") + import_chalk.default.gray("."));
|
|
84
|
+
console.log("");
|
|
85
|
+
const cwd = import_path2.default.basename(process.cwd());
|
|
86
|
+
const branch = getGitBranch();
|
|
87
|
+
const leftStatus = import_chalk.default.gray(`\u{1F4C2} ~/${cwd} `) + import_chalk.default.magenta(`(${branch})`);
|
|
88
|
+
const rightStatus = import_chalk.default.bgCyan.black(` ${modelName} `) + import_chalk.default.greenBright(" (ready)");
|
|
89
|
+
console.log(` ${leftStatus} ${rightStatus}`);
|
|
90
|
+
console.log(import_chalk.default.cyan(" " + "\u2500".repeat(65)));
|
|
91
|
+
console.log("");
|
|
92
|
+
}
|
|
93
|
+
async function startInteractiveSession(initialModel) {
|
|
94
|
+
const cfg = getConfig();
|
|
95
|
+
if (!cfg.apiKey) {
|
|
96
|
+
console.log(import_chalk.default.yellow("Silakan login terlebih dahulu: sarang login"));
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
let currentModel = initialModel || cfg.defaultModel || "minimax/minimax-m2.7";
|
|
100
|
+
const baseUrl = resolveBaseUrl();
|
|
101
|
+
const history = [];
|
|
102
|
+
renderBanner(currentModel);
|
|
103
|
+
const rl = import_readline.default.createInterface({
|
|
104
|
+
input: process.stdin,
|
|
105
|
+
output: process.stdout
|
|
106
|
+
});
|
|
107
|
+
const ask = () => {
|
|
108
|
+
rl.question(import_chalk.default.cyanBright("\u276F "), async (input) => {
|
|
109
|
+
var _a, _b, _c, _d, _e;
|
|
110
|
+
const trimmed = input.trim();
|
|
111
|
+
if (!trimmed) {
|
|
112
|
+
ask();
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (trimmed === "/exit" || trimmed === "exit" || trimmed === ":q") {
|
|
116
|
+
console.log(import_chalk.default.gray("\nSampai jumpa!\n"));
|
|
117
|
+
rl.close();
|
|
118
|
+
process.exit(0);
|
|
119
|
+
}
|
|
120
|
+
if (trimmed === "/clear" || trimmed === "clear") {
|
|
121
|
+
renderBanner(currentModel);
|
|
122
|
+
ask();
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (trimmed.startsWith("/model")) {
|
|
126
|
+
const parts = trimmed.split(" ");
|
|
127
|
+
if (parts[1]) {
|
|
128
|
+
currentModel = parts[1].trim();
|
|
129
|
+
saveConfig({ defaultModel: currentModel });
|
|
130
|
+
console.log(import_chalk.default.green(`
|
|
131
|
+
\u2714 Model dialihkan ke: ${currentModel}
|
|
132
|
+
`));
|
|
133
|
+
} else {
|
|
134
|
+
console.log(import_chalk.default.yellow(`
|
|
135
|
+
Format: /model <model-id> (contoh: /model openai/gpt-4o-mini)
|
|
136
|
+
`));
|
|
137
|
+
}
|
|
138
|
+
ask();
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
history.push({ role: "user", content: trimmed });
|
|
142
|
+
process.stdout.write(import_chalk.default.gray(`
|
|
143
|
+
[${currentModel}]
|
|
144
|
+
`));
|
|
145
|
+
try {
|
|
146
|
+
const res = await fetch(`${baseUrl}/api/gateway/v1/chat/completions`, {
|
|
147
|
+
method: "POST",
|
|
148
|
+
headers: {
|
|
149
|
+
"Content-Type": "application/json",
|
|
150
|
+
Authorization: `Bearer ${cfg.apiKey}`
|
|
151
|
+
},
|
|
152
|
+
body: JSON.stringify({
|
|
153
|
+
model: currentModel,
|
|
154
|
+
messages: history,
|
|
155
|
+
stream: true
|
|
156
|
+
})
|
|
157
|
+
});
|
|
158
|
+
if (!res.ok) {
|
|
159
|
+
const errText = await res.text();
|
|
160
|
+
let parsedErr;
|
|
161
|
+
try {
|
|
162
|
+
parsedErr = JSON.parse(errText);
|
|
163
|
+
} catch {
|
|
164
|
+
}
|
|
165
|
+
throw new Error(((_a = parsedErr == null ? void 0 : parsedErr.error) == null ? void 0 : _a.message) || `HTTP ${res.status}: ${errText.slice(0, 80)}`);
|
|
166
|
+
}
|
|
167
|
+
const reader = (_b = res.body) == null ? void 0 : _b.getReader();
|
|
168
|
+
const decoder = new TextDecoder();
|
|
169
|
+
let assistantReply = "";
|
|
170
|
+
if (reader) {
|
|
171
|
+
while (true) {
|
|
172
|
+
const { done, value } = await reader.read();
|
|
173
|
+
if (done) break;
|
|
174
|
+
const chunk = decoder.decode(value);
|
|
175
|
+
const lines = chunk.split("\n");
|
|
176
|
+
for (const line of lines) {
|
|
177
|
+
if (line.startsWith("data: ") && line !== "data: [DONE]") {
|
|
178
|
+
try {
|
|
179
|
+
const parsed = JSON.parse(line.slice(6));
|
|
180
|
+
const delta = (_e = (_d = (_c = parsed.choices) == null ? void 0 : _c[0]) == null ? void 0 : _d.delta) == null ? void 0 : _e.content;
|
|
181
|
+
if (delta) {
|
|
182
|
+
process.stdout.write(delta);
|
|
183
|
+
assistantReply += delta;
|
|
184
|
+
}
|
|
185
|
+
} catch {
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
console.log("\n");
|
|
191
|
+
history.push({ role: "assistant", content: assistantReply });
|
|
192
|
+
}
|
|
193
|
+
} catch (err) {
|
|
194
|
+
console.error(import_chalk.default.red(`
|
|
195
|
+
Error: ${err.message}
|
|
196
|
+
`));
|
|
197
|
+
}
|
|
198
|
+
ask();
|
|
199
|
+
});
|
|
200
|
+
};
|
|
201
|
+
ask();
|
|
202
|
+
}
|
|
203
|
+
program.name("sarang").description(import_chalk.default.cyanBright("SarangAI CLI \u2014 Gateway ratusan model AI langsung di terminal")).version("1.0.0").action(() => {
|
|
204
|
+
startInteractiveSession();
|
|
205
|
+
});
|
|
206
|
+
program.command("login").description("Hubungkan API Key SarangAI akun Anda").action(async () => {
|
|
207
|
+
const res = await (0, import_prompts.default)({
|
|
208
|
+
type: "password",
|
|
209
|
+
name: "key",
|
|
210
|
+
message: "Masukkan SarangAI API Key Anda (dari dashboard panel):"
|
|
211
|
+
});
|
|
212
|
+
if (!res.key) {
|
|
213
|
+
console.log(import_chalk.default.red("Login dibatalkan."));
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
saveConfig({ apiKey: res.key.trim() });
|
|
217
|
+
console.log(import_chalk.default.green("\u2714 API Key berhasil disimpan di ~/.sarangairc"));
|
|
218
|
+
});
|
|
219
|
+
program.command("balance").description("Cek sisa saldo kredit akun SarangAI Anda").action(async () => {
|
|
220
|
+
const cfg = getConfig();
|
|
221
|
+
if (!cfg.apiKey) {
|
|
222
|
+
console.log(import_chalk.default.yellow("Anda belum login. Jalankan: sarang login"));
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
const baseUrl = resolveBaseUrl();
|
|
226
|
+
const spinner = (0, import_ora.default)("Mengambil status saldo...").start();
|
|
227
|
+
try {
|
|
228
|
+
const res = await fetch(`${baseUrl}/api/gateway/v1/balance`, {
|
|
229
|
+
headers: { Authorization: `Bearer ${cfg.apiKey}` }
|
|
230
|
+
});
|
|
231
|
+
const rawText = await res.text();
|
|
232
|
+
let data = {};
|
|
233
|
+
try {
|
|
234
|
+
data = JSON.parse(rawText);
|
|
235
|
+
} catch {
|
|
236
|
+
throw new Error(`Endpoint mengembalikan respons non-JSON (${res.status}): ${rawText.slice(0, 120)}...`);
|
|
237
|
+
}
|
|
238
|
+
spinner.stop();
|
|
239
|
+
if (!res.ok) throw new Error(data.error || `HTTP ${res.status}`);
|
|
240
|
+
console.log(import_chalk.default.bold("\nStatus Akun SarangAI:"));
|
|
241
|
+
console.log(`Credit Balance : ${import_chalk.default.greenBright(data.balance ?? 0)} CREDIT`);
|
|
242
|
+
console.log(`Email : ${import_chalk.default.gray(data.email || "\u2014")}
|
|
243
|
+
`);
|
|
244
|
+
} catch (err) {
|
|
245
|
+
spinner.fail(import_chalk.default.red(err.message));
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
program.command("models").option("-s, --search <keyword>", "Filter nama model (contoh: claude, deepseek, free)").description("Daftar model AI yang tersedia di SarangAI Gateway").action(async (cmd) => {
|
|
249
|
+
var _a;
|
|
250
|
+
const cfg = getConfig();
|
|
251
|
+
if (!cfg.apiKey) {
|
|
252
|
+
console.log(import_chalk.default.yellow("Anda belum login. Jalankan: sarang login"));
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
const baseUrl = resolveBaseUrl();
|
|
256
|
+
const spinner = (0, import_ora.default)("Mengambil katalog model...").start();
|
|
257
|
+
try {
|
|
258
|
+
const res = await fetch(`${baseUrl}/api/gateway/v1/models`, {
|
|
259
|
+
headers: { Authorization: `Bearer ${cfg.apiKey}` }
|
|
260
|
+
});
|
|
261
|
+
const rawText = await res.text();
|
|
262
|
+
let data = {};
|
|
263
|
+
try {
|
|
264
|
+
data = JSON.parse(rawText);
|
|
265
|
+
} catch {
|
|
266
|
+
throw new Error(`Respons tidak valid: ${rawText.slice(0, 100)}`);
|
|
267
|
+
}
|
|
268
|
+
spinner.stop();
|
|
269
|
+
if (!res.ok) throw new Error(((_a = data.error) == null ? void 0 : _a.message) || data.error || `HTTP ${res.status}`);
|
|
270
|
+
let list = Array.isArray(data) ? data : data.data || [];
|
|
271
|
+
if (list.length === 0) {
|
|
272
|
+
console.log(import_chalk.default.yellow("Tidak ada model aktif yang ditemukan."));
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
if (cmd.search) {
|
|
276
|
+
const query = cmd.search.toLowerCase();
|
|
277
|
+
list = list.filter((m) => {
|
|
278
|
+
const id = (m.id || m.modelId || m.name || "").toLowerCase();
|
|
279
|
+
return id.includes(query);
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
if (list.length === 0) {
|
|
283
|
+
console.log(import_chalk.default.yellow(`
|
|
284
|
+
Tidak ada model yang cocok dengan kata kunci "${cmd.search}".
|
|
285
|
+
`));
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
console.log(import_chalk.default.bold(`
|
|
289
|
+
Model Tersedia (${list.length}${cmd.search ? ` cocok dengan "${cmd.search}"` : ""}):`));
|
|
290
|
+
console.log(import_chalk.default.gray("---------------------------------------------------------"));
|
|
291
|
+
list.forEach((m) => {
|
|
292
|
+
const id = m.id || m.modelId || m.name;
|
|
293
|
+
const isCurrent = id === cfg.defaultModel;
|
|
294
|
+
const prefix = isCurrent ? import_chalk.default.green("\u2714 [Aktif] ") : " ";
|
|
295
|
+
console.log(`${prefix}${import_chalk.default.cyan(id)}`);
|
|
296
|
+
});
|
|
297
|
+
console.log(import_chalk.default.gray("---------------------------------------------------------"));
|
|
298
|
+
console.log(import_chalk.default.dim("Ganti model default : sarang set-model <model-id>"));
|
|
299
|
+
console.log(import_chalk.default.dim('Chat model tertentu : sarang chat -m <model-id> "..."\n'));
|
|
300
|
+
} catch (err) {
|
|
301
|
+
spinner.fail(import_chalk.default.red(err.message));
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
program.command("set-model [modelId]").description("Atur model default untuk chat").action(async (modelId) => {
|
|
305
|
+
let target = modelId;
|
|
306
|
+
if (!target) {
|
|
307
|
+
const res = await (0, import_prompts.default)({
|
|
308
|
+
type: "text",
|
|
309
|
+
name: "model",
|
|
310
|
+
message: "Masukkan Model ID default baru (contoh: minimax/minimax-m2.7):"
|
|
311
|
+
});
|
|
312
|
+
target = res.model;
|
|
313
|
+
}
|
|
314
|
+
if (!target) return;
|
|
315
|
+
saveConfig({ defaultModel: target.trim() });
|
|
316
|
+
console.log(import_chalk.default.green(`\u2714 Default model berhasil diubah ke: ${target.trim()}`));
|
|
317
|
+
});
|
|
318
|
+
program.command("chat [prompt...]").option("-m, --model <modelId>", "Pilih model AI spesifik").description("Kirim instruksi / chat ke AI (Mendukung streaming teks atau Unix Pipe)").action(async (promptArr, cmd) => {
|
|
319
|
+
var _a, _b, _c, _d, _e;
|
|
320
|
+
let prompt = promptArr == null ? void 0 : promptArr.join(" ");
|
|
321
|
+
if (!process.stdin.isTTY) {
|
|
322
|
+
const chunks = [];
|
|
323
|
+
for await (const chunk of process.stdin) {
|
|
324
|
+
chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
|
325
|
+
}
|
|
326
|
+
const pipedData = Buffer.concat(chunks).toString("utf-8").trim();
|
|
327
|
+
prompt = prompt ? `${prompt}
|
|
328
|
+
|
|
329
|
+
${pipedData}` : pipedData;
|
|
330
|
+
}
|
|
331
|
+
if (!prompt) {
|
|
332
|
+
await startInteractiveSession(cmd.model);
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
const cfg = getConfig();
|
|
336
|
+
if (!cfg.apiKey) {
|
|
337
|
+
console.log(import_chalk.default.yellow("Silakan login terlebih dahulu: sarang login"));
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
const baseUrl = resolveBaseUrl();
|
|
341
|
+
const selectedModel = cmd.model || cfg.defaultModel || "minimax/minimax-m2.7";
|
|
342
|
+
console.log(import_chalk.default.gray(`
|
|
343
|
+
[Model: ${selectedModel}]`));
|
|
344
|
+
try {
|
|
345
|
+
const res = await fetch(`${baseUrl}/api/gateway/v1/chat/completions`, {
|
|
346
|
+
method: "POST",
|
|
347
|
+
headers: {
|
|
348
|
+
"Content-Type": "application/json",
|
|
349
|
+
Authorization: `Bearer ${cfg.apiKey}`
|
|
350
|
+
},
|
|
351
|
+
body: JSON.stringify({
|
|
352
|
+
model: selectedModel,
|
|
353
|
+
messages: [{ role: "user", content: prompt }],
|
|
354
|
+
stream: true
|
|
355
|
+
})
|
|
356
|
+
});
|
|
357
|
+
if (!res.ok) {
|
|
358
|
+
const errText = await res.text();
|
|
359
|
+
let parsedErr;
|
|
360
|
+
try {
|
|
361
|
+
parsedErr = JSON.parse(errText);
|
|
362
|
+
} catch {
|
|
363
|
+
}
|
|
364
|
+
throw new Error(((_a = parsedErr == null ? void 0 : parsedErr.error) == null ? void 0 : _a.message) || `HTTP ${res.status}: ${errText.slice(0, 80)}`);
|
|
365
|
+
}
|
|
366
|
+
const reader = (_b = res.body) == null ? void 0 : _b.getReader();
|
|
367
|
+
const decoder = new TextDecoder();
|
|
368
|
+
if (reader) {
|
|
369
|
+
while (true) {
|
|
370
|
+
const { done, value } = await reader.read();
|
|
371
|
+
if (done) break;
|
|
372
|
+
const chunk = decoder.decode(value);
|
|
373
|
+
const lines = chunk.split("\n");
|
|
374
|
+
for (const line of lines) {
|
|
375
|
+
if (line.startsWith("data: ") && line !== "data: [DONE]") {
|
|
376
|
+
try {
|
|
377
|
+
const parsed = JSON.parse(line.slice(6));
|
|
378
|
+
const content = (_e = (_d = (_c = parsed.choices) == null ? void 0 : _c[0]) == null ? void 0 : _d.delta) == null ? void 0 : _e.content;
|
|
379
|
+
if (content) process.stdout.write(content);
|
|
380
|
+
} catch {
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
console.log("\n");
|
|
386
|
+
}
|
|
387
|
+
} catch (err) {
|
|
388
|
+
console.error(import_chalk.default.red(`
|
|
389
|
+
Error: ${err.message}`));
|
|
390
|
+
}
|
|
391
|
+
});
|
|
392
|
+
program.parse(process.argv);
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "sarangai-cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "CLI resmi SarangAI — Gateway multi-model AI langsung dari terminal Anda",
|
|
5
|
+
"main": "dist/index.cjs",
|
|
6
|
+
"bin": {
|
|
7
|
+
"sarang": "dist/index.cjs",
|
|
8
|
+
"sarangai": "dist/index.cjs"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsup",
|
|
15
|
+
"prepublishOnly": "npm run build"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"sarangai",
|
|
19
|
+
"cli",
|
|
20
|
+
"ai",
|
|
21
|
+
"llm",
|
|
22
|
+
"openai",
|
|
23
|
+
"deepseek",
|
|
24
|
+
"claude",
|
|
25
|
+
"gateway"
|
|
26
|
+
],
|
|
27
|
+
"author": "SarangAI Team",
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"chalk": "^4.1.2",
|
|
31
|
+
"commander": "^12.1.0",
|
|
32
|
+
"ora": "^5.4.1",
|
|
33
|
+
"prompts": "^2.4.2",
|
|
34
|
+
"readline-sync": "^1.4.10"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/node": "^20.14.9",
|
|
38
|
+
"@types/prompts": "^2.4.9",
|
|
39
|
+
"@types/readline-sync": "^1.4.8",
|
|
40
|
+
"tsup": "^8.5.1",
|
|
41
|
+
"typescript": "^5.5.2"
|
|
42
|
+
}
|
|
43
|
+
}
|