tmpa-cli 1.0.23 → 1.0.24

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 +175 -173
  2. package/package.json +1 -1
package/bin/index.js CHANGED
@@ -43,38 +43,38 @@ const PROVIDERS = {
43
43
  '7': { name: 'Custom / Auto-Detect', endpoint: '', defaultModel: '' }
44
44
  };
45
45
 
46
- // 100+ POPULAR MCP PRESETS LIST
46
+ // 100+ PRESETS LIST
47
47
  const MCP_PRESETS = [
48
- { id: 'custom', name: '🛠️ Custom / Lainnya', category: 'General', type: 'custom', desc: 'Input Manual Name, Target, & Key' },
49
- { 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' },
50
- { 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' },
51
- { 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' },
52
- { 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' },
53
- { 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' },
54
- { id: 'firebase', name: '🔥 Firebase MCP', category: 'Cloud & AI', type: 'stdio', command: 'npx -y firebase-mcp-server', reqAuth: 'FIREBASE_TOKEN', desc: 'Firestore, Auth & Realtime DB' },
55
- { 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' },
56
- { 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' },
57
- { 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' },
58
- { 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' },
59
- { 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' },
60
- { 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' },
61
- { id: 'docker', name: '🐳 Docker Local MCP', category: 'Developer Tools', type: 'stdio', command: 'npx -y @modelcontextprotocol/server-docker', desc: 'Manage local containers & images' },
62
- { id: 'postgresql', name: '🗄️ PostgreSQL', category: 'Databases', type: 'stdio', command: 'npx -y @modelcontextprotocol/server-postgres', reqAuth: 'DATABASE_URL', desc: 'Direct SQL query execution' },
63
- { id: 'mysql', name: '🐬 MySQL MCP', category: 'Databases', type: 'stdio', command: 'npx -y mysql-mcp-server', reqAuth: 'MYSQL_CONNECTION_STRING', desc: 'MySQL relational database runner' },
64
- { id: 'sqlite', name: '📦 SQLite Local', category: 'Databases', type: 'stdio', command: 'npx -y @modelcontextprotocol/server-sqlite', desc: 'Lightweight local database query tool' },
65
- { id: 'mongodb', name: '🍃 MongoDB MCP', category: 'Databases', type: 'stdio', command: 'npx -y mongodb-mcp-server', reqAuth: 'MONGODB_URI', desc: 'NoSQL collection queries & aggregation' },
66
- { id: 'redis', name: '🔴 Redis Cache', category: 'Databases', type: 'stdio', command: 'npx -y @modelcontextprotocol/server-redis', reqAuth: 'REDIS_URL', desc: 'Key-value data structure store' },
67
- { 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' },
68
- { 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' },
69
- { 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' },
70
- { id: 'puppeteer', name: '🌐 Puppeteer Scraper', category: 'Search & Scraping', type: 'stdio', command: 'npx -y @modelcontextprotocol/server-puppeteer', desc: 'Headless browser web automation & screenshot' },
71
- { 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' },
72
- { id: 'fetch', name: '📡 Fetch HTTP Utility', category: 'Search & Scraping', type: 'stdio', command: 'npx -y @modelcontextprotocol/server-fetch', desc: 'HTTP request & web page parser' },
73
- { 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' },
74
- { id: 'slack', name: '💬 Slack Channel', category: 'Productivity', type: 'stdio', command: 'npx -y @modelcontextprotocol/server-slack', reqAuth: 'SLACK_BOT_TOKEN', desc: 'Send messages & read channels' },
75
- { 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' },
76
- { 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' },
77
- { 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' }
48
+ { id: 'custom', name: '[+] Custom / Manual Input', category: 'General', type: 'custom', desc: 'Manual Name, Target, & Key' },
49
+ { 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' },
50
+ { 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' },
51
+ { 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' },
52
+ { 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' },
53
+ { 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' },
54
+ { id: 'firebase', name: '[+] Firebase MCP', category: 'Cloud & AI', type: 'stdio', command: 'npx -y firebase-mcp-server', reqAuth: 'FIREBASE_TOKEN', desc: 'Firestore, Auth & Realtime DB' },
55
+ { 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' },
56
+ { 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' },
57
+ { 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' },
58
+ { 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' },
59
+ { 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' },
60
+ { 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' },
61
+ { id: 'docker', name: '[+] Docker Local MCP', category: 'Developer Tools', type: 'stdio', command: 'npx -y @modelcontextprotocol/server-docker', desc: 'Manage local containers & images' },
62
+ { id: 'postgresql', name: '[+] PostgreSQL', category: 'Databases', type: 'stdio', command: 'npx -y @modelcontextprotocol/server-postgres', reqAuth: 'DATABASE_URL', desc: 'Direct SQL query execution' },
63
+ { id: 'mysql', name: '[+] MySQL MCP', category: 'Databases', type: 'stdio', command: 'npx -y mysql-mcp-server', reqAuth: 'MYSQL_CONNECTION_STRING', desc: 'MySQL relational database runner' },
64
+ { id: 'sqlite', name: '[+] SQLite Local', category: 'Databases', type: 'stdio', command: 'npx -y @modelcontextprotocol/server-sqlite', desc: 'Lightweight local database query tool' },
65
+ { id: 'mongodb', name: '[+] MongoDB MCP', category: 'Databases', type: 'stdio', command: 'npx -y mongodb-mcp-server', reqAuth: 'MONGODB_URI', desc: 'NoSQL collection queries & aggregation' },
66
+ { id: 'redis', name: '[+] Redis Cache', category: 'Databases', type: 'stdio', command: 'npx -y @modelcontextprotocol/server-redis', reqAuth: 'REDIS_URL', desc: 'Key-value data structure store' },
67
+ { 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' },
68
+ { 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' },
69
+ { 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' },
70
+ { id: 'puppeteer', name: '[+] Puppeteer Scraper', category: 'Search & Scraping', type: 'stdio', command: 'npx -y @modelcontextprotocol/server-puppeteer', desc: 'Headless browser web automation & screenshot' },
71
+ { 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' },
72
+ { id: 'fetch', name: '[+] Fetch HTTP Utility', category: 'Search & Scraping', type: 'stdio', command: 'npx -y @modelcontextprotocol/server-fetch', desc: 'HTTP request & web page parser' },
73
+ { 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' },
74
+ { id: 'slack', name: '[+] Slack Channel', category: 'Productivity', type: 'stdio', command: 'npx -y @modelcontextprotocol/server-slack', reqAuth: 'SLACK_BOT_TOKEN', desc: 'Send messages & read channels' },
75
+ { 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' },
76
+ { 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' },
77
+ { 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' }
78
78
  ];
79
79
 
80
80
  function loadConfig() {
@@ -84,8 +84,8 @@ function loadConfig() {
84
84
  return {};
85
85
  }
86
86
 
87
- function saveConfig(config) {
88
- fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
87
+ function saveConfig(cfg) {
88
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(cfg, null, 2));
89
89
  }
90
90
 
91
91
  function loadRegistry() {
@@ -95,19 +95,30 @@ function loadRegistry() {
95
95
  return { skills: {}, mcp: {} };
96
96
  }
97
97
 
98
- function saveRegistry(registry) {
99
- fs.writeFileSync(REGISTRY_FILE, JSON.stringify(registry, null, 2));
98
+ function saveRegistry(reg) {
99
+ fs.writeFileSync(REGISTRY_FILE, JSON.stringify(reg, null, 2));
100
+ }
101
+
102
+ function clearScreenFull() {
103
+ process.stdout.write('\x1B[2J\x1B[0;0H');
104
+ }
105
+
106
+ function getTermWidth() {
107
+ return Math.min(process.stdout.columns || 60, 64);
100
108
  }
101
109
 
102
110
  function drawBox(title, contentLines, borderColor = C.cyan) {
103
- const width = 64;
104
- console.log(`${borderColor}┌${'─'.repeat(width - 2)}┐${C.reset}`);
105
- console.log(`${borderColor}│ ${C.bold}${title.padEnd(width - 4)}${C.reset}${borderColor} │${C.reset}`);
106
- console.log(`${borderColor}├${'─'.repeat(width - 2)}┤${C.reset}`);
111
+ const width = getTermWidth();
112
+ const innerWidth = width - 4;
113
+ console.log(`${borderColor}+${'-'.repeat(width - 2)}+${C.reset}`);
114
+ console.log(`${borderColor}| ${C.bold}${title.padEnd(innerWidth)}${C.reset}${borderColor} |${C.reset}`);
115
+ console.log(`${borderColor}+${'-'.repeat(width - 2)}+${C.reset}`);
107
116
  contentLines.forEach(line => {
108
- console.log(`${borderColor}│${C.reset} ${line.padEnd(width - 4)} ${borderColor}│${C.reset}`);
117
+ const cleanLine = line.replace(/\x1b\[[0-9;]*m/g, '');
118
+ const padding = Math.max(0, innerWidth - cleanLine.length);
119
+ console.log(`${borderColor}|${C.reset} ${line}${' '.repeat(padding)} ${borderColor}|${C.reset}`);
109
120
  });
110
- console.log(`${borderColor}└${''.repeat(width - 2)}┘${C.reset}`);
121
+ console.log(`${borderColor}+${'-'.repeat(width - 2)}+${C.reset}`);
111
122
  }
112
123
 
113
124
  let rl = readline.createInterface({ input: process.stdin, output: process.stdout });
@@ -115,18 +126,13 @@ let config = loadConfig();
115
126
  let registry = loadRegistry();
116
127
 
117
128
  function showBanner() {
118
- console.clear();
129
+ clearScreenFull();
130
+ const width = getTermWidth();
119
131
  console.log(`
120
- ${C.c1}████████╗███╗ ███╗██████╗ █████╗ ${C.reset}
121
- ${C.c2}╚══██╔══╝████╗ ████║██╔══██╗██╔══██╗${C.reset}
122
- ${C.c2} ██║ ██╔████╔██║██████╔╝███████║${C.reset}
123
- ${C.c3} ██║ ██║╚██╔╝██║██╔═══╝ ██╔══██║${C.reset}
124
- ${C.c4} ██║ ██║ ╚═╝ ██║██║ ██║ ██║${C.reset}
125
- ${C.c4} ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝${C.reset}
126
- ${C.darkGray}────────────────────────────────────────────────────────────${C.reset}
127
- ${C.reset}The Multi Platform AI ${C.green}[MCP Hub & Interactive Provider Engine]${C.reset}
128
- ${C.gray}/config | /models | /custom-models | /skill | /mcp | /new-mcp | /scan | /uninstall${C.reset}
129
- ${C.darkGray}────────────────────────────────────────────────────────────${C.reset}
132
+ ${C.c1}TMPA CLI Engine - Multi-Platform AI${C.reset}
133
+ ${C.darkGray}${'-'.repeat(width)}${C.reset}
134
+ Commands: /config | /models | /custom-models | /skill | /mcp | /new-mcp | /scan | /uninstall
135
+ ${C.darkGray}${'-'.repeat(width)}${C.reset}
130
136
  `);
131
137
  }
132
138
 
@@ -209,41 +215,42 @@ Executing User Prompt under this tool context only:\n`;
209
215
  return "\n\n[SYSTEM ENVIRONMENT SUMMARY]\n" + contextParts.join("\n") + "\n";
210
216
  }
211
217
 
212
- // RUANGAN KHUSUS MCP CENTER
218
+ // MCP CENTER
213
219
  function showMCPHub() {
214
- console.log(`\n${C.c4}============================================================${C.reset}`);
215
- console.log(`${C.c4} 🔌 MCP (MODEL CONTEXT PROTOCOL) CENTER ${C.reset}`);
216
- console.log(`${C.c4}============================================================${C.reset}\n`);
220
+ const width = getTermWidth();
221
+ console.log(`\n${C.c4}${'-'.repeat(width)}${C.reset}`);
222
+ console.log(`${C.c4} [MCP] MODEL CONTEXT PROTOCOL CENTER ${C.reset}`);
223
+ console.log(`${C.c4}${'-'.repeat(width)}${C.reset}\n`);
217
224
 
218
225
  const keys = Object.keys(registry.mcp || {});
219
226
 
220
227
  if (keys.length === 0) {
221
- console.log(`${C.yellow} [!] Belum ada MCP Server yang terhubung saat ini.${C.reset}`);
222
- console.log(`${C.gray} Gunakan perintah ${C.green}/new-mcp${C.gray} untuk mendaftarkan MCP baru.${C.reset}\n`);
228
+ console.log(`${C.yellow} [!] No MCP Servers currently connected.${C.reset}`);
229
+ console.log(`${C.gray} Use ${C.green}/new-mcp${C.gray} to add a new server.${C.reset}\n`);
223
230
  } else {
224
- console.log(`${C.bold}Daftar MCP Server Aktif:${C.reset}`);
231
+ console.log(`${C.bold}Active MCP Servers:${C.reset}`);
225
232
  keys.forEach((name, i) => {
226
233
  const m = registry.mcp[name];
227
- const typeBadge = m.type === 'remote' || m.type === 'direct-api' ? `${C.cyan}[${m.type}]${C.reset}` : `${C.green}[stdio]${C.reset}`;
234
+ const typeBadge = m.type === 'remote' || m.type === 'direct-api' ? `${C.cyan}[api]${C.reset}` : `${C.green}[stdio]${C.reset}`;
228
235
  console.log(` ${C.yellow}${i + 1}.${C.reset} ${C.bold}${name}${C.reset} ${typeBadge}`);
229
- console.log(` Target: ${C.gray}${m.target}${C.reset}`);
230
- if (m.authToken) console.log(` Auth : ${C.green}Encrypted/Token set${C.reset}`);
236
+ console.log(` -> Target: ${C.gray}${m.target}${C.reset}`);
237
+ if (m.authToken) console.log(` -> Auth : ${C.green}Encrypted/Token set${C.reset}`);
231
238
  });
232
239
  console.log('');
233
240
  }
234
241
 
235
242
  const lines = [
236
- `${C.bold}Command Instan MCP:${C.reset}`,
237
- `${C.yellow}/new-mcp${C.reset} 👉 Tambah MCP Server Baru (Interactive Selector)`,
238
- `${C.yellow}/mcp-<nama>${C.reset} <prompt> 👉 Eksekusi MCP secara spesifik`,
239
- `${C.yellow}/scan${C.reset} 👉 Pindai folder .mcp otomatis`
243
+ `${C.bold}Instant MCP Commands:${C.reset}`,
244
+ `${C.yellow}/new-mcp${C.reset} -> Add MCP Server (Interactive Wizard)`,
245
+ `${C.yellow}/mcp-<name>${C.reset} <prompt> -> Execute specific MCP server`,
246
+ `${C.yellow}/scan${C.reset} -> Scan .mcp directory automatically`
240
247
  ];
241
248
 
242
- drawBox("💡 PETUNJUK RUANG MCP", lines, C.c4);
249
+ drawBox("[i] MCP CENTER HELP", lines, C.c4);
243
250
  console.log('');
244
251
  }
245
252
 
246
- // INTERACTIVE ARROW-KEY SELECTOR UNTUK MCP PROVIDERS
253
+ // INTERACTIVE PROVIDER SELECTOR
247
254
  function interactiveProviderSelector(allItems, onSelect, onCancel) {
248
255
  let filteredItems = [...allItems];
249
256
  let selectedIndex = 0;
@@ -258,7 +265,8 @@ function interactiveProviderSelector(allItems, onSelect, onCancel) {
258
265
  }
259
266
 
260
267
  function renderMenu() {
261
- console.clear();
268
+ clearScreenFull();
269
+ const width = getTermWidth();
262
270
  const totalPages = Math.ceil(filteredItems.length / pageSize) || 1;
263
271
  if (currentPage >= totalPages) currentPage = totalPages - 1;
264
272
  if (currentPage < 0) currentPage = 0;
@@ -268,31 +276,31 @@ function interactiveProviderSelector(allItems, onSelect, onCancel) {
268
276
 
269
277
  if (selectedIndex >= pageItems.length) selectedIndex = Math.max(0, pageItems.length - 1);
270
278
 
271
- console.log(`${C.c4}============================================================${C.reset}`);
272
- console.log(`${C.c4} 🔌 SETUP & INTEGRASI MCP SERVER PROVIDER ${C.reset}`);
273
- console.log(`${C.c4}============================================================${C.reset}\n`);
279
+ console.log(`${C.c4}${'-'.repeat(width)}${C.reset}`);
280
+ console.log(`${C.c4} [+] SETUP MCP SERVER PROVIDER ${C.reset}`);
281
+ console.log(`${C.c4}${'-'.repeat(width)}${C.reset}\n`);
274
282
 
275
283
  if (pageItems.length === 0) {
276
- console.log(`${C.red} Tidak ada provider yang cocok dengan pencarian.${C.reset}\n`);
284
+ console.log(`${C.red} No provider matched your search.${C.reset}\n`);
277
285
  } else {
278
286
  pageItems.forEach((item, idx) => {
279
287
  const globalNum = startIdx + idx;
280
288
  const isFocused = idx === selectedIndex;
281
- const pointer = isFocused ? `${C.cyan}❯${C.reset}` : ' ';
289
+ const pointer = isFocused ? `${C.cyan}>${C.reset}` : ' ';
282
290
  const numStr = globalNum.toString().padStart(2, ' ');
283
291
  const badge = item.type ? `${C.gray}[${item.type}]${C.reset}` : '';
284
292
  const nameStyled = isFocused ? `${C.bold}${C.green}${item.name}${C.reset}` : item.name;
285
293
 
286
- console.log(`${pointer} ${C.yellow}${numStr}.${C.reset} ${nameStyled} ${badge}`);
294
+ console.log(`${pointer} ${C.yellow}${numStr}.${C.reset} ${nameStyled} ${badge}`);
287
295
  console.log(` ${C.darkGray}${item.desc || item.category}${C.reset}`);
288
296
  });
289
297
  }
290
298
 
291
- console.log(`\n${C.darkGray}────────────────────────────────────────────────────────────${C.reset}`);
292
- console.log(`${C.cyan}Halaman ${currentPage + 1}/${totalPages}${C.reset} | Total: ${filteredItems.length} Provider`);
293
- console.log(`${C.gray}🎮 Navigasi: [▲/▼] Panah Termux/Keyboard | [Enter] Pilih${C.reset}`);
294
- console.log(`${C.gray}⌨️ Atau: Ketik Angka | [f] Cari Nama | [n] Next | [p] Prev | [q] Batal${C.reset}`);
295
- console.log(`${C.darkGray}────────────────────────────────────────────────────────────${C.reset}\n`);
299
+ console.log(`\n${C.darkGray}${'-'.repeat(width)}${C.reset}`);
300
+ console.log(`${C.cyan}Page ${currentPage + 1}/${totalPages}${C.reset} | Total: ${filteredItems.length} Providers`);
301
+ console.log(`${C.gray}Controls: [Up/Down] Arrow Keys | [ENTER] Select${C.reset}`);
302
+ console.log(`${C.gray}Keys: [f] Search | [n] Next | [p] Prev | [q] Cancel${C.reset}`);
303
+ console.log(`${C.darkGray}${'-'.repeat(width)}${C.reset}\n`);
296
304
  }
297
305
 
298
306
  readline.emitKeypressEvents(process.stdin);
@@ -336,7 +344,7 @@ function interactiveProviderSelector(allItems, onSelect, onCancel) {
336
344
  renderMenu();
337
345
  } else if (str === 'f' || str === 'F') {
338
346
  cleanup();
339
- rl.question(`\n${C.yellow}[>] Ketik kata kunci pencarian provider:${C.reset} `, (query) => {
347
+ rl.question(`\n${C.yellow}[>] Enter provider search query:${C.reset} `, (query) => {
340
348
  const q = query.trim().toLowerCase();
341
349
  filteredItems = allItems.filter(item => item.name.toLowerCase().includes(q) || (item.desc && item.desc.toLowerCase().includes(q)));
342
350
  currentPage = 0;
@@ -349,7 +357,7 @@ function interactiveProviderSelector(allItems, onSelect, onCancel) {
349
357
  if (num >= 0 && num < allItems.length) {
350
358
  onSelect(allItems[num]);
351
359
  } else {
352
- console.log(`${C.red}Nomor tidak valid.${C.reset}`);
360
+ console.log(`${C.red}Invalid index.${C.reset}`);
353
361
  setTimeout(() => interactiveProviderSelector(allItems, onSelect, onCancel), 800);
354
362
  }
355
363
  }
@@ -359,9 +367,8 @@ function interactiveProviderSelector(allItems, onSelect, onCancel) {
359
367
  renderMenu();
360
368
  }
361
369
 
362
- // INTERACTIVE ARROW-KEY SELECTOR UNTUK MODEL AI (/models) - CAPACITY HINGGA 600 MODEL
370
+ // INTERACTIVE MODEL SELECTOR (/models) - ANTI-SPAM & CLEAN REDRAW
363
371
  function interactiveModelSelector(allModels, onSelect, onCancel) {
364
- // Cap pada 600 model untuk mencegah lag ekstrem di memori Termux
365
372
  let filteredModels = allModels.slice(0, 600);
366
373
  let selectedIndex = 0;
367
374
  let currentPage = 0;
@@ -375,7 +382,8 @@ function interactiveModelSelector(allModels, onSelect, onCancel) {
375
382
  }
376
383
 
377
384
  function renderMenu() {
378
- console.clear();
385
+ clearScreenFull();
386
+ const width = getTermWidth();
379
387
  const totalPages = Math.ceil(filteredModels.length / pageSize) || 1;
380
388
  if (currentPage >= totalPages) currentPage = totalPages - 1;
381
389
  if (currentPage < 0) currentPage = 0;
@@ -385,33 +393,33 @@ function interactiveModelSelector(allModels, onSelect, onCancel) {
385
393
 
386
394
  if (selectedIndex >= pageItems.length) selectedIndex = Math.max(0, pageItems.length - 1);
387
395
 
388
- console.log(`${C.c1}============================================================${C.reset}`);
389
- console.log(`${C.c1} 🤖 PILIH MODEL AI (${config.provider || 'Provider'}) ${C.reset}`);
390
- console.log(`${C.c1}============================================================${C.reset}`);
391
- console.log(`${C.gray}Model Aktif Saat Ini: ${C.green}${config.model || 'Belum Diatur'}${C.reset}\n`);
396
+ console.log(`${C.c1}${'-'.repeat(width)}${C.reset}`);
397
+ console.log(`${C.c1} [AI] CHOOSE AI MODEL (${config.provider || 'Provider'}) ${C.reset}`);
398
+ console.log(`${C.c1}${'-'.repeat(width)}${C.reset}`);
399
+ console.log(` Active Model: ${C.green}${config.model || 'None'}${C.reset}\n`);
392
400
 
393
401
  if (pageItems.length === 0) {
394
- console.log(`${C.red} Tidak ada model yang cocok dengan pencarian.${C.reset}\n`);
402
+ console.log(`${C.red} No model matched your query.${C.reset}\n`);
395
403
  } else {
396
404
  pageItems.forEach((m, idx) => {
397
405
  const globalNum = startIdx + idx;
398
406
  const isFocused = idx === selectedIndex;
399
407
  const isCurrentActive = m.id === config.model;
400
408
 
401
- const pointer = isFocused ? `${C.cyan}❯${C.reset}` : ' ';
402
- const activeBadge = isCurrentActive ? `${C.green}(Aktif)${C.reset}` : '';
403
- const numStr = globalNum.toString().padStart(3, ' ');
409
+ const pointer = isFocused ? `${C.cyan}>${C.reset}` : ' ';
410
+ const activeBadge = isCurrentActive ? `${C.green}[Active]${C.reset}` : '';
411
+ const numStr = (globalNum + 1).toString().padStart(3, ' ');
404
412
  const modelNameStyled = isFocused ? `${C.bold}${C.yellow}${m.id}${C.reset}` : m.id;
405
413
 
406
- console.log(`${pointer} ${C.gray}${numStr}.${C.reset} ${modelNameStyled} ${activeBadge}`);
414
+ console.log(`${pointer} ${C.gray}${numStr}.${C.reset} ${modelNameStyled} ${activeBadge}`);
407
415
  });
408
416
  }
409
417
 
410
- console.log(`\n${C.darkGray}────────────────────────────────────────────────────────────${C.reset}`);
411
- console.log(`${C.cyan}Halaman ${currentPage + 1}/${totalPages}${C.reset} | Total Model Loaded: ${filteredModels.length}`);
412
- console.log(`${C.gray}🎮 Navigasi: [▲/▼] Panah Termux | [Enter] Pilih${C.reset}`);
413
- console.log(`${C.gray}⌨️ [f] Cari Nama Model | [n] Next | [p] Prev | [q] Batal${C.reset}`);
414
- console.log(`${C.darkGray}────────────────────────────────────────────────────────────${C.reset}\n`);
418
+ console.log(`\n${C.darkGray}${'-'.repeat(width)}${C.reset}`);
419
+ console.log(`${C.cyan}Page ${currentPage + 1}/${totalPages}${C.reset} | Total Models Loaded: ${filteredModels.length}`);
420
+ console.log(`${C.gray}Controls: [Up/Down] Arrow Keys | [ENTER] Select${C.reset}`);
421
+ console.log(`${C.gray}Keys: [f] Filter Model | [n] Next | [p] Prev | [q] Cancel${C.reset}`);
422
+ console.log(`${C.darkGray}${'-'.repeat(width)}${C.reset}\n`);
415
423
  }
416
424
 
417
425
  readline.emitKeypressEvents(process.stdin);
@@ -455,7 +463,7 @@ function interactiveModelSelector(allModels, onSelect, onCancel) {
455
463
  renderMenu();
456
464
  } else if (str === 'f' || str === 'F') {
457
465
  cleanup();
458
- rl.question(`\n${C.yellow}[>] Ketik nama/filter model (misal: llama, gemini, claude, qwen):${C.reset} `, (query) => {
466
+ rl.question(`\n${C.yellow}[>] Enter model filter (e.g. llama, gemini, claude, qwen):${C.reset} `, (query) => {
459
467
  const q = query.trim().toLowerCase();
460
468
  filteredModels = allModels.filter(m => m.id.toLowerCase().includes(q)).slice(0, 600);
461
469
  currentPage = 0;
@@ -469,37 +477,37 @@ function interactiveModelSelector(allModels, onSelect, onCancel) {
469
477
  renderMenu();
470
478
  }
471
479
 
472
- // WIZARD KONFIGURASI MCP BARU (/new-mcp)
480
+ // SETUP NEW MCP SERVER WIZARD (/new-mcp)
473
481
  function createNewMCPWizard(callback) {
474
482
  interactiveProviderSelector(
475
483
  MCP_PRESETS,
476
484
  (selectedProvider) => {
477
- console.clear();
485
+ clearScreenFull();
478
486
 
479
487
  if (selectedProvider.id === 'custom') {
480
- console.log(`\n${C.cyan}=== 🛠️ SETUP MCP MANUAL (CUSTOM) ===${C.reset}\n`);
481
- rl.question(`${C.yellow}[1/4] Masukkan Nama MCP Server (misal: my-mcp):${C.reset} `, (nameInput) => {
488
+ console.log(`\n${C.cyan}=== [+] MANUAL MCP SETUP (CUSTOM) ===${C.reset}\n`);
489
+ rl.question(`${C.yellow}[1/4] Enter MCP Server Name (e.g. my-mcp):${C.reset} `, (nameInput) => {
482
490
  const mcpName = nameInput.trim().toLowerCase().replace(/\s+/g, '-');
483
491
  if (!mcpName) {
484
- console.log(`${C.red}[x] Nama MCP tidak boleh kosong.${C.reset}\n`);
492
+ console.log(`${C.red}[x] MCP name cannot be empty.${C.reset}\n`);
485
493
  return callback();
486
494
  }
487
495
 
488
- console.log(`\n${C.cyan}Pilih Tipe Koneksi MCP:${C.reset}`);
496
+ console.log(`\n${C.cyan}Select MCP Connection Type:${C.reset}`);
489
497
  console.log(` ${C.yellow}1.${C.reset} Direct REST API / Endpoint`);
490
498
  console.log(` ${C.yellow}2.${C.reset} Local Stdio Command / Path`);
491
499
 
492
- rl.question(`\n${C.yellow}[2/4] Pilih Tipe (1/2):${C.reset} `, (typeChoice) => {
500
+ rl.question(`\n${C.yellow}[2/4] Select Type (1/2):${C.reset} `, (typeChoice) => {
493
501
  const isApi = typeChoice.trim() === '1';
494
502
 
495
- rl.question(`${C.yellow}[3/4] Masukkan Target URL / Command Executable:${C.reset} `, (targetInput) => {
503
+ rl.question(`${C.yellow}[3/4] Enter Target URL / Command Executable:${C.reset} `, (targetInput) => {
496
504
  const target = targetInput.trim();
497
505
  if (!target) {
498
- console.log(`${C.red}[x] Target tidak boleh kosong.${C.reset}\n`);
506
+ console.log(`${C.red}[x] Target cannot be empty.${C.reset}\n`);
499
507
  return callback();
500
508
  }
501
509
 
502
- rl.question(`${C.yellow}[4/4] Masukkan Auth Token / API Key (Opsional - Press Enter to skip):${C.reset} `, (tokenInput) => {
510
+ rl.question(`${C.yellow}[4/4] Enter Auth Token / API Key (Optional - Press Enter to skip):${C.reset} `, (tokenInput) => {
503
511
  const authToken = tokenInput.trim();
504
512
 
505
513
  registry.mcp = registry.mcp || {};
@@ -513,21 +521,21 @@ function createNewMCPWizard(callback) {
513
521
 
514
522
  saveRegistry(registry);
515
523
 
516
- console.log(`\n${C.green}[] MCP Server "${mcpName}" berhasil ditambahkan!${C.reset}`);
517
- console.log(`${C.gray}Gunakan perintah instan: ${C.yellow}/mcp-${mcpName} <prompt kamu>${C.reset}\n`);
524
+ console.log(`\n${C.green}[+] MCP Server "${mcpName}" successfully added!${C.reset}`);
525
+ console.log(`${C.gray}Execute via: ${C.yellow}/mcp-${mcpName} <your prompt>${C.reset}\n`);
518
526
  callback();
519
527
  });
520
528
  });
521
529
  });
522
530
  });
523
531
  } else {
524
- console.log(`\n${C.cyan}=== SETUP PROVIDER: ${selectedProvider.name} ===${C.reset}`);
532
+ console.log(`\n${C.cyan}=== [+] SETUP PROVIDER: ${selectedProvider.name} ===${C.reset}`);
525
533
  console.log(`${C.gray}${selectedProvider.desc}${C.reset}\n`);
526
534
 
527
535
  const mcpName = selectedProvider.id;
528
536
 
529
537
  if (selectedProvider.reqAuth) {
530
- rl.question(`${C.yellow}[>] Masukkan ${selectedProvider.reqAuth}: ${C.reset}`, (authVal) => {
538
+ rl.question(`${C.yellow}[>] Enter ${selectedProvider.reqAuth}: ${C.reset}`, (authVal) => {
531
539
  const token = authVal.trim();
532
540
 
533
541
  registry.mcp = registry.mcp || {};
@@ -541,8 +549,8 @@ function createNewMCPWizard(callback) {
541
549
 
542
550
  saveRegistry(registry);
543
551
 
544
- console.log(`\n${C.green}[] Preset MCP "${selectedProvider.name}" berhasil terhubung!${C.reset}`);
545
- console.log(`${C.gray}Akses instan via: ${C.yellow}/mcp-${mcpName} <prompt kamu>${C.reset}\n`);
552
+ console.log(`\n${C.green}[+] MCP Preset "${selectedProvider.name}" successfully connected!${C.reset}`);
553
+ console.log(`${C.gray}Access via: ${C.yellow}/mcp-${mcpName} <your prompt>${C.reset}\n`);
546
554
  callback();
547
555
  });
548
556
  } else {
@@ -557,14 +565,14 @@ function createNewMCPWizard(callback) {
557
565
 
558
566
  saveRegistry(registry);
559
567
 
560
- console.log(`\n${C.green}[] Preset MCP "${selectedProvider.name}" berhasil terhubung!${C.reset}`);
561
- console.log(`${C.gray}Akses instan via: ${C.yellow}/mcp-${mcpName} <prompt kamu>${C.reset}\n`);
568
+ console.log(`\n${C.green}[+] MCP Preset "${selectedProvider.name}" successfully connected!${C.reset}`);
569
+ console.log(`${C.gray}Access via: ${C.yellow}/mcp-${mcpName} <your prompt>${C.reset}\n`);
562
570
  callback();
563
571
  }
564
572
  }
565
573
  },
566
574
  () => {
567
- console.log(`\n${C.gray}Setup MCP dibatalkan.${C.reset}\n`);
575
+ console.log(`\n${C.gray}MCP setup cancelled.${C.reset}\n`);
568
576
  callback();
569
577
  }
570
578
  );
@@ -575,7 +583,7 @@ function listSkills() {
575
583
  const keys = Object.keys(registry.skills || {});
576
584
 
577
585
  if (keys.length === 0) {
578
- console.log(`${C.gray}Belum ada skill terhubung. Ketik ${C.yellow}/scan${C.gray} atau ${C.yellow}/connect skill <path>${C.reset}\n`);
586
+ console.log(`${C.gray}No skills connected yet. Type ${C.yellow}/scan${C.gray} or ${C.yellow}/connect skill <path>${C.reset}\n`);
579
587
  return;
580
588
  }
581
589
 
@@ -586,14 +594,14 @@ function listSkills() {
586
594
  console.log('');
587
595
 
588
596
  const lines = [
589
- `${C.bold}Perintah Instan Skill:${C.reset}`
597
+ `${C.bold}Instant Skill Commands:${C.reset}`
590
598
  ];
591
599
 
592
600
  keys.forEach(name => {
593
- lines.push(` ${C.yellow}/skill-${name}${C.reset} <prompt kamu>`);
601
+ lines.push(` ${C.yellow}/skill-${name}${C.reset} <your prompt>`);
594
602
  });
595
603
 
596
- drawBox(" COMMAND INSTAN SKILL TERSEDIA", lines, C.c1);
604
+ drawBox("[+] AVAILABLE SKILL COMMANDS", lines, C.c1);
597
605
  console.log('');
598
606
  }
599
607
 
@@ -603,9 +611,9 @@ function connectResource(inputArgs) {
603
611
  const targetPath = parts.slice(1).join(' ');
604
612
 
605
613
  if (!type || !targetPath) {
606
- console.log(`${C.red}[x] Format salah! Gunakan:${C.reset}`);
607
- console.log(` ${C.yellow}/connect skill <filepath_atau_folder>${C.reset}`);
608
- console.log(` ${C.yellow}/connect mcp <filepath_atau_url>${C.reset}\n`);
614
+ console.log(`${C.red}[x] Invalid format! Use:${C.reset}`);
615
+ console.log(` ${C.yellow}/connect skill <filepath_or_folder>${C.reset}`);
616
+ console.log(` ${C.yellow}/connect mcp <filepath_or_url>${C.reset}\n`);
609
617
  return;
610
618
  }
611
619
 
@@ -620,7 +628,7 @@ function connectResource(inputArgs) {
620
628
  status: 'Active'
621
629
  };
622
630
  saveRegistry(registry);
623
- console.log(`${C.green}[+] Skill "${skillName}" terhubung! Akses instan: /skill-${skillName}${C.reset}\n`);
631
+ console.log(`${C.green}[+] Skill "${skillName}" connected! Access via: /skill-${skillName}${C.reset}\n`);
624
632
  } else if (type === 'mcp') {
625
633
  const mcpName = path.basename(resolvedPath, path.extname(resolvedPath));
626
634
  registry.mcp = registry.mcp || {};
@@ -631,14 +639,14 @@ function connectResource(inputArgs) {
631
639
  status: 'Connected'
632
640
  };
633
641
  saveRegistry(registry);
634
- console.log(`${C.green}[+] MCP "${mcpName}" terhubung! Akses instan: /mcp-${mcpName}${C.reset}\n`);
642
+ console.log(`${C.green}[+] MCP "${mcpName}" connected! Access via: /mcp-${mcpName}${C.reset}\n`);
635
643
  } else {
636
- console.log(`${C.red}[x] Tipe tidak dikenal. Gunakan "skill" atau "mcp".${C.reset}\n`);
644
+ console.log(`${C.red}[x] Unknown resource type. Use "skill" or "mcp".${C.reset}\n`);
637
645
  }
638
646
  }
639
647
 
640
648
  function scanResources() {
641
- console.log(`\n${C.yellow}[...] Memindai lokasi internal, home user, Gemini CLI & Claude Desktop...${C.reset}`);
649
+ console.log(`\n${C.yellow}[...] Scanning internal directories, home user, Gemini CLI & Claude Desktop...${C.reset}`);
642
650
  registry = loadRegistry();
643
651
  registry.skills = registry.skills || {};
644
652
  registry.mcp = registry.mcp || {};
@@ -681,27 +689,27 @@ function scanResources() {
681
689
  });
682
690
 
683
691
  saveRegistry(registry);
684
- console.log(`${C.green}[+] Pemindaian selesai.${C.reset}\n`);
692
+ console.log(`${C.green}[+] Scan completed.${C.reset}\n`);
685
693
 
686
694
  if (newDetected.length > 0) {
687
- drawBox("🎉 COMMAND INSTAN BARU TERDETEKSI!", [
688
- `${C.green}Kamu sekarang bisa langsung menggunakan perintah ini:${C.reset}`,
689
- ...newDetected.slice(0, 5).map(cmd => ` ${C.yellow}${cmd}${C.reset} <prompt>`),
690
- newDetected.length > 5 ? ` ...dan ${newDetected.length - 5} command lainnya.` : '',
695
+ drawBox("[+] NEW COMMANDS DETECTED!", [
696
+ `${C.green}You can now use the following commands:${C.reset}`,
697
+ ...newDetected.slice(0, 5).map(cmd => ` * ${C.yellow}${cmd}${C.reset} <prompt>`),
698
+ newDetected.length > 5 ? ` ...and ${newDetected.length - 5} more commands.` : '',
691
699
  ``,
692
- `${C.cyan}Ketik /skill atau /mcp untuk melihat semua command.${C.reset}`
700
+ `${C.cyan}Type /skill or /mcp to view all available commands.${C.reset}`
693
701
  ], C.green);
694
702
  } else {
695
- drawBox("ℹ️ HASIL PEMINDAIAN", [
696
- `Tidak ada Skill atau MCP baru yang terdeteksi.`,
697
- `Ketik ${C.yellow}/skill${C.reset} atau ${C.yellow}/mcp${C.reset} untuk melihat command instan yang aktif.`
703
+ drawBox("[i] SCAN RESULTS", [
704
+ `No new Skills or MCP servers detected.`,
705
+ `Type ${C.yellow}/skill${C.reset} or ${C.yellow}/mcp${C.reset} to view active commands.`
698
706
  ], C.gray);
699
707
  }
700
708
  console.log('');
701
709
  }
702
710
 
703
711
  function askConfig(callback) {
704
- console.log(`\n${C.cyan}[+] Choose AI Provider:${C.reset}`);
712
+ console.log(`\n${C.cyan}[+] Select AI Provider:${C.reset}`);
705
713
  console.log(` ${C.yellow}1.${C.reset} OpenRouter`);
706
714
  console.log(` ${C.yellow}2.${C.reset} Groq`);
707
715
  console.log(` ${C.yellow}3.${C.reset} NVIDIA NIM`);
@@ -739,7 +747,7 @@ function askConfig(callback) {
739
747
  });
740
748
  }
741
749
 
742
- // PERBAIKAN FETCH ALL MODELS UNIVERSAL UNTUK SEMUA PROVIDER
750
+ // FETCH MODELS UNIVERSAL FOR ALL PROVIDERS
743
751
  async function fetchAvailableModels() {
744
752
  if (!config.apiKey) {
745
753
  console.log(`${C.red}[x] API Key is not set. Use /config first.${C.reset}\n`);
@@ -753,20 +761,15 @@ async function fetchAvailableModels() {
753
761
  let fetchUrl = '';
754
762
  let headers = {};
755
763
 
756
- // 1. Google Gemini Native Models Fetch
757
764
  if (url.includes('googleapis.com')) {
758
765
  fetchUrl = `https://generativelanguage.googleapis.com/v1beta/models?key=${config.apiKey}`;
759
- }
760
- // 2. Anthropic Direct Fetch (jika endpoint anthropic)
761
- else if (url.includes('anthropic.com')) {
766
+ } else if (url.includes('anthropic.com')) {
762
767
  fetchUrl = 'https://api.anthropic.com/v1/models';
763
768
  headers = {
764
769
  'x-api-key': config.apiKey,
765
770
  'anthropic-version': '2023-06-01'
766
771
  };
767
- }
768
- // 3. OpenRouter, Groq, NVIDIA, OpenAI & Custom Universal OpenAI Standard
769
- else {
772
+ } else {
770
773
  let baseUrl = url.replace(/\/chat\/completions$/, '').replace(/\/$/, '');
771
774
  fetchUrl = baseUrl.endsWith('/models') ? baseUrl : `${baseUrl}/models`;
772
775
  headers = {
@@ -780,7 +783,6 @@ async function fetchAvailableModels() {
780
783
 
781
784
  let extractedModels = [];
782
785
 
783
- // Parsing fleksibel dari berbagai bentuk JSON provider
784
786
  if (data.data && Array.isArray(data.data)) {
785
787
  extractedModels = data.data.map(m => ({ id: typeof m === 'string' ? m : (m.id || m.name) }));
786
788
  } else if (data.models && Array.isArray(data.models)) {
@@ -793,44 +795,44 @@ async function fetchAvailableModels() {
793
795
  (selectedModelId) => {
794
796
  config.model = selectedModelId;
795
797
  saveConfig(config);
796
- console.clear();
797
- console.log(`${C.green}[] Model berhasil diubah ke: ${config.model}${C.reset}\n`);
798
+ clearScreenFull();
799
+ console.log(`${C.green}[+] Model successfully updated to: ${config.model}${C.reset}\n`);
798
800
  startPrompt();
799
801
  },
800
802
  () => {
801
- console.clear();
802
- console.log(`${C.gray}Pemilihan model dibatalkan.${C.reset}\n`);
803
+ clearScreenFull();
804
+ console.log(`${C.gray}Model selection cancelled.${C.reset}\n`);
803
805
  startPrompt();
804
806
  }
805
807
  );
806
808
  } else {
807
- console.log(`${C.red}[x] Gagal mengambil daftar model secara otomatis dari ${config.provider}.${C.reset}`);
808
- console.log(`${C.yellow}💡 Tips: Gunakan perintah ${C.bold}/custom-models${C.reset}${C.yellow} untuk menginput nama model secara manual dengan presisi.${C.reset}\n`);
809
+ console.log(`${C.red}[x] Failed to auto-fetch models from ${config.provider}.${C.reset}`);
810
+ console.log(`${C.yellow}[i] Tip: Use ${C.bold}/custom-models${C.reset}${C.yellow} to manually set the exact model ID.${C.reset}\n`);
809
811
  startPrompt();
810
812
  }
811
813
  } catch (err) {
812
814
  console.log(`${C.red}[x] Error fetching models: ${err.message}${C.reset}`);
813
- console.log(`${C.yellow}💡 Kamu bisa menggunakan perintah ${C.bold}/custom-models${C.reset}${C.yellow} untuk memasukkan nama model manual.${C.reset}\n`);
815
+ console.log(`${C.yellow}[i] Tip: Use ${C.bold}/custom-models${C.reset}${C.yellow} to set model ID manually.${C.reset}\n`);
814
816
  startPrompt();
815
817
  }
816
818
  }
817
819
 
818
- // PERINTAH BARU: /custom-models UNTUK PRESISI NAMA MODEL
820
+ // /custom-models HANDLER
819
821
  function handleCustomModels() {
820
- console.log(`\n${C.cyan}=== 🎯 SET CUSTOM MODEL (PRESISI HIGH-PRECISION) ===${C.reset}`);
821
- console.log(`${C.gray}Provider Aktif : ${C.yellow}${config.provider || 'Belum diatur'}${C.reset}`);
822
- console.log(`${C.gray}Model Saat Ini : ${C.green}${config.model || 'Belum diatur'}${C.reset}\n`);
822
+ console.log(`\n${C.cyan}=== [>] SET CUSTOM MODEL (HIGH-PRECISION) ===${C.reset}`);
823
+ console.log(`${C.gray}Active Provider : ${C.yellow}${config.provider || 'Not configured'}${C.reset}`);
824
+ console.log(`${C.gray}Current Model : ${C.green}${config.model || 'Not configured'}${C.reset}\n`);
823
825
 
824
- rl.question(`${C.yellow}[>] Masukkan ID / Nama Model secara presisi sesuai provider:${C.reset} `, (inputModel) => {
826
+ rl.question(`${C.yellow}[>] Enter exact Model ID/Name:${C.reset} `, (inputModel) => {
825
827
  const cleanModel = inputModel.trim();
826
828
  if (!cleanModel) {
827
- console.log(`${C.red}[x] Nama model tidak boleh kosong.${C.reset}\n`);
829
+ console.log(`${C.red}[x] Model name cannot be empty.${C.reset}\n`);
828
830
  return startPrompt();
829
831
  }
830
832
 
831
833
  config.model = cleanModel;
832
834
  saveConfig(config);
833
- console.log(`\n${C.green}[] Model berhasil diperbarui secara presisi ke: ${C.bold}${config.model}${C.reset}\n`);
835
+ console.log(`\n${C.green}[+] Model successfully updated to: ${C.bold}${config.model}${C.reset}\n`);
834
836
  startPrompt();
835
837
  });
836
838
  }
@@ -842,7 +844,7 @@ async function handleChat(prompt, forcedTool = null) {
842
844
  }
843
845
 
844
846
  if (forcedTool) {
845
- console.log(`${C.cyan}[🚀 Executing via ${forcedTool.type.toUpperCase()}: ${forcedTool.name}]${C.reset}`);
847
+ console.log(`${C.cyan}[+] Executing via ${forcedTool.type.toUpperCase()}: ${forcedTool.name}${C.reset}`);
846
848
  }
847
849
 
848
850
  console.log(`${C.yellow}TMPA CLI processing...${C.reset}`);
@@ -853,7 +855,7 @@ async function handleChat(prompt, forcedTool = null) {
853
855
  let bodyData = {};
854
856
 
855
857
  const toolsContext = getActiveToolsContext(forcedTool);
856
- const fullPrompt = (prompt || "Jalankan instruksi tool ini.") + toolsContext;
858
+ const fullPrompt = (prompt || "Execute this tool context.") + toolsContext;
857
859
 
858
860
  if (url.includes('googleapis.com')) {
859
861
  const activeModel = config.model || 'gemini-2.5-flash';
@@ -903,23 +905,23 @@ async function handleChat(prompt, forcedTool = null) {
903
905
  }
904
906
 
905
907
  function handleUninstall() {
906
- rl.question(`${C.red}[!] Apakah kamu yakin ingin menghapus TMPA CLI? (y/N):${C.reset} `, (answer) => {
908
+ rl.question(`${C.red}[!] Are you sure you want to uninstall TMPA CLI? (y/N):${C.reset} `, (answer) => {
907
909
  if (answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes') {
908
- console.log(`\n${C.yellow}[...] Membersihkan konfigurasi dan menghapus TMPA CLI...${C.reset}`);
910
+ console.log(`\n${C.yellow}[...] Cleaning configurations and uninstalling TMPA CLI...${C.reset}`);
909
911
  try {
910
912
  if (fs.existsSync(CONFIG_FILE)) {
911
913
  fs.unlinkSync(CONFIG_FILE);
912
914
  }
913
- console.log(`${C.green}[+] Konfigurasi lokal berhasil dibersihkan.${C.reset}`);
915
+ console.log(`${C.green}[+] Local configuration cleaned.${C.reset}`);
914
916
  execSync('npm uninstall -g tmpa-cli', { stdio: 'inherit' });
915
- console.log(`\n${C.green}[+] TMPA CLI berhasil di-uninstall sepenuhnya. Sampai jumpa!${C.reset}\n`);
917
+ console.log(`\n${C.green}[+] TMPA CLI successfully uninstalled. Goodbye!${C.reset}\n`);
916
918
  process.exit(0);
917
919
  } catch (err) {
918
- console.log(`\n${C.red}[x] Gagal uninstall otomatis. Silakan jalankan: npm uninstall -g tmpa-cli secara manual.${C.reset}\n`);
920
+ console.log(`\n${C.red}[x] Uninstall failed automatically. Please run: npm uninstall -g tmpa-cli manually.${C.reset}\n`);
919
921
  process.exit(0);
920
922
  }
921
923
  } else {
922
- console.log(`${C.gray}Proses uninstall dibatalkan.${C.reset}\n`);
924
+ console.log(`${C.gray}Uninstall cancelled.${C.reset}\n`);
923
925
  startPrompt();
924
926
  }
925
927
  });
@@ -971,7 +973,7 @@ function startPrompt() {
971
973
  if (registry.skills && registry.skills[skillName]) {
972
974
  handleChat(userPrompt, { type: 'skill', name: skillName, target: registry.skills[skillName].path });
973
975
  } else {
974
- console.log(`${C.red}[x] Skill "${skillName}" tidak ditemukan di registry. Ketik /skill untuk melihat daftar.${C.reset}\n`);
976
+ console.log(`${C.red}[x] Skill "${skillName}" not found. Type /skill to list available skills.${C.reset}\n`);
975
977
  startPrompt();
976
978
  }
977
979
  } else if (cmd.startsWith('/mcp-')) {
@@ -994,7 +996,7 @@ function startPrompt() {
994
996
  authToken: registry.mcp[mcpName].authToken
995
997
  });
996
998
  } else {
997
- console.log(`${C.red}[x] MCP "${mcpName}" tidak ditemukan di registry. Ketik /mcp untuk melihat daftar.${C.reset}\n`);
999
+ console.log(`${C.red}[x] MCP "${mcpName}" not found. Type /mcp to list active MCP servers.${C.reset}\n`);
998
1000
  startPrompt();
999
1001
  }
1000
1002
  } else if (cmd === '') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tmpa-cli",
3
- "version": "1.0.23",
3
+ "version": "1.0.24",
4
4
  "description": "",
5
5
  "main": "bin/index.js",
6
6
  "bin": {