@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.
Files changed (65) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +32 -3
  3. package/VERSION.md +3 -3
  4. package/bin/lib/client-config.mjs +25 -0
  5. package/bin/lib/menu-core.mjs +469 -101
  6. package/bin/lib/skill-installation.mjs +215 -0
  7. package/bin/skill-master-doctor.mjs +51 -4
  8. package/bin/skill-master-install-global-skills.mjs +3 -41
  9. package/bin/skill-master-install-project-skills.mjs +97 -0
  10. package/bin/skill-master-menu.mjs +91 -6
  11. package/bin/skill-master-register-clients.mjs +58 -3
  12. package/docs/operations/GUIA_MULTI_COMPUTADOR.md +8 -1
  13. package/docs/operations/MENU_VISUAL_EVIDENCE_2026-06-28.md +66 -0
  14. package/docs/operations/assets/menu-frame-compact.html +76 -0
  15. package/docs/operations/assets/menu-frame-compact.png +0 -0
  16. package/docs/operations/assets/menu-frame-large.html +84 -0
  17. package/docs/operations/assets/menu-frame-large.png +0 -0
  18. package/docs/operations/assets/menu-frame-running.html +80 -0
  19. package/docs/operations/assets/menu-frame-running.png +0 -0
  20. package/docs/operations/cross-platform-auth-transfer/ANALISE_COMPATIBILIDADE_MCP_2026-06-28.md +140 -0
  21. package/docs/operations/cross-platform-auth-transfer/README_TRANSFERENCIA.md +85 -0
  22. package/docs/operations/reborn-menu-cyberpunk-transfer/ANALISE_MENU_REBORN_CYBERPUNK_2026-06-28.md +174 -0
  23. package/docs/operations/reborn-menu-cyberpunk-transfer/HANDOFF_IMPLEMENTACAO_REBORN_CYBERPUNK_2026-06-28.md +119 -0
  24. package/docs/operations/reborn-menu-cyberpunk-transfer/ORDEM_DE_EXECUCAO_MENU_REBORN_CYBERPUNK.md +134 -0
  25. package/docs/operations/reborn-menu-cyberpunk-transfer/README_TRANSFERENCIA.md +84 -0
  26. package/docs/operations/reborn-menu-cyberpunk-transfer/README_TRANSFERENCIA_REBORN_PACKAGE.md +56 -0
  27. package/docs/operations/reborn-menu-cyberpunk-transfer/references/cyan-hud-frame-sheet.jpg +0 -0
  28. package/docs/operations/reborn-menu-cyberpunk-transfer/references/cyberpunk-pattern-sheet.jpg +0 -0
  29. package/docs/operations/reborn-menu-cyberpunk-transfer/references/fluid-workflow-windows.gif +0 -0
  30. package/docs/prompt-tasks/PROMPT_TASK_001_BOOTSTRAP_SKILL_MASTER_MCP.md +6 -0
  31. package/docs/prompt-tasks/PROMPT_TASK_002_AUTO_UPDATE_LAUNCHER.md +6 -0
  32. package/docs/prompt-tasks/PROMPT_TASK_003_REMOTE_MANIFEST_AND_RELEASES.md +6 -0
  33. package/docs/prompt-tasks/PROMPT_TASK_004_MULTI_USER_DISTRIBUTION.md +6 -0
  34. package/docs/prompt-tasks/PROMPT_TASK_005_SECURITY_AND_QUALITY_GATE.md +6 -0
  35. package/docs/prompt-tasks/PROMPT_TASK_006_MASTER_ACIONAMENTO_APRENDIZADO.md +83 -0
  36. package/docs/prompt-tasks/PROMPT_TASK_007_PERSONA_ORQUESTRADORA.md +88 -0
  37. package/docs/prompt-tasks/PROMPT_TASK_008_PROMPT_ROUTER_MODOS_ATIVACAO.md +156 -0
  38. package/docs/prompt-tasks/PROMPT_TASK_009_PIPELINE_APRENDIZADO_SUCESSO.md +105 -0
  39. package/docs/prompt-tasks/PROMPT_TASK_010_EVALS_GOVERNANCA_ATIVACAO.md +119 -0
  40. package/docs/prompt-tasks/PROMPT_TASK_011_MENU_NOTIFICACOES_NOTION.md +120 -0
  41. package/docs/prompt-tasks/PROMPT_TASK_012_MENU_CYBERPUNK_PIXEL_FRAME.md +123 -0
  42. package/docs/prompt-tasks/PROMPT_TASK_013_MENU_FLUID_DNA_ANIMATION.md +114 -0
  43. package/docs/prompt-tasks/PROMPT_TASK_014_MENU_FUNCTIONAL_PARITY_QA.md +157 -0
  44. package/docs/prompt-tasks/PROMPT_TASK_015_TRANSFER_RELEASE_HANDOFF.md +127 -0
  45. package/docs/prompt-tasks/PROMPT_TASK_016_CROSS_PLATFORM_MCP_AUTH_REGISTRATION.md +107 -0
  46. package/docs/prompt-tasks/PROMPT_TASK_018_NPM_PUBLISH_2FA_SETUP.md +80 -0
  47. package/docs/prompt-tasks/PROMPT_TASK_MASTER_EXECUTOR.md +6 -0
  48. package/docs/skill-candidates/v0.0.12/csharp-senior-master-engineering/SKILL.md +32 -0
  49. package/docs/skill-candidates/v0.0.12/css-senior-master-engineering/SKILL.md +32 -0
  50. package/docs/skill-candidates/v0.0.12/go-senior-master-engineering/SKILL.md +32 -0
  51. package/docs/skill-candidates/v0.0.12/html-senior-master-engineering/SKILL.md +32 -0
  52. package/docs/skill-candidates/v0.0.12/javascript-senior-master-engineering/SKILL.md +32 -0
  53. package/docs/skill-candidates/v0.0.12/json-senior-master-engineering/SKILL.md +32 -0
  54. package/docs/skill-candidates/v0.0.12/python-senior-master-engineering/SKILL.md +32 -0
  55. package/docs/skill-candidates/v0.0.12/react-senior-master-engineering/SKILL.md +32 -0
  56. package/docs/skill-candidates/v0.0.12/ruby-senior-master-engineering/SKILL.md +32 -0
  57. package/docs/skill-candidates/v0.0.12/senior-master-code-optimizer/SKILL.md +48 -0
  58. package/docs/skill-candidates/v0.0.12/sql-senior-master-engineering/SKILL.md +31 -0
  59. package/docs/skill-candidates/v0.0.12/typescript-senior-master-engineering/SKILL.md +35 -0
  60. package/examples/client-configs/claude-code.commands.md +11 -7
  61. package/manifests/channels/beta.json +6 -6
  62. package/manifests/channels/stable.json +8 -8
  63. package/package.json +9 -1
  64. package/scripts/render-menu-evidence.mjs +130 -0
  65. 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
- ? `${state.mode} em ${state.filePath}`
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, `${name} configurado`, detail));
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, readdirSync, cpSync, mkdirSync } from 'node:fs';
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
- if (!dryRun) {
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 commands = buildMenuCommands({ rootDir, currentFile });
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(240);
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
- renderRunningAction(action, tick);
257
- await sleep(180);
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
  }