tmpa-cli 1.0.22 → 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 +223 -188
  2. package/package.json +1 -1
package/bin/index.js CHANGED
@@ -39,52 +39,42 @@ const PROVIDERS = {
39
39
  '3': { name: 'NVIDIA NIM', endpoint: 'https://integrate.api.nvidia.com/v1', defaultModel: 'meta/llama-3.1-70b-instruct' },
40
40
  '4': { name: 'OpenAI', endpoint: 'https://api.openai.com/v1', defaultModel: 'gpt-4o-mini' },
41
41
  '5': { name: 'Anthropic', endpoint: 'https://api.anthropic.com/v1', defaultModel: 'claude-3-5-haiku-20241022' },
42
- '6': { name: 'Google Gemini', endpoint: 'https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent', defaultModel: 'gemini-2.5-flash' },
42
+ '6': { name: 'Google Gemini', endpoint: 'https://generativelanguage.googleapis.com/v1beta', defaultModel: 'gemini-2.5-flash' },
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
-
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' }
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' }
88
78
  ];
89
79
 
90
80
  function loadConfig() {
@@ -94,8 +84,8 @@ function loadConfig() {
94
84
  return {};
95
85
  }
96
86
 
97
- function saveConfig(config) {
98
- fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
87
+ function saveConfig(cfg) {
88
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(cfg, null, 2));
99
89
  }
100
90
 
101
91
  function loadRegistry() {
@@ -105,19 +95,30 @@ function loadRegistry() {
105
95
  return { skills: {}, mcp: {} };
106
96
  }
107
97
 
108
- function saveRegistry(registry) {
109
- 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);
110
108
  }
111
109
 
112
110
  function drawBox(title, contentLines, borderColor = C.cyan) {
113
- const width = 64;
114
- console.log(`${borderColor}┌${'─'.repeat(width - 2)}┐${C.reset}`);
115
- console.log(`${borderColor}│ ${C.bold}${title.padEnd(width - 4)}${C.reset}${borderColor} │${C.reset}`);
116
- 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}`);
117
116
  contentLines.forEach(line => {
118
- 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}`);
119
120
  });
120
- console.log(`${borderColor}└${''.repeat(width - 2)}┘${C.reset}`);
121
+ console.log(`${borderColor}+${'-'.repeat(width - 2)}+${C.reset}`);
121
122
  }
122
123
 
123
124
  let rl = readline.createInterface({ input: process.stdin, output: process.stdout });
@@ -125,18 +126,13 @@ let config = loadConfig();
125
126
  let registry = loadRegistry();
126
127
 
127
128
  function showBanner() {
128
- console.clear();
129
+ clearScreenFull();
130
+ const width = getTermWidth();
129
131
  console.log(`
130
- ${C.c1}████████╗███╗ ███╗██████╗ █████╗ ${C.reset}
131
- ${C.c2}╚══██╔══╝████╗ ████║██╔══██╗██╔══██╗${C.reset}
132
- ${C.c2} ██║ ██╔████╔██║██████╔╝███████║${C.reset}
133
- ${C.c3} ██║ ██║╚██╔╝██║██╔═══╝ ██╔══██║${C.reset}
134
- ${C.c4} ██║ ██║ ╚═╝ ██║██║ ██║ ██║${C.reset}
135
- ${C.c4} ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝${C.reset}
136
- ${C.darkGray}────────────────────────────────────────────────────────────${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}
139
- ${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}
140
136
  `);
141
137
  }
142
138
 
@@ -219,41 +215,42 @@ Executing User Prompt under this tool context only:\n`;
219
215
  return "\n\n[SYSTEM ENVIRONMENT SUMMARY]\n" + contextParts.join("\n") + "\n";
220
216
  }
221
217
 
222
- // RUANGAN KHUSUS MCP CENTER
218
+ // MCP CENTER
223
219
  function showMCPHub() {
224
- console.log(`\n${C.c4}============================================================${C.reset}`);
225
- console.log(`${C.c4} 🔌 MCP (MODEL CONTEXT PROTOCOL) CENTER ${C.reset}`);
226
- 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`);
227
224
 
228
225
  const keys = Object.keys(registry.mcp || {});
229
226
 
230
227
  if (keys.length === 0) {
231
- console.log(`${C.yellow} [!] Belum ada MCP Server yang terhubung saat ini.${C.reset}`);
232
- 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`);
233
230
  } else {
234
- console.log(`${C.bold}Daftar MCP Server Aktif:${C.reset}`);
231
+ console.log(`${C.bold}Active MCP Servers:${C.reset}`);
235
232
  keys.forEach((name, i) => {
236
233
  const m = registry.mcp[name];
237
- 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}`;
238
235
  console.log(` ${C.yellow}${i + 1}.${C.reset} ${C.bold}${name}${C.reset} ${typeBadge}`);
239
- console.log(` Target: ${C.gray}${m.target}${C.reset}`);
240
- 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}`);
241
238
  });
242
239
  console.log('');
243
240
  }
244
241
 
245
242
  const lines = [
246
- `${C.bold}Command Instan MCP:${C.reset}`,
247
- `${C.yellow}/new-mcp${C.reset} 👉 Tambah MCP Server Baru (Interactive Selector)`,
248
- `${C.yellow}/mcp-<nama>${C.reset} <prompt> 👉 Eksekusi MCP secara spesifik`,
249
- `${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`
250
247
  ];
251
248
 
252
- drawBox("💡 PETUNJUK RUANG MCP", lines, C.c4);
249
+ drawBox("[i] MCP CENTER HELP", lines, C.c4);
253
250
  console.log('');
254
251
  }
255
252
 
256
- // INTERACTIVE ARROW-KEY SELECTOR UNTUK MCP PROVIDERS
253
+ // INTERACTIVE PROVIDER SELECTOR
257
254
  function interactiveProviderSelector(allItems, onSelect, onCancel) {
258
255
  let filteredItems = [...allItems];
259
256
  let selectedIndex = 0;
@@ -268,7 +265,8 @@ function interactiveProviderSelector(allItems, onSelect, onCancel) {
268
265
  }
269
266
 
270
267
  function renderMenu() {
271
- console.clear();
268
+ clearScreenFull();
269
+ const width = getTermWidth();
272
270
  const totalPages = Math.ceil(filteredItems.length / pageSize) || 1;
273
271
  if (currentPage >= totalPages) currentPage = totalPages - 1;
274
272
  if (currentPage < 0) currentPage = 0;
@@ -278,31 +276,31 @@ function interactiveProviderSelector(allItems, onSelect, onCancel) {
278
276
 
279
277
  if (selectedIndex >= pageItems.length) selectedIndex = Math.max(0, pageItems.length - 1);
280
278
 
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`);
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`);
284
282
 
285
283
  if (pageItems.length === 0) {
286
- 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`);
287
285
  } else {
288
286
  pageItems.forEach((item, idx) => {
289
287
  const globalNum = startIdx + idx;
290
288
  const isFocused = idx === selectedIndex;
291
- const pointer = isFocused ? `${C.cyan}❯${C.reset}` : ' ';
289
+ const pointer = isFocused ? `${C.cyan}>${C.reset}` : ' ';
292
290
  const numStr = globalNum.toString().padStart(2, ' ');
293
291
  const badge = item.type ? `${C.gray}[${item.type}]${C.reset}` : '';
294
292
  const nameStyled = isFocused ? `${C.bold}${C.green}${item.name}${C.reset}` : item.name;
295
293
 
296
- console.log(`${pointer} ${C.yellow}${numStr}.${C.reset} ${nameStyled} ${badge}`);
294
+ console.log(`${pointer} ${C.yellow}${numStr}.${C.reset} ${nameStyled} ${badge}`);
297
295
  console.log(` ${C.darkGray}${item.desc || item.category}${C.reset}`);
298
296
  });
299
297
  }
300
298
 
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`);
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`);
306
304
  }
307
305
 
308
306
  readline.emitKeypressEvents(process.stdin);
@@ -346,7 +344,7 @@ function interactiveProviderSelector(allItems, onSelect, onCancel) {
346
344
  renderMenu();
347
345
  } else if (str === 'f' || str === 'F') {
348
346
  cleanup();
349
- 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) => {
350
348
  const q = query.trim().toLowerCase();
351
349
  filteredItems = allItems.filter(item => item.name.toLowerCase().includes(q) || (item.desc && item.desc.toLowerCase().includes(q)));
352
350
  currentPage = 0;
@@ -359,7 +357,7 @@ function interactiveProviderSelector(allItems, onSelect, onCancel) {
359
357
  if (num >= 0 && num < allItems.length) {
360
358
  onSelect(allItems[num]);
361
359
  } else {
362
- console.log(`${C.red}Nomor tidak valid.${C.reset}`);
360
+ console.log(`${C.red}Invalid index.${C.reset}`);
363
361
  setTimeout(() => interactiveProviderSelector(allItems, onSelect, onCancel), 800);
364
362
  }
365
363
  }
@@ -369,9 +367,9 @@ function interactiveProviderSelector(allItems, onSelect, onCancel) {
369
367
  renderMenu();
370
368
  }
371
369
 
372
- // INTERACTIVE ARROW-KEY SELECTOR UNTUK MODEL AI (/models)
370
+ // INTERACTIVE MODEL SELECTOR (/models) - ANTI-SPAM & CLEAN REDRAW
373
371
  function interactiveModelSelector(allModels, onSelect, onCancel) {
374
- let filteredModels = [...allModels];
372
+ let filteredModels = allModels.slice(0, 600);
375
373
  let selectedIndex = 0;
376
374
  let currentPage = 0;
377
375
  const pageSize = 10;
@@ -384,7 +382,8 @@ function interactiveModelSelector(allModels, onSelect, onCancel) {
384
382
  }
385
383
 
386
384
  function renderMenu() {
387
- console.clear();
385
+ clearScreenFull();
386
+ const width = getTermWidth();
388
387
  const totalPages = Math.ceil(filteredModels.length / pageSize) || 1;
389
388
  if (currentPage >= totalPages) currentPage = totalPages - 1;
390
389
  if (currentPage < 0) currentPage = 0;
@@ -394,33 +393,33 @@ function interactiveModelSelector(allModels, onSelect, onCancel) {
394
393
 
395
394
  if (selectedIndex >= pageItems.length) selectedIndex = Math.max(0, pageItems.length - 1);
396
395
 
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`);
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`);
401
400
 
402
401
  if (pageItems.length === 0) {
403
- 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`);
404
403
  } else {
405
404
  pageItems.forEach((m, idx) => {
406
405
  const globalNum = startIdx + idx;
407
406
  const isFocused = idx === selectedIndex;
408
407
  const isCurrentActive = m.id === config.model;
409
408
 
410
- const pointer = isFocused ? `${C.cyan}❯${C.reset}` : ' ';
411
- const activeBadge = isCurrentActive ? `${C.green}(Aktif)${C.reset}` : '';
412
- 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, ' ');
413
412
  const modelNameStyled = isFocused ? `${C.bold}${C.yellow}${m.id}${C.reset}` : m.id;
414
413
 
415
- console.log(`${pointer} ${C.gray}${numStr}.${C.reset} ${modelNameStyled} ${activeBadge}`);
414
+ console.log(`${pointer} ${C.gray}${numStr}.${C.reset} ${modelNameStyled} ${activeBadge}`);
416
415
  });
417
416
  }
418
417
 
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`);
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`);
424
423
  }
425
424
 
426
425
  readline.emitKeypressEvents(process.stdin);
@@ -464,9 +463,9 @@ function interactiveModelSelector(allModels, onSelect, onCancel) {
464
463
  renderMenu();
465
464
  } else if (str === 'f' || str === 'F') {
466
465
  cleanup();
467
- rl.question(`\n${C.yellow}[>] Ketik kata kunci nama model (misal: llama, gemini, claude):${C.reset} `, (query) => {
466
+ rl.question(`\n${C.yellow}[>] Enter model filter (e.g. llama, gemini, claude, qwen):${C.reset} `, (query) => {
468
467
  const q = query.trim().toLowerCase();
469
- filteredModels = allModels.filter(m => m.id.toLowerCase().includes(q));
468
+ filteredModels = allModels.filter(m => m.id.toLowerCase().includes(q)).slice(0, 600);
470
469
  currentPage = 0;
471
470
  selectedIndex = 0;
472
471
  interactiveModelSelector(filteredModels, onSelect, onCancel);
@@ -478,38 +477,37 @@ function interactiveModelSelector(allModels, onSelect, onCancel) {
478
477
  renderMenu();
479
478
  }
480
479
 
481
- // WIZARD KONFIGURASI MCP BARU (/new-mcp)
480
+ // SETUP NEW MCP SERVER WIZARD (/new-mcp)
482
481
  function createNewMCPWizard(callback) {
483
482
  interactiveProviderSelector(
484
483
  MCP_PRESETS,
485
484
  (selectedProvider) => {
486
- console.clear();
485
+ clearScreenFull();
487
486
 
488
- // CASE 1: CUSTOM MCP INPUT MANUAL
489
487
  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) => {
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) => {
492
490
  const mcpName = nameInput.trim().toLowerCase().replace(/\s+/g, '-');
493
491
  if (!mcpName) {
494
- 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`);
495
493
  return callback();
496
494
  }
497
495
 
498
- console.log(`\n${C.cyan}Pilih Tipe Koneksi MCP:${C.reset}`);
496
+ console.log(`\n${C.cyan}Select MCP Connection Type:${C.reset}`);
499
497
  console.log(` ${C.yellow}1.${C.reset} Direct REST API / Endpoint`);
500
498
  console.log(` ${C.yellow}2.${C.reset} Local Stdio Command / Path`);
501
499
 
502
- 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) => {
503
501
  const isApi = typeChoice.trim() === '1';
504
502
 
505
- 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) => {
506
504
  const target = targetInput.trim();
507
505
  if (!target) {
508
- 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`);
509
507
  return callback();
510
508
  }
511
509
 
512
- 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) => {
513
511
  const authToken = tokenInput.trim();
514
512
 
515
513
  registry.mcp = registry.mcp || {};
@@ -523,23 +521,21 @@ function createNewMCPWizard(callback) {
523
521
 
524
522
  saveRegistry(registry);
525
523
 
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`);
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`);
528
526
  callback();
529
527
  });
530
528
  });
531
529
  });
532
530
  });
533
- }
534
- // CASE 2: PRESET PROVIDER POPULER
535
- else {
536
- console.log(`\n${C.cyan}=== ⚡ SETUP PROVIDER: ${selectedProvider.name} ===${C.reset}`);
531
+ } else {
532
+ console.log(`\n${C.cyan}=== [+] SETUP PROVIDER: ${selectedProvider.name} ===${C.reset}`);
537
533
  console.log(`${C.gray}${selectedProvider.desc}${C.reset}\n`);
538
534
 
539
535
  const mcpName = selectedProvider.id;
540
536
 
541
537
  if (selectedProvider.reqAuth) {
542
- rl.question(`${C.yellow}[>] Masukkan ${selectedProvider.reqAuth}: ${C.reset}`, (authVal) => {
538
+ rl.question(`${C.yellow}[>] Enter ${selectedProvider.reqAuth}: ${C.reset}`, (authVal) => {
543
539
  const token = authVal.trim();
544
540
 
545
541
  registry.mcp = registry.mcp || {};
@@ -553,8 +549,8 @@ function createNewMCPWizard(callback) {
553
549
 
554
550
  saveRegistry(registry);
555
551
 
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`);
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`);
558
554
  callback();
559
555
  });
560
556
  } else {
@@ -569,14 +565,14 @@ function createNewMCPWizard(callback) {
569
565
 
570
566
  saveRegistry(registry);
571
567
 
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`);
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`);
574
570
  callback();
575
571
  }
576
572
  }
577
573
  },
578
574
  () => {
579
- console.log(`\n${C.gray}Setup MCP dibatalkan.${C.reset}\n`);
575
+ console.log(`\n${C.gray}MCP setup cancelled.${C.reset}\n`);
580
576
  callback();
581
577
  }
582
578
  );
@@ -587,7 +583,7 @@ function listSkills() {
587
583
  const keys = Object.keys(registry.skills || {});
588
584
 
589
585
  if (keys.length === 0) {
590
- 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`);
591
587
  return;
592
588
  }
593
589
 
@@ -598,14 +594,14 @@ function listSkills() {
598
594
  console.log('');
599
595
 
600
596
  const lines = [
601
- `${C.bold}Perintah Instan Skill:${C.reset}`
597
+ `${C.bold}Instant Skill Commands:${C.reset}`
602
598
  ];
603
599
 
604
600
  keys.forEach(name => {
605
- lines.push(` ${C.yellow}/skill-${name}${C.reset} <prompt kamu>`);
601
+ lines.push(` ${C.yellow}/skill-${name}${C.reset} <your prompt>`);
606
602
  });
607
603
 
608
- drawBox(" COMMAND INSTAN SKILL TERSEDIA", lines, C.c1);
604
+ drawBox("[+] AVAILABLE SKILL COMMANDS", lines, C.c1);
609
605
  console.log('');
610
606
  }
611
607
 
@@ -615,9 +611,9 @@ function connectResource(inputArgs) {
615
611
  const targetPath = parts.slice(1).join(' ');
616
612
 
617
613
  if (!type || !targetPath) {
618
- console.log(`${C.red}[x] Format salah! Gunakan:${C.reset}`);
619
- console.log(` ${C.yellow}/connect skill <filepath_atau_folder>${C.reset}`);
620
- 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`);
621
617
  return;
622
618
  }
623
619
 
@@ -632,7 +628,7 @@ function connectResource(inputArgs) {
632
628
  status: 'Active'
633
629
  };
634
630
  saveRegistry(registry);
635
- 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`);
636
632
  } else if (type === 'mcp') {
637
633
  const mcpName = path.basename(resolvedPath, path.extname(resolvedPath));
638
634
  registry.mcp = registry.mcp || {};
@@ -643,14 +639,14 @@ function connectResource(inputArgs) {
643
639
  status: 'Connected'
644
640
  };
645
641
  saveRegistry(registry);
646
- 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`);
647
643
  } else {
648
- 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`);
649
645
  }
650
646
  }
651
647
 
652
648
  function scanResources() {
653
- 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}`);
654
650
  registry = loadRegistry();
655
651
  registry.skills = registry.skills || {};
656
652
  registry.mcp = registry.mcp || {};
@@ -693,33 +689,33 @@ function scanResources() {
693
689
  });
694
690
 
695
691
  saveRegistry(registry);
696
- console.log(`${C.green}[+] Pemindaian selesai.${C.reset}\n`);
692
+ console.log(`${C.green}[+] Scan completed.${C.reset}\n`);
697
693
 
698
694
  if (newDetected.length > 0) {
699
- drawBox("🎉 COMMAND INSTAN BARU TERDETEKSI!", [
700
- `${C.green}Kamu sekarang bisa langsung menggunakan perintah ini:${C.reset}`,
701
- ...newDetected.slice(0, 5).map(cmd => ` ${C.yellow}${cmd}${C.reset} <prompt>`),
702
- 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.` : '',
703
699
  ``,
704
- `${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}`
705
701
  ], C.green);
706
702
  } else {
707
- drawBox("ℹ️ HASIL PEMINDAIAN", [
708
- `Tidak ada Skill atau MCP baru yang terdeteksi.`,
709
- `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.`
710
706
  ], C.gray);
711
707
  }
712
708
  console.log('');
713
709
  }
714
710
 
715
711
  function askConfig(callback) {
716
- console.log(`\n${C.cyan}[+] Choose AI Provider:${C.reset}`);
712
+ console.log(`\n${C.cyan}[+] Select AI Provider:${C.reset}`);
717
713
  console.log(` ${C.yellow}1.${C.reset} OpenRouter`);
718
714
  console.log(` ${C.yellow}2.${C.reset} Groq`);
719
715
  console.log(` ${C.yellow}3.${C.reset} NVIDIA NIM`);
720
716
  console.log(` ${C.yellow}4.${C.reset} OpenAI`);
721
717
  console.log(` ${C.yellow}5.${C.reset} Anthropic`);
722
- console.log(` ${C.yellow}6.${C.reset} Google Gemini (Default)`);
718
+ console.log(` ${C.yellow}6.${C.reset} Google Gemini`);
723
719
  console.log(` ${C.yellow}7.${C.reset} Custom / Auto-Detect Endpoint\n`);
724
720
 
725
721
  rl.question(`${C.yellow}[>] Select Provider (1-7):${C.reset} `, (choice) => {
@@ -751,6 +747,7 @@ function askConfig(callback) {
751
747
  });
752
748
  }
753
749
 
750
+ // FETCH MODELS UNIVERSAL FOR ALL PROVIDERS
754
751
  async function fetchAvailableModels() {
755
752
  if (!config.apiKey) {
756
753
  console.log(`${C.red}[x] API Key is not set. Use /config first.${C.reset}\n`);
@@ -761,49 +758,85 @@ async function fetchAvailableModels() {
761
758
 
762
759
  try {
763
760
  let url = config.endpoint;
764
- if (url.includes('googleapis.com')) {
765
- console.log(`${C.yellow}[!] Gemini Default REST API uses fixed model: ${config.model}${C.reset}\n`);
766
- return startPrompt();
767
- }
761
+ let fetchUrl = '';
762
+ let headers = {};
768
763
 
769
- let modelsUrl = url.endsWith('/models') ? url : `${url.replace(/\/chat\/completions$/, '')}/models`;
770
-
771
- const response = await fetch(modelsUrl, {
772
- method: 'GET',
773
- headers: {
764
+ if (url.includes('googleapis.com')) {
765
+ fetchUrl = `https://generativelanguage.googleapis.com/v1beta/models?key=${config.apiKey}`;
766
+ } else if (url.includes('anthropic.com')) {
767
+ fetchUrl = 'https://api.anthropic.com/v1/models';
768
+ headers = {
769
+ 'x-api-key': config.apiKey,
770
+ 'anthropic-version': '2023-06-01'
771
+ };
772
+ } else {
773
+ let baseUrl = url.replace(/\/chat\/completions$/, '').replace(/\/$/, '');
774
+ fetchUrl = baseUrl.endsWith('/models') ? baseUrl : `${baseUrl}/models`;
775
+ headers = {
774
776
  'Authorization': `Bearer ${config.apiKey}`,
775
777
  'Content-Type': 'application/json'
776
- }
777
- });
778
+ };
779
+ }
778
780
 
781
+ const response = await fetch(fetchUrl, { method: 'GET', headers: headers });
779
782
  const data = await response.json();
780
783
 
781
- if (data.data && Array.isArray(data.data) && data.data.length > 0) {
784
+ let extractedModels = [];
785
+
786
+ if (data.data && Array.isArray(data.data)) {
787
+ extractedModels = data.data.map(m => ({ id: typeof m === 'string' ? m : (m.id || m.name) }));
788
+ } else if (data.models && Array.isArray(data.models)) {
789
+ extractedModels = data.models.map(m => ({ id: typeof m === 'string' ? m : (m.name?.replace('models/', '') || m.id) }));
790
+ }
791
+
792
+ if (extractedModels.length > 0) {
782
793
  interactiveModelSelector(
783
- data.data,
794
+ extractedModels,
784
795
  (selectedModelId) => {
785
796
  config.model = selectedModelId;
786
797
  saveConfig(config);
787
- console.clear();
788
- 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`);
789
800
  startPrompt();
790
801
  },
791
802
  () => {
792
- console.clear();
793
- console.log(`${C.gray}Pemilihan model dibatalkan.${C.reset}\n`);
803
+ clearScreenFull();
804
+ console.log(`${C.gray}Model selection cancelled.${C.reset}\n`);
794
805
  startPrompt();
795
806
  }
796
807
  );
797
808
  } else {
798
- console.log(`${C.red}[x] Could not retrieve models list automatically from ${config.provider}.${C.reset}`);
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`);
799
811
  startPrompt();
800
812
  }
801
813
  } catch (err) {
802
- console.log(`${C.red}[x] Error fetching models: ${err.message}${C.reset}\n`);
814
+ console.log(`${C.red}[x] Error fetching models: ${err.message}${C.reset}`);
815
+ console.log(`${C.yellow}[i] Tip: Use ${C.bold}/custom-models${C.reset}${C.yellow} to set model ID manually.${C.reset}\n`);
803
816
  startPrompt();
804
817
  }
805
818
  }
806
819
 
820
+ // /custom-models HANDLER
821
+ function handleCustomModels() {
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`);
825
+
826
+ rl.question(`${C.yellow}[>] Enter exact Model ID/Name:${C.reset} `, (inputModel) => {
827
+ const cleanModel = inputModel.trim();
828
+ if (!cleanModel) {
829
+ console.log(`${C.red}[x] Model name cannot be empty.${C.reset}\n`);
830
+ return startPrompt();
831
+ }
832
+
833
+ config.model = cleanModel;
834
+ saveConfig(config);
835
+ console.log(`\n${C.green}[+] Model successfully updated to: ${C.bold}${config.model}${C.reset}\n`);
836
+ startPrompt();
837
+ });
838
+ }
839
+
807
840
  async function handleChat(prompt, forcedTool = null) {
808
841
  if (!config.apiKey) {
809
842
  console.log(`${C.yellow}[!] API Key is not set.${C.reset}`);
@@ -811,7 +844,7 @@ async function handleChat(prompt, forcedTool = null) {
811
844
  }
812
845
 
813
846
  if (forcedTool) {
814
- 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}`);
815
848
  }
816
849
 
817
850
  console.log(`${C.yellow}TMPA CLI processing...${C.reset}`);
@@ -822,10 +855,11 @@ async function handleChat(prompt, forcedTool = null) {
822
855
  let bodyData = {};
823
856
 
824
857
  const toolsContext = getActiveToolsContext(forcedTool);
825
- const fullPrompt = (prompt || "Jalankan instruksi tool ini.") + toolsContext;
858
+ const fullPrompt = (prompt || "Execute this tool context.") + toolsContext;
826
859
 
827
860
  if (url.includes('googleapis.com')) {
828
- url = `${url}?key=${config.apiKey}`;
861
+ const activeModel = config.model || 'gemini-2.5-flash';
862
+ url = `https://generativelanguage.googleapis.com/v1beta/models/${activeModel}:generateContent?key=${config.apiKey}`;
829
863
  bodyData = { contents: [{ parts: [{ text: fullPrompt }] }] };
830
864
  } else {
831
865
  if (!url.endsWith('/chat/completions')) {
@@ -848,7 +882,6 @@ async function handleChat(prompt, forcedTool = null) {
848
882
 
849
883
  let aiResponse = "";
850
884
 
851
- // DUKUNGAN UNTUK REASONING MODELS & STANDAR OPENAI/GEMINI
852
885
  if (data.choices && data.choices[0] && data.choices[0].message) {
853
886
  const msg = data.choices[0].message;
854
887
  aiResponse = msg.content || msg.reasoning || msg.reasoning_content || "";
@@ -872,23 +905,23 @@ async function handleChat(prompt, forcedTool = null) {
872
905
  }
873
906
 
874
907
  function handleUninstall() {
875
- 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) => {
876
909
  if (answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes') {
877
- 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}`);
878
911
  try {
879
912
  if (fs.existsSync(CONFIG_FILE)) {
880
913
  fs.unlinkSync(CONFIG_FILE);
881
914
  }
882
- console.log(`${C.green}[+] Konfigurasi lokal berhasil dibersihkan.${C.reset}`);
915
+ console.log(`${C.green}[+] Local configuration cleaned.${C.reset}`);
883
916
  execSync('npm uninstall -g tmpa-cli', { stdio: 'inherit' });
884
- 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`);
885
918
  process.exit(0);
886
919
  } catch (err) {
887
- 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`);
888
921
  process.exit(0);
889
922
  }
890
923
  } else {
891
- console.log(`${C.gray}Proses uninstall dibatalkan.${C.reset}\n`);
924
+ console.log(`${C.gray}Uninstall cancelled.${C.reset}\n`);
892
925
  startPrompt();
893
926
  }
894
927
  });
@@ -908,6 +941,8 @@ function startPrompt() {
908
941
  askConfig(() => startPrompt());
909
942
  } else if (cmd === '/models') {
910
943
  fetchAvailableModels();
944
+ } else if (cmd === '/custom-models') {
945
+ handleCustomModels();
911
946
  } else if (cmd === '/skill' || cmd === '/skills') {
912
947
  listSkills();
913
948
  startPrompt();
@@ -938,7 +973,7 @@ function startPrompt() {
938
973
  if (registry.skills && registry.skills[skillName]) {
939
974
  handleChat(userPrompt, { type: 'skill', name: skillName, target: registry.skills[skillName].path });
940
975
  } else {
941
- 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`);
942
977
  startPrompt();
943
978
  }
944
979
  } else if (cmd.startsWith('/mcp-')) {
@@ -961,7 +996,7 @@ function startPrompt() {
961
996
  authToken: registry.mcp[mcpName].authToken
962
997
  });
963
998
  } else {
964
- 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`);
965
1000
  startPrompt();
966
1001
  }
967
1002
  } else if (cmd === '') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tmpa-cli",
3
- "version": "1.0.22",
3
+ "version": "1.0.24",
4
4
  "description": "",
5
5
  "main": "bin/index.js",
6
6
  "bin": {