@fprad0/skill-master-mcp 0.0.11 → 0.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/CHANGELOG.md +7 -0
- package/README.md +32 -3
- package/VERSION.md +3 -3
- package/bin/lib/client-config.mjs +25 -0
- package/bin/lib/menu-core.mjs +469 -101
- package/bin/lib/skill-installation.mjs +215 -0
- package/bin/skill-master-doctor.mjs +51 -4
- package/bin/skill-master-install-global-skills.mjs +3 -41
- package/bin/skill-master-install-project-skills.mjs +97 -0
- package/bin/skill-master-menu.mjs +91 -6
- package/bin/skill-master-register-clients.mjs +58 -3
- package/docs/operations/GUIA_MULTI_COMPUTADOR.md +8 -1
- package/docs/operations/MENU_VISUAL_EVIDENCE_2026-06-28.md +66 -0
- package/docs/operations/assets/menu-frame-compact.html +76 -0
- package/docs/operations/assets/menu-frame-compact.png +0 -0
- package/docs/operations/assets/menu-frame-large.html +84 -0
- package/docs/operations/assets/menu-frame-large.png +0 -0
- package/docs/operations/assets/menu-frame-running.html +80 -0
- package/docs/operations/assets/menu-frame-running.png +0 -0
- package/docs/operations/cross-platform-auth-transfer/ANALISE_COMPATIBILIDADE_MCP_2026-06-28.md +140 -0
- package/docs/operations/cross-platform-auth-transfer/README_TRANSFERENCIA.md +85 -0
- package/docs/operations/reborn-menu-cyberpunk-transfer/ANALISE_MENU_REBORN_CYBERPUNK_2026-06-28.md +174 -0
- package/docs/operations/reborn-menu-cyberpunk-transfer/HANDOFF_IMPLEMENTACAO_REBORN_CYBERPUNK_2026-06-28.md +119 -0
- package/docs/operations/reborn-menu-cyberpunk-transfer/ORDEM_DE_EXECUCAO_MENU_REBORN_CYBERPUNK.md +134 -0
- package/docs/operations/reborn-menu-cyberpunk-transfer/README_TRANSFERENCIA.md +84 -0
- package/docs/operations/reborn-menu-cyberpunk-transfer/README_TRANSFERENCIA_REBORN_PACKAGE.md +56 -0
- package/docs/operations/reborn-menu-cyberpunk-transfer/references/cyan-hud-frame-sheet.jpg +0 -0
- package/docs/operations/reborn-menu-cyberpunk-transfer/references/cyberpunk-pattern-sheet.jpg +0 -0
- package/docs/operations/reborn-menu-cyberpunk-transfer/references/fluid-workflow-windows.gif +0 -0
- package/docs/prompt-tasks/PROMPT_TASK_001_BOOTSTRAP_SKILL_MASTER_MCP.md +6 -0
- package/docs/prompt-tasks/PROMPT_TASK_002_AUTO_UPDATE_LAUNCHER.md +6 -0
- package/docs/prompt-tasks/PROMPT_TASK_003_REMOTE_MANIFEST_AND_RELEASES.md +6 -0
- package/docs/prompt-tasks/PROMPT_TASK_004_MULTI_USER_DISTRIBUTION.md +6 -0
- package/docs/prompt-tasks/PROMPT_TASK_005_SECURITY_AND_QUALITY_GATE.md +6 -0
- package/docs/prompt-tasks/PROMPT_TASK_006_MASTER_ACIONAMENTO_APRENDIZADO.md +83 -0
- package/docs/prompt-tasks/PROMPT_TASK_007_PERSONA_ORQUESTRADORA.md +88 -0
- package/docs/prompt-tasks/PROMPT_TASK_008_PROMPT_ROUTER_MODOS_ATIVACAO.md +156 -0
- package/docs/prompt-tasks/PROMPT_TASK_009_PIPELINE_APRENDIZADO_SUCESSO.md +105 -0
- package/docs/prompt-tasks/PROMPT_TASK_010_EVALS_GOVERNANCA_ATIVACAO.md +119 -0
- package/docs/prompt-tasks/PROMPT_TASK_011_MENU_NOTIFICACOES_NOTION.md +120 -0
- package/docs/prompt-tasks/PROMPT_TASK_012_MENU_CYBERPUNK_PIXEL_FRAME.md +123 -0
- package/docs/prompt-tasks/PROMPT_TASK_013_MENU_FLUID_DNA_ANIMATION.md +114 -0
- package/docs/prompt-tasks/PROMPT_TASK_014_MENU_FUNCTIONAL_PARITY_QA.md +157 -0
- package/docs/prompt-tasks/PROMPT_TASK_015_TRANSFER_RELEASE_HANDOFF.md +127 -0
- package/docs/prompt-tasks/PROMPT_TASK_016_CROSS_PLATFORM_MCP_AUTH_REGISTRATION.md +107 -0
- package/docs/prompt-tasks/PROMPT_TASK_018_NPM_PUBLISH_2FA_SETUP.md +80 -0
- package/docs/prompt-tasks/PROMPT_TASK_MASTER_EXECUTOR.md +6 -0
- package/docs/skill-candidates/v0.0.12/csharp-senior-master-engineering/SKILL.md +32 -0
- package/docs/skill-candidates/v0.0.12/css-senior-master-engineering/SKILL.md +32 -0
- package/docs/skill-candidates/v0.0.12/go-senior-master-engineering/SKILL.md +32 -0
- package/docs/skill-candidates/v0.0.12/html-senior-master-engineering/SKILL.md +32 -0
- package/docs/skill-candidates/v0.0.12/javascript-senior-master-engineering/SKILL.md +32 -0
- package/docs/skill-candidates/v0.0.12/json-senior-master-engineering/SKILL.md +32 -0
- package/docs/skill-candidates/v0.0.12/python-senior-master-engineering/SKILL.md +32 -0
- package/docs/skill-candidates/v0.0.12/react-senior-master-engineering/SKILL.md +32 -0
- package/docs/skill-candidates/v0.0.12/ruby-senior-master-engineering/SKILL.md +32 -0
- package/docs/skill-candidates/v0.0.12/senior-master-code-optimizer/SKILL.md +48 -0
- package/docs/skill-candidates/v0.0.12/sql-senior-master-engineering/SKILL.md +31 -0
- package/docs/skill-candidates/v0.0.12/typescript-senior-master-engineering/SKILL.md +35 -0
- package/examples/client-configs/claude-code.commands.md +11 -7
- package/manifests/channels/beta.json +6 -6
- package/manifests/channels/stable.json +8 -8
- package/package.json +9 -1
- package/scripts/render-menu-evidence.mjs +130 -0
- package/scripts/verify-menu-actions.mjs +2 -0
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import {
|
|
2
|
+
cpSync,
|
|
3
|
+
existsSync,
|
|
4
|
+
mkdirSync,
|
|
5
|
+
readFileSync,
|
|
6
|
+
readdirSync,
|
|
7
|
+
writeFileSync,
|
|
8
|
+
} from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
|
|
11
|
+
export const DEFAULT_PROJECT_SKILL_ROOTS = [
|
|
12
|
+
'.agents/skills',
|
|
13
|
+
'.codex/skills',
|
|
14
|
+
'.claude/skills',
|
|
15
|
+
'.gemini/skills',
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
export function listSkillSources(sourcePath) {
|
|
19
|
+
const entries = readdirSync(sourcePath, { withFileTypes: true });
|
|
20
|
+
const directSkills = entries
|
|
21
|
+
.filter((entry) => entry.isDirectory() && existsSync(path.join(sourcePath, entry.name, 'SKILL.md')))
|
|
22
|
+
.map((entry) => ({ name: entry.name, path: path.join(sourcePath, entry.name) }));
|
|
23
|
+
|
|
24
|
+
if (directSkills.length) {
|
|
25
|
+
return directSkills.sort((left, right) => left.name.localeCompare(right.name));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return entries
|
|
29
|
+
.filter((entry) => entry.isDirectory())
|
|
30
|
+
.flatMap((entry) => {
|
|
31
|
+
const versionPath = path.join(sourcePath, entry.name);
|
|
32
|
+
return readdirSync(versionPath, { withFileTypes: true })
|
|
33
|
+
.filter((skillEntry) => skillEntry.isDirectory() && existsSync(path.join(versionPath, skillEntry.name, 'SKILL.md')))
|
|
34
|
+
.map((skillEntry) => ({ name: skillEntry.name, path: path.join(versionPath, skillEntry.name) }));
|
|
35
|
+
})
|
|
36
|
+
.sort((left, right) => left.name.localeCompare(right.name));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function parseRootList(value) {
|
|
40
|
+
return String(value ?? '')
|
|
41
|
+
.split(',')
|
|
42
|
+
.map((entry) => entry.trim())
|
|
43
|
+
.filter(Boolean);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function normalizeProjectSkillRoots(projectRoot, roots = DEFAULT_PROJECT_SKILL_ROOTS) {
|
|
47
|
+
const uniqueRoots = Array.from(new Set(roots.map((root) => {
|
|
48
|
+
const normalized = root.replace(/\\/g, '/');
|
|
49
|
+
return path.isAbsolute(normalized) ? normalized : normalized.replace(/^\.\//, '');
|
|
50
|
+
})));
|
|
51
|
+
return uniqueRoots.map((root) => ({
|
|
52
|
+
relative: root,
|
|
53
|
+
absolute: path.isAbsolute(root) ? root : path.resolve(projectRoot, root),
|
|
54
|
+
}));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function installSkillsToTarget({ skills, target, overwrite = false, dryRun = false }) {
|
|
58
|
+
if (!dryRun) {
|
|
59
|
+
mkdirSync(target, { recursive: true });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const installed = [];
|
|
63
|
+
const skipped = [];
|
|
64
|
+
|
|
65
|
+
for (const skill of skills) {
|
|
66
|
+
const destination = path.join(target, skill.name);
|
|
67
|
+
if (existsSync(destination) && !overwrite) {
|
|
68
|
+
skipped.push(skill.name);
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (!dryRun) {
|
|
73
|
+
cpSync(skill.path, destination, { recursive: true, force: overwrite });
|
|
74
|
+
}
|
|
75
|
+
installed.push(skill.name);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return { target, installed, skipped };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function buildProjectSkillCatalog({
|
|
82
|
+
projectRoot,
|
|
83
|
+
skillRoots,
|
|
84
|
+
skills,
|
|
85
|
+
packageName,
|
|
86
|
+
packageVersion,
|
|
87
|
+
}) {
|
|
88
|
+
return {
|
|
89
|
+
schemaVersion: 1,
|
|
90
|
+
generatedAt: new Date().toISOString(),
|
|
91
|
+
packageName,
|
|
92
|
+
packageVersion,
|
|
93
|
+
projectRoot,
|
|
94
|
+
skillRoots: skillRoots.map((root) => root.relative),
|
|
95
|
+
skillCount: Array.from(new Set(skills.map((skill) => skill.name))).length,
|
|
96
|
+
skills: Array.from(new Set(skills.map((skill) => skill.name))).sort(),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function writeProjectSkillCatalog({ projectRoot, catalog, catalogPath = '.skill-master/catalog.json', dryRun = false }) {
|
|
101
|
+
const absolutePath = path.isAbsolute(catalogPath)
|
|
102
|
+
? catalogPath
|
|
103
|
+
: path.resolve(projectRoot, catalogPath);
|
|
104
|
+
|
|
105
|
+
if (!dryRun) {
|
|
106
|
+
mkdirSync(path.dirname(absolutePath), { recursive: true });
|
|
107
|
+
writeFileSync(absolutePath, `${JSON.stringify(catalog, null, 2)}\n`, 'utf8');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return absolutePath;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function syncProjectPackageJson({
|
|
114
|
+
projectRoot,
|
|
115
|
+
skillRoots,
|
|
116
|
+
catalogPath = '.skill-master/catalog.json',
|
|
117
|
+
packageName,
|
|
118
|
+
packageVersion,
|
|
119
|
+
createIfMissing = false,
|
|
120
|
+
dryRun = false,
|
|
121
|
+
}) {
|
|
122
|
+
const packageJsonPath = path.join(projectRoot, 'package.json');
|
|
123
|
+
const packageJsonExists = existsSync(packageJsonPath);
|
|
124
|
+
|
|
125
|
+
if (!packageJsonExists && !createIfMissing) {
|
|
126
|
+
return { path: packageJsonPath, status: 'missing' };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const current = packageJsonExists
|
|
130
|
+
? JSON.parse(readFileSync(packageJsonPath, 'utf8'))
|
|
131
|
+
: { private: true };
|
|
132
|
+
|
|
133
|
+
const scripts = current.scripts && typeof current.scripts === 'object' ? current.scripts : {};
|
|
134
|
+
const installScript = 'skill-master-install-project-skills --sync-package-json';
|
|
135
|
+
|
|
136
|
+
const next = {
|
|
137
|
+
...current,
|
|
138
|
+
scripts: {
|
|
139
|
+
...scripts,
|
|
140
|
+
'skill-master:install-project-skills': scripts['skill-master:install-project-skills'] ?? installScript,
|
|
141
|
+
},
|
|
142
|
+
skillMaster: {
|
|
143
|
+
...(current.skillMaster && typeof current.skillMaster === 'object' ? current.skillMaster : {}),
|
|
144
|
+
package: packageName,
|
|
145
|
+
packageVersion,
|
|
146
|
+
projectSkillRoots: skillRoots.map((root) => root.relative),
|
|
147
|
+
projectSkillCatalog: catalogPath,
|
|
148
|
+
installCommand: installScript,
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
if (!dryRun) {
|
|
153
|
+
writeFileSync(packageJsonPath, `${JSON.stringify(next, null, 2)}\n`, 'utf8');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (dryRun) {
|
|
157
|
+
return { path: packageJsonPath, status: packageJsonExists ? 'would-update' : 'would-create' };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return { path: packageJsonPath, status: packageJsonExists ? 'updated' : 'created' };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function installProjectSkills({
|
|
164
|
+
source,
|
|
165
|
+
projectRoot,
|
|
166
|
+
roots = DEFAULT_PROJECT_SKILL_ROOTS,
|
|
167
|
+
overwrite = false,
|
|
168
|
+
dryRun = false,
|
|
169
|
+
writeCatalog = true,
|
|
170
|
+
syncPackageJson = true,
|
|
171
|
+
createPackageJson = false,
|
|
172
|
+
catalogPath = '.skill-master/catalog.json',
|
|
173
|
+
packageName,
|
|
174
|
+
packageVersion,
|
|
175
|
+
}) {
|
|
176
|
+
const skills = listSkillSources(source);
|
|
177
|
+
const skillRoots = normalizeProjectSkillRoots(projectRoot, roots);
|
|
178
|
+
const targets = skillRoots.map((root) => installSkillsToTarget({
|
|
179
|
+
skills,
|
|
180
|
+
target: root.absolute,
|
|
181
|
+
overwrite,
|
|
182
|
+
dryRun,
|
|
183
|
+
}));
|
|
184
|
+
const catalog = buildProjectSkillCatalog({
|
|
185
|
+
projectRoot,
|
|
186
|
+
skillRoots,
|
|
187
|
+
skills,
|
|
188
|
+
packageName,
|
|
189
|
+
packageVersion,
|
|
190
|
+
});
|
|
191
|
+
const writtenCatalogPath = writeCatalog
|
|
192
|
+
? writeProjectSkillCatalog({ projectRoot, catalog, catalogPath, dryRun })
|
|
193
|
+
: null;
|
|
194
|
+
const packageJson = syncPackageJson
|
|
195
|
+
? syncProjectPackageJson({
|
|
196
|
+
projectRoot,
|
|
197
|
+
skillRoots,
|
|
198
|
+
catalogPath,
|
|
199
|
+
packageName,
|
|
200
|
+
packageVersion,
|
|
201
|
+
createIfMissing: createPackageJson,
|
|
202
|
+
dryRun,
|
|
203
|
+
})
|
|
204
|
+
: { path: path.join(projectRoot, 'package.json'), status: 'skipped' };
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
source,
|
|
208
|
+
projectRoot,
|
|
209
|
+
roots: skillRoots,
|
|
210
|
+
skills,
|
|
211
|
+
targets,
|
|
212
|
+
catalogPath: writtenCatalogPath,
|
|
213
|
+
packageJson,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
@@ -23,7 +23,7 @@ Uso:
|
|
|
23
23
|
skill-master-doctor --strict
|
|
24
24
|
|
|
25
25
|
Valida pacote, binarios, skills globais e registro MCP em Codex, Claude,
|
|
26
|
-
Gemini e Antigravity. Nao publica versoes e nao altera configuracoes.
|
|
26
|
+
Claude Code, Gemini e Antigravity. Nao publica versoes e nao altera configuracoes.
|
|
27
27
|
|
|
28
28
|
O registro recomendado usa Node absoluto + bin/skill-master.mjs absoluto para
|
|
29
29
|
evitar falhas de PATH em apps desktop no Windows, Linux e macOS.
|
|
@@ -45,6 +45,18 @@ const REQUIRED_GLOBAL_SKILLS = [
|
|
|
45
45
|
'mcp-client-readiness',
|
|
46
46
|
'terminal-menu-operations',
|
|
47
47
|
'terminal-pixel-art-tui',
|
|
48
|
+
'senior-master-code-optimizer',
|
|
49
|
+
'typescript-senior-master-engineering',
|
|
50
|
+
'python-senior-master-engineering',
|
|
51
|
+
'javascript-senior-master-engineering',
|
|
52
|
+
'go-senior-master-engineering',
|
|
53
|
+
'sql-senior-master-engineering',
|
|
54
|
+
'json-senior-master-engineering',
|
|
55
|
+
'ruby-senior-master-engineering',
|
|
56
|
+
'react-senior-master-engineering',
|
|
57
|
+
'html-senior-master-engineering',
|
|
58
|
+
'css-senior-master-engineering',
|
|
59
|
+
'csharp-senior-master-engineering',
|
|
48
60
|
];
|
|
49
61
|
|
|
50
62
|
const REQUIRED_BINS = [
|
|
@@ -53,6 +65,7 @@ const REQUIRED_BINS = [
|
|
|
53
65
|
'skill-master-update',
|
|
54
66
|
'skill-master-bootstrap-global',
|
|
55
67
|
'skill-master-install-global-skills',
|
|
68
|
+
'skill-master-install-project-skills',
|
|
56
69
|
'skill-master-register-clients',
|
|
57
70
|
'skill-master-doctor',
|
|
58
71
|
];
|
|
@@ -74,6 +87,35 @@ function commandExists(command) {
|
|
|
74
87
|
return result.status === 0;
|
|
75
88
|
}
|
|
76
89
|
|
|
90
|
+
function claudeCodeState() {
|
|
91
|
+
if (!commandExists('claude')) {
|
|
92
|
+
return {
|
|
93
|
+
filePath: 'claude CLI',
|
|
94
|
+
present: false,
|
|
95
|
+
configured: true,
|
|
96
|
+
optionalMissing: true,
|
|
97
|
+
mode: 'cli-missing',
|
|
98
|
+
command: null,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const result = spawnSync('claude', ['mcp', 'list'], {
|
|
103
|
+
encoding: 'utf8',
|
|
104
|
+
timeout: 10000,
|
|
105
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
106
|
+
});
|
|
107
|
+
const output = `${result.stdout ?? ''}${result.stderr ?? ''}`;
|
|
108
|
+
const configured = /skill[-_]master/i.test(output);
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
filePath: 'claude mcp list',
|
|
112
|
+
present: true,
|
|
113
|
+
configured,
|
|
114
|
+
mode: configured ? 'claude-cli' : result.status === 0 ? 'missing' : 'list-failed',
|
|
115
|
+
command: configured ? 'claude mcp list' : output.trim() || null,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
77
119
|
function jsonClientState(filePath) {
|
|
78
120
|
const parsed = readJson(filePath);
|
|
79
121
|
if (parsed.invalid) {
|
|
@@ -128,6 +170,7 @@ const clientPaths = defaultClientConfigPaths();
|
|
|
128
170
|
const clients = [
|
|
129
171
|
['Codex', codexState(clientPaths.codex)],
|
|
130
172
|
['Claude Desktop', jsonClientState(clientPaths.claude)],
|
|
173
|
+
['Claude Code', claudeCodeState()],
|
|
131
174
|
['Gemini CLI', jsonClientState(clientPaths.gemini)],
|
|
132
175
|
['Antigravity', jsonClientState(clientPaths.antigravity)],
|
|
133
176
|
];
|
|
@@ -155,12 +198,15 @@ console.log(`- Skills root: ${globalSkillsRoot}`);
|
|
|
155
198
|
|
|
156
199
|
console.log('');
|
|
157
200
|
for (const [name, state] of clients) {
|
|
201
|
+
const label = state.optionalMissing ? `${name} opcional` : `${name} configurado`;
|
|
158
202
|
const detail = state.configured
|
|
159
|
-
?
|
|
203
|
+
? state.optionalMissing
|
|
204
|
+
? `CLI nao encontrado; pule se voce nao usa Claude Code neste computador`
|
|
205
|
+
: `${state.mode} em ${state.filePath}`
|
|
160
206
|
: state.present
|
|
161
207
|
? `presente, mas mode=${state.mode} command=${state.command ?? 'nao encontrado'} em ${state.filePath}`
|
|
162
208
|
: `ausente em ${state.filePath}`;
|
|
163
|
-
console.log(resultLine(state.configured,
|
|
209
|
+
console.log(resultLine(state.configured, label, detail));
|
|
164
210
|
}
|
|
165
211
|
|
|
166
212
|
const ready = packageOk && allBinsOk && allSkillsOk && allClientsOk;
|
|
@@ -171,9 +217,10 @@ if (!ready) {
|
|
|
171
217
|
console.log('');
|
|
172
218
|
console.log('Proximo comando recomendado:');
|
|
173
219
|
console.log(' skill-master-register-clients --apply-all --force');
|
|
220
|
+
console.log(' skill-master-register-clients --apply-claude-code --claude-code-scope user');
|
|
174
221
|
console.log(' skill-master-menu --run bootstrap-global --yes');
|
|
175
222
|
console.log('');
|
|
176
|
-
console.log('Depois reinicie Codex, Claude, Gemini e Antigravity.');
|
|
223
|
+
console.log('Depois reinicie Codex, Claude Desktop, Claude Code, Gemini e Antigravity.');
|
|
177
224
|
}
|
|
178
225
|
|
|
179
226
|
if (has('--strict') && !ready) {
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { existsSync
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { installSkillsToTarget, listSkillSources } from './lib/skill-installation.mjs';
|
|
6
7
|
|
|
7
8
|
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
8
9
|
const rootDir = path.resolve(here, '..');
|
|
@@ -43,48 +44,9 @@ if (!existsSync(source)) {
|
|
|
43
44
|
process.exit(1);
|
|
44
45
|
}
|
|
45
46
|
|
|
46
|
-
const listSkillSources = (sourcePath) => {
|
|
47
|
-
const entries = readdirSync(sourcePath, { withFileTypes: true });
|
|
48
|
-
const directSkills = entries
|
|
49
|
-
.filter((entry) => entry.isDirectory() && existsSync(path.join(sourcePath, entry.name, 'SKILL.md')))
|
|
50
|
-
.map((entry) => ({ name: entry.name, path: path.join(sourcePath, entry.name) }));
|
|
51
|
-
|
|
52
|
-
if (directSkills.length) {
|
|
53
|
-
return directSkills;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
return entries
|
|
57
|
-
.filter((entry) => entry.isDirectory())
|
|
58
|
-
.flatMap((entry) => {
|
|
59
|
-
const versionPath = path.join(sourcePath, entry.name);
|
|
60
|
-
return readdirSync(versionPath, { withFileTypes: true })
|
|
61
|
-
.filter((skillEntry) => skillEntry.isDirectory() && existsSync(path.join(versionPath, skillEntry.name, 'SKILL.md')))
|
|
62
|
-
.map((skillEntry) => ({ name: skillEntry.name, path: path.join(versionPath, skillEntry.name) }));
|
|
63
|
-
})
|
|
64
|
-
.sort((left, right) => left.name.localeCompare(right.name));
|
|
65
|
-
};
|
|
66
|
-
|
|
67
47
|
const skills = listSkillSources(source);
|
|
68
48
|
|
|
69
|
-
|
|
70
|
-
mkdirSync(target, { recursive: true });
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const installed = [];
|
|
74
|
-
const skipped = [];
|
|
75
|
-
|
|
76
|
-
for (const skill of skills) {
|
|
77
|
-
const from = skill.path;
|
|
78
|
-
const to = path.join(target, skill.name);
|
|
79
|
-
if (existsSync(to) && !overwrite) {
|
|
80
|
-
skipped.push(skill.name);
|
|
81
|
-
continue;
|
|
82
|
-
}
|
|
83
|
-
if (!dryRun) {
|
|
84
|
-
cpSync(from, to, { recursive: true, force: overwrite });
|
|
85
|
-
}
|
|
86
|
-
installed.push(skill.name);
|
|
87
|
-
}
|
|
49
|
+
const { installed, skipped } = installSkillsToTarget({ skills, target, overwrite, dryRun });
|
|
88
50
|
|
|
89
51
|
console.log('[skill_master] Global skills installer');
|
|
90
52
|
console.log(`- Source: ${source}`);
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import {
|
|
6
|
+
DEFAULT_PROJECT_SKILL_ROOTS,
|
|
7
|
+
installProjectSkills,
|
|
8
|
+
parseRootList,
|
|
9
|
+
} from './lib/skill-installation.mjs';
|
|
10
|
+
|
|
11
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
const rootDir = path.resolve(here, '..');
|
|
13
|
+
const defaultSource = path.join(rootDir, 'docs', 'skill-candidates');
|
|
14
|
+
const args = process.argv.slice(2);
|
|
15
|
+
|
|
16
|
+
const has = (flag) => args.includes(flag);
|
|
17
|
+
const readValue = (flag, fallback) => {
|
|
18
|
+
const index = args.indexOf(flag);
|
|
19
|
+
return index >= 0 ? args[index + 1] : fallback;
|
|
20
|
+
};
|
|
21
|
+
const readValues = (flag) => args.flatMap((arg, index) => arg === flag ? [args[index + 1]] : []).filter(Boolean);
|
|
22
|
+
|
|
23
|
+
if (has('--help') || has('-h')) {
|
|
24
|
+
console.log(`Skill Master project skills installer
|
|
25
|
+
|
|
26
|
+
Uso:
|
|
27
|
+
skill-master-install-project-skills
|
|
28
|
+
skill-master-install-project-skills --project-root .
|
|
29
|
+
skill-master-install-project-skills --sync-package-json
|
|
30
|
+
skill-master-install-project-skills --roots ".agents/skills,.codex/skills,.claude/skills,.gemini/skills"
|
|
31
|
+
skill-master-install-project-skills --root .custom/skills --root .codex/skills
|
|
32
|
+
skill-master-install-project-skills --overwrite
|
|
33
|
+
skill-master-install-project-skills --dry-run
|
|
34
|
+
|
|
35
|
+
Instala as skills embutidas em roots de projeto para agentes e clientes locais.
|
|
36
|
+
Por padrao usa o diretorio atual como projeto e as roots:
|
|
37
|
+
${DEFAULT_PROJECT_SKILL_ROOTS.join(', ')}
|
|
38
|
+
|
|
39
|
+
Tambem escreve .skill-master/catalog.json. Quando --sync-package-json e usado,
|
|
40
|
+
registra scripts e metadados em package.json sem alterar package-lock.json
|
|
41
|
+
manualmente; para projetos npm, rode npm install --package-lock-only se quiser
|
|
42
|
+
materializar o lock do proprio projeto.
|
|
43
|
+
`);
|
|
44
|
+
process.exit(0);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const packageJson = JSON.parse(readFileSync(path.join(rootDir, 'package.json'), 'utf8'));
|
|
48
|
+
const source = readValue('--source', defaultSource);
|
|
49
|
+
const projectRoot = path.resolve(readValue('--project-root', process.cwd()));
|
|
50
|
+
const explicitRoots = readValues('--root');
|
|
51
|
+
const csvRoots = parseRootList(readValue('--roots', ''));
|
|
52
|
+
const roots = explicitRoots.length ? explicitRoots : csvRoots.length ? csvRoots : DEFAULT_PROJECT_SKILL_ROOTS;
|
|
53
|
+
const overwrite = has('--overwrite');
|
|
54
|
+
const dryRun = has('--dry-run');
|
|
55
|
+
const writeCatalog = !has('--no-catalog');
|
|
56
|
+
const syncPackageJson = has('--sync-package-json') || (!has('--no-package-json') && existsSync(path.join(projectRoot, 'package.json')));
|
|
57
|
+
const createPackageJson = has('--create-package-json');
|
|
58
|
+
const catalogPath = readValue('--catalog-path', '.skill-master/catalog.json');
|
|
59
|
+
|
|
60
|
+
if (!existsSync(source)) {
|
|
61
|
+
console.error(`[skill_master] Source not found: ${source}`);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (!existsSync(projectRoot)) {
|
|
66
|
+
console.error(`[skill_master] Project root not found: ${projectRoot}`);
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const result = installProjectSkills({
|
|
71
|
+
source,
|
|
72
|
+
projectRoot,
|
|
73
|
+
roots,
|
|
74
|
+
overwrite,
|
|
75
|
+
dryRun,
|
|
76
|
+
writeCatalog,
|
|
77
|
+
syncPackageJson,
|
|
78
|
+
createPackageJson,
|
|
79
|
+
catalogPath,
|
|
80
|
+
packageName: packageJson.name,
|
|
81
|
+
packageVersion: packageJson.version,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const uniqueSkillCount = new Set(result.skills.map((skill) => skill.name)).size;
|
|
85
|
+
|
|
86
|
+
console.log('[skill_master] Project skills installer');
|
|
87
|
+
console.log(`- Source: ${source}`);
|
|
88
|
+
console.log(`- Project: ${projectRoot}`);
|
|
89
|
+
console.log(`- Skill roots: ${result.roots.map((root) => root.relative).join(', ')}`);
|
|
90
|
+
console.log(`- Skills cataloged: ${uniqueSkillCount}`);
|
|
91
|
+
for (const target of result.targets) {
|
|
92
|
+
console.log(`- Target ${target.target}: installed ${target.installed.length}, skipped ${target.skipped.length}`);
|
|
93
|
+
}
|
|
94
|
+
if (result.catalogPath) console.log(`- Catalog: ${result.catalogPath}`);
|
|
95
|
+
console.log(`- package.json: ${result.packageJson.status} (${result.packageJson.path})`);
|
|
96
|
+
if (dryRun) console.log('- Dry run: no files were written.');
|
|
97
|
+
console.log('[skill_master] Restart or rescan the MCP client so project-local skills can be discovered.');
|
|
@@ -7,10 +7,12 @@ import { fileURLToPath } from 'node:url';
|
|
|
7
7
|
import {
|
|
8
8
|
buildMenuCommands,
|
|
9
9
|
formatActionHeader,
|
|
10
|
+
formatCyberConfirmFrame,
|
|
10
11
|
formatCyberMenuFrame,
|
|
11
12
|
formatHelp,
|
|
12
13
|
formatRunningActionFrame,
|
|
13
14
|
formatResultMessage,
|
|
15
|
+
formatSkillMasterIntroFrame,
|
|
14
16
|
formatStatusReport,
|
|
15
17
|
getMenuStatus,
|
|
16
18
|
isInteractiveTerminal,
|
|
@@ -20,7 +22,8 @@ import {
|
|
|
20
22
|
|
|
21
23
|
const currentFile = fileURLToPath(import.meta.url);
|
|
22
24
|
const rootDir = dirname(dirname(currentFile));
|
|
23
|
-
const
|
|
25
|
+
const invocationCwd = process.cwd();
|
|
26
|
+
const commands = buildMenuCommands({ rootDir, currentFile, invocationCwd });
|
|
24
27
|
readline.emitKeypressEvents(process.stdin);
|
|
25
28
|
const CONTROL = {
|
|
26
29
|
alternateScreenIn: '\x1b[?1049h',
|
|
@@ -100,7 +103,7 @@ async function runSelectedAction(action, { yes = false, useColor = false } = {})
|
|
|
100
103
|
console.log('');
|
|
101
104
|
console.log(formatActionHeader(action, { useColor }));
|
|
102
105
|
console.log('');
|
|
103
|
-
const code = await runCommand(action, { cwd: rootDir });
|
|
106
|
+
const code = await runCommand(action, { cwd: action.cwd ?? rootDir });
|
|
104
107
|
console.log('');
|
|
105
108
|
console.log(formatResultMessage(code, { useColor }));
|
|
106
109
|
return { cancelled: false, code };
|
|
@@ -183,6 +186,13 @@ function writeStableFrame(frame, { clear = false } = {}) {
|
|
|
183
186
|
].join(''));
|
|
184
187
|
}
|
|
185
188
|
|
|
189
|
+
function visualHudAllowed() {
|
|
190
|
+
return isInteractiveTerminal()
|
|
191
|
+
&& process.env.SKILL_MASTER_NO_VISUAL !== '1'
|
|
192
|
+
&& process.env.CI !== 'true'
|
|
193
|
+
&& process.env.TERM !== 'dumb';
|
|
194
|
+
}
|
|
195
|
+
|
|
186
196
|
function renderCyberMenu(selectedIndex, tick, { clear = false } = {}) {
|
|
187
197
|
const status = getMenuStatus(rootDir);
|
|
188
198
|
const frame = formatCyberMenuFrame(status, commands, selectedIndex, tick, {
|
|
@@ -203,8 +213,65 @@ function renderRunningAction(action, tick) {
|
|
|
203
213
|
writeStableFrame(frame, { clear: true });
|
|
204
214
|
}
|
|
205
215
|
|
|
216
|
+
function renderIntro(tick, message) {
|
|
217
|
+
const status = getMenuStatus(rootDir);
|
|
218
|
+
const frame = formatSkillMasterIntroFrame(status, tick, {
|
|
219
|
+
columns: process.stdout.columns ?? 120,
|
|
220
|
+
rows: process.stdout.rows ?? 32,
|
|
221
|
+
useColor: true,
|
|
222
|
+
message,
|
|
223
|
+
});
|
|
224
|
+
writeStableFrame(frame, { clear: tick === 0 });
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function renderConfirmAction(action, tick) {
|
|
228
|
+
const status = getMenuStatus(rootDir);
|
|
229
|
+
const frame = formatCyberConfirmFrame(status, action, tick, {
|
|
230
|
+
columns: process.stdout.columns ?? 120,
|
|
231
|
+
rows: process.stdout.rows ?? 32,
|
|
232
|
+
useColor: true,
|
|
233
|
+
});
|
|
234
|
+
writeStableFrame(frame, { clear: tick === 0 });
|
|
235
|
+
}
|
|
236
|
+
|
|
206
237
|
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
207
238
|
|
|
239
|
+
async function animateIntro(message = 'skill_master assumindo o workflow', frames = 6) {
|
|
240
|
+
for (let frame = 0; frame < frames; frame += 1) {
|
|
241
|
+
renderIntro(frame, message);
|
|
242
|
+
await sleep(70);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async function animateRunningAction(action, startTick) {
|
|
247
|
+
for (let frame = 0; frame < 5; frame += 1) {
|
|
248
|
+
renderRunningAction(action, startTick + frame);
|
|
249
|
+
await sleep(90);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
async function confirmVisualAction(action, startTick) {
|
|
254
|
+
let tick = startTick;
|
|
255
|
+
const reader = createKeyReader();
|
|
256
|
+
|
|
257
|
+
try {
|
|
258
|
+
while (true) {
|
|
259
|
+
renderConfirmAction(action, tick);
|
|
260
|
+
const key = await reader.read(180);
|
|
261
|
+
if (!key) {
|
|
262
|
+
tick += 1;
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (key.ctrl && key.name === 'c') return { confirmed: false, code: 130 };
|
|
267
|
+
if (key.name === 'return' || key.name === 'enter' || key.name === 'y') return { confirmed: true, code: 0 };
|
|
268
|
+
if (key.name === 'escape' || key.name === 'q' || key.name === 'n') return { confirmed: false, code: 0 };
|
|
269
|
+
}
|
|
270
|
+
} finally {
|
|
271
|
+
reader.close();
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
208
275
|
async function waitForAnyKey(message = 'Pressione qualquer tecla para voltar ao menu...') {
|
|
209
276
|
process.stdout.write(`\n${message}`);
|
|
210
277
|
setRawInput(true);
|
|
@@ -225,6 +292,8 @@ async function runVisualMenu() {
|
|
|
225
292
|
restoreMenuInput();
|
|
226
293
|
|
|
227
294
|
try {
|
|
295
|
+
await animateIntro('skill_master online / carregando menu operacional', 7);
|
|
296
|
+
|
|
228
297
|
while (true) {
|
|
229
298
|
const columns = process.stdout.columns ?? 120;
|
|
230
299
|
const rows = process.stdout.rows ?? 32;
|
|
@@ -233,7 +302,7 @@ async function runVisualMenu() {
|
|
|
233
302
|
previousRows = rows;
|
|
234
303
|
renderCyberMenu(selectedIndex, tick, { clear: needsClear || resized });
|
|
235
304
|
needsClear = false;
|
|
236
|
-
const key = await keyReader.read(
|
|
305
|
+
const key = await keyReader.read(160);
|
|
237
306
|
|
|
238
307
|
if (!key) {
|
|
239
308
|
tick += 1;
|
|
@@ -253,12 +322,20 @@ async function runVisualMenu() {
|
|
|
253
322
|
if (key.name === 'return' || key.name === 'enter') {
|
|
254
323
|
const action = commands[selectedIndex];
|
|
255
324
|
keyReader.close();
|
|
256
|
-
|
|
257
|
-
|
|
325
|
+
if (action.confirmMessage) {
|
|
326
|
+
const confirmation = await confirmVisualAction(action, tick);
|
|
327
|
+
if (confirmation.code === 130) return 130;
|
|
328
|
+
if (!confirmation.confirmed) {
|
|
329
|
+
keyReader = createKeyReader();
|
|
330
|
+
needsClear = true;
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
await animateRunningAction(action, tick);
|
|
258
335
|
setRawInput(false);
|
|
259
336
|
leaveVisualScreen();
|
|
260
337
|
try {
|
|
261
|
-
const result = await runSelectedAction(action, { useColor: true });
|
|
338
|
+
const result = await runSelectedAction(action, { yes: Boolean(action.confirmMessage), useColor: true });
|
|
262
339
|
lastCode = result.code;
|
|
263
340
|
} catch (error) {
|
|
264
341
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -300,6 +377,14 @@ async function main() {
|
|
|
300
377
|
}
|
|
301
378
|
|
|
302
379
|
const action = commands.find((entry) => entry.key === actionKey);
|
|
380
|
+
if (visualHudAllowed()) {
|
|
381
|
+
enterVisualScreen();
|
|
382
|
+
try {
|
|
383
|
+
await animateIntro(`skill_master executando ${action.label}`, 6);
|
|
384
|
+
} finally {
|
|
385
|
+
leaveVisualScreen();
|
|
386
|
+
}
|
|
387
|
+
}
|
|
303
388
|
const result = await runSelectedAction(action, { yes: args.yes, useColor: isInteractiveTerminal() });
|
|
304
389
|
return result.code;
|
|
305
390
|
}
|