tmpa-cli 1.0.0 → 1.0.1

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 +117 -176
  2. package/package.json +1 -1
package/bin/index.js CHANGED
@@ -4,212 +4,153 @@ const readline = require('readline');
4
4
  const fs = require('fs');
5
5
  const path = require('path');
6
6
  const os = require('os');
7
+ const { execSync } = require('child_process');
7
8
 
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";
9
+ const CONFIG_FILE = path.join(os.homedir(), '.tmpa_config.json');
10
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'));
11
+ function loadConfig() {
12
+ if (fs.existsSync(CONFIG_FILE)) {
13
+ try {
14
+ return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
15
+ } catch (e) {
16
+ return {};
24
17
  }
25
- } catch (err) {
26
- return null;
27
18
  }
28
- return null;
19
+ return {};
29
20
  }
30
21
 
31
22
  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
- }
23
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
83
24
  }
84
25
 
85
26
  function showBanner() {
86
27
  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`));
28
+ console.log(`\x1b[36m
29
+ ████████╗███╗ ███╗██████╗ █████╗
30
+ ╚══██╔══╝████╗ ████║██╔══██╗██╔══██╗
31
+ ██║ ██╔████╔██║██████╔╝███████║
32
+ ██║ ██║╚██╔╝██║██╔═══╝ ██╔══██║
33
+ ██║ ██║ ╚═╝ ██║██║ ██║ ██║
34
+ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝
35
+ \x1b[0m───────────────────────────────────────────────────
36
+ \x1b[32mThe Multi Platform AI [Interactive Mode]\x1b[0m
37
+ /config : Ubah API | /connect : Hubungkan Web | /uninstall : Hapus CLI | /exit : Keluar
38
+ ───────────────────────────────────────────────────\n`);
107
39
  }
108
40
 
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);
41
+ const rl = readline.createInterface({
42
+ input: process.stdin,
43
+ output: process.stdout
44
+ });
45
+
46
+ let config = loadConfig();
47
+
48
+ function askConfig(callback) {
49
+ rl.question('🔑 Masukkan API Key TMPA/Gemini: ', (apiKey) => {
50
+ rl.question('🌐 Masukkan Endpoint API (Tekan Enter untuk Default Gemini): ', (endpoint) => {
51
+ config.apiKey = apiKey.trim();
52
+ config.endpoint = endpoint.trim() || 'https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent';
53
+ saveConfig(config);
54
+ console.log(' Konfigurasi berhasil disimpan!\n');
55
+ if (callback) callback();
138
56
  });
139
57
  });
140
58
  }
141
59
 
142
- async function startInteractiveSession() {
143
- const config = getConfig();
144
-
145
- if (!config || !config.apiKey) {
146
- promptConfig(startInteractiveSession);
147
- return;
60
+ async function handleChat(prompt) {
61
+ if (!config.apiKey) {
62
+ console.log('⚠️ API Key belum diset!');
63
+ return askConfig(() => startPrompt());
148
64
  }
149
65
 
150
- showBanner();
66
+ console.log('\x1b[33mTMPA CLI memproses...\x1b[0m');
151
67
 
152
- const rl = readline.createInterface({
153
- input: process.stdin,
154
- output: process.stdout,
155
- prompt: color.rgb(0, 210, 255, color.bold('TMPA > '))
156
- });
68
+ try {
69
+ const url = config.endpoint.includes('googleapis.com')
70
+ ? `${config.endpoint}?key=${config.apiKey}`
71
+ : config.endpoint;
157
72
 
158
- rl.prompt();
73
+ const bodyData = config.endpoint.includes('googleapis.com')
74
+ ? { contents: [{ parts: [{ text: prompt }] }] }
75
+ : { prompt: prompt, apiKey: config.apiKey };
159
76
 
160
- rl.on('line', async (line) => {
161
- const input = line.trim();
77
+ const response = await fetch(url, {
78
+ method: 'POST',
79
+ headers: { 'Content-Type': 'application/json' },
80
+ body: JSON.stringify(bodyData)
81
+ });
162
82
 
163
- if (input === '/exit' || input === 'exit') {
164
- console.log(color.gray('\nSampai jumpa di TMPA CLI!\n'));
165
- process.exit(0);
83
+ const data = await response.json();
84
+ let reply = 'Tidak ada respon dari server.';
85
+
86
+ if (data.candidates && data.candidates[0]?.content?.parts[0]?.text) {
87
+ reply = data.candidates[0].content.parts[0].text;
88
+ } else if (data.reply) {
89
+ reply = data.reply;
90
+ } else if (data.message) {
91
+ reply = data.message;
166
92
  }
167
93
 
168
- if (input === '/clear' || input === 'clear') {
169
- showBanner();
170
- rl.prompt();
171
- return;
172
- }
94
+ console.log(`\n\x1b[34mTMPA CLI :\x1b[0m ${reply}\n`);
95
+ } catch (error) {
96
+ console.log(`\n❌ Error: ${error.message}\n`);
97
+ }
173
98
 
174
- if (input === '/config') {
175
- rl.close();
176
- promptConfig(startInteractiveSession);
177
- return;
178
- }
99
+ startPrompt();
100
+ }
179
101
 
180
- if (input === '') {
181
- rl.prompt();
182
- return;
102
+ function handleUninstall() {
103
+ rl.question('⚠️ Apakah Anda yakin ingin menghapus TMPA CLI? (y/N): ', (answer) => {
104
+ if (answer.toLowerCase() === 'y' || answer.toLowerCase() === 'ya') {
105
+ console.log('\n⏳ Menghapus TMPA CLI dan konfigurasi...');
106
+ try {
107
+ if (fs.existsSync(CONFIG_FILE)) {
108
+ fs.unlinkSync(CONFIG_FILE);
109
+ }
110
+ console.log('✅ Konfigurasi lokal dibersihkan.');
111
+ execSync('npm uninstall -g tmpa-cli', { stdio: 'inherit' });
112
+ console.log('\n👋 TMPA CLI berhasil di-uninstall. Sampai jumpa!\n');
113
+ process.exit(0);
114
+ } catch (err) {
115
+ console.log('\n❌ Gagal uninstall otomatis. Jalankan: npm uninstall -g tmpa-cli secara manual.\n');
116
+ startPrompt();
117
+ }
118
+ } else {
119
+ console.log('Batal menghapus.\n');
120
+ startPrompt();
183
121
  }
122
+ });
123
+ }
184
124
 
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
- }
125
+ function startPrompt() {
126
+ rl.question('\x1b[36mTMPA >\x1b[0m ', (input) => {
127
+ const cmd = input.trim();
209
128
 
210
- console.log(`${color.bold('TMPA CLI :')} ${answer}\n`);
211
- rl.prompt();
129
+ if (cmd === '/exit') {
130
+ console.log('Sampai jumpa! 👋');
131
+ process.exit(0);
132
+ } else if (cmd === '/clear') {
133
+ showBanner();
134
+ startPrompt();
135
+ } else if (cmd === '/config') {
136
+ askConfig(() => startPrompt());
137
+ } else if (cmd === '/connect') {
138
+ console.log('\n🔗 [COMING SOON] Fitur login & integrasi website TMPA akan segera hadir pada update berikutnya!\n');
139
+ startPrompt();
140
+ } else if (cmd === '/uninstall') {
141
+ handleUninstall();
142
+ } else if (cmd === '') {
143
+ startPrompt();
144
+ } else {
145
+ handleChat(cmd);
146
+ }
212
147
  });
213
148
  }
214
149
 
215
- startInteractiveSession();
150
+ // Jalankan Program
151
+ showBanner();
152
+ if (!config.apiKey) {
153
+ askConfig(() => startPrompt());
154
+ } else {
155
+ startPrompt();
156
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tmpa-cli",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "",
5
5
  "main": "bin/index.js",
6
6
  "bin": {