tmpa-cli 1.0.21 → 1.0.23

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 +197 -116
  2. package/package.json +1 -1
package/bin/index.js CHANGED
@@ -39,15 +39,13 @@ 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
46
  // 100+ POPULAR MCP PRESETS LIST
47
47
  const MCP_PRESETS = [
48
48
  { id: 'custom', name: '🛠️ Custom / Lainnya', category: 'General', type: 'custom', desc: 'Input Manual Name, Target, & Key' },
49
-
50
- // Cloud, AI & UI
51
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' },
52
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' },
53
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' },
@@ -56,30 +54,22 @@ const MCP_PRESETS = [
56
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' },
57
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' },
58
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' },
59
-
60
- // Developer Tools & VCS
61
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' },
62
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' },
63
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' },
64
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' },
65
61
  { 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
62
  { id: 'postgresql', name: '🗄️ PostgreSQL', category: 'Databases', type: 'stdio', command: 'npx -y @modelcontextprotocol/server-postgres', reqAuth: 'DATABASE_URL', desc: 'Direct SQL query execution' },
69
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' },
70
64
  { id: 'sqlite', name: '📦 SQLite Local', category: 'Databases', type: 'stdio', command: 'npx -y @modelcontextprotocol/server-sqlite', desc: 'Lightweight local database query tool' },
71
65
  { id: 'mongodb', name: '🍃 MongoDB MCP', category: 'Databases', type: 'stdio', command: 'npx -y mongodb-mcp-server', reqAuth: 'MONGODB_URI', desc: 'NoSQL collection queries & aggregation' },
72
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' },
73
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' },
74
-
75
- // Search & Web Scraping
76
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' },
77
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' },
78
70
  { id: 'puppeteer', name: '🌐 Puppeteer Scraper', category: 'Search & Scraping', type: 'stdio', command: 'npx -y @modelcontextprotocol/server-puppeteer', desc: 'Headless browser web automation & screenshot' },
79
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' },
80
72
  { 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
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' },
84
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' },
85
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' },
@@ -135,7 +125,7 @@ ${C.c4} ██║ ██║ ╚═╝ ██║██║ ██║ █
135
125
  ${C.c4} ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝${C.reset}
136
126
  ${C.darkGray}────────────────────────────────────────────────────────────${C.reset}
137
127
  ${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 | /exit${C.reset}
128
+ ${C.gray}/config | /models | /custom-models | /skill | /mcp | /new-mcp | /scan | /uninstall${C.reset}
139
129
  ${C.darkGray}────────────────────────────────────────────────────────────${C.reset}
140
130
  `);
141
131
  }
@@ -253,7 +243,7 @@ function showMCPHub() {
253
243
  console.log('');
254
244
  }
255
245
 
256
- // INTERACTIVE ARROW-KEY SELECTOR FOR TERMUX & COMPUTERS
246
+ // INTERACTIVE ARROW-KEY SELECTOR UNTUK MCP PROVIDERS
257
247
  function interactiveProviderSelector(allItems, onSelect, onCancel) {
258
248
  let filteredItems = [...allItems];
259
249
  let selectedIndex = 0;
@@ -369,6 +359,116 @@ function interactiveProviderSelector(allItems, onSelect, onCancel) {
369
359
  renderMenu();
370
360
  }
371
361
 
362
+ // INTERACTIVE ARROW-KEY SELECTOR UNTUK MODEL AI (/models) - CAPACITY HINGGA 600 MODEL
363
+ function interactiveModelSelector(allModels, onSelect, onCancel) {
364
+ // Cap pada 600 model untuk mencegah lag ekstrem di memori Termux
365
+ let filteredModels = allModels.slice(0, 600);
366
+ let selectedIndex = 0;
367
+ let currentPage = 0;
368
+ const pageSize = 10;
369
+
370
+ const wasRaw = process.stdin.isRaw;
371
+
372
+ function cleanup() {
373
+ process.stdin.removeListener('keypress', handleKeypress);
374
+ if (process.stdin.setRawMode) process.stdin.setRawMode(wasRaw || false);
375
+ }
376
+
377
+ function renderMenu() {
378
+ console.clear();
379
+ const totalPages = Math.ceil(filteredModels.length / pageSize) || 1;
380
+ if (currentPage >= totalPages) currentPage = totalPages - 1;
381
+ if (currentPage < 0) currentPage = 0;
382
+
383
+ const startIdx = currentPage * pageSize;
384
+ const pageItems = filteredModels.slice(startIdx, startIdx + pageSize);
385
+
386
+ if (selectedIndex >= pageItems.length) selectedIndex = Math.max(0, pageItems.length - 1);
387
+
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`);
392
+
393
+ if (pageItems.length === 0) {
394
+ console.log(`${C.red} Tidak ada model yang cocok dengan pencarian.${C.reset}\n`);
395
+ } else {
396
+ pageItems.forEach((m, idx) => {
397
+ const globalNum = startIdx + idx;
398
+ const isFocused = idx === selectedIndex;
399
+ const isCurrentActive = m.id === config.model;
400
+
401
+ const pointer = isFocused ? `${C.cyan}❯${C.reset}` : ' ';
402
+ const activeBadge = isCurrentActive ? `${C.green}(Aktif)${C.reset}` : '';
403
+ const numStr = globalNum.toString().padStart(3, ' ');
404
+ const modelNameStyled = isFocused ? `${C.bold}${C.yellow}${m.id}${C.reset}` : m.id;
405
+
406
+ console.log(`${pointer} ${C.gray}${numStr}.${C.reset} • ${modelNameStyled} ${activeBadge}`);
407
+ });
408
+ }
409
+
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`);
415
+ }
416
+
417
+ readline.emitKeypressEvents(process.stdin);
418
+ if (process.stdin.setRawMode) process.stdin.setRawMode(true);
419
+
420
+ function handleKeypress(str, key) {
421
+ const totalPages = Math.ceil(filteredModels.length / pageSize) || 1;
422
+ const startIdx = currentPage * pageSize;
423
+ const pageItems = filteredModels.slice(startIdx, startIdx + pageSize);
424
+
425
+ if (key.name === 'up') {
426
+ if (selectedIndex > 0) {
427
+ selectedIndex--;
428
+ } else if (currentPage > 0) {
429
+ currentPage--;
430
+ selectedIndex = pageSize - 1;
431
+ }
432
+ renderMenu();
433
+ } else if (key.name === 'down') {
434
+ if (selectedIndex < pageItems.length - 1) {
435
+ selectedIndex++;
436
+ } else if (currentPage < totalPages - 1) {
437
+ currentPage++;
438
+ selectedIndex = 0;
439
+ }
440
+ renderMenu();
441
+ } else if (key.name === 'return') {
442
+ cleanup();
443
+ const selectedModelObj = pageItems[selectedIndex];
444
+ if (selectedModelObj) onSelect(selectedModelObj.id);
445
+ } else if (str === 'q' || str === 'Q' || (key.ctrl && key.name === 'c')) {
446
+ cleanup();
447
+ onCancel();
448
+ } else if (str === 'n' || str === 'N') {
449
+ if (currentPage < totalPages - 1) currentPage++;
450
+ selectedIndex = 0;
451
+ renderMenu();
452
+ } else if (str === 'p' || str === 'P') {
453
+ if (currentPage > 0) currentPage--;
454
+ selectedIndex = 0;
455
+ renderMenu();
456
+ } else if (str === 'f' || str === 'F') {
457
+ cleanup();
458
+ rl.question(`\n${C.yellow}[>] Ketik nama/filter model (misal: llama, gemini, claude, qwen):${C.reset} `, (query) => {
459
+ const q = query.trim().toLowerCase();
460
+ filteredModels = allModels.filter(m => m.id.toLowerCase().includes(q)).slice(0, 600);
461
+ currentPage = 0;
462
+ selectedIndex = 0;
463
+ interactiveModelSelector(filteredModels, onSelect, onCancel);
464
+ });
465
+ }
466
+ }
467
+
468
+ process.stdin.on('keypress', handleKeypress);
469
+ renderMenu();
470
+ }
471
+
372
472
  // WIZARD KONFIGURASI MCP BARU (/new-mcp)
373
473
  function createNewMCPWizard(callback) {
374
474
  interactiveProviderSelector(
@@ -376,7 +476,6 @@ function createNewMCPWizard(callback) {
376
476
  (selectedProvider) => {
377
477
  console.clear();
378
478
 
379
- // CASE 1: CUSTOM MCP INPUT MANUAL
380
479
  if (selectedProvider.id === 'custom') {
381
480
  console.log(`\n${C.cyan}=== 🛠️ SETUP MCP MANUAL (CUSTOM) ===${C.reset}\n`);
382
481
  rl.question(`${C.yellow}[1/4] Masukkan Nama MCP Server (misal: my-mcp):${C.reset} `, (nameInput) => {
@@ -421,9 +520,7 @@ function createNewMCPWizard(callback) {
421
520
  });
422
521
  });
423
522
  });
424
- }
425
- // CASE 2: PRESET PROVIDER POPULER
426
- else {
523
+ } else {
427
524
  console.log(`\n${C.cyan}=== ⚡ SETUP PROVIDER: ${selectedProvider.name} ===${C.reset}`);
428
525
  console.log(`${C.gray}${selectedProvider.desc}${C.reset}\n`);
429
526
 
@@ -610,7 +707,7 @@ function askConfig(callback) {
610
707
  console.log(` ${C.yellow}3.${C.reset} NVIDIA NIM`);
611
708
  console.log(` ${C.yellow}4.${C.reset} OpenAI`);
612
709
  console.log(` ${C.yellow}5.${C.reset} Anthropic`);
613
- console.log(` ${C.yellow}6.${C.reset} Google Gemini (Default)`);
710
+ console.log(` ${C.yellow}6.${C.reset} Google Gemini`);
614
711
  console.log(` ${C.yellow}7.${C.reset} Custom / Auto-Detect Endpoint\n`);
615
712
 
616
713
  rl.question(`${C.yellow}[>] Select Provider (1-7):${C.reset} `, (choice) => {
@@ -642,81 +739,7 @@ function askConfig(callback) {
642
739
  });
643
740
  }
644
741
 
645
- function selectModelCLI(allModels) {
646
- let filteredModels = [...allModels];
647
- let currentPage = 0;
648
- const pageSize = 12;
649
-
650
- function displayList() {
651
- console.clear();
652
- const totalPages = Math.ceil(filteredModels.length / pageSize) || 1;
653
- if (currentPage >= totalPages) currentPage = totalPages - 1;
654
- if (currentPage < 0) currentPage = 0;
655
-
656
- const startIdx = currentPage * pageSize;
657
- const pageItems = filteredModels.slice(startIdx, startIdx + pageSize);
658
-
659
- console.log(`${C.cyan}=== Models Selector for ${config.provider} (${filteredModels.length} models) ===${C.reset}`);
660
- console.log(`${C.gray}Current Active: ${C.green}${config.model || 'None'}${C.reset}`);
661
- console.log(`${C.darkGray}────────────────────────────────────────────────────────────${C.reset}`);
662
-
663
- if (pageItems.length === 0) {
664
- console.log(`${C.red} No models found matching filter.${C.reset}`);
665
- } else {
666
- pageItems.forEach((m, idx) => {
667
- const itemNum = startIdx + idx + 1;
668
- const isCurrent = m.id === config.model;
669
- const bullet = isCurrent ? `${C.green}●${C.reset}` : `${C.gray}○${C.reset}`;
670
- console.log(` ${bullet} ${C.yellow}${itemNum.toString().padStart(3, ' ')}.${C.reset} ${m.id}`);
671
- });
672
- }
673
-
674
- console.log(`${C.darkGray}────────────────────────────────────────────────────────────${C.reset}`);
675
- console.log(`${C.cyan}Page ${currentPage + 1}/${totalPages}${C.reset} | ${C.gray}Commands: [n]ext | [p]rev | [f]ilter <keyword> | [q]uit${C.reset}`);
676
-
677
- rl.question(`\n${C.yellow}[>] Enter Number / Model ID / Command:${C.reset} `, (answer) => {
678
- const input = answer.trim();
679
-
680
- if (input.toLowerCase() === 'n') {
681
- if (currentPage < totalPages - 1) currentPage++;
682
- displayList();
683
- } else if (input.toLowerCase() === 'p') {
684
- if (currentPage > 0) currentPage--;
685
- displayList();
686
- } else if (input.toLowerCase() === 'q' || input.toLowerCase() === 'exit') {
687
- console.log(`${C.gray}Model selection cancelled.${C.reset}\n`);
688
- startPrompt();
689
- } else if (input.toLowerCase().startsWith('f ')) {
690
- const query = input.slice(2).trim().toLowerCase();
691
- filteredModels = allModels.filter(m => m.id.toLowerCase().includes(query));
692
- currentPage = 0;
693
- displayList();
694
- } else if (!isNaN(input) && input !== '') {
695
- const num = parseInt(input);
696
- if (num >= 1 && num <= filteredModels.length) {
697
- const selected = filteredModels[num - 1].id;
698
- config.model = selected;
699
- saveConfig(config);
700
- console.log(`\n${C.green}[+] Model changed to: ${config.model}${C.reset}\n`);
701
- startPrompt();
702
- } else {
703
- console.log(`${C.red}[x] Invalid number!${C.reset}`);
704
- setTimeout(displayList, 1000);
705
- }
706
- } else if (input.length > 0) {
707
- config.model = input;
708
- saveConfig(config);
709
- console.log(`\n${C.green}[+] Custom Model ID set to: ${config.model}${C.reset}\n`);
710
- startPrompt();
711
- } else {
712
- startPrompt();
713
- }
714
- });
715
- }
716
-
717
- displayList();
718
- }
719
-
742
+ // PERBAIKAN FETCH ALL MODELS UNIVERSAL UNTUK SEMUA PROVIDER
720
743
  async function fetchAvailableModels() {
721
744
  if (!config.apiKey) {
722
745
  console.log(`${C.red}[x] API Key is not set. Use /config first.${C.reset}\n`);
@@ -727,35 +750,91 @@ async function fetchAvailableModels() {
727
750
 
728
751
  try {
729
752
  let url = config.endpoint;
730
- if (url.includes('googleapis.com')) {
731
- console.log(`${C.yellow}[!] Gemini Default REST API uses fixed model: ${config.model}${C.reset}\n`);
732
- return startPrompt();
733
- }
734
-
735
- let modelsUrl = url.endsWith('/models') ? url : `${url.replace(/\/chat\/completions$/, '')}/models`;
753
+ let fetchUrl = '';
754
+ let headers = {};
736
755
 
737
- const response = await fetch(modelsUrl, {
738
- method: 'GET',
739
- headers: {
756
+ // 1. Google Gemini Native Models Fetch
757
+ if (url.includes('googleapis.com')) {
758
+ 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')) {
762
+ fetchUrl = 'https://api.anthropic.com/v1/models';
763
+ headers = {
764
+ 'x-api-key': config.apiKey,
765
+ 'anthropic-version': '2023-06-01'
766
+ };
767
+ }
768
+ // 3. OpenRouter, Groq, NVIDIA, OpenAI & Custom Universal OpenAI Standard
769
+ else {
770
+ let baseUrl = url.replace(/\/chat\/completions$/, '').replace(/\/$/, '');
771
+ fetchUrl = baseUrl.endsWith('/models') ? baseUrl : `${baseUrl}/models`;
772
+ headers = {
740
773
  'Authorization': `Bearer ${config.apiKey}`,
741
774
  'Content-Type': 'application/json'
742
- }
743
- });
775
+ };
776
+ }
744
777
 
778
+ const response = await fetch(fetchUrl, { method: 'GET', headers: headers });
745
779
  const data = await response.json();
746
780
 
747
- if (data.data && Array.isArray(data.data) && data.data.length > 0) {
748
- selectModelCLI(data.data);
781
+ let extractedModels = [];
782
+
783
+ // Parsing fleksibel dari berbagai bentuk JSON provider
784
+ if (data.data && Array.isArray(data.data)) {
785
+ extractedModels = data.data.map(m => ({ id: typeof m === 'string' ? m : (m.id || m.name) }));
786
+ } else if (data.models && Array.isArray(data.models)) {
787
+ extractedModels = data.models.map(m => ({ id: typeof m === 'string' ? m : (m.name?.replace('models/', '') || m.id) }));
788
+ }
789
+
790
+ if (extractedModels.length > 0) {
791
+ interactiveModelSelector(
792
+ extractedModels,
793
+ (selectedModelId) => {
794
+ config.model = selectedModelId;
795
+ saveConfig(config);
796
+ console.clear();
797
+ console.log(`${C.green}[✔] Model berhasil diubah ke: ${config.model}${C.reset}\n`);
798
+ startPrompt();
799
+ },
800
+ () => {
801
+ console.clear();
802
+ console.log(`${C.gray}Pemilihan model dibatalkan.${C.reset}\n`);
803
+ startPrompt();
804
+ }
805
+ );
749
806
  } else {
750
- console.log(`${C.red}[x] Could not retrieve models list automatically from ${config.provider}.${C.reset}`);
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`);
751
809
  startPrompt();
752
810
  }
753
811
  } catch (err) {
754
- console.log(`${C.red}[x] Error fetching models: ${err.message}${C.reset}\n`);
812
+ 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`);
755
814
  startPrompt();
756
815
  }
757
816
  }
758
817
 
818
+ // PERINTAH BARU: /custom-models UNTUK PRESISI NAMA MODEL
819
+ 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`);
823
+
824
+ rl.question(`${C.yellow}[>] Masukkan ID / Nama Model secara presisi sesuai provider:${C.reset} `, (inputModel) => {
825
+ const cleanModel = inputModel.trim();
826
+ if (!cleanModel) {
827
+ console.log(`${C.red}[x] Nama model tidak boleh kosong.${C.reset}\n`);
828
+ return startPrompt();
829
+ }
830
+
831
+ config.model = cleanModel;
832
+ saveConfig(config);
833
+ console.log(`\n${C.green}[✔] Model berhasil diperbarui secara presisi ke: ${C.bold}${config.model}${C.reset}\n`);
834
+ startPrompt();
835
+ });
836
+ }
837
+
759
838
  async function handleChat(prompt, forcedTool = null) {
760
839
  if (!config.apiKey) {
761
840
  console.log(`${C.yellow}[!] API Key is not set.${C.reset}`);
@@ -777,7 +856,8 @@ async function handleChat(prompt, forcedTool = null) {
777
856
  const fullPrompt = (prompt || "Jalankan instruksi tool ini.") + toolsContext;
778
857
 
779
858
  if (url.includes('googleapis.com')) {
780
- url = `${url}?key=${config.apiKey}`;
859
+ const activeModel = config.model || 'gemini-2.5-flash';
860
+ url = `https://generativelanguage.googleapis.com/v1beta/models/${activeModel}:generateContent?key=${config.apiKey}`;
781
861
  bodyData = { contents: [{ parts: [{ text: fullPrompt }] }] };
782
862
  } else {
783
863
  if (!url.endsWith('/chat/completions')) {
@@ -800,7 +880,6 @@ async function handleChat(prompt, forcedTool = null) {
800
880
 
801
881
  let aiResponse = "";
802
882
 
803
- // DUKUNGAN UNTUK REASONING MODELS & STANDAR OPENAI/GEMINI
804
883
  if (data.choices && data.choices[0] && data.choices[0].message) {
805
884
  const msg = data.choices[0].message;
806
885
  aiResponse = msg.content || msg.reasoning || msg.reasoning_content || "";
@@ -824,23 +903,23 @@ async function handleChat(prompt, forcedTool = null) {
824
903
  }
825
904
 
826
905
  function handleUninstall() {
827
- rl.question(`${C.red}[!] Are you sure you want to uninstall TMPA CLI? (y/N):${C.reset} `, (answer) => {
906
+ rl.question(`${C.red}[!] Apakah kamu yakin ingin menghapus TMPA CLI? (y/N):${C.reset} `, (answer) => {
828
907
  if (answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes') {
829
- console.log(`\n${C.yellow}[...] Removing TMPA CLI and cleaning configuration...${C.reset}`);
908
+ console.log(`\n${C.yellow}[...] Membersihkan konfigurasi dan menghapus TMPA CLI...${C.reset}`);
830
909
  try {
831
910
  if (fs.existsSync(CONFIG_FILE)) {
832
911
  fs.unlinkSync(CONFIG_FILE);
833
912
  }
834
- console.log(`${C.green}[+] Local configuration cleared.${C.reset}`);
913
+ console.log(`${C.green}[+] Konfigurasi lokal berhasil dibersihkan.${C.reset}`);
835
914
  execSync('npm uninstall -g tmpa-cli', { stdio: 'inherit' });
836
- console.log(`\n${C.green}[+] TMPA CLI uninstalled successfully. Goodbye!${C.reset}\n`);
915
+ console.log(`\n${C.green}[+] TMPA CLI berhasil di-uninstall sepenuhnya. Sampai jumpa!${C.reset}\n`);
837
916
  process.exit(0);
838
917
  } catch (err) {
839
- console.log(`\n${C.red}[x] Failed to uninstall automatically. Run: npm uninstall -g tmpa-cli manually.${C.reset}\n`);
918
+ console.log(`\n${C.red}[x] Gagal uninstall otomatis. Silakan jalankan: npm uninstall -g tmpa-cli secara manual.${C.reset}\n`);
840
919
  process.exit(0);
841
920
  }
842
921
  } else {
843
- console.log(`${C.gray}Uninstall process cancelled.${C.reset}\n`);
922
+ console.log(`${C.gray}Proses uninstall dibatalkan.${C.reset}\n`);
844
923
  startPrompt();
845
924
  }
846
925
  });
@@ -860,6 +939,8 @@ function startPrompt() {
860
939
  askConfig(() => startPrompt());
861
940
  } else if (cmd === '/models') {
862
941
  fetchAvailableModels();
942
+ } else if (cmd === '/custom-models') {
943
+ handleCustomModels();
863
944
  } else if (cmd === '/skill' || cmd === '/skills') {
864
945
  listSkills();
865
946
  startPrompt();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tmpa-cli",
3
- "version": "1.0.21",
3
+ "version": "1.0.23",
4
4
  "description": "",
5
5
  "main": "bin/index.js",
6
6
  "bin": {