tmpa-cli 1.0.20 → 1.0.22
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 +384 -120
- package/package.json +1 -1
package/bin/index.js
CHANGED
|
@@ -43,6 +43,50 @@ const PROVIDERS = {
|
|
|
43
43
|
'7': { name: 'Custom / Auto-Detect', endpoint: '', defaultModel: '' }
|
|
44
44
|
};
|
|
45
45
|
|
|
46
|
+
// 100+ POPULAR MCP PRESETS LIST
|
|
47
|
+
const MCP_PRESETS = [
|
|
48
|
+
{ id: 'custom', name: '🛠️ Custom / Lainnya', category: 'General', type: 'custom', desc: 'Input Manual Name, Target, & Key' },
|
|
49
|
+
|
|
50
|
+
// Cloud, AI & UI
|
|
51
|
+
{ id: 'google-stitch', name: '⚡ Google Stitch', category: 'Cloud & AI', type: 'direct-api', endpoint: 'https://stitch.googleapis.com/v1', reqAuth: 'GOOGLE_STITCH_API_KEY', desc: 'UI & App Prototyping MCP by Google' },
|
|
52
|
+
{ id: 'supabase', name: '⚡ Supabase MCP', category: 'Cloud & AI', type: 'direct-api', endpoint: 'https://api.supabase.com/mcp', reqAuth: 'SUPABASE_ACCESS_TOKEN', desc: 'Database, Auth & Storage Management' },
|
|
53
|
+
{ id: 'cloudflare-workers', name: '☁️ Cloudflare Workers', category: 'Cloud & AI', type: 'stdio', command: 'npx -y @cloudflare/mcp-server-cloudflare', reqAuth: 'CLOUDFLARE_API_TOKEN', desc: 'Deploy & manage edge functions' },
|
|
54
|
+
{ id: 'vercel', name: '▲ Vercel MCP', category: 'Cloud & AI', type: 'direct-api', endpoint: 'https://api.vercel.com/v1/mcp', reqAuth: 'VERCEL_TOKEN', desc: 'Manage projects & deployments' },
|
|
55
|
+
{ id: 'railway', name: '🚂 Railway MCP', category: 'Cloud & AI', type: 'direct-api', endpoint: 'https://backboard.railway.app/mcp', reqAuth: 'RAILWAY_API_TOKEN', desc: 'Cloud infrastructure & DB runner' },
|
|
56
|
+
{ id: 'firebase', name: '🔥 Firebase MCP', category: 'Cloud & AI', type: 'stdio', command: 'npx -y firebase-mcp-server', reqAuth: 'FIREBASE_TOKEN', desc: 'Firestore, Auth & Realtime DB' },
|
|
57
|
+
{ id: 'huggingface', name: '🤗 HuggingFace Spaces', category: 'Cloud & AI', type: 'direct-api', endpoint: 'https://huggingface.co/api/mcp', reqAuth: 'HF_TOKEN', desc: 'Run open-source AI models & spaces' },
|
|
58
|
+
{ id: 'replicate', name: '🌀 Replicate MCP', category: 'Cloud & AI', type: 'stdio', command: 'npx -y @replicate/mcp-server', reqAuth: 'REPLICATE_API_TOKEN', desc: 'Run image/video AI generation models' },
|
|
59
|
+
|
|
60
|
+
// Developer Tools & VCS
|
|
61
|
+
{ id: 'github', name: '🐙 GitHub Official', category: 'Developer Tools', type: 'stdio', command: 'npx -y @modelcontextprotocol/server-github', reqAuth: 'GITHUB_PERSONAL_ACCESS_TOKEN', desc: 'Repository, Issues, PRs & Gists' },
|
|
62
|
+
{ id: 'gitlab', name: '🦊 GitLab MCP', category: 'Developer Tools', type: 'stdio', command: 'npx -y @modelcontextprotocol/server-gitlab', reqAuth: 'GITLAB_PERSONAL_ACCESS_TOKEN', desc: 'GitLab projects & CI/CD pipelines' },
|
|
63
|
+
{ id: 'postman', name: '🚀 Postman MCP', category: 'Developer Tools', type: 'stdio', command: 'npx -y @postman/mcp-server', reqAuth: 'POSTMAN_API_KEY', desc: 'API Collection testing & documentation' },
|
|
64
|
+
{ id: 'sentry', name: '🛡️ Sentry Error Tracker', category: 'Developer Tools', type: 'stdio', command: 'npx -y @sentry/mcp-server', reqAuth: 'SENTRY_AUTH_TOKEN', desc: 'Inspect application bugs & stacktraces' },
|
|
65
|
+
{ id: 'docker', name: '🐳 Docker Local MCP', category: 'Developer Tools', type: 'stdio', command: 'npx -y @modelcontextprotocol/server-docker', desc: 'Manage local containers & images' },
|
|
66
|
+
|
|
67
|
+
// Databases & Storage
|
|
68
|
+
{ id: 'postgresql', name: '🗄️ PostgreSQL', category: 'Databases', type: 'stdio', command: 'npx -y @modelcontextprotocol/server-postgres', reqAuth: 'DATABASE_URL', desc: 'Direct SQL query execution' },
|
|
69
|
+
{ id: 'mysql', name: '🐬 MySQL MCP', category: 'Databases', type: 'stdio', command: 'npx -y mysql-mcp-server', reqAuth: 'MYSQL_CONNECTION_STRING', desc: 'MySQL relational database runner' },
|
|
70
|
+
{ id: 'sqlite', name: '📦 SQLite Local', category: 'Databases', type: 'stdio', command: 'npx -y @modelcontextprotocol/server-sqlite', desc: 'Lightweight local database query tool' },
|
|
71
|
+
{ id: 'mongodb', name: '🍃 MongoDB MCP', category: 'Databases', type: 'stdio', command: 'npx -y mongodb-mcp-server', reqAuth: 'MONGODB_URI', desc: 'NoSQL collection queries & aggregation' },
|
|
72
|
+
{ id: 'redis', name: '🔴 Redis Cache', category: 'Databases', type: 'stdio', command: 'npx -y @modelcontextprotocol/server-redis', reqAuth: 'REDIS_URL', desc: 'Key-value data structure store' },
|
|
73
|
+
{ id: 'pinecone', name: '🌲 Pinecone Vector DB', category: 'Databases', type: 'direct-api', endpoint: 'https://api.pinecone.io/mcp', reqAuth: 'PINECONE_API_KEY', desc: 'Vector database for AI embeddings' },
|
|
74
|
+
|
|
75
|
+
// Search & Web Scraping
|
|
76
|
+
{ id: 'brave-search', name: '🦁 Brave Search', category: 'Search & Scraping', type: 'stdio', command: 'npx -y @modelcontextprotocol/server-brave-search', reqAuth: 'BRAVE_API_KEY', desc: 'Real-time global web search engine' },
|
|
77
|
+
{ id: 'tavily', name: '🔍 Tavily AI Search', category: 'Search & Scraping', type: 'stdio', command: 'npx -y tavily-mcp', reqAuth: 'TAVILY_API_KEY', desc: 'Optimized search engine for LLMs' },
|
|
78
|
+
{ id: 'puppeteer', name: '🌐 Puppeteer Scraper', category: 'Search & Scraping', type: 'stdio', command: 'npx -y @modelcontextprotocol/server-puppeteer', desc: 'Headless browser web automation & screenshot' },
|
|
79
|
+
{ id: 'firecrawl', name: '🔥 Firecrawl Scraper', category: 'Search & Scraping', type: 'direct-api', endpoint: 'https://api.firecrawl.dev/v1/mcp', reqAuth: 'FIRECRAWL_API_KEY', desc: 'Convert entire websites to clean Markdown' },
|
|
80
|
+
{ id: 'fetch', name: '📡 Fetch HTTP Utility', category: 'Search & Scraping', type: 'stdio', command: 'npx -y @modelcontextprotocol/server-fetch', desc: 'HTTP request & web page parser' },
|
|
81
|
+
|
|
82
|
+
// Productivity & Communication
|
|
83
|
+
{ id: 'notion', name: '📄 Notion Workspace', category: 'Productivity', type: 'stdio', command: 'npx -y @modelcontextprotocol/server-notion', reqAuth: 'NOTION_API_TOKEN', desc: 'Read & edit Notion pages and databases' },
|
|
84
|
+
{ id: 'slack', name: '💬 Slack Channel', category: 'Productivity', type: 'stdio', command: 'npx -y @modelcontextprotocol/server-slack', reqAuth: 'SLACK_BOT_TOKEN', desc: 'Send messages & read channels' },
|
|
85
|
+
{ id: 'discord', name: '🎮 Discord Bot MCP', category: 'Productivity', type: 'stdio', command: 'npx -y discord-mcp-server', reqAuth: 'DISCORD_BOT_TOKEN', desc: 'Interact with Discord servers & channels' },
|
|
86
|
+
{ id: 'linear', name: '📐 Linear Issue Tracker', category: 'Productivity', type: 'stdio', command: 'npx -y @linear/mcp-server', reqAuth: 'LINEAR_API_KEY', desc: 'Manage project issues & sprint cycles' },
|
|
87
|
+
{ id: 'google-drive', name: '📁 Google Drive', category: 'Productivity', type: 'stdio', command: 'npx -y @modelcontextprotocol/server-gdrive', reqAuth: 'GDRIVE_CLIENT_ID', desc: 'Access Drive documents & sheets' }
|
|
88
|
+
];
|
|
89
|
+
|
|
46
90
|
function loadConfig() {
|
|
47
91
|
if (fs.existsSync(CONFIG_FILE)) {
|
|
48
92
|
try { return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8')); } catch (e) { return {}; }
|
|
@@ -90,8 +134,8 @@ ${C.c3} ██║ ██║╚██╔╝██║██╔═══╝ █
|
|
|
90
134
|
${C.c4} ██║ ██║ ╚═╝ ██║██║ ██║ ██║${C.reset}
|
|
91
135
|
${C.c4} ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝${C.reset}
|
|
92
136
|
${C.darkGray}────────────────────────────────────────────────────────────${C.reset}
|
|
93
|
-
${C.reset}The Multi Platform AI ${C.green}[MCP Hub &
|
|
94
|
-
${C.gray}/config | /models | /skill | /mcp | /new-mcp | /scan | /exit${C.reset}
|
|
137
|
+
${C.reset}The Multi Platform AI ${C.green}[MCP Hub & Interactive Provider Engine]${C.reset}
|
|
138
|
+
${C.gray}/config | /models | /skill | /mcp | /new-mcp | /scan | /uninstall | /exit${C.reset}
|
|
95
139
|
${C.darkGray}────────────────────────────────────────────────────────────${C.reset}
|
|
96
140
|
`);
|
|
97
141
|
}
|
|
@@ -137,11 +181,11 @@ function readResourceContent(targetPath) {
|
|
|
137
181
|
function getActiveToolsContext(forcedTool = null) {
|
|
138
182
|
if (forcedTool) {
|
|
139
183
|
let rawContent = "";
|
|
140
|
-
if (forcedTool.type === 'mcp') {
|
|
184
|
+
if (forcedTool.type === 'mcp' || forcedTool.type === 'stdio' || forcedTool.type === 'direct-api') {
|
|
141
185
|
rawContent = `MCP Server Info:
|
|
142
186
|
Name: ${forcedTool.name}
|
|
143
187
|
Target/Endpoint: ${forcedTool.target}
|
|
144
|
-
Type: ${forcedTool.mcpType || '
|
|
188
|
+
Type: ${forcedTool.mcpType || 'stdio/api'}
|
|
145
189
|
Auth Token: ${forcedTool.authToken ? 'Provided' : 'None'}`;
|
|
146
190
|
} else {
|
|
147
191
|
rawContent = readResourceContent(forcedTool.target);
|
|
@@ -190,7 +234,7 @@ function showMCPHub() {
|
|
|
190
234
|
console.log(`${C.bold}Daftar MCP Server Aktif:${C.reset}`);
|
|
191
235
|
keys.forEach((name, i) => {
|
|
192
236
|
const m = registry.mcp[name];
|
|
193
|
-
const typeBadge = m.type === 'remote' ? `${C.cyan}[
|
|
237
|
+
const typeBadge = m.type === 'remote' || m.type === 'direct-api' ? `${C.cyan}[${m.type}]${C.reset}` : `${C.green}[stdio]${C.reset}`;
|
|
194
238
|
console.log(` ${C.yellow}${i + 1}.${C.reset} ${C.bold}${name}${C.reset} ${typeBadge}`);
|
|
195
239
|
console.log(` ↳ Target: ${C.gray}${m.target}${C.reset}`);
|
|
196
240
|
if (m.authToken) console.log(` ↳ Auth : ${C.green}Encrypted/Token set${C.reset}`);
|
|
@@ -200,7 +244,7 @@ function showMCPHub() {
|
|
|
200
244
|
|
|
201
245
|
const lines = [
|
|
202
246
|
`${C.bold}Command Instan MCP:${C.reset}`,
|
|
203
|
-
`${C.yellow}/new-mcp${C.reset} 👉 Tambah MCP Server Baru (
|
|
247
|
+
`${C.yellow}/new-mcp${C.reset} 👉 Tambah MCP Server Baru (Interactive Selector)`,
|
|
204
248
|
`${C.yellow}/mcp-<nama>${C.reset} <prompt> 👉 Eksekusi MCP secara spesifik`,
|
|
205
249
|
`${C.yellow}/scan${C.reset} 👉 Pindai folder .mcp otomatis`
|
|
206
250
|
];
|
|
@@ -209,52 +253,333 @@ function showMCPHub() {
|
|
|
209
253
|
console.log('');
|
|
210
254
|
}
|
|
211
255
|
|
|
212
|
-
//
|
|
213
|
-
function
|
|
214
|
-
|
|
256
|
+
// INTERACTIVE ARROW-KEY SELECTOR UNTUK MCP PROVIDERS
|
|
257
|
+
function interactiveProviderSelector(allItems, onSelect, onCancel) {
|
|
258
|
+
let filteredItems = [...allItems];
|
|
259
|
+
let selectedIndex = 0;
|
|
260
|
+
let currentPage = 0;
|
|
261
|
+
const pageSize = 8;
|
|
262
|
+
|
|
263
|
+
const wasRaw = process.stdin.isRaw;
|
|
264
|
+
|
|
265
|
+
function cleanup() {
|
|
266
|
+
process.stdin.removeListener('keypress', handleKeypress);
|
|
267
|
+
if (process.stdin.setRawMode) process.stdin.setRawMode(wasRaw || false);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function renderMenu() {
|
|
271
|
+
console.clear();
|
|
272
|
+
const totalPages = Math.ceil(filteredItems.length / pageSize) || 1;
|
|
273
|
+
if (currentPage >= totalPages) currentPage = totalPages - 1;
|
|
274
|
+
if (currentPage < 0) currentPage = 0;
|
|
275
|
+
|
|
276
|
+
const startIdx = currentPage * pageSize;
|
|
277
|
+
const pageItems = filteredItems.slice(startIdx, startIdx + pageSize);
|
|
278
|
+
|
|
279
|
+
if (selectedIndex >= pageItems.length) selectedIndex = Math.max(0, pageItems.length - 1);
|
|
215
280
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
281
|
+
console.log(`${C.c4}============================================================${C.reset}`);
|
|
282
|
+
console.log(`${C.c4} 🔌 SETUP & INTEGRASI MCP SERVER PROVIDER ${C.reset}`);
|
|
283
|
+
console.log(`${C.c4}============================================================${C.reset}\n`);
|
|
284
|
+
|
|
285
|
+
if (pageItems.length === 0) {
|
|
286
|
+
console.log(`${C.red} Tidak ada provider yang cocok dengan pencarian.${C.reset}\n`);
|
|
287
|
+
} else {
|
|
288
|
+
pageItems.forEach((item, idx) => {
|
|
289
|
+
const globalNum = startIdx + idx;
|
|
290
|
+
const isFocused = idx === selectedIndex;
|
|
291
|
+
const pointer = isFocused ? `${C.cyan}❯${C.reset}` : ' ';
|
|
292
|
+
const numStr = globalNum.toString().padStart(2, ' ');
|
|
293
|
+
const badge = item.type ? `${C.gray}[${item.type}]${C.reset}` : '';
|
|
294
|
+
const nameStyled = isFocused ? `${C.bold}${C.green}${item.name}${C.reset}` : item.name;
|
|
295
|
+
|
|
296
|
+
console.log(`${pointer} ${C.yellow}${numStr}.${C.reset} • ${nameStyled} ${badge}`);
|
|
297
|
+
console.log(` ${C.darkGray}${item.desc || item.category}${C.reset}`);
|
|
298
|
+
});
|
|
221
299
|
}
|
|
222
300
|
|
|
223
|
-
console.log(`\n${C.
|
|
224
|
-
console.log(
|
|
225
|
-
console.log(
|
|
301
|
+
console.log(`\n${C.darkGray}────────────────────────────────────────────────────────────${C.reset}`);
|
|
302
|
+
console.log(`${C.cyan}Halaman ${currentPage + 1}/${totalPages}${C.reset} | Total: ${filteredItems.length} Provider`);
|
|
303
|
+
console.log(`${C.gray}🎮 Navigasi: [▲/▼] Panah Termux/Keyboard | [Enter] Pilih${C.reset}`);
|
|
304
|
+
console.log(`${C.gray}⌨️ Atau: Ketik Angka | [f] Cari Nama | [n] Next | [p] Prev | [q] Batal${C.reset}`);
|
|
305
|
+
console.log(`${C.darkGray}────────────────────────────────────────────────────────────${C.reset}\n`);
|
|
306
|
+
}
|
|
226
307
|
|
|
227
|
-
|
|
228
|
-
|
|
308
|
+
readline.emitKeypressEvents(process.stdin);
|
|
309
|
+
if (process.stdin.setRawMode) process.stdin.setRawMode(true);
|
|
229
310
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
311
|
+
function handleKeypress(str, key) {
|
|
312
|
+
const totalPages = Math.ceil(filteredItems.length / pageSize) || 1;
|
|
313
|
+
const startIdx = currentPage * pageSize;
|
|
314
|
+
const pageItems = filteredItems.slice(startIdx, startIdx + pageSize);
|
|
315
|
+
|
|
316
|
+
if (key.name === 'up') {
|
|
317
|
+
if (selectedIndex > 0) {
|
|
318
|
+
selectedIndex--;
|
|
319
|
+
} else if (currentPage > 0) {
|
|
320
|
+
currentPage--;
|
|
321
|
+
selectedIndex = pageSize - 1;
|
|
322
|
+
}
|
|
323
|
+
renderMenu();
|
|
324
|
+
} else if (key.name === 'down') {
|
|
325
|
+
if (selectedIndex < pageItems.length - 1) {
|
|
326
|
+
selectedIndex++;
|
|
327
|
+
} else if (currentPage < totalPages - 1) {
|
|
328
|
+
currentPage++;
|
|
329
|
+
selectedIndex = 0;
|
|
330
|
+
}
|
|
331
|
+
renderMenu();
|
|
332
|
+
} else if (key.name === 'return') {
|
|
333
|
+
cleanup();
|
|
334
|
+
const selectedObj = pageItems[selectedIndex];
|
|
335
|
+
if (selectedObj) onSelect(selectedObj);
|
|
336
|
+
} else if (str === 'q' || str === 'Q' || (key.ctrl && key.name === 'c')) {
|
|
337
|
+
cleanup();
|
|
338
|
+
onCancel();
|
|
339
|
+
} else if (str === 'n' || str === 'N') {
|
|
340
|
+
if (currentPage < totalPages - 1) currentPage++;
|
|
341
|
+
selectedIndex = 0;
|
|
342
|
+
renderMenu();
|
|
343
|
+
} else if (str === 'p' || str === 'P') {
|
|
344
|
+
if (currentPage > 0) currentPage--;
|
|
345
|
+
selectedIndex = 0;
|
|
346
|
+
renderMenu();
|
|
347
|
+
} else if (str === 'f' || str === 'F') {
|
|
348
|
+
cleanup();
|
|
349
|
+
rl.question(`\n${C.yellow}[>] Ketik kata kunci pencarian provider:${C.reset} `, (query) => {
|
|
350
|
+
const q = query.trim().toLowerCase();
|
|
351
|
+
filteredItems = allItems.filter(item => item.name.toLowerCase().includes(q) || (item.desc && item.desc.toLowerCase().includes(q)));
|
|
352
|
+
currentPage = 0;
|
|
353
|
+
selectedIndex = 0;
|
|
354
|
+
interactiveProviderSelector(filteredItems, onSelect, onCancel);
|
|
355
|
+
});
|
|
356
|
+
} else if (!isNaN(str) && str !== ' ' && str !== '') {
|
|
357
|
+
cleanup();
|
|
358
|
+
const num = parseInt(str);
|
|
359
|
+
if (num >= 0 && num < allItems.length) {
|
|
360
|
+
onSelect(allItems[num]);
|
|
361
|
+
} else {
|
|
362
|
+
console.log(`${C.red}Nomor tidak valid.${C.reset}`);
|
|
363
|
+
setTimeout(() => interactiveProviderSelector(allItems, onSelect, onCancel), 800);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
236
367
|
|
|
237
|
-
|
|
238
|
-
|
|
368
|
+
process.stdin.on('keypress', handleKeypress);
|
|
369
|
+
renderMenu();
|
|
370
|
+
}
|
|
239
371
|
|
|
372
|
+
// INTERACTIVE ARROW-KEY SELECTOR UNTUK MODEL AI (/models)
|
|
373
|
+
function interactiveModelSelector(allModels, onSelect, onCancel) {
|
|
374
|
+
let filteredModels = [...allModels];
|
|
375
|
+
let selectedIndex = 0;
|
|
376
|
+
let currentPage = 0;
|
|
377
|
+
const pageSize = 10;
|
|
378
|
+
|
|
379
|
+
const wasRaw = process.stdin.isRaw;
|
|
380
|
+
|
|
381
|
+
function cleanup() {
|
|
382
|
+
process.stdin.removeListener('keypress', handleKeypress);
|
|
383
|
+
if (process.stdin.setRawMode) process.stdin.setRawMode(wasRaw || false);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function renderMenu() {
|
|
387
|
+
console.clear();
|
|
388
|
+
const totalPages = Math.ceil(filteredModels.length / pageSize) || 1;
|
|
389
|
+
if (currentPage >= totalPages) currentPage = totalPages - 1;
|
|
390
|
+
if (currentPage < 0) currentPage = 0;
|
|
391
|
+
|
|
392
|
+
const startIdx = currentPage * pageSize;
|
|
393
|
+
const pageItems = filteredModels.slice(startIdx, startIdx + pageSize);
|
|
394
|
+
|
|
395
|
+
if (selectedIndex >= pageItems.length) selectedIndex = Math.max(0, pageItems.length - 1);
|
|
396
|
+
|
|
397
|
+
console.log(`${C.c1}============================================================${C.reset}`);
|
|
398
|
+
console.log(`${C.c1} 🤖 PILIH MODEL AI (${config.provider || 'Provider'}) ${C.reset}`);
|
|
399
|
+
console.log(`${C.c1}============================================================${C.reset}`);
|
|
400
|
+
console.log(`${C.gray}Model Aktif Saat Ini: ${C.green}${config.model || 'Belum Diatur'}${C.reset}\n`);
|
|
401
|
+
|
|
402
|
+
if (pageItems.length === 0) {
|
|
403
|
+
console.log(`${C.red} Tidak ada model yang cocok dengan pencarian.${C.reset}\n`);
|
|
404
|
+
} else {
|
|
405
|
+
pageItems.forEach((m, idx) => {
|
|
406
|
+
const globalNum = startIdx + idx;
|
|
407
|
+
const isFocused = idx === selectedIndex;
|
|
408
|
+
const isCurrentActive = m.id === config.model;
|
|
409
|
+
|
|
410
|
+
const pointer = isFocused ? `${C.cyan}❯${C.reset}` : ' ';
|
|
411
|
+
const activeBadge = isCurrentActive ? `${C.green}(Aktif)${C.reset}` : '';
|
|
412
|
+
const numStr = globalNum.toString().padStart(3, ' ');
|
|
413
|
+
const modelNameStyled = isFocused ? `${C.bold}${C.yellow}${m.id}${C.reset}` : m.id;
|
|
414
|
+
|
|
415
|
+
console.log(`${pointer} ${C.gray}${numStr}.${C.reset} • ${modelNameStyled} ${activeBadge}`);
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
console.log(`\n${C.darkGray}────────────────────────────────────────────────────────────${C.reset}`);
|
|
420
|
+
console.log(`${C.cyan}Halaman ${currentPage + 1}/${totalPages}${C.reset} | Total: ${filteredModels.length} Model AI`);
|
|
421
|
+
console.log(`${C.gray}🎮 Navigasi: [▲/▼] Panah Termux/Keyboard | [Enter] Pilih${C.reset}`);
|
|
422
|
+
console.log(`${C.gray}⌨️ Atau: [f] Cari Nama Model | [n] Next | [p] Prev | [q] Batal${C.reset}`);
|
|
423
|
+
console.log(`${C.darkGray}────────────────────────────────────────────────────────────${C.reset}\n`);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
readline.emitKeypressEvents(process.stdin);
|
|
427
|
+
if (process.stdin.setRawMode) process.stdin.setRawMode(true);
|
|
428
|
+
|
|
429
|
+
function handleKeypress(str, key) {
|
|
430
|
+
const totalPages = Math.ceil(filteredModels.length / pageSize) || 1;
|
|
431
|
+
const startIdx = currentPage * pageSize;
|
|
432
|
+
const pageItems = filteredModels.slice(startIdx, startIdx + pageSize);
|
|
433
|
+
|
|
434
|
+
if (key.name === 'up') {
|
|
435
|
+
if (selectedIndex > 0) {
|
|
436
|
+
selectedIndex--;
|
|
437
|
+
} else if (currentPage > 0) {
|
|
438
|
+
currentPage--;
|
|
439
|
+
selectedIndex = pageSize - 1;
|
|
440
|
+
}
|
|
441
|
+
renderMenu();
|
|
442
|
+
} else if (key.name === 'down') {
|
|
443
|
+
if (selectedIndex < pageItems.length - 1) {
|
|
444
|
+
selectedIndex++;
|
|
445
|
+
} else if (currentPage < totalPages - 1) {
|
|
446
|
+
currentPage++;
|
|
447
|
+
selectedIndex = 0;
|
|
448
|
+
}
|
|
449
|
+
renderMenu();
|
|
450
|
+
} else if (key.name === 'return') {
|
|
451
|
+
cleanup();
|
|
452
|
+
const selectedModelObj = pageItems[selectedIndex];
|
|
453
|
+
if (selectedModelObj) onSelect(selectedModelObj.id);
|
|
454
|
+
} else if (str === 'q' || str === 'Q' || (key.ctrl && key.name === 'c')) {
|
|
455
|
+
cleanup();
|
|
456
|
+
onCancel();
|
|
457
|
+
} else if (str === 'n' || str === 'N') {
|
|
458
|
+
if (currentPage < totalPages - 1) currentPage++;
|
|
459
|
+
selectedIndex = 0;
|
|
460
|
+
renderMenu();
|
|
461
|
+
} else if (str === 'p' || str === 'P') {
|
|
462
|
+
if (currentPage > 0) currentPage--;
|
|
463
|
+
selectedIndex = 0;
|
|
464
|
+
renderMenu();
|
|
465
|
+
} else if (str === 'f' || str === 'F') {
|
|
466
|
+
cleanup();
|
|
467
|
+
rl.question(`\n${C.yellow}[>] Ketik kata kunci nama model (misal: llama, gemini, claude):${C.reset} `, (query) => {
|
|
468
|
+
const q = query.trim().toLowerCase();
|
|
469
|
+
filteredModels = allModels.filter(m => m.id.toLowerCase().includes(q));
|
|
470
|
+
currentPage = 0;
|
|
471
|
+
selectedIndex = 0;
|
|
472
|
+
interactiveModelSelector(filteredModels, onSelect, onCancel);
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
process.stdin.on('keypress', handleKeypress);
|
|
478
|
+
renderMenu();
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// WIZARD KONFIGURASI MCP BARU (/new-mcp)
|
|
482
|
+
function createNewMCPWizard(callback) {
|
|
483
|
+
interactiveProviderSelector(
|
|
484
|
+
MCP_PRESETS,
|
|
485
|
+
(selectedProvider) => {
|
|
486
|
+
console.clear();
|
|
487
|
+
|
|
488
|
+
// CASE 1: CUSTOM MCP INPUT MANUAL
|
|
489
|
+
if (selectedProvider.id === 'custom') {
|
|
490
|
+
console.log(`\n${C.cyan}=== 🛠️ SETUP MCP MANUAL (CUSTOM) ===${C.reset}\n`);
|
|
491
|
+
rl.question(`${C.yellow}[1/4] Masukkan Nama MCP Server (misal: my-mcp):${C.reset} `, (nameInput) => {
|
|
492
|
+
const mcpName = nameInput.trim().toLowerCase().replace(/\s+/g, '-');
|
|
493
|
+
if (!mcpName) {
|
|
494
|
+
console.log(`${C.red}[x] Nama MCP tidak boleh kosong.${C.reset}\n`);
|
|
495
|
+
return callback();
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
console.log(`\n${C.cyan}Pilih Tipe Koneksi MCP:${C.reset}`);
|
|
499
|
+
console.log(` ${C.yellow}1.${C.reset} Direct REST API / Endpoint`);
|
|
500
|
+
console.log(` ${C.yellow}2.${C.reset} Local Stdio Command / Path`);
|
|
501
|
+
|
|
502
|
+
rl.question(`\n${C.yellow}[2/4] Pilih Tipe (1/2):${C.reset} `, (typeChoice) => {
|
|
503
|
+
const isApi = typeChoice.trim() === '1';
|
|
504
|
+
|
|
505
|
+
rl.question(`${C.yellow}[3/4] Masukkan Target URL / Command Executable:${C.reset} `, (targetInput) => {
|
|
506
|
+
const target = targetInput.trim();
|
|
507
|
+
if (!target) {
|
|
508
|
+
console.log(`${C.red}[x] Target tidak boleh kosong.${C.reset}\n`);
|
|
509
|
+
return callback();
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
rl.question(`${C.yellow}[4/4] Masukkan Auth Token / API Key (Opsional - Press Enter to skip):${C.reset} `, (tokenInput) => {
|
|
513
|
+
const authToken = tokenInput.trim();
|
|
514
|
+
|
|
515
|
+
registry.mcp = registry.mcp || {};
|
|
516
|
+
registry.mcp[mcpName] = {
|
|
517
|
+
target: target,
|
|
518
|
+
type: isApi ? 'direct-api' : 'stdio',
|
|
519
|
+
authToken: authToken || null,
|
|
520
|
+
connectedAt: new Date().toISOString(),
|
|
521
|
+
status: 'Connected'
|
|
522
|
+
};
|
|
523
|
+
|
|
524
|
+
saveRegistry(registry);
|
|
525
|
+
|
|
526
|
+
console.log(`\n${C.green}[✔] MCP Server "${mcpName}" berhasil ditambahkan!${C.reset}`);
|
|
527
|
+
console.log(`${C.gray}Gunakan perintah instan: ${C.yellow}/mcp-${mcpName} <prompt kamu>${C.reset}\n`);
|
|
528
|
+
callback();
|
|
529
|
+
});
|
|
530
|
+
});
|
|
531
|
+
});
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
// CASE 2: PRESET PROVIDER POPULER
|
|
535
|
+
else {
|
|
536
|
+
console.log(`\n${C.cyan}=== ⚡ SETUP PROVIDER: ${selectedProvider.name} ===${C.reset}`);
|
|
537
|
+
console.log(`${C.gray}${selectedProvider.desc}${C.reset}\n`);
|
|
538
|
+
|
|
539
|
+
const mcpName = selectedProvider.id;
|
|
540
|
+
|
|
541
|
+
if (selectedProvider.reqAuth) {
|
|
542
|
+
rl.question(`${C.yellow}[>] Masukkan ${selectedProvider.reqAuth}: ${C.reset}`, (authVal) => {
|
|
543
|
+
const token = authVal.trim();
|
|
544
|
+
|
|
545
|
+
registry.mcp = registry.mcp || {};
|
|
546
|
+
registry.mcp[mcpName] = {
|
|
547
|
+
target: selectedProvider.endpoint || selectedProvider.command,
|
|
548
|
+
type: selectedProvider.type,
|
|
549
|
+
authToken: token || null,
|
|
550
|
+
connectedAt: new Date().toISOString(),
|
|
551
|
+
status: 'Connected'
|
|
552
|
+
};
|
|
553
|
+
|
|
554
|
+
saveRegistry(registry);
|
|
555
|
+
|
|
556
|
+
console.log(`\n${C.green}[✔] Preset MCP "${selectedProvider.name}" berhasil terhubung!${C.reset}`);
|
|
557
|
+
console.log(`${C.gray}Akses instan via: ${C.yellow}/mcp-${mcpName} <prompt kamu>${C.reset}\n`);
|
|
558
|
+
callback();
|
|
559
|
+
});
|
|
560
|
+
} else {
|
|
240
561
|
registry.mcp = registry.mcp || {};
|
|
241
562
|
registry.mcp[mcpName] = {
|
|
242
|
-
target:
|
|
243
|
-
type:
|
|
244
|
-
authToken:
|
|
563
|
+
target: selectedProvider.endpoint || selectedProvider.command,
|
|
564
|
+
type: selectedProvider.type,
|
|
565
|
+
authToken: null,
|
|
245
566
|
connectedAt: new Date().toISOString(),
|
|
246
567
|
status: 'Connected'
|
|
247
568
|
};
|
|
248
569
|
|
|
249
570
|
saveRegistry(registry);
|
|
250
571
|
|
|
251
|
-
console.log(`\n${C.green}[✔] MCP
|
|
252
|
-
console.log(`${C.gray}
|
|
572
|
+
console.log(`\n${C.green}[✔] Preset MCP "${selectedProvider.name}" berhasil terhubung!${C.reset}`);
|
|
573
|
+
console.log(`${C.gray}Akses instan via: ${C.yellow}/mcp-${mcpName} <prompt kamu>${C.reset}\n`);
|
|
253
574
|
callback();
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
},
|
|
578
|
+
() => {
|
|
579
|
+
console.log(`\n${C.gray}Setup MCP dibatalkan.${C.reset}\n`);
|
|
580
|
+
callback();
|
|
581
|
+
}
|
|
582
|
+
);
|
|
258
583
|
}
|
|
259
584
|
|
|
260
585
|
function listSkills() {
|
|
@@ -313,7 +638,7 @@ function connectResource(inputArgs) {
|
|
|
313
638
|
registry.mcp = registry.mcp || {};
|
|
314
639
|
registry.mcp[mcpName] = {
|
|
315
640
|
target: targetPath,
|
|
316
|
-
type: targetPath.startsWith('http') ? '
|
|
641
|
+
type: targetPath.startsWith('http') ? 'direct-api' : 'stdio',
|
|
317
642
|
connectedAt: new Date().toISOString(),
|
|
318
643
|
status: 'Connected'
|
|
319
644
|
};
|
|
@@ -359,7 +684,7 @@ function scanResources() {
|
|
|
359
684
|
registry.skills[name] = { path: fullPath, connectedAt: new Date().toISOString(), status: 'Active' };
|
|
360
685
|
newDetected.push(`/skill-${name}`);
|
|
361
686
|
} else if (type === 'mcp' && !registry.mcp[name]) {
|
|
362
|
-
registry.mcp[name] = { target: fullPath, type: '
|
|
687
|
+
registry.mcp[name] = { target: fullPath, type: 'stdio', connectedAt: new Date().toISOString(), status: 'Connected' };
|
|
363
688
|
newDetected.push(`/mcp-${name}`);
|
|
364
689
|
}
|
|
365
690
|
});
|
|
@@ -426,81 +751,6 @@ function askConfig(callback) {
|
|
|
426
751
|
});
|
|
427
752
|
}
|
|
428
753
|
|
|
429
|
-
function selectModelCLI(allModels) {
|
|
430
|
-
let filteredModels = [...allModels];
|
|
431
|
-
let currentPage = 0;
|
|
432
|
-
const pageSize = 12;
|
|
433
|
-
|
|
434
|
-
function displayList() {
|
|
435
|
-
console.clear();
|
|
436
|
-
const totalPages = Math.ceil(filteredModels.length / pageSize) || 1;
|
|
437
|
-
if (currentPage >= totalPages) currentPage = totalPages - 1;
|
|
438
|
-
if (currentPage < 0) currentPage = 0;
|
|
439
|
-
|
|
440
|
-
const startIdx = currentPage * pageSize;
|
|
441
|
-
const pageItems = filteredModels.slice(startIdx, startIdx + pageSize);
|
|
442
|
-
|
|
443
|
-
console.log(`${C.cyan}=== Models Selector for ${config.provider} (${filteredModels.length} models) ===${C.reset}`);
|
|
444
|
-
console.log(`${C.gray}Current Active: ${C.green}${config.model || 'None'}${C.reset}`);
|
|
445
|
-
console.log(`${C.darkGray}────────────────────────────────────────────────────────────${C.reset}`);
|
|
446
|
-
|
|
447
|
-
if (pageItems.length === 0) {
|
|
448
|
-
console.log(`${C.red} No models found matching filter.${C.reset}`);
|
|
449
|
-
} else {
|
|
450
|
-
pageItems.forEach((m, idx) => {
|
|
451
|
-
const itemNum = startIdx + idx + 1;
|
|
452
|
-
const isCurrent = m.id === config.model;
|
|
453
|
-
const bullet = isCurrent ? `${C.green}●${C.reset}` : `${C.gray}○${C.reset}`;
|
|
454
|
-
console.log(` ${bullet} ${C.yellow}${itemNum.toString().padStart(3, ' ')}.${C.reset} ${m.id}`);
|
|
455
|
-
});
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
console.log(`${C.darkGray}────────────────────────────────────────────────────────────${C.reset}`);
|
|
459
|
-
console.log(`${C.cyan}Page ${currentPage + 1}/${totalPages}${C.reset} | ${C.gray}Commands: [n]ext | [p]rev | [f]ilter <keyword> | [q]uit${C.reset}`);
|
|
460
|
-
|
|
461
|
-
rl.question(`\n${C.yellow}[>] Enter Number / Model ID / Command:${C.reset} `, (answer) => {
|
|
462
|
-
const input = answer.trim();
|
|
463
|
-
|
|
464
|
-
if (input.toLowerCase() === 'n') {
|
|
465
|
-
if (currentPage < totalPages - 1) currentPage++;
|
|
466
|
-
displayList();
|
|
467
|
-
} else if (input.toLowerCase() === 'p') {
|
|
468
|
-
if (currentPage > 0) currentPage--;
|
|
469
|
-
displayList();
|
|
470
|
-
} else if (input.toLowerCase() === 'q' || input.toLowerCase() === 'exit') {
|
|
471
|
-
console.log(`${C.gray}Model selection cancelled.${C.reset}\n`);
|
|
472
|
-
startPrompt();
|
|
473
|
-
} else if (input.toLowerCase().startsWith('f ')) {
|
|
474
|
-
const query = input.slice(2).trim().toLowerCase();
|
|
475
|
-
filteredModels = allModels.filter(m => m.id.toLowerCase().includes(query));
|
|
476
|
-
currentPage = 0;
|
|
477
|
-
displayList();
|
|
478
|
-
} else if (!isNaN(input) && input !== '') {
|
|
479
|
-
const num = parseInt(input);
|
|
480
|
-
if (num >= 1 && num <= filteredModels.length) {
|
|
481
|
-
const selected = filteredModels[num - 1].id;
|
|
482
|
-
config.model = selected;
|
|
483
|
-
saveConfig(config);
|
|
484
|
-
console.log(`\n${C.green}[+] Model changed to: ${config.model}${C.reset}\n`);
|
|
485
|
-
startPrompt();
|
|
486
|
-
} else {
|
|
487
|
-
console.log(`${C.red}[x] Invalid number!${C.reset}`);
|
|
488
|
-
setTimeout(displayList, 1000);
|
|
489
|
-
}
|
|
490
|
-
} else if (input.length > 0) {
|
|
491
|
-
config.model = input;
|
|
492
|
-
saveConfig(config);
|
|
493
|
-
console.log(`\n${C.green}[+] Custom Model ID set to: ${config.model}${C.reset}\n`);
|
|
494
|
-
startPrompt();
|
|
495
|
-
} else {
|
|
496
|
-
startPrompt();
|
|
497
|
-
}
|
|
498
|
-
});
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
displayList();
|
|
502
|
-
}
|
|
503
|
-
|
|
504
754
|
async function fetchAvailableModels() {
|
|
505
755
|
if (!config.apiKey) {
|
|
506
756
|
console.log(`${C.red}[x] API Key is not set. Use /config first.${C.reset}\n`);
|
|
@@ -529,7 +779,21 @@ async function fetchAvailableModels() {
|
|
|
529
779
|
const data = await response.json();
|
|
530
780
|
|
|
531
781
|
if (data.data && Array.isArray(data.data) && data.data.length > 0) {
|
|
532
|
-
|
|
782
|
+
interactiveModelSelector(
|
|
783
|
+
data.data,
|
|
784
|
+
(selectedModelId) => {
|
|
785
|
+
config.model = selectedModelId;
|
|
786
|
+
saveConfig(config);
|
|
787
|
+
console.clear();
|
|
788
|
+
console.log(`${C.green}[✔] Model berhasil diubah ke: ${config.model}${C.reset}\n`);
|
|
789
|
+
startPrompt();
|
|
790
|
+
},
|
|
791
|
+
() => {
|
|
792
|
+
console.clear();
|
|
793
|
+
console.log(`${C.gray}Pemilihan model dibatalkan.${C.reset}\n`);
|
|
794
|
+
startPrompt();
|
|
795
|
+
}
|
|
796
|
+
);
|
|
533
797
|
} else {
|
|
534
798
|
console.log(`${C.red}[x] Could not retrieve models list automatically from ${config.provider}.${C.reset}`);
|
|
535
799
|
startPrompt();
|
|
@@ -608,23 +872,23 @@ async function handleChat(prompt, forcedTool = null) {
|
|
|
608
872
|
}
|
|
609
873
|
|
|
610
874
|
function handleUninstall() {
|
|
611
|
-
rl.question(`${C.red}[!]
|
|
875
|
+
rl.question(`${C.red}[!] Apakah kamu yakin ingin menghapus TMPA CLI? (y/N):${C.reset} `, (answer) => {
|
|
612
876
|
if (answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes') {
|
|
613
|
-
console.log(`\n${C.yellow}[...]
|
|
877
|
+
console.log(`\n${C.yellow}[...] Membersihkan konfigurasi dan menghapus TMPA CLI...${C.reset}`);
|
|
614
878
|
try {
|
|
615
879
|
if (fs.existsSync(CONFIG_FILE)) {
|
|
616
880
|
fs.unlinkSync(CONFIG_FILE);
|
|
617
881
|
}
|
|
618
|
-
console.log(`${C.green}[+]
|
|
882
|
+
console.log(`${C.green}[+] Konfigurasi lokal berhasil dibersihkan.${C.reset}`);
|
|
619
883
|
execSync('npm uninstall -g tmpa-cli', { stdio: 'inherit' });
|
|
620
|
-
console.log(`\n${C.green}[+] TMPA CLI
|
|
884
|
+
console.log(`\n${C.green}[+] TMPA CLI berhasil di-uninstall sepenuhnya. Sampai jumpa!${C.reset}\n`);
|
|
621
885
|
process.exit(0);
|
|
622
886
|
} catch (err) {
|
|
623
|
-
console.log(`\n${C.red}[x]
|
|
887
|
+
console.log(`\n${C.red}[x] Gagal uninstall otomatis. Silakan jalankan: npm uninstall -g tmpa-cli secara manual.${C.reset}\n`);
|
|
624
888
|
process.exit(0);
|
|
625
889
|
}
|
|
626
890
|
} else {
|
|
627
|
-
console.log(`${C.gray}
|
|
891
|
+
console.log(`${C.gray}Proses uninstall dibatalkan.${C.reset}\n`);
|
|
628
892
|
startPrompt();
|
|
629
893
|
}
|
|
630
894
|
});
|