tmpa-cli 1.0.11 → 1.0.13

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 +190 -19
  2. package/package.json +1 -1
package/bin/index.js CHANGED
@@ -4,9 +4,19 @@ 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 } = require('child_process');
7
+ const { execSync, spawn } = require('child_process');
8
8
 
9
+ // Base Paths
10
+ const TMPA_DIR = path.join(os.homedir(), '.tmpa');
11
+ const SKILLS_DIR = path.join(TMPA_DIR, 'skills');
12
+ const MCP_DIR = path.join(TMPA_DIR, 'mcp');
9
13
  const CONFIG_FILE = path.join(os.homedir(), '.tmpa_config.json');
14
+ const REGISTRY_FILE = path.join(TMPA_DIR, 'registry.json');
15
+
16
+ // Ensure directories exist
17
+ if (!fs.existsSync(TMPA_DIR)) fs.mkdirSync(TMPA_DIR, { recursive: true });
18
+ if (!fs.existsSync(SKILLS_DIR)) fs.mkdirSync(SKILLS_DIR, { recursive: true });
19
+ if (!fs.existsSync(MCP_DIR)) fs.mkdirSync(MCP_DIR, { recursive: true });
10
20
 
11
21
  const C = {
12
22
  reset: '\x1b[0m',
@@ -32,13 +42,10 @@ const PROVIDERS = {
32
42
  '7': { name: 'Custom / Auto-Detect', endpoint: '', defaultModel: '' }
33
43
  };
34
44
 
45
+ // Config & Registry Helpers
35
46
  function loadConfig() {
36
47
  if (fs.existsSync(CONFIG_FILE)) {
37
- try {
38
- return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
39
- } catch (e) {
40
- return {};
41
- }
48
+ try { return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8')); } catch (e) { return {}; }
42
49
  }
43
50
  return {};
44
51
  }
@@ -47,6 +54,21 @@ function saveConfig(config) {
47
54
  fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
48
55
  }
49
56
 
57
+ function loadRegistry() {
58
+ if (fs.existsSync(REGISTRY_FILE)) {
59
+ try { return JSON.parse(fs.readFileSync(REGISTRY_FILE, 'utf8')); } catch (e) { return { skills: {}, mcp: {} }; }
60
+ }
61
+ return { skills: {}, mcp: {} };
62
+ }
63
+
64
+ function saveRegistry(registry) {
65
+ fs.writeFileSync(REGISTRY_FILE, JSON.stringify(registry, null, 2));
66
+ }
67
+
68
+ let rl = readline.createInterface({ input: process.stdin, output: process.stdout });
69
+ let config = loadConfig();
70
+ let registry = loadRegistry();
71
+
50
72
  function showBanner() {
51
73
  console.clear();
52
74
  console.log(`
@@ -57,18 +79,142 @@ ${C.c3} ██║ ██║╚██╔╝██║██╔═══╝ █
57
79
  ${C.c4} ██║ ██║ ╚═╝ ██║██║ ██║ ██║${C.reset}
58
80
  ${C.c4} ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝${C.reset}
59
81
  ${C.darkGray}────────────────────────────────────────────────────────────${C.reset}
60
- ${C.reset}The Multi Platform AI ${C.green}[Interactive Mode]${C.reset}
61
- ${C.gray}/config : Set API | /models : View & Select Models | /uninstall : Remove | /exit : Exit${C.reset}
82
+ ${C.reset}The Multi Platform AI ${C.green}[Interactive Mode + Tool Runner]${C.reset}
83
+ ${C.gray}/config | /models | /skill | /mcp | /connect | /scan | /exit${C.reset}
62
84
  ${C.darkGray}────────────────────────────────────────────────────────────${C.reset}
63
85
  `);
64
86
  }
65
87
 
66
- let rl = readline.createInterface({
67
- input: process.stdin,
68
- output: process.stdout
69
- });
88
+ // System Prompt Builder for Skills and MCPs
89
+ function getActiveToolsContext() {
90
+ let contextParts = [];
91
+
92
+ // Skills Context
93
+ const activeSkills = Object.keys(registry.skills || {});
94
+ if (activeSkills.length > 0) {
95
+ contextParts.push("AVAILABLE SKILLS:");
96
+ activeSkills.forEach(name => {
97
+ const s = registry.skills[name];
98
+ contextParts.push(`- Skill: ${name} (Path: ${s.path})`);
99
+ });
100
+ }
70
101
 
71
- let config = loadConfig();
102
+ // MCP Context
103
+ const activeMCP = Object.keys(registry.mcp || {});
104
+ if (activeMCP.length > 0) {
105
+ contextParts.push("AVAILABLE MCP SERVERS:");
106
+ activeMCP.forEach(name => {
107
+ const m = registry.mcp[name];
108
+ contextParts.push(`- MCP Server: ${name} (Target: ${m.target}, Type: ${m.type})`);
109
+ });
110
+ }
111
+
112
+ if (contextParts.length === 0) return "";
113
+
114
+ return "\n\n[SYSTEM CONTEXT: ACTIVE TOOLS & PROTOCOLS]\n" + contextParts.join("\n") +
115
+ "\nIf you need to execute a connected JS skill or run local tools, specify standard instructions or output [EXEC_TOOL: tool_name(params)].\n";
116
+ }
117
+
118
+ // Handlers for Skills and MCP
119
+ function listSkills() {
120
+ console.log(`\n${C.cyan}=== Registered Skills ===${C.reset}`);
121
+ const keys = Object.keys(registry.skills || {});
122
+ if (keys.length === 0) {
123
+ console.log(`${C.gray}Belum ada skill yang terhubung. Gunakan ${C.yellow}/connect skill <path>${C.gray} untuk menghubungkan.${C.reset}\n`);
124
+ return;
125
+ }
126
+ keys.forEach((name, i) => {
127
+ const item = registry.skills[name];
128
+ console.log(` ${C.yellow}${i + 1}.${C.reset} ${C.green}${name}${C.reset} -> ${C.gray}${item.path}${C.reset} [${item.status || 'Active'}]`);
129
+ });
130
+ console.log('');
131
+ }
132
+
133
+ function listMCP() {
134
+ console.log(`\n${C.cyan}=== Registered MCP (Model Context Protocol) Servers ===${C.reset}`);
135
+ const keys = Object.keys(registry.mcp || {});
136
+ if (keys.length === 0) {
137
+ console.log(`${C.gray}Belum ada MCP server terhubung. Gunakan ${C.yellow}/connect mcp <target>${C.gray} untuk menghubungkan.${C.reset}\n`);
138
+ return;
139
+ }
140
+ keys.forEach((name, i) => {
141
+ const item = registry.mcp[name];
142
+ console.log(` ${C.yellow}${i + 1}.${C.reset} ${C.green}${name}${C.reset} -> ${C.gray}${item.target}${C.reset} [${item.type || 'local'}]`);
143
+ });
144
+ console.log('');
145
+ }
146
+
147
+ function connectResource(inputArgs) {
148
+ const parts = inputArgs.trim().split(/\s+/);
149
+ const type = parts[0]?.toLowerCase();
150
+ const targetPath = parts.slice(1).join(' ');
151
+
152
+ if (!type || !targetPath) {
153
+ console.log(`${C.red}[x] Format salah! Gunakan:${C.reset}`);
154
+ console.log(` ${C.yellow}/connect skill <filepath_atau_folder>${C.reset}`);
155
+ console.log(` ${C.yellow}/connect mcp <filepath_atau_url>${C.reset}\n`);
156
+ return;
157
+ }
158
+
159
+ const resolvedPath = path.resolve(targetPath);
160
+
161
+ if (type === 'skill') {
162
+ const skillName = path.basename(resolvedPath, path.extname(resolvedPath));
163
+ registry.skills = registry.skills || {};
164
+ registry.skills[skillName] = {
165
+ path: resolvedPath,
166
+ connectedAt: new Date().toISOString(),
167
+ status: 'Active'
168
+ };
169
+ saveRegistry(registry);
170
+ console.log(`${C.green}[+] Skill "${skillName}" berhasil dihubungkan dari: ${resolvedPath}${C.reset}\n`);
171
+ } else if (type === 'mcp') {
172
+ const mcpName = path.basename(resolvedPath, path.extname(resolvedPath));
173
+ registry.mcp = registry.mcp || {};
174
+ registry.mcp[mcpName] = {
175
+ target: targetPath,
176
+ type: targetPath.startsWith('http') ? 'remote' : 'local',
177
+ connectedAt: new Date().toISOString(),
178
+ status: 'Connected'
179
+ };
180
+ saveRegistry(registry);
181
+ console.log(`${C.green}[+] MCP Server "${mcpName}" berhasil dihubungkan!${C.reset}\n`);
182
+ } else {
183
+ console.log(`${C.red}[x] Tipe tidak dikenal. Gunakan "skill" atau "mcp".${C.reset}\n`);
184
+ }
185
+ }
186
+
187
+ function scanResources() {
188
+ console.log(`${C.yellow}[...] Memindai folder ~/.tmpa/skills dan ~/.tmpa/mcp...${C.reset}`);
189
+ registry = loadRegistry();
190
+
191
+ if (fs.existsSync(SKILLS_DIR)) {
192
+ const files = fs.readdirSync(SKILLS_DIR);
193
+ files.forEach(file => {
194
+ const fullPath = path.join(SKILLS_DIR, file);
195
+ const name = path.basename(file, path.extname(file));
196
+ if (!registry.skills[name]) {
197
+ registry.skills[name] = { path: fullPath, connectedAt: new Date().toISOString(), status: 'Active' };
198
+ console.log(`${C.green}[+] Auto-detected skill: ${name}${C.reset}`);
199
+ }
200
+ });
201
+ }
202
+
203
+ if (fs.existsSync(MCP_DIR)) {
204
+ const files = fs.readdirSync(MCP_DIR);
205
+ files.forEach(file => {
206
+ const fullPath = path.join(MCP_DIR, file);
207
+ const name = path.basename(file, path.extname(file));
208
+ if (!registry.mcp[name]) {
209
+ registry.mcp[name] = { target: fullPath, type: 'local', connectedAt: new Date().toISOString(), status: 'Connected' };
210
+ console.log(`${C.green}[+] Auto-detected MCP: ${name}${C.reset}`);
211
+ }
212
+ });
213
+ }
214
+
215
+ saveRegistry(registry);
216
+ console.log(`${C.green}[+] Pemindaian selesai.${C.reset}\n`);
217
+ }
72
218
 
73
219
  function askConfig(callback) {
74
220
  console.log(`\n${C.cyan}[+] Choose AI Provider:${C.reset}`);
@@ -171,7 +317,6 @@ function selectModelCLI(allModels) {
171
317
  setTimeout(displayList, 1000);
172
318
  }
173
319
  } else if (input.length > 0) {
174
- // Jika memasukkan nama model spesifik secara langsung
175
320
  config.model = input;
176
321
  saveConfig(config);
177
322
  console.log(`\n${C.green}[+] Custom Model ID set to: ${config.model}${C.reset}\n`);
@@ -237,9 +382,13 @@ async function handleChat(prompt) {
237
382
  let headers = { 'Content-Type': 'application/json' };
238
383
  let bodyData = {};
239
384
 
385
+ // Combine User Prompt + Active Tools Context
386
+ const toolsContext = getActiveToolsContext();
387
+ const fullPrompt = prompt + toolsContext;
388
+
240
389
  if (url.includes('googleapis.com')) {
241
390
  url = `${url}?key=${config.apiKey}`;
242
- bodyData = { contents: [{ parts: [{ text: prompt }] }] };
391
+ bodyData = { contents: [{ parts: [{ text: fullPrompt }] }] };
243
392
  } else {
244
393
  if (!url.endsWith('/chat/completions')) {
245
394
  url = `${url.replace(/\/$/, '')}/chat/completions`;
@@ -247,7 +396,7 @@ async function handleChat(prompt) {
247
396
  headers['Authorization'] = `Bearer ${config.apiKey}`;
248
397
  bodyData = {
249
398
  model: config.model || 'gpt-3.5-turbo',
250
- messages: [{ role: 'user', content: prompt }]
399
+ messages: [{ role: 'user', content: fullPrompt }]
251
400
  };
252
401
  }
253
402
 
@@ -259,15 +408,26 @@ async function handleChat(prompt) {
259
408
 
260
409
  const data = await response.json();
261
410
 
411
+ let aiResponse = "";
262
412
  if (data.choices && data.choices[0]?.message?.content) {
263
- console.log(`\n${C.c1}TMPA CLI (${config.model || 'AI'}) :${C.reset} ${data.choices[0].message.content}\n`);
413
+ aiResponse = data.choices[0].message.content;
264
414
  } else if (data.candidates && data.candidates[0]?.content?.parts[0]?.text) {
265
- console.log(`\n${C.c1}TMPA CLI :${C.reset} ${data.candidates[0].content.parts[0].text}\n`);
415
+ aiResponse = data.candidates[0].content.parts[0].text;
266
416
  } else if (data.error) {
267
417
  console.log(`\n${C.red}[x] API Error: ${data.error.message || JSON.stringify(data.error)}${C.reset}\n`);
418
+ return startPrompt();
268
419
  } else {
269
420
  console.log(`\n${C.red}[x] Response: ${JSON.stringify(data)}${C.reset}\n`);
421
+ return startPrompt();
270
422
  }
423
+
424
+ console.log(`\n${C.c1}TMPA CLI (${config.model || 'AI'}) :${C.reset} ${aiResponse}\n`);
425
+
426
+ // Check if AI requested execution of a local tool/skill
427
+ if (aiResponse.includes('[EXEC_TOOL:')) {
428
+ console.log(`${C.cyan}[+] Detected Tool Execution Request from AI...${C.reset}`);
429
+ }
430
+
271
431
  } catch (error) {
272
432
  console.log(`\n${C.red}[x] Fetch Error: ${error.message}${C.reset}\n`);
273
433
  }
@@ -312,6 +472,18 @@ function startPrompt() {
312
472
  askConfig(() => startPrompt());
313
473
  } else if (cmd === '/models') {
314
474
  fetchAvailableModels();
475
+ } else if (cmd === '/skill' || cmd === '/skills') {
476
+ listSkills();
477
+ startPrompt();
478
+ } else if (cmd === '/mcp') {
479
+ listMCP();
480
+ startPrompt();
481
+ } else if (cmd.startsWith('/connect')) {
482
+ connectResource(cmd.replace('/connect', ''));
483
+ startPrompt();
484
+ } else if (cmd === '/scan') {
485
+ scanResources();
486
+ startPrompt();
315
487
  } else if (cmd === '/uninstall') {
316
488
  handleUninstall();
317
489
  } else if (cmd === '') {
@@ -322,7 +494,6 @@ function startPrompt() {
322
494
  });
323
495
  }
324
496
 
325
- // Program Execution
326
497
  showBanner();
327
498
  if (!config.apiKey) {
328
499
  askConfig(() => startPrompt());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tmpa-cli",
3
- "version": "1.0.11",
3
+ "version": "1.0.13",
4
4
  "description": "",
5
5
  "main": "bin/index.js",
6
6
  "bin": {