tmpa-cli 1.0.12 → 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 +156 -107
  2. package/package.json +1 -1
package/bin/index.js CHANGED
@@ -4,14 +4,16 @@ 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
9
10
  const TMPA_DIR = path.join(os.homedir(), '.tmpa');
10
11
  const SKILLS_DIR = path.join(TMPA_DIR, 'skills');
11
12
  const MCP_DIR = path.join(TMPA_DIR, 'mcp');
12
13
  const CONFIG_FILE = path.join(os.homedir(), '.tmpa_config.json');
13
14
  const REGISTRY_FILE = path.join(TMPA_DIR, 'registry.json');
14
15
 
16
+ // Ensure directories exist
15
17
  if (!fs.existsSync(TMPA_DIR)) fs.mkdirSync(TMPA_DIR, { recursive: true });
16
18
  if (!fs.existsSync(SKILLS_DIR)) fs.mkdirSync(SKILLS_DIR, { recursive: true });
17
19
  if (!fs.existsSync(MCP_DIR)) fs.mkdirSync(MCP_DIR, { recursive: true });
@@ -40,6 +42,7 @@ const PROVIDERS = {
40
42
  '7': { name: 'Custom / Auto-Detect', endpoint: '', defaultModel: '' }
41
43
  };
42
44
 
45
+ // Config & Registry Helpers
43
46
  function loadConfig() {
44
47
  if (fs.existsSync(CONFIG_FILE)) {
45
48
  try { return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8')); } catch (e) { return {}; }
@@ -76,12 +79,143 @@ ${C.c3} ██║ ██║╚██╔╝██║██╔═══╝ █
76
79
  ${C.c4} ██║ ██║ ╚═╝ ██║██║ ██║ ██║${C.reset}
77
80
  ${C.c4} ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝${C.reset}
78
81
  ${C.darkGray}────────────────────────────────────────────────────────────${C.reset}
79
- ${C.reset}The Multi Platform AI ${C.green}[Interactive Mode]${C.reset}
82
+ ${C.reset}The Multi Platform AI ${C.green}[Interactive Mode + Tool Runner]${C.reset}
80
83
  ${C.gray}/config | /models | /skill | /mcp | /connect | /scan | /exit${C.reset}
81
84
  ${C.darkGray}────────────────────────────────────────────────────────────${C.reset}
82
85
  `);
83
86
  }
84
87
 
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
+ }
101
+
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
+ }
218
+
85
219
  function askConfig(callback) {
86
220
  console.log(`\n${C.cyan}[+] Choose AI Provider:${C.reset}`);
87
221
  console.log(` ${C.yellow}1.${C.reset} OpenRouter`);
@@ -248,9 +382,13 @@ async function handleChat(prompt) {
248
382
  let headers = { 'Content-Type': 'application/json' };
249
383
  let bodyData = {};
250
384
 
385
+ // Combine User Prompt + Active Tools Context
386
+ const toolsContext = getActiveToolsContext();
387
+ const fullPrompt = prompt + toolsContext;
388
+
251
389
  if (url.includes('googleapis.com')) {
252
390
  url = `${url}?key=${config.apiKey}`;
253
- bodyData = { contents: [{ parts: [{ text: prompt }] }] };
391
+ bodyData = { contents: [{ parts: [{ text: fullPrompt }] }] };
254
392
  } else {
255
393
  if (!url.endsWith('/chat/completions')) {
256
394
  url = `${url.replace(/\/$/, '')}/chat/completions`;
@@ -258,7 +396,7 @@ async function handleChat(prompt) {
258
396
  headers['Authorization'] = `Bearer ${config.apiKey}`;
259
397
  bodyData = {
260
398
  model: config.model || 'gpt-3.5-turbo',
261
- messages: [{ role: 'user', content: prompt }]
399
+ messages: [{ role: 'user', content: fullPrompt }]
262
400
  };
263
401
  }
264
402
 
@@ -270,15 +408,26 @@ async function handleChat(prompt) {
270
408
 
271
409
  const data = await response.json();
272
410
 
411
+ let aiResponse = "";
273
412
  if (data.choices && data.choices[0]?.message?.content) {
274
- 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;
275
414
  } else if (data.candidates && data.candidates[0]?.content?.parts[0]?.text) {
276
- 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;
277
416
  } else if (data.error) {
278
417
  console.log(`\n${C.red}[x] API Error: ${data.error.message || JSON.stringify(data.error)}${C.reset}\n`);
418
+ return startPrompt();
279
419
  } else {
280
420
  console.log(`\n${C.red}[x] Response: ${JSON.stringify(data)}${C.reset}\n`);
421
+ return startPrompt();
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}`);
281
429
  }
430
+
282
431
  } catch (error) {
283
432
  console.log(`\n${C.red}[x] Fetch Error: ${error.message}${C.reset}\n`);
284
433
  }
@@ -309,106 +458,6 @@ function handleUninstall() {
309
458
  });
310
459
  }
311
460
 
312
- function listSkills() {
313
- console.log(`\n${C.cyan}=== Registered Skills ===${C.reset}`);
314
- const keys = Object.keys(registry.skills || {});
315
- if (keys.length === 0) {
316
- console.log(`${C.gray}Belum ada skill yang terhubung. Gunakan ${C.yellow}/connect skill <path>${C.gray} untuk menghubungkan.${C.reset}\n`);
317
- return;
318
- }
319
- keys.forEach((name, i) => {
320
- const item = registry.skills[name];
321
- console.log(` ${C.yellow}${i + 1}.${C.reset} ${C.green}${name}${C.reset} -> ${C.gray}${item.path}${C.reset} [${item.status || 'Active'}]`);
322
- });
323
- console.log('');
324
- }
325
-
326
- function listMCP() {
327
- console.log(`\n${C.cyan}=== Registered MCP (Model Context Protocol) Servers ===${C.reset}`);
328
- const keys = Object.keys(registry.mcp || {});
329
- if (keys.length === 0) {
330
- console.log(`${C.gray}Belum ada MCP server terhubung. Gunakan ${C.yellow}/connect mcp <target>${C.gray} untuk menghubungkan.${C.reset}\n`);
331
- return;
332
- }
333
- keys.forEach((name, i) => {
334
- const item = registry.mcp[name];
335
- console.log(` ${C.yellow}${i + 1}.${C.reset} ${C.green}${name}${C.reset} -> ${C.gray}${item.target}${C.reset} [${item.type || 'local'}]`);
336
- });
337
- console.log('');
338
- }
339
-
340
- function connectResource(inputArgs) {
341
- const parts = inputArgs.trim().split(/\s+/);
342
- const type = parts[0]?.toLowerCase();
343
- const targetPath = parts.slice(1).join(' ');
344
-
345
- if (!type || !targetPath) {
346
- console.log(`${C.red}[x] Format salah! Gunakan:${C.reset}`);
347
- console.log(` ${C.yellow}/connect skill <filepath_atau_folder>${C.reset}`);
348
- console.log(` ${C.yellow}/connect mcp <filepath_atau_url>${C.reset}\n`);
349
- return;
350
- }
351
-
352
- const resolvedPath = path.resolve(targetPath);
353
-
354
- if (type === 'skill') {
355
- const skillName = path.basename(resolvedPath, path.extname(resolvedPath));
356
- registry.skills = registry.skills || {};
357
- registry.skills[skillName] = {
358
- path: resolvedPath,
359
- connectedAt: new Date().toISOString(),
360
- status: 'Active'
361
- };
362
- saveRegistry(registry);
363
- console.log(`${C.green}[+] Skill "${skillName}" berhasil dihubungkan dari: ${resolvedPath}${C.reset}\n`);
364
- } else if (type === 'mcp') {
365
- const mcpName = path.basename(resolvedPath, path.extname(resolvedPath));
366
- registry.mcp = registry.mcp || {};
367
- registry.mcp[mcpName] = {
368
- target: targetPath,
369
- type: targetPath.startsWith('http') ? 'remote' : 'local',
370
- connectedAt: new Date().toISOString(),
371
- status: 'Connected'
372
- };
373
- saveRegistry(registry);
374
- console.log(`${C.green}[+] MCP Server "${mcpName}" berhasil dihubungkan!${C.reset}\n`);
375
- } else {
376
- console.log(`${C.red}[x] Tipe tidak dikenal. Gunakan "skill" atau "mcp".${C.reset}\n`);
377
- }
378
- }
379
-
380
- function scanResources() {
381
- console.log(`${C.yellow}[...] Memindai folder ~/.tmpa/skills dan ~/.tmpa/mcp...${C.reset}`);
382
- registry = loadRegistry();
383
-
384
- if (fs.existsSync(SKILLS_DIR)) {
385
- const files = fs.readdirSync(SKILLS_DIR);
386
- files.forEach(file => {
387
- const fullPath = path.join(SKILLS_DIR, file);
388
- const name = path.basename(file, path.extname(file));
389
- if (!registry.skills[name]) {
390
- registry.skills[name] = { path: fullPath, connectedAt: new Date().toISOString(), status: 'Active' };
391
- console.log(`${C.green}[+] Auto-detected skill: ${name}${C.reset}`);
392
- }
393
- });
394
- }
395
-
396
- if (fs.existsSync(MCP_DIR)) {
397
- const files = fs.readdirSync(MCP_DIR);
398
- files.forEach(file => {
399
- const fullPath = path.join(MCP_DIR, file);
400
- const name = path.basename(file, path.extname(file));
401
- if (!registry.mcp[name]) {
402
- registry.mcp[name] = { target: fullPath, type: 'local', connectedAt: new Date().toISOString(), status: 'Connected' };
403
- console.log(`${C.green}[+] Auto-detected MCP: ${name}${C.reset}`);
404
- }
405
- });
406
- }
407
-
408
- saveRegistry(registry);
409
- console.log(`${C.green}[+] Pemindaian selesai.${C.reset}\n`);
410
- }
411
-
412
461
  function startPrompt() {
413
462
  rl.question(`${C.c1}TMPA >${C.reset} `, (input) => {
414
463
  const cmd = input.trim();
@@ -423,7 +472,7 @@ function startPrompt() {
423
472
  askConfig(() => startPrompt());
424
473
  } else if (cmd === '/models') {
425
474
  fetchAvailableModels();
426
- } else if (cmd === '/skill') {
475
+ } else if (cmd === '/skill' || cmd === '/skills') {
427
476
  listSkills();
428
477
  startPrompt();
429
478
  } else if (cmd === '/mcp') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tmpa-cli",
3
- "version": "1.0.12",
3
+ "version": "1.0.13",
4
4
  "description": "",
5
5
  "main": "bin/index.js",
6
6
  "bin": {