@fprad0/skill-master-mcp 0.0.10 → 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 +12 -1
- package/README.md +59 -10
- package/VERSION.md +3 -3
- package/bin/lib/client-config.mjs +293 -0
- package/bin/lib/menu-core.mjs +509 -131
- package/bin/lib/skill-installation.mjs +215 -0
- package/bin/skill-master-bootstrap-global.mjs +2 -1
- package/bin/skill-master-doctor.mjs +92 -32
- package/bin/skill-master-install-global-skills.mjs +4 -42
- 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 +91 -115
- package/docs/operations/GUIA_MULTI_COMPUTADOR.md +262 -0
- package/docs/operations/GUIA_NPM_PUBLICO.md +147 -0
- 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.11/frontend-dev-guidelines/SKILL.md +399 -0
- package/docs/skill-candidates/v0.0.11/frontend-dev-guidelines/resources/common-patterns.md +331 -0
- package/docs/skill-candidates/v0.0.11/frontend-dev-guidelines/resources/complete-examples.md +872 -0
- package/docs/skill-candidates/v0.0.11/frontend-dev-guidelines/resources/component-patterns.md +502 -0
- package/docs/skill-candidates/v0.0.11/frontend-dev-guidelines/resources/data-fetching.md +767 -0
- package/docs/skill-candidates/v0.0.11/frontend-dev-guidelines/resources/file-organization.md +502 -0
- package/docs/skill-candidates/v0.0.11/frontend-dev-guidelines/resources/loading-and-error-states.md +501 -0
- package/docs/skill-candidates/v0.0.11/frontend-dev-guidelines/resources/performance.md +406 -0
- package/docs/skill-candidates/v0.0.11/frontend-dev-guidelines/resources/routing-guide.md +364 -0
- package/docs/skill-candidates/v0.0.11/frontend-dev-guidelines/resources/styling-guide.md +428 -0
- package/docs/skill-candidates/v0.0.11/frontend-dev-guidelines/resources/typescript-standards.md +418 -0
- package/docs/skill-candidates/v0.0.11/git-version-control-ops/SKILL.md +34 -0
- package/docs/skill-candidates/v0.0.11/go-engineering/SKILL.md +34 -0
- package/docs/skill-candidates/v0.0.11/java-engineering/SKILL.md +34 -0
- package/docs/skill-candidates/v0.0.11/javascript-engineering/SKILL.md +34 -0
- package/docs/skill-candidates/v0.0.11/json-contract-design/SKILL.md +34 -0
- package/docs/skill-candidates/v0.0.11/multi-client-mcp-ops/SKILL.md +36 -0
- package/docs/skill-candidates/v0.0.11/nextjs/SKILL.md +745 -0
- package/docs/skill-candidates/v0.0.11/nextjs/agents/openai.yaml +3 -0
- package/docs/skill-candidates/v0.0.11/nextjs/references/app-router-files.md +94 -0
- package/docs/skill-candidates/v0.0.11/python-engineering/SKILL.md +34 -0
- package/docs/skill-candidates/v0.0.11/ruby-engineering/SKILL.md +34 -0
- package/docs/skill-candidates/v0.0.11/senior-fullstack/SKILL.md +209 -0
- package/docs/skill-candidates/v0.0.11/senior-fullstack/references/architecture_patterns.md +103 -0
- package/docs/skill-candidates/v0.0.11/senior-fullstack/references/development_workflows.md +103 -0
- package/docs/skill-candidates/v0.0.11/senior-fullstack/references/tech_stack_guide.md +103 -0
- package/docs/skill-candidates/v0.0.11/senior-fullstack/scripts/code_quality_analyzer.py +114 -0
- package/docs/skill-candidates/v0.0.11/senior-fullstack/scripts/fullstack_scaffolder.py +114 -0
- package/docs/skill-candidates/v0.0.11/senior-fullstack/scripts/project_scaffolder.py +114 -0
- package/docs/skill-candidates/v0.0.11/shadcn/SKILL.md +573 -0
- package/docs/skill-candidates/v0.0.11/shadcn/agents/openai.yaml +3 -0
- package/docs/skill-candidates/v0.0.11/sql-postgresql-engineering/SKILL.md +34 -0
- package/docs/skill-candidates/v0.0.11/terminal-shell-ops/SKILL.md +34 -0
- package/docs/skill-candidates/v0.0.11/typescript-expert/SKILL.md +429 -0
- package/docs/skill-candidates/v0.0.11/typescript-expert/references/tsconfig-strict.json +92 -0
- package/docs/skill-candidates/v0.0.11/typescript-expert/references/typescript-cheatsheet.md +383 -0
- package/docs/skill-candidates/v0.0.11/typescript-expert/references/utility-types.ts +335 -0
- package/docs/skill-candidates/v0.0.11/typescript-expert/scripts/ts_diagnostic.py +203 -0
- package/docs/skill-candidates/v0.0.11/ui-component-primitives/SKILL.md +34 -0
- package/docs/skill-candidates/v0.0.11/web-mobile-design-systems/SKILL.md +34 -0
- package/docs/skill-candidates/v0.0.11/windows-linux-platform-ops/SKILL.md +34 -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 +7 -7
- package/manifests/channels/stable.json +8 -8
- package/package.json +14 -2
- package/scripts/render-menu-evidence.mjs +130 -0
- package/scripts/verify-menu-actions.mjs +117 -0
|
@@ -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
|
}
|
|
@@ -1,7 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { spawnSync } from 'node:child_process';
|
|
3
|
+
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
3
4
|
import os from 'node:os';
|
|
4
|
-
import path from 'node:path';
|
|
5
|
+
import path, { dirname } from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
import {
|
|
8
|
+
appendCodexServer,
|
|
9
|
+
buildClaudeCodeAddArgs,
|
|
10
|
+
buildCodexBlock,
|
|
11
|
+
buildJsonMcpSnippet,
|
|
12
|
+
buildSkillMasterServerConfig,
|
|
13
|
+
defaultClientConfigPaths,
|
|
14
|
+
formatShellCommand,
|
|
15
|
+
mergeMcpServer,
|
|
16
|
+
writeJson,
|
|
17
|
+
} from './lib/client-config.mjs';
|
|
5
18
|
|
|
6
19
|
const args = process.argv.slice(2);
|
|
7
20
|
const has = (flag) => args.includes(flag);
|
|
@@ -10,6 +23,9 @@ const readValue = (flag, fallback) => {
|
|
|
10
23
|
return index >= 0 ? args[index + 1] : fallback;
|
|
11
24
|
};
|
|
12
25
|
|
|
26
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
27
|
+
const rootDir = dirname(here);
|
|
28
|
+
|
|
13
29
|
if (has('--help')) {
|
|
14
30
|
console.log(`Skill Master client registration
|
|
15
31
|
|
|
@@ -17,136 +33,87 @@ Uso:
|
|
|
17
33
|
skill-master-register-clients --write-snippets
|
|
18
34
|
skill-master-register-clients --apply-codex
|
|
19
35
|
skill-master-register-clients --apply-claude
|
|
36
|
+
skill-master-register-clients --apply-claude-code
|
|
20
37
|
skill-master-register-clients --apply-gemini
|
|
21
38
|
skill-master-register-clients --apply-antigravity
|
|
22
39
|
skill-master-register-clients --apply-all
|
|
23
40
|
skill-master-register-clients --apply-codex --force
|
|
41
|
+
skill-master-register-clients --apply-claude-code --claude-code-scope user
|
|
42
|
+
|
|
43
|
+
Registra o servidor MCP skill_master como stdio usando Node absoluto e o
|
|
44
|
+
entrypoint absoluto do pacote. Isso evita falhas de PATH em Windows, Linux,
|
|
45
|
+
macOS e apps desktop que nao herdam o terminal.
|
|
24
46
|
|
|
25
|
-
|
|
26
|
-
skill-master-mcp
|
|
47
|
+
Para Claude Code, o registrador usa o CLI oficial: claude mcp add.
|
|
27
48
|
`);
|
|
28
49
|
process.exit(0);
|
|
29
50
|
}
|
|
30
51
|
|
|
31
|
-
const
|
|
32
|
-
const snippetsDir = readValue('--snippets-dir', path.join(
|
|
33
|
-
const codexConfig = readValue('--codex-config',
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
return path.join(home, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
|
|
40
|
-
}
|
|
41
|
-
return path.join(home, '.config', 'Claude', 'claude_desktop_config.json');
|
|
42
|
-
};
|
|
43
|
-
const claudeConfig = readValue('--claude-config', defaultClaudeConfig());
|
|
44
|
-
const geminiConfig = readValue('--gemini-config', path.join(home, '.gemini', 'settings.json'));
|
|
45
|
-
const antigravityConfig = readValue('--antigravity-config', path.join(home, '.gemini', 'config', 'mcp_config.json'));
|
|
52
|
+
const clientPaths = defaultClientConfigPaths();
|
|
53
|
+
const snippetsDir = readValue('--snippets-dir', path.join(process.env.SKILL_MASTER_HOME ?? path.join(os.homedir(), '.skill-master'), 'client-configs'));
|
|
54
|
+
const codexConfig = readValue('--codex-config', clientPaths.codex);
|
|
55
|
+
const claudeConfig = readValue('--claude-config', clientPaths.claude);
|
|
56
|
+
const geminiConfig = readValue('--gemini-config', clientPaths.gemini);
|
|
57
|
+
const antigravityConfig = readValue('--antigravity-config', clientPaths.antigravity);
|
|
58
|
+
const claudeCodeScope = readValue('--claude-code-scope', 'user');
|
|
59
|
+
const claudeCodeCommand = readValue('--claude-code-command', 'claude');
|
|
46
60
|
|
|
47
61
|
const applyAll = has('--apply-all');
|
|
48
62
|
const applyCodex = applyAll || has('--apply-codex');
|
|
49
63
|
const applyClaude = applyAll || has('--apply-claude');
|
|
64
|
+
const applyClaudeCode = applyAll || has('--apply-claude-code');
|
|
50
65
|
const applyGemini = applyAll || has('--apply-gemini');
|
|
51
66
|
const applyAntigravity = applyAll || has('--apply-antigravity');
|
|
52
|
-
const writeSnippets = applyAll || has('--write-snippets') || !(applyCodex || applyClaude || applyGemini || applyAntigravity);
|
|
67
|
+
const writeSnippets = applyAll || has('--write-snippets') || !(applyCodex || applyClaude || applyClaudeCode || applyGemini || applyAntigravity);
|
|
53
68
|
const force = has('--force');
|
|
69
|
+
const serverConfig = buildSkillMasterServerConfig({ rootDir });
|
|
70
|
+
const codexBlock = buildCodexBlock(serverConfig);
|
|
71
|
+
const jsonSnippet = buildJsonMcpSnippet(serverConfig);
|
|
72
|
+
const claudeCodeArgs = buildClaudeCodeAddArgs(serverConfig, { scope: claudeCodeScope });
|
|
73
|
+
|
|
74
|
+
function commandExists(command) {
|
|
75
|
+
const lookup = process.platform === 'win32' ? 'where' : 'command';
|
|
76
|
+
const lookupArgs = process.platform === 'win32' ? [command] : ['-v', command];
|
|
77
|
+
const result = spawnSync(lookup, lookupArgs, { shell: process.platform !== 'win32', stdio: 'ignore' });
|
|
78
|
+
return result.status === 0;
|
|
79
|
+
}
|
|
54
80
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
const codexBlock = `
|
|
64
|
-
[mcp_servers.skill_master]
|
|
65
|
-
command = "skill-master-mcp"
|
|
66
|
-
startup_timeout_sec = 120
|
|
67
|
-
|
|
68
|
-
[mcp_servers.skill_master.env]
|
|
69
|
-
SKILL_MASTER_UPDATE_CHANNEL = "stable"
|
|
70
|
-
`;
|
|
71
|
-
|
|
72
|
-
const claudeSnippet = {
|
|
73
|
-
mcpServers: {
|
|
74
|
-
skill_master: mcpServer,
|
|
75
|
-
},
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
const geminiSnippet = {
|
|
79
|
-
mcpServers: {
|
|
80
|
-
skill_master: mcpServer,
|
|
81
|
-
},
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
const antigravitySnippet = {
|
|
85
|
-
mcpServers: {
|
|
86
|
-
skill_master: mcpServer,
|
|
87
|
-
},
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
const ensureParent = (filePath) => mkdirSync(path.dirname(filePath), { recursive: true });
|
|
91
|
-
|
|
92
|
-
const readJsonOrEmpty = (filePath) => {
|
|
93
|
-
if (!existsSync(filePath)) return {};
|
|
94
|
-
const raw = readFileSync(filePath, 'utf8').trim();
|
|
95
|
-
return raw ? JSON.parse(raw) : {};
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
const writeJson = (filePath, value) => {
|
|
99
|
-
ensureParent(filePath);
|
|
100
|
-
writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
|
|
101
|
-
};
|
|
81
|
+
function runClaudeCodeRegistration() {
|
|
82
|
+
if (!commandExists(claudeCodeCommand)) {
|
|
83
|
+
return {
|
|
84
|
+
ok: false,
|
|
85
|
+
skipped: true,
|
|
86
|
+
message: `Claude Code CLI '${claudeCodeCommand}' not found; run manually: ${formatShellCommand(claudeCodeCommand, claudeCodeArgs)}`,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
102
89
|
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
90
|
+
const result = spawnSync(claudeCodeCommand, claudeCodeArgs, {
|
|
91
|
+
encoding: 'utf8',
|
|
92
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
93
|
+
});
|
|
94
|
+
const output = `${result.stdout ?? ''}${result.stderr ?? ''}`.trim();
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
ok: result.status === 0,
|
|
98
|
+
skipped: false,
|
|
99
|
+
message: result.status === 0
|
|
100
|
+
? `Claude Code registered via ${formatShellCommand(claudeCodeCommand, claudeCodeArgs)}`
|
|
101
|
+
: `Claude Code registration failed (${result.status ?? 'unknown'}): ${output || 'no output'}`,
|
|
111
102
|
};
|
|
112
|
-
|
|
113
|
-
};
|
|
103
|
+
}
|
|
114
104
|
|
|
115
105
|
const writeSnippetsFiles = () => {
|
|
116
106
|
mkdirSync(snippetsDir, { recursive: true });
|
|
117
107
|
writeFileSync(path.join(snippetsDir, 'codex.config.toml'), codexBlock.trimStart(), 'utf8');
|
|
118
|
-
writeJson(path.join(snippetsDir, 'claude_desktop_config.skill_master.json'),
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
for (const line of lines) {
|
|
128
|
-
const trimmed = line.trim();
|
|
129
|
-
if (trimmed === '[mcp_servers.skill_master]') {
|
|
130
|
-
skipping = true;
|
|
131
|
-
continue;
|
|
132
|
-
}
|
|
133
|
-
if (skipping && trimmed.startsWith('[') && !trimmed.startsWith('[mcp_servers.skill_master')) {
|
|
134
|
-
skipping = false;
|
|
135
|
-
}
|
|
136
|
-
if (!skipping) kept.push(line);
|
|
137
|
-
}
|
|
138
|
-
return kept.join('\n').trimEnd();
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
const appendCodex = () => {
|
|
142
|
-
ensureParent(codexConfig);
|
|
143
|
-
const current = existsSync(codexConfig) ? readFileSync(codexConfig, 'utf8') : '';
|
|
144
|
-
if (current.includes('[mcp_servers.skill_master]') && !force) {
|
|
145
|
-
return false;
|
|
146
|
-
}
|
|
147
|
-
const base = force ? removeCodexBlock(current) : current.trimEnd();
|
|
148
|
-
writeFileSync(codexConfig, `${base}\n${codexBlock}`, 'utf8');
|
|
149
|
-
return true;
|
|
108
|
+
writeJson(path.join(snippetsDir, 'claude_desktop_config.skill_master.json'), jsonSnippet);
|
|
109
|
+
writeFileSync(
|
|
110
|
+
path.join(snippetsDir, 'claude-code.commands.md'),
|
|
111
|
+
`# Claude Code - registrar skill_master\n\n\`\`\`bash\n${formatShellCommand(claudeCodeCommand, claudeCodeArgs)}\n\`\`\`\n\nDepois reinicie a sessao do Claude Code e valide com:\n\n\`\`\`bash\nclaude mcp list\n\`\`\`\n`,
|
|
112
|
+
'utf8',
|
|
113
|
+
);
|
|
114
|
+
writeJson(path.join(snippetsDir, 'claude-code.project.mcp.json'), jsonSnippet);
|
|
115
|
+
writeJson(path.join(snippetsDir, 'gemini.settings.skill_master.json'), jsonSnippet);
|
|
116
|
+
writeJson(path.join(snippetsDir, 'antigravity.mcp_config.skill_master.json'), jsonSnippet);
|
|
150
117
|
};
|
|
151
118
|
|
|
152
119
|
const actions = [];
|
|
@@ -155,23 +122,32 @@ if (writeSnippets) {
|
|
|
155
122
|
actions.push(`snippets written to ${snippetsDir}`);
|
|
156
123
|
}
|
|
157
124
|
if (applyCodex) {
|
|
158
|
-
actions.push(
|
|
125
|
+
actions.push(appendCodexServer(codexConfig, serverConfig, { force }) ? `Codex registered at ${codexConfig}` : `Codex already had skill_master at ${codexConfig}`);
|
|
159
126
|
}
|
|
160
127
|
if (applyClaude) {
|
|
161
|
-
mergeMcpServer(claudeConfig);
|
|
162
|
-
actions.push(`Claude config merged at ${claudeConfig}`);
|
|
128
|
+
mergeMcpServer(claudeConfig, serverConfig, { recoverInvalidJson: force });
|
|
129
|
+
actions.push(`Claude Desktop config merged at ${claudeConfig}`);
|
|
130
|
+
}
|
|
131
|
+
if (applyClaudeCode) {
|
|
132
|
+
const result = runClaudeCodeRegistration();
|
|
133
|
+
actions.push(result.message);
|
|
134
|
+
if (!result.ok && !result.skipped) {
|
|
135
|
+
process.exitCode = 1;
|
|
136
|
+
}
|
|
163
137
|
}
|
|
164
138
|
if (applyGemini) {
|
|
165
|
-
mergeMcpServer(geminiConfig);
|
|
139
|
+
mergeMcpServer(geminiConfig, serverConfig, { recoverInvalidJson: force });
|
|
166
140
|
actions.push(`Gemini config merged at ${geminiConfig}`);
|
|
167
141
|
}
|
|
168
142
|
if (applyAntigravity) {
|
|
169
|
-
mergeMcpServer(antigravityConfig);
|
|
143
|
+
mergeMcpServer(antigravityConfig, serverConfig, { recoverInvalidJson: force });
|
|
170
144
|
actions.push(`Antigravity config merged at ${antigravityConfig}`);
|
|
171
145
|
}
|
|
172
146
|
|
|
173
147
|
console.log('[skill_master] Client registration');
|
|
148
|
+
console.log(`- Command: ${serverConfig.command}`);
|
|
149
|
+
console.log(`- Args: ${serverConfig.args.join(' ')}`);
|
|
174
150
|
for (const action of actions) {
|
|
175
151
|
console.log(`- ${action}`);
|
|
176
152
|
}
|
|
177
|
-
console.log('- Restart Codex, Claude, Gemini or Antigravity after registration.');
|
|
153
|
+
console.log('- Restart Codex, Claude Desktop, Claude Code, Gemini or Antigravity after registration.');
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
# Guia Multi-Computador - Skill Master MCP
|
|
2
|
+
|
|
3
|
+
## 1. Cenario
|
|
4
|
+
|
|
5
|
+
Voce quer usar o mesmo MCP `skill_master` em varios computadores, mantendo todos atualizados e escolhendo o canal de distribuicao mais adequado para cada caso.
|
|
6
|
+
|
|
7
|
+
## 2. Modelos recomendados
|
|
8
|
+
|
|
9
|
+
Hoje existem dois caminhos validos.
|
|
10
|
+
|
|
11
|
+
### Modelo A - npm publico
|
|
12
|
+
|
|
13
|
+
Melhor para:
|
|
14
|
+
|
|
15
|
+
- notebooks novos
|
|
16
|
+
- instalacao rapida
|
|
17
|
+
- ambientes onde o usuario nao deve clonar o repositorio
|
|
18
|
+
- Claude, Codex, Gemini e clientes MCP que aceitam `npx`
|
|
19
|
+
|
|
20
|
+
Base:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npx -y @fprad0/skill-master-mcp@latest
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Modelo B - clone + launcher com auto-update
|
|
27
|
+
|
|
28
|
+
Melhor para:
|
|
29
|
+
|
|
30
|
+
- desenvolvimento do MCP
|
|
31
|
+
- testes de `stable` e `beta`
|
|
32
|
+
- controle maior sobre manifests e atualizacao por `git pull --ff-only`
|
|
33
|
+
- ambiente com repo privado
|
|
34
|
+
|
|
35
|
+
Cada computador tera:
|
|
36
|
+
|
|
37
|
+
- clone local do repositorio
|
|
38
|
+
- `git`
|
|
39
|
+
- `node` 18+
|
|
40
|
+
- launcher configurado no cliente MCP
|
|
41
|
+
- configuracao local em `%USERPROFILE%/.skill-master/config.json` no Windows ou `~/.skill-master/config.json` no Linux/macOS
|
|
42
|
+
|
|
43
|
+
Quando o menu detectar que o computador ainda nao esta globalmente pronto, ele vai mostrar o caminho de bootstrap e o atalho unico:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
skill-master-bootstrap-global
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Esse atalho instala as skills globais embutidas e registra Codex, Claude, Gemini e Antigravity para reconhecer `skill_master` como parte do sistema no cliente local.
|
|
50
|
+
|
|
51
|
+
A partir da correcao multi-OS, o registro recomendado nao depende mais de `PATH`. O registrador grava o Node.js absoluto como `command` e o `bin/skill-master.mjs` absoluto como `args`, o que evita falhas comuns em Windows, Linux, macOS e apps desktop que nao herdam o ambiente do terminal.
|
|
52
|
+
|
|
53
|
+
A partir da preparacao `0.0.11`, use o doctor para validar notebooks novos:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
skill-master-menu --run doctor
|
|
57
|
+
skill-master-menu --run bootstrap-global --yes
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
O bootstrap registra Codex, Claude Desktop, Claude Code, Gemini CLI e Antigravity quando os clientes locais estiverem disponiveis. Depois, reinicie os clientes.
|
|
61
|
+
|
|
62
|
+
## 3. Instalacao em um novo computador
|
|
63
|
+
|
|
64
|
+
### 3.1 Opcao rapida - npm publico
|
|
65
|
+
|
|
66
|
+
#### Windows
|
|
67
|
+
|
|
68
|
+
```powershell
|
|
69
|
+
npm install -g @fprad0/skill-master-mcp
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
ou:
|
|
73
|
+
|
|
74
|
+
```powershell
|
|
75
|
+
npx -y @fprad0/skill-master-mcp@latest
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
#### Linux / macOS
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
npm install -g @fprad0/skill-master-mcp
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
ou:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
npx -y @fprad0/skill-master-mcp@latest
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### 3.2 Opcao controlada - clone local
|
|
91
|
+
|
|
92
|
+
Pre-requisitos:
|
|
93
|
+
|
|
94
|
+
- Git instalado
|
|
95
|
+
- Node.js 18+
|
|
96
|
+
- acesso ao GitHub
|
|
97
|
+
- token read-only se o repositorio for privado
|
|
98
|
+
|
|
99
|
+
Clone:
|
|
100
|
+
|
|
101
|
+
```powershell
|
|
102
|
+
cd C:\Users\CDT\Documents
|
|
103
|
+
git clone https://github.com/FPrad0/skill-master-mcp.git
|
|
104
|
+
cd skill-master-mcp
|
|
105
|
+
npm ci
|
|
106
|
+
npm run build
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### 3.3 Configuracao local
|
|
110
|
+
|
|
111
|
+
Criar:
|
|
112
|
+
|
|
113
|
+
```text
|
|
114
|
+
C:\Users\<USER>\.skill-master\config.json
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Modelo:
|
|
118
|
+
|
|
119
|
+
```json
|
|
120
|
+
{
|
|
121
|
+
"channel": "stable",
|
|
122
|
+
"autoUpdate": true,
|
|
123
|
+
"updateMode": "on-start",
|
|
124
|
+
"manifestUrl": "https://raw.githubusercontent.com/FPrad0/skill-master-mcp/main/manifests/channels/stable.json",
|
|
125
|
+
"localSkillRoots": [
|
|
126
|
+
"%USERPROFILE%/.codex/skills",
|
|
127
|
+
"%USERPROFILE%/.agents/skills"
|
|
128
|
+
],
|
|
129
|
+
"allowWebSources": false
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## 4. Configuracao do cliente MCP
|
|
134
|
+
|
|
135
|
+
No modelo npm global, prefira sempre o registro gerado por `skill-master-register-clients`, porque ele usa caminhos absolutos.
|
|
136
|
+
|
|
137
|
+
No modelo clone + launcher, o launcher ainda e valido para desenvolvimento, mas o doctor vai classificar esse modo como `launcher`, nao como o modo robusto de pacote global.
|
|
138
|
+
|
|
139
|
+
Exemplo robusto para Codex:
|
|
140
|
+
|
|
141
|
+
```toml
|
|
142
|
+
[mcp_servers.skill_master]
|
|
143
|
+
command = "C:\\Program Files\\nodejs\\node.exe"
|
|
144
|
+
args = ["C:\\Users\\CDT\\AppData\\Roaming\\npm\\node_modules\\@fprad0\\skill-master-mcp\\bin\\skill-master.mjs"]
|
|
145
|
+
startup_timeout_sec = 120
|
|
146
|
+
|
|
147
|
+
[mcp_servers.skill_master.env]
|
|
148
|
+
SKILL_MASTER_UPDATE_CHANNEL = "stable"
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Para migrar clientes antigos que ainda usam `skill-master-mcp` via PATH, `npx` ou launcher local:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
skill-master-register-clients --apply-all --force
|
|
155
|
+
skill-master-menu --run doctor
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Para corrigir apenas o Claude Code depois de `npm install -g @fprad0/skill-master-mcp`, use:
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
skill-master-register-clients --apply-claude-code --claude-code-scope user
|
|
162
|
+
claude mcp list
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
No modelo clone + launcher, o cliente deve chamar o launcher, nao o `dist/index.js` diretamente.
|
|
166
|
+
|
|
167
|
+
Exemplo conceitual:
|
|
168
|
+
|
|
169
|
+
```json
|
|
170
|
+
{
|
|
171
|
+
"mcpServers": {
|
|
172
|
+
"skill_master": {
|
|
173
|
+
"command": "powershell",
|
|
174
|
+
"args": [
|
|
175
|
+
"-ExecutionPolicy",
|
|
176
|
+
"Bypass",
|
|
177
|
+
"-File",
|
|
178
|
+
"C:/Users/CDT/Documents/skill-master-mcp/scripts/skill-master-launcher.ps1"
|
|
179
|
+
]
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## 5. Como as atualizacoes chegam automaticamente
|
|
186
|
+
|
|
187
|
+
### No modelo npm publico
|
|
188
|
+
|
|
189
|
+
1. voce publica uma nova versao no npm publico
|
|
190
|
+
2. a maquina cliente usa `@latest`
|
|
191
|
+
3. no proximo `npx` ou reinstalacao, a nova versao e resolvida
|
|
192
|
+
|
|
193
|
+
### No modelo clone + launcher
|
|
194
|
+
|
|
195
|
+
1. voce faz push no GitHub
|
|
196
|
+
2. voce atualiza o manifesto `stable.json`
|
|
197
|
+
3. o usuario abre o MCP
|
|
198
|
+
4. o launcher verifica o manifesto
|
|
199
|
+
5. se houver versao nova e o clone local estiver limpo, o launcher atualiza
|
|
200
|
+
6. o MCP inicia ja atualizado
|
|
201
|
+
|
|
202
|
+
## 6. Como publicar uma atualizacao
|
|
203
|
+
|
|
204
|
+
### Caminho publico
|
|
205
|
+
|
|
206
|
+
1. subir a nova versao para `main`
|
|
207
|
+
2. publicar pelo workflow `Publish Skill Master to npmjs` somente com autorizacao explicita
|
|
208
|
+
3. validar com `npm view` e `npx`
|
|
209
|
+
|
|
210
|
+
### Caminho clone + launcher
|
|
211
|
+
|
|
212
|
+
1. subir a nova versao para `main`
|
|
213
|
+
2. atualizar `manifests/channels/stable.json`
|
|
214
|
+
3. criar release/tag quando fizer sentido
|
|
215
|
+
4. deixar o launcher puxar com `git pull --ff-only`
|
|
216
|
+
|
|
217
|
+
## 7. Estrategia para varios usuarios
|
|
218
|
+
|
|
219
|
+
### Poucos usuarios tecnicos
|
|
220
|
+
|
|
221
|
+
Use clone privado + launcher. E a rota com mais controle.
|
|
222
|
+
|
|
223
|
+
### Varios usuarios sem perfil tecnico
|
|
224
|
+
|
|
225
|
+
Use npm publico.
|
|
226
|
+
|
|
227
|
+
Se for necessario restringir distribuicao, evolua para:
|
|
228
|
+
|
|
229
|
+
- pacote npm privado
|
|
230
|
+
- instalador/bundle
|
|
231
|
+
- MCP remoto
|
|
232
|
+
|
|
233
|
+
### Equipe ou empresa
|
|
234
|
+
|
|
235
|
+
Evoluir para:
|
|
236
|
+
|
|
237
|
+
- GitHub Packages com permissoes
|
|
238
|
+
- workflow de release
|
|
239
|
+
- canais `stable` e `beta`
|
|
240
|
+
- logs e telemetria minima opcional
|
|
241
|
+
|
|
242
|
+
## 8. Manutencao
|
|
243
|
+
|
|
244
|
+
Recomendacoes:
|
|
245
|
+
|
|
246
|
+
- manter changelog curto
|
|
247
|
+
- nunca quebrar configuracao sem migracao
|
|
248
|
+
- testar update em uma maquina secundaria antes de mover `stable`
|
|
249
|
+
- manter `beta` para validacao
|
|
250
|
+
- documentar breaking changes
|
|
251
|
+
|
|
252
|
+
## 9. Quando nao atualizar automaticamente
|
|
253
|
+
|
|
254
|
+
No modelo clone + launcher, o updater deve recusar atualizacao automatica quando:
|
|
255
|
+
|
|
256
|
+
- houver arquivos locais modificados
|
|
257
|
+
- o GitHub estiver inacessivel
|
|
258
|
+
- o manifesto estiver invalido
|
|
259
|
+
- houver mudanca de versao de Node nao atendida
|
|
260
|
+
- `npm ci` ou build falhar
|
|
261
|
+
|
|
262
|
+
Nesses casos, ele deve iniciar a versao local e mostrar o status pela ferramenta `skill_master_sync_status`.
|