tmpa-cli 1.0.15 → 1.0.17

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 +141 -67
  2. package/package.json +1 -1
package/bin/index.js CHANGED
@@ -4,7 +4,7 @@ const readline = require('readline');
4
4
  const fs = require('fs');
5
5
  const path = require('path');
6
6
  const os = require('os');
7
- const { execSync, spawn } = require('child_process');
7
+ const { execSync } = require('child_process');
8
8
 
9
9
  // Base Paths
10
10
  const TMPA_DIR = path.join(os.homedir(), '.tmpa');
@@ -43,7 +43,6 @@ const PROVIDERS = {
43
43
  '7': { name: 'Custom / Auto-Detect', endpoint: '', defaultModel: '' }
44
44
  };
45
45
 
46
- // Config & Registry Helpers
47
46
  function loadConfig() {
48
47
  if (fs.existsSync(CONFIG_FILE)) {
49
48
  try { return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8')); } catch (e) { return {}; }
@@ -66,14 +65,12 @@ function saveRegistry(registry) {
66
65
  fs.writeFileSync(REGISTRY_FILE, JSON.stringify(registry, null, 2));
67
66
  }
68
67
 
69
- // UI Popup Card Helper
70
68
  function drawBox(title, contentLines, borderColor = C.cyan) {
71
- const width = 60;
69
+ const width = 64;
72
70
  console.log(`${borderColor}┌${'─'.repeat(width - 2)}┐${C.reset}`);
73
71
  console.log(`${borderColor}│ ${C.bold}${title.padEnd(width - 4)}${C.reset}${borderColor} │${C.reset}`);
74
72
  console.log(`${borderColor}├${'─'.repeat(width - 2)}┤${C.reset}`);
75
73
  contentLines.forEach(line => {
76
- // Basic text padding layout
77
74
  console.log(`${borderColor}│${C.reset} ${line.padEnd(width - 4)} ${borderColor}│${C.reset}`);
78
75
  });
79
76
  console.log(`${borderColor}└${'─'.repeat(width - 2)}┘${C.reset}`);
@@ -93,41 +90,88 @@ ${C.c3} ██║ ██║╚██╔╝██║██╔═══╝ █
93
90
  ${C.c4} ██║ ██║ ╚═╝ ██║██║ ██║ ██║${C.reset}
94
91
  ${C.c4} ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝${C.reset}
95
92
  ${C.darkGray}────────────────────────────────────────────────────────────${C.reset}
96
- ${C.reset}The Multi Platform AI ${C.green}[Interactive Popup UI Engine]${C.reset}
93
+ ${C.reset}The Multi Platform AI ${C.green}[Isolated Tool Execution Engine]${C.reset}
97
94
  ${C.gray}/config | /models | /skill | /mcp | /connect | /scan | /exit${C.reset}
98
95
  ${C.darkGray}────────────────────────────────────────────────────────────${C.reset}
99
96
  `);
100
97
  }
101
98
 
102
- // System Context Builder
103
- function getActiveToolsContext() {
99
+ // Helper untuk membaca isi file skill/MCP secara otomatis
100
+ function readResourceContent(targetPath) {
101
+ if (!fs.existsSync(targetPath)) return "[Resource file/directory not found on local system]";
102
+
103
+ const stat = fs.statSync(targetPath);
104
+ if (stat.isFile()) {
105
+ try {
106
+ return fs.readFileSync(targetPath, 'utf8');
107
+ } catch (e) {
108
+ return `[Error reading file: ${e.message}]`;
109
+ }
110
+ } else if (stat.isDirectory()) {
111
+ // Cari file instruksi utama seperti SKILL.md, README.md, index.js, prompt.txt
112
+ const candidateFiles = ['SKILL.md', 'skill.md', 'PROMPT.md', 'README.md', 'index.js', 'index.ts', 'main.py'];
113
+ for (const file of candidateFiles) {
114
+ const full = path.join(targetPath, file);
115
+ if (fs.existsSync(full)) {
116
+ try {
117
+ return `--- Content from ${file} ---\n` + fs.readFileSync(full, 'utf8');
118
+ } catch (e) {}
119
+ }
120
+ }
121
+
122
+ // Jika tidak ada file markdown/skrip khusus, baca beberapa file pertama
123
+ try {
124
+ const files = fs.readdirSync(targetPath);
125
+ let combinedText = `Directory Contents for ${path.basename(targetPath)}:\n`;
126
+ files.slice(0, 5).forEach(f => {
127
+ const fp = path.join(targetPath, f);
128
+ if (fs.statSync(fp).isFile()) {
129
+ combinedText += `\n--- File: ${f} ---\n` + fs.readFileSync(fp, 'utf8').slice(0, 2000);
130
+ }
131
+ });
132
+ return combinedText;
133
+ } catch (e) {
134
+ return `[Directory found at ${targetPath}, but unable to parse files]`;
135
+ }
136
+ }
137
+ return "[Unknown resource format]";
138
+ }
139
+
140
+ // System Context Builder dengan ISOLASI KETAT
141
+ function getActiveToolsContext(forcedTool = null) {
142
+ // 1. Jika User Memanggil Tool Khusus (Misal: /skill-remotion-video)
143
+ if (forcedTool) {
144
+ const rawContent = readResourceContent(forcedTool.target);
145
+
146
+ return `\n\n[STRICT TOOL EXECUTION SYSTEM DIRECTIVE]
147
+ YOU ARE NOW STRICTLY ACTING AS THE FOLLOWING ${forcedTool.type.toUpperCase()} TOOL: "${forcedTool.name}".
148
+ DO NOT REFER TO OTHER TOOLS OR GENERAL ASSISTANT CAPABILITIES. FOCUS 100% ON EXECUTING THIS TOOL INSTRUCTION.
149
+
150
+ --- TOOL INSTRUCTION & CODE CONTENT ---
151
+ ${rawContent}
152
+ --- END TOOL INSTRUCTION ---
153
+
154
+ Executing User Prompt under this tool context only:\n`;
155
+ }
156
+
157
+ // 2. Jika Chat Biasa (Tanpa Slash Specific Tool), tampilkan ringkasan umum
104
158
  let contextParts = [];
105
-
106
159
  const activeSkills = Object.keys(registry.skills || {});
107
160
  if (activeSkills.length > 0) {
108
- contextParts.push("AVAILABLE SKILLS:");
109
- activeSkills.forEach(name => {
110
- const s = registry.skills[name];
111
- contextParts.push(`- Skill: ${name} (Path: ${s.path})`);
112
- });
161
+ contextParts.push("AVAILABLE SKILLS (Use /skill-<name> to invoke directly):");
162
+ activeSkills.forEach(name => contextParts.push(`- /skill-${name}`));
113
163
  }
114
164
 
115
165
  const activeMCP = Object.keys(registry.mcp || {});
116
166
  if (activeMCP.length > 0) {
117
- contextParts.push("AVAILABLE MCP SERVERS:");
118
- activeMCP.forEach(name => {
119
- const m = registry.mcp[name];
120
- contextParts.push(`- MCP Server: ${name} (Target: ${m.target}, Type: ${m.type})`);
121
- });
167
+ contextParts.push("AVAILABLE MCP SERVERS (Use /mcp-<name> to invoke directly):");
168
+ activeMCP.forEach(name => contextParts.push(`- /mcp-${name}`));
122
169
  }
123
170
 
124
171
  if (contextParts.length === 0) return "";
125
-
126
- return "\n\n[SYSTEM CONTEXT: ACTIVE TOOLS & PROTOCOLS]\n" + contextParts.join("\n") +
127
- "\nIf user wants to execute a skill/MCP, assist them using available tools.\n";
172
+ return "\n\n[SYSTEM ENVIRONMENT SUMMARY]\n" + contextParts.join("\n") + "\n";
128
173
  }
129
174
 
130
- // Handlers for Skills with Popup Helper
131
175
  function listSkills() {
132
176
  console.log(`\n${C.cyan}=== Registered Skills ===${C.reset}`);
133
177
  const keys = Object.keys(registry.skills || {});
@@ -143,23 +187,21 @@ function listSkills() {
143
187
  });
144
188
  console.log('');
145
189
 
146
- // Interactive Action Helper Popup
147
- const sampleSkill = keys[0] || 'nama_skill';
148
- drawBox("💡 CARA MENGGUNAKAN SKILL TERDAFTAR", [
149
- `${C.bold}Perintah yang Bisa Kamu Ketik di Chat:${C.reset}`,
150
- ` 1. Langsung minta AI panggil skill:`,
151
- ` ${C.yellow}TMPA > "Jalankan skill ${sampleSkill}"${C.reset}`,
152
- ` 2. Tanya fungsi skill:`,
153
- ` ${C.yellow}TMPA > "Apa kegunaan dari skill ${sampleSkill}?"${C.reset}`,
154
- ` 3. Hubungkan file JS baru:`,
155
- ` ${C.yellow}TMPA > /connect skill ./path/ke/file.js${C.reset}`
156
- ], C.c1);
190
+ const lines = [
191
+ `${C.bold}Perintah Instan (Fokus 100% Khusus Tool Tersebut):${C.reset}`,
192
+ ``
193
+ ];
194
+
195
+ keys.forEach(name => {
196
+ lines.push(` ${C.yellow}/skill-${name}${C.reset} <prompt kamu>`);
197
+ });
198
+
199
+ drawBox("⚡ COMMAND INSTAN SKILL TERSEDIA", lines, C.c1);
157
200
  console.log('');
158
201
  }
159
202
 
160
- // Handlers for MCP with Popup Helper
161
203
  function listMCP() {
162
- console.log(`\n${C.cyan}=== Registered MCP (Model Context Protocol) Servers ===${C.reset}`);
204
+ console.log(`\n${C.cyan}=== Registered MCP Servers ===${C.reset}`);
163
205
  const keys = Object.keys(registry.mcp || {});
164
206
 
165
207
  if (keys.length === 0) {
@@ -173,17 +215,16 @@ function listMCP() {
173
215
  });
174
216
  console.log('');
175
217
 
176
- // Interactive Action Helper Popup
177
- const sampleMcp = keys[0] || 'filesystem';
178
- drawBox("⚡ CARA MENGGUNAKAN MCP SERVER", [
179
- `${C.bold}Perintah & Integrasi yang Bisa Kamu Gunakan:${C.reset}`,
180
- ` 1. Minta AI akses fitur MCP:`,
181
- ` ${C.yellow}TMPA > "Gunakan MCP ${sampleMcp} untuk analisis data"${C.reset}`,
182
- ` 2. Hubungkan MCP via URL / HTTP SSE:`,
183
- ` ${C.yellow}TMPA > /connect mcp http://localhost:3000/sse${C.reset}`,
184
- ` 3. Pindai server MCP otomatis:`,
185
- ` ${C.yellow}TMPA > /scan${C.reset}`
186
- ], C.c4);
218
+ const lines = [
219
+ `${C.bold}Perintah Instan MCP (Fokus Khusus Tool MCP):${C.reset}`,
220
+ ``
221
+ ];
222
+
223
+ keys.forEach(name => {
224
+ lines.push(` ${C.yellow}/mcp-${name}${C.reset} <prompt kamu>`);
225
+ });
226
+
227
+ drawBox("🔌 COMMAND INSTAN MCP TERSEDIA", lines, C.c4);
187
228
  console.log('');
188
229
  }
189
230
 
@@ -210,7 +251,7 @@ function connectResource(inputArgs) {
210
251
  status: 'Active'
211
252
  };
212
253
  saveRegistry(registry);
213
- console.log(`${C.green}[+] Skill "${skillName}" berhasil terhubung!${C.reset}\n`);
254
+ console.log(`${C.green}[+] Skill "${skillName}" terhubung! Akses instan: /skill-${skillName}${C.reset}\n`);
214
255
  } else if (type === 'mcp') {
215
256
  const mcpName = path.basename(resolvedPath, path.extname(resolvedPath));
216
257
  registry.mcp = registry.mcp || {};
@@ -221,13 +262,12 @@ function connectResource(inputArgs) {
221
262
  status: 'Connected'
222
263
  };
223
264
  saveRegistry(registry);
224
- console.log(`${C.green}[+] MCP Server "${mcpName}" berhasil terhubung!${C.reset}\n`);
265
+ console.log(`${C.green}[+] MCP "${mcpName}" terhubung! Akses instan: /mcp-${mcpName}${C.reset}\n`);
225
266
  } else {
226
267
  console.log(`${C.red}[x] Tipe tidak dikenal. Gunakan "skill" atau "mcp".${C.reset}\n`);
227
268
  }
228
269
  }
229
270
 
230
- // Deep Multi-Location Scanner Function with Result Popup Card
231
271
  function scanResources() {
232
272
  console.log(`\n${C.yellow}[...] Memindai lokasi internal, home user, Gemini CLI & Claude Desktop...${C.reset}`);
233
273
  registry = loadRegistry();
@@ -261,10 +301,10 @@ function scanResources() {
261
301
 
262
302
  if (type === 'skill' && !registry.skills[name]) {
263
303
  registry.skills[name] = { path: fullPath, connectedAt: new Date().toISOString(), status: 'Active' };
264
- newDetected.push(`[Skill] ${name}`);
304
+ newDetected.push(`/skill-${name}`);
265
305
  } else if (type === 'mcp' && !registry.mcp[name]) {
266
306
  registry.mcp[name] = { target: fullPath, type: 'local', connectedAt: new Date().toISOString(), status: 'Connected' };
267
- newDetected.push(`[MCP] ${name}`);
307
+ newDetected.push(`/mcp-${name}`);
268
308
  }
269
309
  });
270
310
  } catch (e) {}
@@ -272,24 +312,20 @@ function scanResources() {
272
312
  });
273
313
 
274
314
  saveRegistry(registry);
275
-
276
315
  console.log(`${C.green}[+] Pemindaian selesai.${C.reset}\n`);
277
316
 
278
- // POPUP CARD SUMMARY FOR SCAN RESULTS
279
317
  if (newDetected.length > 0) {
280
- drawBox("🎉 ITEM BARU BERHASIL DITEMUKAN!", [
281
- `${C.green}Berhasil menambahkan ${newDetected.length} resource baru:${C.reset}`,
282
- ...newDetected.slice(0, 4).map(item => ` • ${item}`),
283
- newDetected.length > 4 ? ` ...dan ${newDetected.length - 4} item lainnya.` : '',
318
+ drawBox("🎉 COMMAND INSTAN BARU TERDETEKSI!", [
319
+ `${C.green}Kamu sekarang bisa langsung menggunakan perintah ini:${C.reset}`,
320
+ ...newDetected.slice(0, 5).map(cmd => ` • ${C.yellow}${cmd}${C.reset} <prompt>`),
321
+ newDetected.length > 5 ? ` ...dan ${newDetected.length - 5} command lainnya.` : '',
284
322
  ``,
285
- `${C.yellow}Ketik /skill atau /mcp untuk melihat daftar lengkap!${C.reset}`
323
+ `${C.cyan}Ketik /skill atau /mcp untuk melihat semua command.${C.reset}`
286
324
  ], C.green);
287
325
  } else {
288
326
  drawBox("ℹ️ HASIL PEMINDAIAN", [
289
- `Tidak ada file Skill atau MCP baru yang terdeteksi.`,
290
- `Semua resource sudah terhubung di registry.`,
291
- ``,
292
- `Gunakan ${C.yellow}/skill${C.reset} atau ${C.yellow}/mcp${C.reset} untuk melihat item aktif.`
327
+ `Tidak ada Skill atau MCP baru yang terdeteksi.`,
328
+ `Ketik ${C.yellow}/skill${C.reset} atau ${C.yellow}/mcp${C.reset} untuk melihat command instan yang aktif.`
293
329
  ], C.gray);
294
330
  }
295
331
  console.log('');
@@ -448,12 +484,16 @@ async function fetchAvailableModels() {
448
484
  }
449
485
  }
450
486
 
451
- async function handleChat(prompt) {
487
+ async function handleChat(prompt, forcedTool = null) {
452
488
  if (!config.apiKey) {
453
489
  console.log(`${C.yellow}[!] API Key is not set.${C.reset}`);
454
490
  return askConfig(() => startPrompt());
455
491
  }
456
492
 
493
+ if (forcedTool) {
494
+ console.log(`${C.cyan}[🚀 Executing via ${forcedTool.type.toUpperCase()}: ${forcedTool.name}]${C.reset}`);
495
+ }
496
+
457
497
  console.log(`${C.yellow}TMPA CLI processing...${C.reset}`);
458
498
 
459
499
  try {
@@ -461,8 +501,8 @@ async function handleChat(prompt) {
461
501
  let headers = { 'Content-Type': 'application/json' };
462
502
  let bodyData = {};
463
503
 
464
- const toolsContext = getActiveToolsContext();
465
- const fullPrompt = prompt + toolsContext;
504
+ const toolsContext = getActiveToolsContext(forcedTool);
505
+ const fullPrompt = (prompt || "Jalankan instruksi tool ini.") + toolsContext;
466
506
 
467
507
  if (url.includes('googleapis.com')) {
468
508
  url = `${url}?key=${config.apiKey}`;
@@ -499,7 +539,7 @@ async function handleChat(prompt) {
499
539
  return startPrompt();
500
540
  }
501
541
 
502
- console.log(`\n${C.c1}TMPA CLI (${config.model || 'AI'}) :${C.reset} ${aiResponse}\n`);
542
+ console.log(`\n${C.c1}TMPA CLI (${config.model || 'AI'}) :${C.reset}\n${aiResponse}\n`);
503
543
 
504
544
  } catch (error) {
505
545
  console.log(`\n${C.red}[x] Fetch Error: ${error.message}${C.reset}\n`);
@@ -559,6 +599,40 @@ function startPrompt() {
559
599
  startPrompt();
560
600
  } else if (cmd === '/uninstall') {
561
601
  handleUninstall();
602
+ } else if (cmd.startsWith('/skill-')) {
603
+ const fullCmd = cmd.slice(7).trim();
604
+ const spaceIdx = fullCmd.indexOf(' ');
605
+ let skillName = fullCmd;
606
+ let userPrompt = '';
607
+
608
+ if (spaceIdx !== -1) {
609
+ skillName = fullCmd.slice(0, spaceIdx);
610
+ userPrompt = fullCmd.slice(spaceIdx + 1).trim();
611
+ }
612
+
613
+ if (registry.skills && registry.skills[skillName]) {
614
+ handleChat(userPrompt, { type: 'skill', name: skillName, target: registry.skills[skillName].path });
615
+ } else {
616
+ console.log(`${C.red}[x] Skill "${skillName}" tidak ditemukan di registry. Ketik /skill untuk melihat daftar.${C.reset}\n`);
617
+ startPrompt();
618
+ }
619
+ } else if (cmd.startsWith('/mcp-')) {
620
+ const fullCmd = cmd.slice(5).trim();
621
+ const spaceIdx = fullCmd.indexOf(' ');
622
+ let mcpName = fullCmd;
623
+ let userPrompt = '';
624
+
625
+ if (spaceIdx !== -1) {
626
+ mcpName = fullCmd.slice(0, spaceIdx);
627
+ userPrompt = fullCmd.slice(spaceIdx + 1).trim();
628
+ }
629
+
630
+ if (registry.mcp && registry.mcp[mcpName]) {
631
+ handleChat(userPrompt, { type: 'mcp', name: mcpName, target: registry.mcp[mcpName].target });
632
+ } else {
633
+ console.log(`${C.red}[x] MCP "${mcpName}" tidak ditemukan di registry. Ketik /mcp untuk melihat daftar.${C.reset}\n`);
634
+ startPrompt();
635
+ }
562
636
  } else if (cmd === '') {
563
637
  startPrompt();
564
638
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tmpa-cli",
3
- "version": "1.0.15",
3
+ "version": "1.0.17",
4
4
  "description": "",
5
5
  "main": "bin/index.js",
6
6
  "bin": {