tmpa-cli 1.0.12 → 1.0.14

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 +177 -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,170 @@ ${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 + Multi-Location Scanner]${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
+ const activeSkills = Object.keys(registry.skills || {});
93
+ if (activeSkills.length > 0) {
94
+ contextParts.push("AVAILABLE SKILLS:");
95
+ activeSkills.forEach(name => {
96
+ const s = registry.skills[name];
97
+ contextParts.push(`- Skill: ${name} (Path: ${s.path})`);
98
+ });
99
+ }
100
+
101
+ const activeMCP = Object.keys(registry.mcp || {});
102
+ if (activeMCP.length > 0) {
103
+ contextParts.push("AVAILABLE MCP SERVERS:");
104
+ activeMCP.forEach(name => {
105
+ const m = registry.mcp[name];
106
+ contextParts.push(`- MCP Server: ${name} (Target: ${m.target}, Type: ${m.type})`);
107
+ });
108
+ }
109
+
110
+ if (contextParts.length === 0) return "";
111
+
112
+ return "\n\n[SYSTEM CONTEXT: ACTIVE TOOLS & PROTOCOLS]\n" + contextParts.join("\n") +
113
+ "\nIf you need to execute a connected JS skill or run local tools, specify standard instructions or output [EXEC_TOOL: tool_name(params)].\n";
114
+ }
115
+
116
+ // Handlers for Skills and MCP
117
+ function listSkills() {
118
+ console.log(`\n${C.cyan}=== Registered Skills ===${C.reset}`);
119
+ const keys = Object.keys(registry.skills || {});
120
+ if (keys.length === 0) {
121
+ console.log(`${C.gray}Belum ada skill yang terhubung. Gunakan ${C.yellow}/connect skill <path>${C.gray} atau ${C.yellow}/scan${C.gray} untuk memindai.${C.reset}\n`);
122
+ return;
123
+ }
124
+ keys.forEach((name, i) => {
125
+ const item = registry.skills[name];
126
+ console.log(` ${C.yellow}${i + 1}.${C.reset} ${C.green}${name}${C.reset} -> ${C.gray}${item.path}${C.reset} [${item.status || 'Active'}]`);
127
+ });
128
+ console.log('');
129
+ }
130
+
131
+ function listMCP() {
132
+ console.log(`\n${C.cyan}=== Registered MCP (Model Context Protocol) Servers ===${C.reset}`);
133
+ const keys = Object.keys(registry.mcp || {});
134
+ if (keys.length === 0) {
135
+ console.log(`${C.gray}Belum ada MCP server terhubung. Gunakan ${C.yellow}/connect mcp <target>${C.gray} atau ${C.yellow}/scan${C.gray} untuk memindai.${C.reset}\n`);
136
+ return;
137
+ }
138
+ keys.forEach((name, i) => {
139
+ const item = registry.mcp[name];
140
+ console.log(` ${C.yellow}${i + 1}.${C.reset} ${C.green}${name}${C.reset} -> ${C.gray}${item.target}${C.reset} [${item.type || 'local'}]`);
141
+ });
142
+ console.log('');
143
+ }
144
+
145
+ function connectResource(inputArgs) {
146
+ const parts = inputArgs.trim().split(/\s+/);
147
+ const type = parts[0]?.toLowerCase();
148
+ const targetPath = parts.slice(1).join(' ');
149
+
150
+ if (!type || !targetPath) {
151
+ console.log(`${C.red}[x] Format salah! Gunakan:${C.reset}`);
152
+ console.log(` ${C.yellow}/connect skill <filepath_atau_folder>${C.reset}`);
153
+ console.log(` ${C.yellow}/connect mcp <filepath_atau_url>${C.reset}\n`);
154
+ return;
155
+ }
156
+
157
+ const resolvedPath = path.resolve(targetPath);
158
+
159
+ if (type === 'skill') {
160
+ const skillName = path.basename(resolvedPath, path.extname(resolvedPath));
161
+ registry.skills = registry.skills || {};
162
+ registry.skills[skillName] = {
163
+ path: resolvedPath,
164
+ connectedAt: new Date().toISOString(),
165
+ status: 'Active'
166
+ };
167
+ saveRegistry(registry);
168
+ console.log(`${C.green}[+] Skill "${skillName}" berhasil dihubungkan dari: ${resolvedPath}${C.reset}\n`);
169
+ } else if (type === 'mcp') {
170
+ const mcpName = path.basename(resolvedPath, path.extname(resolvedPath));
171
+ registry.mcp = registry.mcp || {};
172
+ registry.mcp[mcpName] = {
173
+ target: targetPath,
174
+ type: targetPath.startsWith('http') ? 'remote' : 'local',
175
+ connectedAt: new Date().toISOString(),
176
+ status: 'Connected'
177
+ };
178
+ saveRegistry(registry);
179
+ console.log(`${C.green}[+] MCP Server "${mcpName}" berhasil dihubungkan!${C.reset}\n`);
180
+ } else {
181
+ console.log(`${C.red}[x] Tipe tidak dikenal. Gunakan "skill" atau "mcp".${C.reset}\n`);
182
+ }
183
+ }
184
+
185
+ // Deep Multi-Location Scanner Function
186
+ function scanResources() {
187
+ console.log(`${C.yellow}[...] Memindai folder internal & lokasi umum pengguna (Gemini/Claude/System)...${C.reset}`);
188
+ registry = loadRegistry();
189
+ registry.skills = registry.skills || {};
190
+ registry.mcp = registry.mcp || {};
191
+
192
+ const home = os.homedir();
193
+ const searchTargets = [
194
+ // Folder Internal TMPA
195
+ { type: 'skill', dir: SKILLS_DIR },
196
+ { type: 'mcp', dir: MCP_DIR },
197
+
198
+ // Folder Umum Pengguna (Direct Home Directory)
199
+ { type: 'skill', dir: path.join(home, 'skills') },
200
+ { type: 'mcp', dir: path.join(home, 'mcp') },
201
+ { type: 'mcp', dir: path.join(home, '.mcp') },
202
+
203
+ // Folder Konfigurasi Gemini CLI & Claude Desktop
204
+ { type: 'skill', dir: path.join(home, '.gemini', 'skills') },
205
+ { type: 'mcp', dir: path.join(home, '.gemini', 'mcp') },
206
+ { type: 'skill', dir: path.join(home, '.config', 'gemini', 'skills') },
207
+ { type: 'mcp', dir: path.join(home, '.config', 'gemini', 'mcp') },
208
+ { type: 'mcp', dir: path.join(home, '.claude', 'mcp') },
209
+ { type: 'mcp', dir: path.join(home, '.config', 'claude', 'mcp') }
210
+ ];
211
+
212
+ let addedCount = 0;
213
+
214
+ searchTargets.forEach(({ type, dir }) => {
215
+ if (fs.existsSync(dir)) {
216
+ try {
217
+ const items = fs.readdirSync(dir);
218
+ items.forEach(item => {
219
+ const fullPath = path.join(dir, item);
220
+ const name = path.basename(item, path.extname(item));
221
+
222
+ if (type === 'skill') {
223
+ if (!registry.skills[name]) {
224
+ registry.skills[name] = { path: fullPath, connectedAt: new Date().toISOString(), status: 'Active' };
225
+ console.log(`${C.green}[+] Terdeteksi Skill: ${name}${C.reset} ${C.gray}(${fullPath})${C.reset}`);
226
+ addedCount++;
227
+ }
228
+ } else if (type === 'mcp') {
229
+ if (!registry.mcp[name]) {
230
+ registry.mcp[name] = { target: fullPath, type: 'local', connectedAt: new Date().toISOString(), status: 'Connected' };
231
+ console.log(`${C.green}[+] Terdeteksi MCP: ${name}${C.reset} ${C.gray}(${fullPath})${C.reset}`);
232
+ addedCount++;
233
+ }
234
+ }
235
+ });
236
+ } catch (e) {
237
+ // Skip folder jika permission terikat
238
+ }
239
+ }
240
+ });
241
+
242
+ saveRegistry(registry);
243
+ console.log(`${C.green}[+] Pemindaian selesai. Total item baru terdeteksi: ${addedCount}${C.reset}\n`);
244
+ }
245
+
85
246
  function askConfig(callback) {
86
247
  console.log(`\n${C.cyan}[+] Choose AI Provider:${C.reset}`);
87
248
  console.log(` ${C.yellow}1.${C.reset} OpenRouter`);
@@ -248,9 +409,12 @@ async function handleChat(prompt) {
248
409
  let headers = { 'Content-Type': 'application/json' };
249
410
  let bodyData = {};
250
411
 
412
+ const toolsContext = getActiveToolsContext();
413
+ const fullPrompt = prompt + toolsContext;
414
+
251
415
  if (url.includes('googleapis.com')) {
252
416
  url = `${url}?key=${config.apiKey}`;
253
- bodyData = { contents: [{ parts: [{ text: prompt }] }] };
417
+ bodyData = { contents: [{ parts: [{ text: fullPrompt }] }] };
254
418
  } else {
255
419
  if (!url.endsWith('/chat/completions')) {
256
420
  url = `${url.replace(/\/$/, '')}/chat/completions`;
@@ -258,7 +422,7 @@ async function handleChat(prompt) {
258
422
  headers['Authorization'] = `Bearer ${config.apiKey}`;
259
423
  bodyData = {
260
424
  model: config.model || 'gpt-3.5-turbo',
261
- messages: [{ role: 'user', content: prompt }]
425
+ messages: [{ role: 'user', content: fullPrompt }]
262
426
  };
263
427
  }
264
428
 
@@ -270,15 +434,21 @@ async function handleChat(prompt) {
270
434
 
271
435
  const data = await response.json();
272
436
 
437
+ let aiResponse = "";
273
438
  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`);
439
+ aiResponse = data.choices[0].message.content;
275
440
  } 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`);
441
+ aiResponse = data.candidates[0].content.parts[0].text;
277
442
  } else if (data.error) {
278
443
  console.log(`\n${C.red}[x] API Error: ${data.error.message || JSON.stringify(data.error)}${C.reset}\n`);
444
+ return startPrompt();
279
445
  } else {
280
446
  console.log(`\n${C.red}[x] Response: ${JSON.stringify(data)}${C.reset}\n`);
447
+ return startPrompt();
281
448
  }
449
+
450
+ console.log(`\n${C.c1}TMPA CLI (${config.model || 'AI'}) :${C.reset} ${aiResponse}\n`);
451
+
282
452
  } catch (error) {
283
453
  console.log(`\n${C.red}[x] Fetch Error: ${error.message}${C.reset}\n`);
284
454
  }
@@ -309,106 +479,6 @@ function handleUninstall() {
309
479
  });
310
480
  }
311
481
 
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
482
  function startPrompt() {
413
483
  rl.question(`${C.c1}TMPA >${C.reset} `, (input) => {
414
484
  const cmd = input.trim();
@@ -423,7 +493,7 @@ function startPrompt() {
423
493
  askConfig(() => startPrompt());
424
494
  } else if (cmd === '/models') {
425
495
  fetchAvailableModels();
426
- } else if (cmd === '/skill') {
496
+ } else if (cmd === '/skill' || cmd === '/skills') {
427
497
  listSkills();
428
498
  startPrompt();
429
499
  } 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.14",
4
4
  "description": "",
5
5
  "main": "bin/index.js",
6
6
  "bin": {