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.
- package/bin/index.js +177 -107
- 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:
|
|
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:
|
|
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
|
-
|
|
439
|
+
aiResponse = data.choices[0].message.content;
|
|
275
440
|
} else if (data.candidates && data.candidates[0]?.content?.parts[0]?.text) {
|
|
276
|
-
|
|
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') {
|