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.
- package/bin/index.js +190 -19
- 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
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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
|
-
|
|
413
|
+
aiResponse = data.choices[0].message.content;
|
|
264
414
|
} else if (data.candidates && data.candidates[0]?.content?.parts[0]?.text) {
|
|
265
|
-
|
|
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());
|