tmpa-cli 1.0.10 → 1.0.12
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 +202 -82
- package/package.json +1 -1
package/bin/index.js
CHANGED
|
@@ -6,7 +6,15 @@ const path = require('path');
|
|
|
6
6
|
const os = require('os');
|
|
7
7
|
const { execSync } = require('child_process');
|
|
8
8
|
|
|
9
|
+
const TMPA_DIR = path.join(os.homedir(), '.tmpa');
|
|
10
|
+
const SKILLS_DIR = path.join(TMPA_DIR, 'skills');
|
|
11
|
+
const MCP_DIR = path.join(TMPA_DIR, 'mcp');
|
|
9
12
|
const CONFIG_FILE = path.join(os.homedir(), '.tmpa_config.json');
|
|
13
|
+
const REGISTRY_FILE = path.join(TMPA_DIR, 'registry.json');
|
|
14
|
+
|
|
15
|
+
if (!fs.existsSync(TMPA_DIR)) fs.mkdirSync(TMPA_DIR, { recursive: true });
|
|
16
|
+
if (!fs.existsSync(SKILLS_DIR)) fs.mkdirSync(SKILLS_DIR, { recursive: true });
|
|
17
|
+
if (!fs.existsSync(MCP_DIR)) fs.mkdirSync(MCP_DIR, { recursive: true });
|
|
10
18
|
|
|
11
19
|
const C = {
|
|
12
20
|
reset: '\x1b[0m',
|
|
@@ -34,11 +42,7 @@ const PROVIDERS = {
|
|
|
34
42
|
|
|
35
43
|
function loadConfig() {
|
|
36
44
|
if (fs.existsSync(CONFIG_FILE)) {
|
|
37
|
-
try {
|
|
38
|
-
return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
|
|
39
|
-
} catch (e) {
|
|
40
|
-
return {};
|
|
41
|
-
}
|
|
45
|
+
try { return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8')); } catch (e) { return {}; }
|
|
42
46
|
}
|
|
43
47
|
return {};
|
|
44
48
|
}
|
|
@@ -47,6 +51,21 @@ function saveConfig(config) {
|
|
|
47
51
|
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
48
52
|
}
|
|
49
53
|
|
|
54
|
+
function loadRegistry() {
|
|
55
|
+
if (fs.existsSync(REGISTRY_FILE)) {
|
|
56
|
+
try { return JSON.parse(fs.readFileSync(REGISTRY_FILE, 'utf8')); } catch (e) { return { skills: {}, mcp: {} }; }
|
|
57
|
+
}
|
|
58
|
+
return { skills: {}, mcp: {} };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function saveRegistry(registry) {
|
|
62
|
+
fs.writeFileSync(REGISTRY_FILE, JSON.stringify(registry, null, 2));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
66
|
+
let config = loadConfig();
|
|
67
|
+
let registry = loadRegistry();
|
|
68
|
+
|
|
50
69
|
function showBanner() {
|
|
51
70
|
console.clear();
|
|
52
71
|
console.log(`
|
|
@@ -58,18 +77,11 @@ ${C.c4} ██║ ██║ ╚═╝ ██║██║ ██║ █
|
|
|
58
77
|
${C.c4} ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝${C.reset}
|
|
59
78
|
${C.darkGray}────────────────────────────────────────────────────────────${C.reset}
|
|
60
79
|
${C.reset}The Multi Platform AI ${C.green}[Interactive Mode]${C.reset}
|
|
61
|
-
${C.gray}/config
|
|
80
|
+
${C.gray}/config | /models | /skill | /mcp | /connect | /scan | /exit${C.reset}
|
|
62
81
|
${C.darkGray}────────────────────────────────────────────────────────────${C.reset}
|
|
63
82
|
`);
|
|
64
83
|
}
|
|
65
84
|
|
|
66
|
-
let rl = readline.createInterface({
|
|
67
|
-
input: process.stdin,
|
|
68
|
-
output: process.stdout
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
let config = loadConfig();
|
|
72
|
-
|
|
73
85
|
function askConfig(callback) {
|
|
74
86
|
console.log(`\n${C.cyan}[+] Choose AI Provider:${C.reset}`);
|
|
75
87
|
console.log(` ${C.yellow}1.${C.reset} OpenRouter`);
|
|
@@ -109,81 +121,79 @@ function askConfig(callback) {
|
|
|
109
121
|
});
|
|
110
122
|
}
|
|
111
123
|
|
|
112
|
-
function
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
console.log(`${C.darkGray}────────────────────────────────────────────────────────────${C.reset}`);
|
|
117
|
-
|
|
118
|
-
const visibleModels = models.slice(pageOffset, pageOffset + pageSize);
|
|
119
|
-
|
|
120
|
-
visibleModels.forEach((m, idx) => {
|
|
121
|
-
const realIndex = pageOffset + idx;
|
|
122
|
-
const isSelected = realIndex === selectedIndex;
|
|
123
|
-
const bullet = isSelected ? `${C.green}●${C.reset}` : `${C.gray}○${C.reset}`;
|
|
124
|
-
const modelText = isSelected ? `${C.green}${C.c1}${m.id}${C.reset}` : `${C.gray}${m.id}${C.reset}`;
|
|
125
|
-
console.log(` ${bullet} ${modelText}`);
|
|
126
|
-
});
|
|
124
|
+
function selectModelCLI(allModels) {
|
|
125
|
+
let filteredModels = [...allModels];
|
|
126
|
+
let currentPage = 0;
|
|
127
|
+
const pageSize = 12;
|
|
127
128
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
129
|
+
function displayList() {
|
|
130
|
+
console.clear();
|
|
131
|
+
const totalPages = Math.ceil(filteredModels.length / pageSize) || 1;
|
|
132
|
+
if (currentPage >= totalPages) currentPage = totalPages - 1;
|
|
133
|
+
if (currentPage < 0) currentPage = 0;
|
|
131
134
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
let pageOffset = 0;
|
|
135
|
-
const pageSize = 10; // Menampilkan 10 item per halaman agar rapi
|
|
135
|
+
const startIdx = currentPage * pageSize;
|
|
136
|
+
const pageItems = filteredModels.slice(startIdx, startIdx + pageSize);
|
|
136
137
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
if (process.stdin.isTTY) process.stdin.setRawMode(true);
|
|
138
|
+
console.log(`${C.cyan}=== Models Selector for ${config.provider} (${filteredModels.length} models) ===${C.reset}`);
|
|
139
|
+
console.log(`${C.gray}Current Active: ${C.green}${config.model || 'None'}${C.reset}`);
|
|
140
|
+
console.log(`${C.darkGray}────────────────────────────────────────────────────────────${C.reset}`);
|
|
141
141
|
|
|
142
|
-
|
|
142
|
+
if (pageItems.length === 0) {
|
|
143
|
+
console.log(`${C.red} No models found matching filter.${C.reset}`);
|
|
144
|
+
} else {
|
|
145
|
+
pageItems.forEach((m, idx) => {
|
|
146
|
+
const itemNum = startIdx + idx + 1;
|
|
147
|
+
const isCurrent = m.id === config.model;
|
|
148
|
+
const bullet = isCurrent ? `${C.green}●${C.reset}` : `${C.gray}○${C.reset}`;
|
|
149
|
+
console.log(` ${bullet} ${C.yellow}${itemNum.toString().padStart(3, ' ')}.${C.reset} ${m.id}`);
|
|
150
|
+
});
|
|
151
|
+
}
|
|
143
152
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
153
|
+
console.log(`${C.darkGray}────────────────────────────────────────────────────────────${C.reset}`);
|
|
154
|
+
console.log(`${C.cyan}Page ${currentPage + 1}/${totalPages}${C.reset} | ${C.gray}Commands: [n]ext | [p]rev | [f]ilter <keyword> | [q]uit${C.reset}`);
|
|
155
|
+
|
|
156
|
+
rl.question(`\n${C.yellow}[>] Enter Number / Model ID / Command:${C.reset} `, (answer) => {
|
|
157
|
+
const input = answer.trim();
|
|
158
|
+
|
|
159
|
+
if (input.toLowerCase() === 'n') {
|
|
160
|
+
if (currentPage < totalPages - 1) currentPage++;
|
|
161
|
+
displayList();
|
|
162
|
+
} else if (input.toLowerCase() === 'p') {
|
|
163
|
+
if (currentPage > 0) currentPage--;
|
|
164
|
+
displayList();
|
|
165
|
+
} else if (input.toLowerCase() === 'q' || input.toLowerCase() === 'exit') {
|
|
166
|
+
console.log(`${C.gray}Model selection cancelled.${C.reset}\n`);
|
|
167
|
+
startPrompt();
|
|
168
|
+
} else if (input.toLowerCase().startsWith('f ')) {
|
|
169
|
+
const query = input.slice(2).trim().toLowerCase();
|
|
170
|
+
filteredModels = allModels.filter(m => m.id.toLowerCase().includes(query));
|
|
171
|
+
currentPage = 0;
|
|
172
|
+
displayList();
|
|
173
|
+
} else if (!isNaN(input) && input !== '') {
|
|
174
|
+
const num = parseInt(input);
|
|
175
|
+
if (num >= 1 && num <= filteredModels.length) {
|
|
176
|
+
const selected = filteredModels[num - 1].id;
|
|
177
|
+
config.model = selected;
|
|
178
|
+
saveConfig(config);
|
|
179
|
+
console.log(`\n${C.green}[+] Model changed to: ${config.model}${C.reset}\n`);
|
|
180
|
+
startPrompt();
|
|
181
|
+
} else {
|
|
182
|
+
console.log(`${C.red}[x] Invalid number!${C.reset}`);
|
|
183
|
+
setTimeout(displayList, 1000);
|
|
156
184
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
cleanup();
|
|
160
|
-
const chosen = models[selectedIndex].id;
|
|
161
|
-
config.model = chosen;
|
|
185
|
+
} else if (input.length > 0) {
|
|
186
|
+
config.model = input;
|
|
162
187
|
saveConfig(config);
|
|
163
|
-
console.log(`\n${C.green}[+]
|
|
164
|
-
|
|
165
|
-
} else
|
|
166
|
-
|
|
167
|
-
process.exit(0);
|
|
168
|
-
} else if (key.name === 'escape') {
|
|
169
|
-
cleanup();
|
|
170
|
-
console.log(`\n${C.gray}Model selection cancelled.${C.reset}\n`);
|
|
171
|
-
resolve();
|
|
188
|
+
console.log(`\n${C.green}[+] Custom Model ID set to: ${config.model}${C.reset}\n`);
|
|
189
|
+
startPrompt();
|
|
190
|
+
} else {
|
|
191
|
+
startPrompt();
|
|
172
192
|
}
|
|
173
|
-
};
|
|
174
|
-
|
|
175
|
-
function cleanup() {
|
|
176
|
-
process.stdin.removeListener('keypress', onKeypress);
|
|
177
|
-
if (process.stdin.isTTY) process.stdin.setRawMode(false);
|
|
178
|
-
// Restart readline interface untuk percakapan selanjutnya
|
|
179
|
-
rl = readline.createInterface({
|
|
180
|
-
input: process.stdin,
|
|
181
|
-
output: process.stdout
|
|
182
|
-
});
|
|
183
|
-
}
|
|
193
|
+
});
|
|
194
|
+
}
|
|
184
195
|
|
|
185
|
-
|
|
186
|
-
});
|
|
196
|
+
displayList();
|
|
187
197
|
}
|
|
188
198
|
|
|
189
199
|
async function fetchAvailableModels() {
|
|
@@ -214,8 +224,7 @@ async function fetchAvailableModels() {
|
|
|
214
224
|
const data = await response.json();
|
|
215
225
|
|
|
216
226
|
if (data.data && Array.isArray(data.data) && data.data.length > 0) {
|
|
217
|
-
|
|
218
|
-
startPrompt();
|
|
227
|
+
selectModelCLI(data.data);
|
|
219
228
|
} else {
|
|
220
229
|
console.log(`${C.red}[x] Could not retrieve models list automatically from ${config.provider}.${C.reset}`);
|
|
221
230
|
startPrompt();
|
|
@@ -300,6 +309,106 @@ function handleUninstall() {
|
|
|
300
309
|
});
|
|
301
310
|
}
|
|
302
311
|
|
|
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
|
+
|
|
303
412
|
function startPrompt() {
|
|
304
413
|
rl.question(`${C.c1}TMPA >${C.reset} `, (input) => {
|
|
305
414
|
const cmd = input.trim();
|
|
@@ -314,6 +423,18 @@ function startPrompt() {
|
|
|
314
423
|
askConfig(() => startPrompt());
|
|
315
424
|
} else if (cmd === '/models') {
|
|
316
425
|
fetchAvailableModels();
|
|
426
|
+
} else if (cmd === '/skill') {
|
|
427
|
+
listSkills();
|
|
428
|
+
startPrompt();
|
|
429
|
+
} else if (cmd === '/mcp') {
|
|
430
|
+
listMCP();
|
|
431
|
+
startPrompt();
|
|
432
|
+
} else if (cmd.startsWith('/connect')) {
|
|
433
|
+
connectResource(cmd.replace('/connect', ''));
|
|
434
|
+
startPrompt();
|
|
435
|
+
} else if (cmd === '/scan') {
|
|
436
|
+
scanResources();
|
|
437
|
+
startPrompt();
|
|
317
438
|
} else if (cmd === '/uninstall') {
|
|
318
439
|
handleUninstall();
|
|
319
440
|
} else if (cmd === '') {
|
|
@@ -324,7 +445,6 @@ function startPrompt() {
|
|
|
324
445
|
});
|
|
325
446
|
}
|
|
326
447
|
|
|
327
|
-
// Program Execution
|
|
328
448
|
showBanner();
|
|
329
449
|
if (!config.apiKey) {
|
|
330
450
|
askConfig(() => startPrompt());
|