tmpa-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.
Files changed (2) hide show
  1. package/bin/index.js +215 -0
  2. package/package.json +21 -0
package/bin/index.js ADDED
@@ -0,0 +1,215 @@
1
+ #!/usr/bin/env node
2
+
3
+ const readline = require('readline');
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const os = require('os');
7
+
8
+ const CONFIG_PATH = path.join(os.homedir(), '.tmpa_config.json');
9
+ const DEFAULT_ENDPOINT = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent";
10
+
11
+ const color = {
12
+ gray: (t) => `\x1b[90m${t}\x1b[0m`,
13
+ bold: (t) => `\x1b[1m${t}\x1b[0m`,
14
+ yellow: (t) => `\x1b[33m${t}\x1b[0m`,
15
+ green: (t) => `\x1b[32m${t}\x1b[0m`,
16
+ red: (t) => `\x1b[31m${t}\x1b[0m`,
17
+ rgb: (r, g, b, t) => `\x1b[38;2;${r};${g};${b}m${t}\x1b[0m`
18
+ };
19
+
20
+ function getConfig() {
21
+ try {
22
+ if (fs.existsSync(CONFIG_PATH)) {
23
+ return JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
24
+ }
25
+ } catch (err) {
26
+ return null;
27
+ }
28
+ return null;
29
+ }
30
+
31
+ function saveConfig(config) {
32
+ try {
33
+ fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), 'utf8');
34
+ } catch (err) {
35
+ console.error(color.red('Gagal menyimpan konfigurasi!'));
36
+ }
37
+ }
38
+
39
+ async function callTmpaApi(prompt, config) {
40
+ const apiKey = config.apiKey;
41
+ // Gunakan fallback defaultEndpoint jika config lama belum punya apiEndpoint
42
+ const apiEndpoint = config.apiEndpoint || DEFAULT_ENDPOINT;
43
+
44
+ if (apiEndpoint.includes('googleapis.com')) {
45
+ const url = apiEndpoint.includes('key=') ? apiEndpoint : `${apiEndpoint}?key=${apiKey}`;
46
+ const response = await fetch(url, {
47
+ method: 'POST',
48
+ headers: { 'Content-Type': 'application/json' },
49
+ body: JSON.stringify({
50
+ contents: [{ parts: [{ text: prompt }] }]
51
+ })
52
+ });
53
+
54
+ if (!response.ok) {
55
+ const errData = await response.json().catch(() => ({}));
56
+ throw new Error(errData.error?.message || `HTTP Error ${response.status}`);
57
+ }
58
+
59
+ const data = await response.json();
60
+ const text = data.candidates?.[0]?.content?.parts?.[0]?.text;
61
+ if (!text) throw new Error('Respons kosong dari API.');
62
+ return text.trim();
63
+ } else {
64
+ const response = await fetch(apiEndpoint, {
65
+ method: 'POST',
66
+ headers: {
67
+ 'Content-Type': 'application/json',
68
+ 'Authorization': `Bearer ${apiKey}`,
69
+ 'x-api-key': apiKey
70
+ },
71
+ body: JSON.stringify({ prompt: prompt, messages: [{ role: 'user', content: prompt }] })
72
+ });
73
+
74
+ if (!response.ok) {
75
+ const errData = await response.json().catch(() => ({}));
76
+ throw new Error(errData.message || errData.error || `HTTP Error ${response.status}`);
77
+ }
78
+
79
+ const data = await response.json();
80
+ const text = data.reply || data.response || data.choices?.[0]?.message?.content || data.text || (typeof data === 'string' ? data : JSON.stringify(data));
81
+ return String(text).trim();
82
+ }
83
+ }
84
+
85
+ function showBanner() {
86
+ console.clear();
87
+ const logoLines = [
88
+ " ████████╗███╗ ███╗██████╗ █████╗ ",
89
+ " ╚══██╔══╝████╗ ████║██╔══██╗██╔══██╗",
90
+ " ██║ ██╔████╔██║██████╔╝███████║",
91
+ " ██║ ██║╚██╔╝██║██╔═══╝ ██╔══██║",
92
+ " ██║ ██║ ╚═╝ ██║██║ ██║ ██║",
93
+ " ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝"
94
+ ];
95
+ const colors = [[0, 210, 255], [35, 180, 252], [70, 150, 249], [105, 120, 246], [140, 90, 243], [180, 60, 240]];
96
+
97
+ console.log("");
98
+ logoLines.forEach((line, i) => {
99
+ const [r, g, b] = colors[i];
100
+ console.log(color.rgb(r, g, b, color.bold(line)));
101
+ });
102
+
103
+ console.log(color.gray(` ───────────────────────────────────────────────────`));
104
+ console.log(` ${color.bold('The Multi Platform AI')} ${color.green('[Interactive Mode]')}`);
105
+ console.log(color.gray(` /config : Ubah API | /clear : Hapus Layar | /exit : Keluar`));
106
+ console.log(color.gray(` ───────────────────────────────────────────────────\n`));
107
+ }
108
+
109
+ function promptConfig(callback) {
110
+ console.clear();
111
+ console.log(color.rgb(0, 210, 255, color.bold('\n=== KONFIGURASI TMPA CLI ===\n')));
112
+ console.log('Masukkan pengaturan API milik kamu untuk melanjutkan.\n');
113
+
114
+ const rlConfig = readline.createInterface({
115
+ input: process.stdin,
116
+ output: process.stdout
117
+ });
118
+
119
+ rlConfig.question(color.yellow('1. Masukkan API Key: '), (key) => {
120
+ const cleanKey = key.trim();
121
+ if (!cleanKey) {
122
+ console.log(color.red('API Key tidak boleh kosong!\n'));
123
+ rlConfig.close();
124
+ return promptConfig(callback);
125
+ }
126
+
127
+ rlConfig.question(color.yellow(`2. Masukkan URL Endpoint API\n (Tekan Enter untuk default Gemini API): `), (endpoint) => {
128
+ const cleanEndpoint = endpoint.trim() || DEFAULT_ENDPOINT;
129
+
130
+ saveConfig({
131
+ apiKey: cleanKey,
132
+ apiEndpoint: cleanEndpoint
133
+ });
134
+
135
+ console.log(color.green('\nKonfigurasi disimpan!'));
136
+ rlConfig.close();
137
+ setTimeout(callback, 1000);
138
+ });
139
+ });
140
+ }
141
+
142
+ async function startInteractiveSession() {
143
+ const config = getConfig();
144
+
145
+ if (!config || !config.apiKey) {
146
+ promptConfig(startInteractiveSession);
147
+ return;
148
+ }
149
+
150
+ showBanner();
151
+
152
+ const rl = readline.createInterface({
153
+ input: process.stdin,
154
+ output: process.stdout,
155
+ prompt: color.rgb(0, 210, 255, color.bold('TMPA > '))
156
+ });
157
+
158
+ rl.prompt();
159
+
160
+ rl.on('line', async (line) => {
161
+ const input = line.trim();
162
+
163
+ if (input === '/exit' || input === 'exit') {
164
+ console.log(color.gray('\nSampai jumpa di TMPA CLI!\n'));
165
+ process.exit(0);
166
+ }
167
+
168
+ if (input === '/clear' || input === 'clear') {
169
+ showBanner();
170
+ rl.prompt();
171
+ return;
172
+ }
173
+
174
+ if (input === '/config') {
175
+ rl.close();
176
+ promptConfig(startInteractiveSession);
177
+ return;
178
+ }
179
+
180
+ if (input === '') {
181
+ rl.prompt();
182
+ return;
183
+ }
184
+
185
+ const loadingFrames = [
186
+ 'TMPA CLI memproses.',
187
+ 'TMPA CLI memproses..',
188
+ 'TMPA CLI memproses...',
189
+ 'TMPA CLI memproses..'
190
+ ];
191
+
192
+ let frameIndex = 0;
193
+ const loadingInterval = setInterval(() => {
194
+ readline.cursorTo(process.stdout, 0);
195
+ process.stdout.write(color.gray(loadingFrames[frameIndex]));
196
+ frameIndex = (frameIndex + 1) % loadingFrames.length;
197
+ }, 250);
198
+
199
+ let answer = "";
200
+ try {
201
+ answer = await callTmpaApi(input, config);
202
+ } catch (error) {
203
+ answer = color.red(`Error: ${error.message}`);
204
+ } finally {
205
+ clearInterval(loadingInterval);
206
+ readline.cursorTo(process.stdout, 0);
207
+ readline.clearLine(process.stdout, 0);
208
+ }
209
+
210
+ console.log(`${color.bold('TMPA CLI :')} ${answer}\n`);
211
+ rl.prompt();
212
+ });
213
+ }
214
+
215
+ startInteractiveSession();
package/package.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "tmpa-cli",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "main": "bin/index.js",
6
+ "bin": {
7
+ "tmpa": "./bin/index.js"
8
+ },
9
+ "type": "commonjs",
10
+ "scripts": {
11
+ "test": "echo \"Error: no test specified\" && exit 1"
12
+ },
13
+ "keywords": [],
14
+ "author": "",
15
+ "license": "ISC",
16
+ "dependencies": {
17
+ "chalk": "^4.1.2",
18
+ "commander": "^11.0.0",
19
+ "ora": "^5.4.1"
20
+ }
21
+ }