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.
- package/bin/index.js +117 -176
- 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
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
|
19
|
+
return {};
|
|
29
20
|
}
|
|
30
21
|
|
|
31
22
|
function saveConfig(config) {
|
|
32
|
-
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
console.log(
|
|
123
|
-
|
|
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
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
-
|
|
66
|
+
console.log('\x1b[33mTMPA CLI memproses...\x1b[0m');
|
|
151
67
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
});
|
|
68
|
+
try {
|
|
69
|
+
const url = config.endpoint.includes('googleapis.com')
|
|
70
|
+
? `${config.endpoint}?key=${config.apiKey}`
|
|
71
|
+
: config.endpoint;
|
|
157
72
|
|
|
158
|
-
|
|
73
|
+
const bodyData = config.endpoint.includes('googleapis.com')
|
|
74
|
+
? { contents: [{ parts: [{ text: prompt }] }] }
|
|
75
|
+
: { prompt: prompt, apiKey: config.apiKey };
|
|
159
76
|
|
|
160
|
-
|
|
161
|
-
|
|
77
|
+
const response = await fetch(url, {
|
|
78
|
+
method: 'POST',
|
|
79
|
+
headers: { 'Content-Type': 'application/json' },
|
|
80
|
+
body: JSON.stringify(bodyData)
|
|
81
|
+
});
|
|
162
82
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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
|
-
|
|
175
|
-
|
|
176
|
-
promptConfig(startInteractiveSession);
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
99
|
+
startPrompt();
|
|
100
|
+
}
|
|
179
101
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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
|
-
|
|
211
|
-
|
|
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
|
-
|
|
150
|
+
// Jalankan Program
|
|
151
|
+
showBanner();
|
|
152
|
+
if (!config.apiKey) {
|
|
153
|
+
askConfig(() => startPrompt());
|
|
154
|
+
} else {
|
|
155
|
+
startPrompt();
|
|
156
|
+
}
|