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