@fprad0/skill-master-mcp 0.0.6 → 0.0.7
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/CHANGELOG.md +6 -0
- package/README.md +12 -2
- package/VERSION.md +2 -2
- package/bin/lib/menu-core.mjs +227 -0
- package/bin/skill-master-menu.mjs +136 -143
- package/manifests/channels/beta.json +5 -5
- package/manifests/channels/stable.json +6 -6
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,12 @@ All notable changes to `skill_master` will be tracked here.
|
|
|
7
7
|
- Prepare a public npm publication path with dedicated workflow and operator guide.
|
|
8
8
|
- Upgrade release automation from Node `20` to Node `22`.
|
|
9
9
|
|
|
10
|
+
## [0.0.7] - 2026-06-26
|
|
11
|
+
|
|
12
|
+
- Add a visual terminal menu for `skill-master-menu` using `prompts`, with TTY detection and direct action mode.
|
|
13
|
+
- Extract the menu core into a reusable module for tests and future UI changes.
|
|
14
|
+
- Keep the package compatible with Node `18+` instead of moving to a heavier TUI stack.
|
|
15
|
+
|
|
10
16
|
## [0.0.6] - 2026-06-26
|
|
11
17
|
|
|
12
18
|
- Harden `skill-master-configure-private-registry` with HTTPS registry validation, scoped token handling, redacted dry-runs, timestamped backups, and POSIX `0600` permissions.
|
package/README.md
CHANGED
|
@@ -110,8 +110,8 @@ npx -y @fprad0/skill-master-mcp@latest
|
|
|
110
110
|
|
|
111
111
|
Estado atual validado em `2026-06-26`:
|
|
112
112
|
|
|
113
|
-
- pacote publico publicado: `@fprad0/skill-master-mcp@0.0.
|
|
114
|
-
- dist-tag `latest` apontando para `0.0.
|
|
113
|
+
- pacote publico publicado: `@fprad0/skill-master-mcp@0.0.7`
|
|
114
|
+
- dist-tag `latest` apontando para `0.0.7`
|
|
115
115
|
- workflow `Publish Skill Master to npmjs` concluido com sucesso
|
|
116
116
|
- `npm install` e `npx` confirmados
|
|
117
117
|
|
|
@@ -130,6 +130,16 @@ O `skill-master-mcp` deve continuar reservado para clientes MCP via `stdio`. Par
|
|
|
130
130
|
skill-master-menu
|
|
131
131
|
```
|
|
132
132
|
|
|
133
|
+
O menu agora possui modo visual interativo para terminal com TTY e tambem aceita execucao direta por acao:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
skill-master-menu --run status
|
|
137
|
+
skill-master-menu --run check
|
|
138
|
+
skill-master-menu --run update --yes
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
O menu visual usa `prompts` para ficar mais legivel no terminal e manter compatibilidade com `Node 18+`.
|
|
142
|
+
|
|
133
143
|
Para atualizar a instalacao global via npm sem iniciar o servidor MCP:
|
|
134
144
|
|
|
135
145
|
```bash
|
package/VERSION.md
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Versão funcional planejada: `00.02`
|
|
4
4
|
|
|
5
|
-
Versão técnica para empacotamento semântico: `0.0.
|
|
5
|
+
Versão técnica para empacotamento semântico: `0.0.7`
|
|
6
6
|
|
|
7
7
|
## Observação
|
|
8
8
|
|
|
9
|
-
O nome `00.02` será usado na comunicação e nos documentos. Para ferramentas que exigem SemVer, como npm e alguns fluxos de release, usar `0.0.
|
|
9
|
+
O nome `00.02` será usado na comunicação e nos documentos. Para ferramentas que exigem SemVer, como npm e alguns fluxos de release, usar `0.0.7`.
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import process from 'node:process';
|
|
5
|
+
|
|
6
|
+
const ANSI = {
|
|
7
|
+
reset: '\x1b[0m',
|
|
8
|
+
bold: '\x1b[1m',
|
|
9
|
+
dim: '\x1b[2m',
|
|
10
|
+
cyan: '\x1b[36m',
|
|
11
|
+
green: '\x1b[32m',
|
|
12
|
+
yellow: '\x1b[33m',
|
|
13
|
+
red: '\x1b[31m',
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
function colorize(text, color, enabled) {
|
|
17
|
+
return enabled ? `${color}${text}${ANSI.reset}` : text;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function readJson(rootDir, relativePath) {
|
|
21
|
+
const target = join(rootDir, relativePath);
|
|
22
|
+
if (!existsSync(target)) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return JSON.parse(readFileSync(target, 'utf8'));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function readText(rootDir, relativePath) {
|
|
30
|
+
const target = join(rootDir, relativePath);
|
|
31
|
+
if (!existsSync(target)) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return readFileSync(target, 'utf8').trim();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function getMenuStatus(rootDir) {
|
|
39
|
+
const packageJson = readJson(rootDir, 'package.json');
|
|
40
|
+
const stableManifest = readJson(rootDir, 'manifests/channels/stable.json');
|
|
41
|
+
const versionText = readText(rootDir, 'VERSION.md');
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
packageName: packageJson?.name ?? 'nao encontrado',
|
|
45
|
+
semver: packageJson?.version ?? 'nao encontrado',
|
|
46
|
+
manifestVersion: stableManifest?.version ?? 'nao encontrado',
|
|
47
|
+
manifestSemver: stableManifest?.semver ?? 'nao encontrado',
|
|
48
|
+
versionText,
|
|
49
|
+
rootDir,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function buildMenuCommands({ rootDir, currentFile, nodeExecPath = process.execPath }) {
|
|
54
|
+
return [
|
|
55
|
+
{
|
|
56
|
+
key: 'status',
|
|
57
|
+
aliases: ['status'],
|
|
58
|
+
label: 'Status local',
|
|
59
|
+
description: 'Mostra versao local, manifesto e informacoes do pacote.',
|
|
60
|
+
command: nodeExecPath,
|
|
61
|
+
args: [currentFile, '--status'],
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
key: 'check',
|
|
65
|
+
aliases: ['check', 'gate'],
|
|
66
|
+
label: 'Rodar gate completo',
|
|
67
|
+
description: 'Executa build, testes e validacao de manifestos.',
|
|
68
|
+
command: 'npm',
|
|
69
|
+
args: ['run', 'check'],
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
key: 'build',
|
|
73
|
+
aliases: ['build'],
|
|
74
|
+
label: 'Rodar build',
|
|
75
|
+
description: 'Compila o projeto localmente.',
|
|
76
|
+
command: 'npm',
|
|
77
|
+
args: ['run', 'build'],
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
key: 'publicNpm',
|
|
81
|
+
aliases: ['public-npm', 'npm', 'registry'],
|
|
82
|
+
label: 'Validar pacote no npm publico',
|
|
83
|
+
description: 'Consulta a versao publicada em registry.npmjs.org.',
|
|
84
|
+
command: 'npm',
|
|
85
|
+
args: [
|
|
86
|
+
'view',
|
|
87
|
+
'@fprad0/skill-master-mcp',
|
|
88
|
+
'version',
|
|
89
|
+
'--registry=https://registry.npmjs.org',
|
|
90
|
+
],
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
key: 'updateGlobal',
|
|
94
|
+
aliases: ['update', 'update-global'],
|
|
95
|
+
label: 'Atualizar pacote global via npm',
|
|
96
|
+
description: 'Atualiza a instalacao global e exige reinicio do cliente MCP.',
|
|
97
|
+
command: nodeExecPath,
|
|
98
|
+
args: [join(rootDir, 'bin', 'skill-master-update.mjs')],
|
|
99
|
+
confirmMessage: 'Atualizar o pacote global agora?',
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
key: 'privateRegistry',
|
|
103
|
+
aliases: ['private-registry', 'private', 'github-packages'],
|
|
104
|
+
label: 'Configurar registry privado GitHub Packages',
|
|
105
|
+
description: 'Prepara o .npmrc e valida o acesso ao pacote privado.',
|
|
106
|
+
command: nodeExecPath,
|
|
107
|
+
args: ['scripts/configure-private-registry.mjs', '--validate'],
|
|
108
|
+
confirmMessage: 'Rodar a configuracao de registry privado agora?',
|
|
109
|
+
},
|
|
110
|
+
];
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function buildMenuChoices(commands) {
|
|
114
|
+
return commands.map((command) => ({
|
|
115
|
+
title: command.label,
|
|
116
|
+
description: command.description,
|
|
117
|
+
value: command.key,
|
|
118
|
+
})).concat({
|
|
119
|
+
title: 'Sair',
|
|
120
|
+
description: 'Fecha o menu operacional.',
|
|
121
|
+
value: '__exit__',
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function resolveActionKey(input, commands) {
|
|
126
|
+
const normalized = (input ?? '').trim().toLowerCase();
|
|
127
|
+
if (!normalized) {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return commands.find((command) =>
|
|
132
|
+
command.key.toLowerCase() === normalized || command.aliases.includes(normalized),
|
|
133
|
+
)?.key ?? null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function isInteractiveTerminal() {
|
|
137
|
+
return Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function formatStatusReport(status) {
|
|
141
|
+
const lines = [
|
|
142
|
+
'Skill Master MCP - status local',
|
|
143
|
+
`Diretorio: ${status.rootDir}`,
|
|
144
|
+
`Pacote: ${status.packageName}`,
|
|
145
|
+
`Semver local: ${status.semver}`,
|
|
146
|
+
`Manifesto stable: ${status.manifestVersion}`,
|
|
147
|
+
`Manifesto semver: ${status.manifestSemver}`,
|
|
148
|
+
];
|
|
149
|
+
|
|
150
|
+
if (status.versionText) {
|
|
151
|
+
lines.push('', 'VERSION.md:');
|
|
152
|
+
lines.push(...status.versionText.split('\n').slice(0, 6));
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return lines.join('\n');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function renderPanelLines(lines, { color = ANSI.cyan, useColor = false } = {}) {
|
|
159
|
+
const width = Math.max(...lines.map((line) => line.length), 24);
|
|
160
|
+
const border = `+${'-'.repeat(width + 2)}+`;
|
|
161
|
+
const paintedBorder = colorize(border, color, useColor);
|
|
162
|
+
const body = lines.map((line) => `| ${line.padEnd(width)} |`);
|
|
163
|
+
return [paintedBorder, ...body, paintedBorder].join('\n');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function formatMenuBanner(status, { useColor = false } = {}) {
|
|
167
|
+
const lines = [
|
|
168
|
+
colorize('Skill Master MCP', ANSI.bold, useColor),
|
|
169
|
+
'Menu operacional para manutencao local',
|
|
170
|
+
`Versao local ${status.semver} | canal ${status.manifestVersion}`,
|
|
171
|
+
colorize('Setas + Enter para navegar', ANSI.dim, useColor),
|
|
172
|
+
];
|
|
173
|
+
return renderPanelLines(lines, { color: ANSI.cyan, useColor });
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function formatActionHeader(action, { useColor = false } = {}) {
|
|
177
|
+
return renderPanelLines(
|
|
178
|
+
[
|
|
179
|
+
colorize(action.label, ANSI.bold, useColor),
|
|
180
|
+
action.description,
|
|
181
|
+
],
|
|
182
|
+
{ color: ANSI.green, useColor },
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export function formatResultMessage(code, { useColor = false } = {}) {
|
|
187
|
+
if (code === 0) {
|
|
188
|
+
return colorize('Comando concluido com sucesso.', ANSI.green, useColor);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return colorize(`Comando terminou com codigo ${code}.`, ANSI.red, useColor);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export function formatHelp(commands) {
|
|
195
|
+
const actionList = commands.map((command) => ` - ${command.key}: ${command.label}`).join('\n');
|
|
196
|
+
|
|
197
|
+
return `Skill Master Menu
|
|
198
|
+
|
|
199
|
+
Uso:
|
|
200
|
+
skill-master-menu
|
|
201
|
+
skill-master-menu --status
|
|
202
|
+
skill-master-menu --run <acao>
|
|
203
|
+
skill-master-menu --run update --yes
|
|
204
|
+
skill-master-menu --help
|
|
205
|
+
|
|
206
|
+
Acoes disponiveis:
|
|
207
|
+
${actionList}
|
|
208
|
+
|
|
209
|
+
O comando de menu e voltado para operacao humana. O binario MCP stdio continua sendo:
|
|
210
|
+
skill-master-mcp
|
|
211
|
+
`;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export function runCommand(action, { cwd, env = process.env } = {}) {
|
|
215
|
+
return new Promise((resolve) => {
|
|
216
|
+
const child = spawn(action.command, action.args, {
|
|
217
|
+
cwd,
|
|
218
|
+
env,
|
|
219
|
+
shell: process.platform === 'win32',
|
|
220
|
+
stdio: 'inherit',
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
child.on('close', (code) => {
|
|
224
|
+
resolve(code ?? 1);
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
}
|
|
@@ -1,179 +1,172 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import { dirname, join } from 'node:path';
|
|
3
|
+
import prompts from 'prompts';
|
|
4
|
+
import { dirname } from 'node:path';
|
|
6
5
|
import { fileURLToPath } from 'node:url';
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
['2', commands.check],
|
|
53
|
-
['3', commands.build],
|
|
54
|
-
['4', commands.publicNpm],
|
|
55
|
-
['5', commands.updateGlobal],
|
|
56
|
-
['6', commands.privateRegistry],
|
|
57
|
-
];
|
|
58
|
-
|
|
59
|
-
function readJson(relativePath) {
|
|
60
|
-
const target = join(rootDir, relativePath);
|
|
61
|
-
if (!existsSync(target)) {
|
|
62
|
-
return null;
|
|
6
|
+
import {
|
|
7
|
+
buildMenuChoices,
|
|
8
|
+
buildMenuCommands,
|
|
9
|
+
formatActionHeader,
|
|
10
|
+
formatHelp,
|
|
11
|
+
formatMenuBanner,
|
|
12
|
+
formatResultMessage,
|
|
13
|
+
formatStatusReport,
|
|
14
|
+
getMenuStatus,
|
|
15
|
+
isInteractiveTerminal,
|
|
16
|
+
resolveActionKey,
|
|
17
|
+
runCommand,
|
|
18
|
+
} from './lib/menu-core.mjs';
|
|
19
|
+
|
|
20
|
+
const currentFile = fileURLToPath(import.meta.url);
|
|
21
|
+
const rootDir = dirname(dirname(currentFile));
|
|
22
|
+
const commands = buildMenuCommands({ rootDir, currentFile });
|
|
23
|
+
|
|
24
|
+
function parseArgs(argv) {
|
|
25
|
+
const parsed = {
|
|
26
|
+
help: false,
|
|
27
|
+
status: false,
|
|
28
|
+
yes: false,
|
|
29
|
+
run: null,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
33
|
+
const arg = argv[i];
|
|
34
|
+
switch (arg) {
|
|
35
|
+
case '--help':
|
|
36
|
+
case '-h':
|
|
37
|
+
parsed.help = true;
|
|
38
|
+
break;
|
|
39
|
+
case '--status':
|
|
40
|
+
parsed.status = true;
|
|
41
|
+
break;
|
|
42
|
+
case '--yes':
|
|
43
|
+
parsed.yes = true;
|
|
44
|
+
break;
|
|
45
|
+
case '--run':
|
|
46
|
+
parsed.run = argv[++i] ?? null;
|
|
47
|
+
break;
|
|
48
|
+
default:
|
|
49
|
+
throw new Error(`Unknown argument: ${arg}`);
|
|
50
|
+
}
|
|
63
51
|
}
|
|
64
52
|
|
|
65
|
-
return
|
|
53
|
+
return parsed;
|
|
66
54
|
}
|
|
67
55
|
|
|
68
|
-
function
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
return null;
|
|
72
|
-
}
|
|
56
|
+
function printHelp() {
|
|
57
|
+
console.log(formatHelp(commands));
|
|
58
|
+
}
|
|
73
59
|
|
|
74
|
-
|
|
60
|
+
function printStatus() {
|
|
61
|
+
console.log(formatStatusReport(getMenuStatus(rootDir)));
|
|
75
62
|
}
|
|
76
63
|
|
|
77
|
-
function
|
|
78
|
-
|
|
64
|
+
async function runSelectedAction(action, { yes = false, useColor = false } = {}) {
|
|
65
|
+
if (action.confirmMessage && !yes) {
|
|
66
|
+
if (!isInteractiveTerminal()) {
|
|
67
|
+
throw new Error(`Action ${action.key} requires --yes outside an interactive terminal.`);
|
|
68
|
+
}
|
|
79
69
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
70
|
+
const confirmation = await prompts({
|
|
71
|
+
type: 'confirm',
|
|
72
|
+
name: 'confirmed',
|
|
73
|
+
message: action.confirmMessage,
|
|
74
|
+
initial: false,
|
|
75
|
+
});
|
|
84
76
|
|
|
85
|
-
|
|
86
|
-
|
|
77
|
+
if (!confirmation.confirmed) {
|
|
78
|
+
return { cancelled: true, code: 0 };
|
|
79
|
+
}
|
|
80
|
+
}
|
|
87
81
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
82
|
+
console.log('');
|
|
83
|
+
console.log(formatActionHeader(action, { useColor }));
|
|
84
|
+
console.log('');
|
|
85
|
+
const code = await runCommand(action, { cwd: rootDir });
|
|
86
|
+
console.log('');
|
|
87
|
+
console.log(formatResultMessage(code, { useColor }));
|
|
88
|
+
return { cancelled: false, code };
|
|
91
89
|
}
|
|
92
90
|
|
|
93
|
-
function
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
console.log('\nVERSION.md:');
|
|
108
|
-
console.log(firstLines);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
91
|
+
async function runVisualMenu() {
|
|
92
|
+
while (true) {
|
|
93
|
+
console.clear();
|
|
94
|
+
const status = getMenuStatus(rootDir);
|
|
95
|
+
console.log(formatMenuBanner(status, { useColor: true }));
|
|
96
|
+
console.log('');
|
|
97
|
+
|
|
98
|
+
const selection = await prompts({
|
|
99
|
+
type: 'select',
|
|
100
|
+
name: 'action',
|
|
101
|
+
message: 'Escolha uma acao',
|
|
102
|
+
choices: buildMenuChoices(commands),
|
|
103
|
+
initial: 0,
|
|
104
|
+
});
|
|
111
105
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
106
|
+
if (!selection.action || selection.action === '__exit__') {
|
|
107
|
+
return 0;
|
|
108
|
+
}
|
|
115
109
|
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
stdio: 'inherit',
|
|
121
|
-
});
|
|
110
|
+
const action = commands.find((entry) => entry.key === selection.action);
|
|
111
|
+
if (!action) {
|
|
112
|
+
return 1;
|
|
113
|
+
}
|
|
122
114
|
|
|
123
|
-
|
|
124
|
-
|
|
115
|
+
const result = await runSelectedAction(action, { useColor: true });
|
|
116
|
+
if (result.cancelled) {
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const nextStep = await prompts({
|
|
121
|
+
type: 'toggle',
|
|
122
|
+
name: 'keepGoing',
|
|
123
|
+
message: 'Voltar ao menu?',
|
|
124
|
+
initial: true,
|
|
125
|
+
active: 'sim',
|
|
126
|
+
inactive: 'nao',
|
|
125
127
|
});
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
128
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
try {
|
|
133
|
-
while (true) {
|
|
134
|
-
console.log('\nSkill Master Menu');
|
|
135
|
-
for (const [key, item] of menuItems) {
|
|
136
|
-
console.log(` ${key}. ${item.label}`);
|
|
137
|
-
}
|
|
138
|
-
console.log(' 0. Sair');
|
|
139
|
-
|
|
140
|
-
const answer = (await rl.question('\nEscolha uma opcao: ')).trim();
|
|
141
|
-
|
|
142
|
-
if (answer === '0' || answer.toLowerCase() === 'q') {
|
|
143
|
-
return 0;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const selected = menuItems.find(([key]) => key === answer)?.[1];
|
|
147
|
-
if (!selected) {
|
|
148
|
-
console.log('Opcao invalida.');
|
|
149
|
-
continue;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
const code = await runCommand(selected);
|
|
153
|
-
if (code !== 0) {
|
|
154
|
-
console.log(`\nComando terminou com codigo ${code}.`);
|
|
155
|
-
}
|
|
129
|
+
if (!nextStep.keepGoing) {
|
|
130
|
+
return result.code;
|
|
156
131
|
}
|
|
157
|
-
} finally {
|
|
158
|
-
rl.close();
|
|
159
132
|
}
|
|
160
133
|
}
|
|
161
134
|
|
|
162
135
|
async function main() {
|
|
163
|
-
const args = process.argv.slice(2);
|
|
136
|
+
const args = parseArgs(process.argv.slice(2));
|
|
164
137
|
|
|
165
|
-
if (args.
|
|
138
|
+
if (args.help) {
|
|
166
139
|
printHelp();
|
|
167
140
|
return 0;
|
|
168
141
|
}
|
|
169
142
|
|
|
170
|
-
if (args.
|
|
143
|
+
if (args.status) {
|
|
171
144
|
printStatus();
|
|
172
145
|
return 0;
|
|
173
146
|
}
|
|
174
147
|
|
|
175
|
-
|
|
148
|
+
if (args.run) {
|
|
149
|
+
const actionKey = resolveActionKey(args.run, commands);
|
|
150
|
+
if (!actionKey) {
|
|
151
|
+
throw new Error(`Unknown action: ${args.run}`);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const action = commands.find((entry) => entry.key === actionKey);
|
|
155
|
+
const result = await runSelectedAction(action, { yes: args.yes, useColor: isInteractiveTerminal() });
|
|
156
|
+
return result.code;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (!isInteractiveTerminal()) {
|
|
160
|
+
throw new Error('Interactive menu requires a TTY. Use --run <acao> or --status in automated environments.');
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return runVisualMenu();
|
|
176
164
|
}
|
|
177
165
|
|
|
178
|
-
|
|
179
|
-
process.exitCode =
|
|
166
|
+
try {
|
|
167
|
+
process.exitCode = await main();
|
|
168
|
+
} catch (error) {
|
|
169
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
170
|
+
process.stderr.write(`[skill_master] ${message}\n`);
|
|
171
|
+
process.exitCode = 1;
|
|
172
|
+
}
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
"name": "skill_master",
|
|
3
3
|
"channel": "beta",
|
|
4
4
|
"version": "00.02-beta",
|
|
5
|
-
"semver": "0.0.
|
|
6
|
-
"publishedAt": "2026-06-
|
|
5
|
+
"semver": "0.0.7",
|
|
6
|
+
"publishedAt": "2026-06-26T20:15:00-03:00",
|
|
7
7
|
"git": {
|
|
8
8
|
"repo": "https://github.com/FPrad0/skill-master-mcp",
|
|
9
9
|
"branch": "main",
|
|
10
|
-
"commit": "
|
|
10
|
+
"commit": "ec57c95"
|
|
11
11
|
},
|
|
12
12
|
"node": {
|
|
13
13
|
"minimum": "18.0.0"
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"buildCommand": "npm run build"
|
|
20
20
|
},
|
|
21
21
|
"changelog": [
|
|
22
|
-
"Canal beta acompanha o
|
|
23
|
-
"
|
|
22
|
+
"Canal beta acompanha o menu visual no terminal.",
|
|
23
|
+
"Fluxo interativo agora tem selecao visual, confirmacao e modo direto por acao."
|
|
24
24
|
]
|
|
25
25
|
}
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
"name": "skill_master",
|
|
3
3
|
"channel": "stable",
|
|
4
4
|
"version": "00.02",
|
|
5
|
-
"semver": "0.0.
|
|
6
|
-
"publishedAt": "2026-06-
|
|
5
|
+
"semver": "0.0.7",
|
|
6
|
+
"publishedAt": "2026-06-26T20:15:00-03:00",
|
|
7
7
|
"git": {
|
|
8
8
|
"repo": "https://github.com/FPrad0/skill-master-mcp",
|
|
9
9
|
"branch": "main",
|
|
10
|
-
"commit": "
|
|
10
|
+
"commit": "ec57c95"
|
|
11
11
|
},
|
|
12
12
|
"node": {
|
|
13
13
|
"minimum": "18.0.0"
|
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
"buildCommand": "npm run build"
|
|
20
20
|
},
|
|
21
21
|
"changelog": [
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
22
|
+
"Menu operacional ganha interface visual no terminal com prompts.",
|
|
23
|
+
"Core do menu foi extraido para modulo reutilizavel e testavel.",
|
|
24
|
+
"Compatibilidade Node 18+ foi preservada sem migrar para TUI pesada."
|
|
25
25
|
]
|
|
26
26
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fprad0/skill-master-mcp",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Personal MCP server that catalogs, recommends and reports skills with update-aware metadata.",
|
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
53
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
54
|
+
"prompts": "^2.4.2",
|
|
54
55
|
"zod": "^4.4.3"
|
|
55
56
|
},
|
|
56
57
|
"devDependencies": {
|