@fprad0/skill-master-mcp 0.0.4 → 0.0.6
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 -0
- package/README.md +55 -3
- package/VERSION.md +2 -2
- package/bin/skill-master-menu.mjs +10 -1
- package/bin/skill-master-update.mjs +72 -0
- package/docs/operations/GUIA_NPM_PRIVADO.md +47 -3
- package/manifests/channels/beta.json +5 -5
- package/manifests/channels/stable.json +6 -6
- package/package.json +3 -2
- package/scripts/configure-private-registry.mjs +91 -7
- package/scripts/lib/private-registry.mjs +50 -9
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,18 @@ All notable changes to `skill_master` will be tracked here.
|
|
|
7
7
|
- Prepare a public npm publication path with dedicated workflow and operator guide.
|
|
8
8
|
- Upgrade release automation from Node `20` to Node `22`.
|
|
9
9
|
|
|
10
|
+
## [0.0.6] - 2026-06-26
|
|
11
|
+
|
|
12
|
+
- Harden `skill-master-configure-private-registry` with HTTPS registry validation, scoped token handling, redacted dry-runs, timestamped backups, and POSIX `0600` permissions.
|
|
13
|
+
- Store `${GITHUB_PACKAGES_TOKEN}` in `.npmrc` by default instead of persisting a literal token.
|
|
14
|
+
- Add `--token-file`, `--token-stdin`, and explicit `--store-token` support for safer operator workflows.
|
|
15
|
+
|
|
16
|
+
## [0.0.5] - 2026-06-26
|
|
17
|
+
|
|
18
|
+
- Add `skill-master-update` to update the global npm installation safely outside the MCP stdio process.
|
|
19
|
+
- Add a `skill-master-menu` option to run the global npm update flow.
|
|
20
|
+
- Document npm-based auto-update usage for operators and scheduled tasks.
|
|
21
|
+
|
|
10
22
|
## [0.0.4] - 2026-06-26
|
|
11
23
|
|
|
12
24
|
- Add `skill_master_activation_check` to decide when the MCP should activate for a prompt.
|
package/README.md
CHANGED
|
@@ -81,6 +81,8 @@ Bootstrap direto pelo pacote, para maquinas que ja conseguem resolver o escopo `
|
|
|
81
81
|
- PowerShell: `$env:GITHUB_PACKAGES_TOKEN='SEU_TOKEN'; npx -y -p @fprad0/skill-master-mcp skill-master-configure-private-registry --validate`
|
|
82
82
|
- Bash: `GITHUB_PACKAGES_TOKEN='SEU_TOKEN' npx -y -p @fprad0/skill-master-mcp skill-master-configure-private-registry --validate`
|
|
83
83
|
|
|
84
|
+
Por padrao, o configurador grava o `.npmrc` usando `${GITHUB_PACKAGES_TOKEN}` em vez de persistir o token literal. Para ambientes que exigem token gravado no arquivo, use `--store-token` de forma consciente.
|
|
85
|
+
|
|
84
86
|
Exemplo de `.npmrc`:
|
|
85
87
|
|
|
86
88
|
```ini
|
|
@@ -108,16 +110,17 @@ npx -y @fprad0/skill-master-mcp@latest
|
|
|
108
110
|
|
|
109
111
|
Estado atual validado em `2026-06-26`:
|
|
110
112
|
|
|
111
|
-
- pacote publico publicado: `@fprad0/skill-master-mcp@0.0.
|
|
112
|
-
- dist-tag `latest` apontando para `0.0.
|
|
113
|
+
- pacote publico publicado: `@fprad0/skill-master-mcp@0.0.5`
|
|
114
|
+
- dist-tag `latest` apontando para `0.0.5`
|
|
113
115
|
- workflow `Publish Skill Master to npmjs` concluido com sucesso
|
|
114
116
|
- `npm install` e `npx` confirmados
|
|
115
117
|
|
|
116
|
-
|
|
118
|
+
Comandos globais principais:
|
|
117
119
|
|
|
118
120
|
```bash
|
|
119
121
|
skill-master-mcp
|
|
120
122
|
skill-master-menu
|
|
123
|
+
skill-master-update
|
|
121
124
|
skill-master-configure-private-registry
|
|
122
125
|
```
|
|
123
126
|
|
|
@@ -127,6 +130,20 @@ O `skill-master-mcp` deve continuar reservado para clientes MCP via `stdio`. Par
|
|
|
127
130
|
skill-master-menu
|
|
128
131
|
```
|
|
129
132
|
|
|
133
|
+
Para atualizar a instalacao global via npm sem iniciar o servidor MCP:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
skill-master-update
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Esse comando executa o equivalente a:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
npm install -g @fprad0/skill-master-mcp@latest --registry=https://registry.npmjs.org
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Use `skill-master-update` antes de abrir ou depois de fechar o cliente MCP. Evite atualizar o pacote enquanto `skill-master-mcp` esta rodando em `stdio`, porque o cliente pode manter o processo antigo carregado ate ser reiniciado.
|
|
146
|
+
|
|
130
147
|
Leia:
|
|
131
148
|
|
|
132
149
|
- [Guia de publicacao no npm publico](docs/operations/GUIA_NPM_PUBLICO.md)
|
|
@@ -214,6 +231,41 @@ Cliente: chama skill_master_activation_check.
|
|
|
214
231
|
Cliente: se ativar, chama skill_master_recommend ou segue as recomendacoes retornadas.
|
|
215
232
|
```
|
|
216
233
|
|
|
234
|
+
## Atualizacao automatica via npm
|
|
235
|
+
|
|
236
|
+
Para instalacoes feitas por `npm install -g`, o caminho seguro de auto-update e executar `skill-master-update` fora do processo MCP.
|
|
237
|
+
|
|
238
|
+
Uso manual:
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
skill-master-update
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
Uso em agendamento Linux/macOS:
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
skill-master-update
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Uso em agendamento Windows:
|
|
251
|
+
|
|
252
|
+
```powershell
|
|
253
|
+
skill-master-update
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Validacao sem aplicar update:
|
|
257
|
+
|
|
258
|
+
```bash
|
|
259
|
+
skill-master-update --dry-run
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Regra operacional:
|
|
263
|
+
|
|
264
|
+
- nao atualizar dentro do processo `skill-master-mcp` em `stdio`
|
|
265
|
+
- atualizar antes de iniciar o cliente MCP ou em janela agendada
|
|
266
|
+
- reiniciar Codex, Claude, Gemini ou outro cliente MCP depois da atualizacao
|
|
267
|
+
- para desenvolvimento com clone, continuar usando o launcher por manifesto e `git pull --ff-only`
|
|
268
|
+
|
|
217
269
|
## Arquivos principais
|
|
218
270
|
|
|
219
271
|
- [Plano geral](docs/PLANO_SKILL_MASTER_00_01.md)
|
package/VERSION.md
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Versão funcional planejada: `00.02`
|
|
4
4
|
|
|
5
|
-
Versão técnica para empacotamento semântico: `0.0.
|
|
5
|
+
Versão técnica para empacotamento semântico: `0.0.6`
|
|
6
6
|
|
|
7
7
|
## Observação
|
|
8
8
|
|
|
9
|
-
O nome `00.02` será usado na comunicação e nos documentos. Para ferramentas que exigem SemVer, como npm e alguns fluxos de release, usar `0.0.
|
|
9
|
+
O nome `00.02` será usado na comunicação e nos documentos. Para ferramentas que exigem SemVer, como npm e alguns fluxos de release, usar `0.0.6`.
|
|
@@ -35,6 +35,11 @@ const commands = {
|
|
|
35
35
|
'--registry=https://registry.npmjs.org',
|
|
36
36
|
],
|
|
37
37
|
},
|
|
38
|
+
updateGlobal: {
|
|
39
|
+
label: 'Atualizar pacote global via npm',
|
|
40
|
+
command: process.execPath,
|
|
41
|
+
args: [join(rootDir, 'bin', 'skill-master-update.mjs')],
|
|
42
|
+
},
|
|
38
43
|
privateRegistry: {
|
|
39
44
|
label: 'Configurar registry privado GitHub Packages',
|
|
40
45
|
command: process.execPath,
|
|
@@ -47,7 +52,8 @@ const menuItems = [
|
|
|
47
52
|
['2', commands.check],
|
|
48
53
|
['3', commands.build],
|
|
49
54
|
['4', commands.publicNpm],
|
|
50
|
-
['5', commands.
|
|
55
|
+
['5', commands.updateGlobal],
|
|
56
|
+
['6', commands.privateRegistry],
|
|
51
57
|
];
|
|
52
58
|
|
|
53
59
|
function readJson(relativePath) {
|
|
@@ -76,6 +82,9 @@ Uso:
|
|
|
76
82
|
skill-master-menu --status
|
|
77
83
|
skill-master-menu --help
|
|
78
84
|
|
|
85
|
+
Opcao 5 do menu:
|
|
86
|
+
atualiza o pacote global com npm install -g @fprad0/skill-master-mcp@latest
|
|
87
|
+
|
|
79
88
|
O comando de menu e voltado para operacao humana. O binario MCP stdio continua sendo:
|
|
80
89
|
skill-master-mcp
|
|
81
90
|
`);
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { spawn } from 'node:child_process';
|
|
4
|
+
|
|
5
|
+
const DEFAULT_PACKAGE = '@fprad0/skill-master-mcp@latest';
|
|
6
|
+
const DEFAULT_REGISTRY = 'https://registry.npmjs.org';
|
|
7
|
+
|
|
8
|
+
function printHelp() {
|
|
9
|
+
console.log(`Skill Master Update
|
|
10
|
+
|
|
11
|
+
Uso:
|
|
12
|
+
skill-master-update
|
|
13
|
+
skill-master-update --dry-run
|
|
14
|
+
skill-master-update --package @fprad0/skill-master-mcp@latest
|
|
15
|
+
skill-master-update --registry https://registry.npmjs.org
|
|
16
|
+
|
|
17
|
+
Este comando atualiza a instalacao global via npm. Ele deve ser executado fora
|
|
18
|
+
do processo MCP stdio para evitar substituir arquivos enquanto o servidor esta
|
|
19
|
+
em uso por um cliente.
|
|
20
|
+
`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function readOption(args, name, fallback) {
|
|
24
|
+
const index = args.indexOf(name);
|
|
25
|
+
if (index === -1) {
|
|
26
|
+
return fallback;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return args[index + 1] || fallback;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function buildInstallArgs(args) {
|
|
33
|
+
const packageName = readOption(args, '--package', DEFAULT_PACKAGE);
|
|
34
|
+
const registry = readOption(args, '--registry', DEFAULT_REGISTRY);
|
|
35
|
+
return ['install', '-g', packageName, `--registry=${registry}`];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function run(command, args) {
|
|
39
|
+
return new Promise((resolve) => {
|
|
40
|
+
console.log(`> ${command} ${args.join(' ')}`);
|
|
41
|
+
const child = spawn(command, args, {
|
|
42
|
+
env: process.env,
|
|
43
|
+
shell: process.platform === 'win32',
|
|
44
|
+
stdio: 'inherit',
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
child.on('close', (code) => resolve(code ?? 1));
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function main() {
|
|
52
|
+
const args = process.argv.slice(2);
|
|
53
|
+
|
|
54
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
55
|
+
printHelp();
|
|
56
|
+
return 0;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const installArgs = buildInstallArgs(args);
|
|
60
|
+
|
|
61
|
+
if (args.includes('--dry-run')) {
|
|
62
|
+
console.log(['npm', ...installArgs].join(' '));
|
|
63
|
+
return 0;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
console.log('Atualizando Skill Master MCP por npm global.');
|
|
67
|
+
console.log('Feche/reinicie clientes MCP depois da atualizacao para carregar a nova versao.\n');
|
|
68
|
+
|
|
69
|
+
return run('npm', installArgs);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
process.exitCode = await main();
|
|
@@ -11,6 +11,23 @@ O fluxo usa:
|
|
|
11
11
|
- execucao via `npx @fprad0/skill-master-mcp`;
|
|
12
12
|
- configuracao MCP por `stdio`.
|
|
13
13
|
|
|
14
|
+
## Modelo de seguranca do `.npmrc`
|
|
15
|
+
|
|
16
|
+
O configurador segue tres regras:
|
|
17
|
+
|
|
18
|
+
- o token fica escopado ao host do registry, por exemplo `//npm.pkg.github.com/:_authToken=...`;
|
|
19
|
+
- o registry precisa usar HTTPS por padrao;
|
|
20
|
+
- o arquivo `.npmrc` usa referencia de variavel de ambiente por padrao, evitando gravar o token literal.
|
|
21
|
+
|
|
22
|
+
Conteudo recomendado:
|
|
23
|
+
|
|
24
|
+
```ini
|
|
25
|
+
@fprad0:registry=https://npm.pkg.github.com
|
|
26
|
+
//npm.pkg.github.com/:_authToken=${GITHUB_PACKAGES_TOKEN}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Se voce precisar persistir o token literal dentro do `.npmrc`, use `--store-token` de forma consciente. Esse modo e menos seguro, mas pode ser necessario em maquinas sem suporte simples a variaveis de ambiente persistentes.
|
|
30
|
+
|
|
14
31
|
## Pre-requisitos
|
|
15
32
|
|
|
16
33
|
- Node.js e npm instalados;
|
|
@@ -65,7 +82,8 @@ O script:
|
|
|
65
82
|
|
|
66
83
|
- pede o token de forma segura se ele no estiver em variavel de ambiente;
|
|
67
84
|
- atualiza apenas as linhas necessrias do `%USERPROFILE%\.npmrc`;
|
|
68
|
-
- cria backup
|
|
85
|
+
- cria backup com timestamp se o arquivo ja existir;
|
|
86
|
+
- grava o `.npmrc` apontando para `${GITHUB_PACKAGES_TOKEN}` por padrao;
|
|
69
87
|
- valida acesso ao pacote com `npm view`;
|
|
70
88
|
- mostra o comando final de `npx`.
|
|
71
89
|
|
|
@@ -75,11 +93,17 @@ Crie ou atualize o arquivo:
|
|
|
75
93
|
%USERPROFILE%\.npmrc
|
|
76
94
|
```
|
|
77
95
|
|
|
78
|
-
Com o conteudo:
|
|
96
|
+
Com o conteudo recomendado:
|
|
79
97
|
|
|
80
98
|
```ini
|
|
81
99
|
@fprad0:registry=https://npm.pkg.github.com
|
|
82
|
-
//npm.pkg.github.com/:_authToken
|
|
100
|
+
//npm.pkg.github.com/:_authToken=${GITHUB_PACKAGES_TOKEN}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Se preferir gravar o token literal no `.npmrc`:
|
|
104
|
+
|
|
105
|
+
```powershell
|
|
106
|
+
powershell -ExecutionPolicy Bypass -File .\scripts\configure-private-registry.ps1 -Validate -StoreToken
|
|
83
107
|
```
|
|
84
108
|
|
|
85
109
|
### Jeito rapido sem checkout local
|
|
@@ -91,6 +115,12 @@ $env:GITHUB_PACKAGES_TOKEN='SEU_TOKEN'
|
|
|
91
115
|
npx -y -p @fprad0/skill-master-mcp skill-master-configure-private-registry --validate
|
|
92
116
|
```
|
|
93
117
|
|
|
118
|
+
Para ler o token de arquivo, evitando argumento em historico de shell:
|
|
119
|
+
|
|
120
|
+
```powershell
|
|
121
|
+
npx -y -p @fprad0/skill-master-mcp skill-master-configure-private-registry --token-file .\github-packages-token.txt --validate
|
|
122
|
+
```
|
|
123
|
+
|
|
94
124
|
## Validar acesso ao pacote
|
|
95
125
|
|
|
96
126
|
```bat
|
|
@@ -112,6 +142,8 @@ O script:
|
|
|
112
142
|
- pede o token sem eco no terminal quando necessario;
|
|
113
143
|
- atualiza apenas as linhas do escopo `@fprad0`;
|
|
114
144
|
- cria ou reaproveita `${HOME}/.npmrc`;
|
|
145
|
+
- aplica permissao `0600` no `${HOME}/.npmrc`;
|
|
146
|
+
- grava o `.npmrc` apontando para `${GITHUB_PACKAGES_TOKEN}` por padrao;
|
|
115
147
|
- valida acesso ao pacote;
|
|
116
148
|
- mostra o comando final de `npx`.
|
|
117
149
|
|
|
@@ -123,6 +155,18 @@ Use esta opcao quando a maquina ja consegue resolver o escopo `@fprad0` no GitHu
|
|
|
123
155
|
GITHUB_PACKAGES_TOKEN='SEU_TOKEN' npx -y -p @fprad0/skill-master-mcp skill-master-configure-private-registry --validate
|
|
124
156
|
```
|
|
125
157
|
|
|
158
|
+
Para ler o token via stdin:
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
printf '%s' "$GITHUB_PACKAGES_TOKEN" | npx -y -p @fprad0/skill-master-mcp skill-master-configure-private-registry --token-stdin --validate
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Para gravar token literal no `.npmrc` quando necessario:
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
bash ./scripts/configure-private-registry.sh --validate --store-token
|
|
168
|
+
```
|
|
169
|
+
|
|
126
170
|
## Publicacao pelo workflow
|
|
127
171
|
|
|
128
172
|
O workflow de release usa:
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
"name": "skill_master",
|
|
3
3
|
"channel": "beta",
|
|
4
4
|
"version": "00.02-beta",
|
|
5
|
-
"semver": "0.0.
|
|
6
|
-
"publishedAt": "2026-06-
|
|
5
|
+
"semver": "0.0.6",
|
|
6
|
+
"publishedAt": "2026-06-26T16:45:00-03:00",
|
|
7
7
|
"git": {
|
|
8
8
|
"repo": "https://github.com/FPrad0/skill-master-mcp",
|
|
9
9
|
"branch": "main",
|
|
10
|
-
"commit": "
|
|
10
|
+
"commit": "c203ead"
|
|
11
11
|
},
|
|
12
12
|
"node": {
|
|
13
13
|
"minimum": "18.0.0"
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"buildCommand": "npm run build"
|
|
20
20
|
},
|
|
21
21
|
"changelog": [
|
|
22
|
-
"Canal beta acompanha
|
|
23
|
-
"
|
|
22
|
+
"Canal beta acompanha o configurador privado mais seguro.",
|
|
23
|
+
"Fluxos de token passam a suportar arquivo, stdin e armazenamento literal somente com opt-in."
|
|
24
24
|
]
|
|
25
25
|
}
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
"name": "skill_master",
|
|
3
3
|
"channel": "stable",
|
|
4
4
|
"version": "00.02",
|
|
5
|
-
"semver": "0.0.
|
|
6
|
-
"publishedAt": "2026-06-
|
|
5
|
+
"semver": "0.0.6",
|
|
6
|
+
"publishedAt": "2026-06-26T16:45:00-03:00",
|
|
7
7
|
"git": {
|
|
8
8
|
"repo": "https://github.com/FPrad0/skill-master-mcp",
|
|
9
9
|
"branch": "main",
|
|
10
|
-
"commit": "
|
|
10
|
+
"commit": "c203ead"
|
|
11
11
|
},
|
|
12
12
|
"node": {
|
|
13
13
|
"minimum": "18.0.0"
|
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
"buildCommand": "npm run build"
|
|
20
20
|
},
|
|
21
21
|
"changelog": [
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
22
|
+
"Configurador privado passa a gravar referencia de variavel de ambiente por padrao.",
|
|
23
|
+
"Validacao de registry HTTPS, backups com timestamp, dry-run redigido e permissao 0600 em POSIX.",
|
|
24
|
+
"Novas entradas --token-file, --token-stdin e --store-token."
|
|
25
25
|
]
|
|
26
26
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fprad0/skill-master-mcp",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Personal MCP server that catalogs, recommends and reports skills with update-aware metadata.",
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
"bin": {
|
|
9
9
|
"skill-master-configure-private-registry": "./bin/skill-master-configure-private-registry.mjs",
|
|
10
10
|
"skill-master-menu": "./bin/skill-master-menu.mjs",
|
|
11
|
-
"skill-master-mcp": "./bin/skill-master.mjs"
|
|
11
|
+
"skill-master-mcp": "./bin/skill-master.mjs",
|
|
12
|
+
"skill-master-update": "./bin/skill-master-update.mjs"
|
|
12
13
|
},
|
|
13
14
|
"files": [
|
|
14
15
|
"bin",
|
|
@@ -1,14 +1,39 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process';
|
|
2
|
-
import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { chmodSync, copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { dirname, resolve } from 'node:path';
|
|
4
4
|
import {
|
|
5
5
|
DEFAULT_PACKAGE_NAME,
|
|
6
6
|
DEFAULT_REGISTRY_URL,
|
|
7
7
|
DEFAULT_SCOPE,
|
|
8
8
|
DEFAULT_TOKEN_ENV,
|
|
9
|
+
redactSensitiveNpmrc,
|
|
9
10
|
upsertNpmrc,
|
|
10
11
|
} from './lib/private-registry.mjs';
|
|
11
12
|
|
|
13
|
+
function printHelp() {
|
|
14
|
+
process.stdout.write(`Skill Master private registry configurator
|
|
15
|
+
|
|
16
|
+
Usage:
|
|
17
|
+
skill-master-configure-private-registry [options]
|
|
18
|
+
|
|
19
|
+
Options:
|
|
20
|
+
--npmrc <path> Target .npmrc path. Defaults to the user .npmrc.
|
|
21
|
+
--scope <scope> npm scope. Defaults to @fprad0.
|
|
22
|
+
--registry <url> Registry URL. Defaults to https://npm.pkg.github.com.
|
|
23
|
+
--token <token> Token for validation. Avoid in shared shell history.
|
|
24
|
+
--token-file <path> Read token from a local file.
|
|
25
|
+
--token-stdin Read token from stdin.
|
|
26
|
+
--token-env <name> Environment variable name. Defaults to GITHUB_PACKAGES_TOKEN.
|
|
27
|
+
--store-token Persist the token literal in .npmrc. Default stores \${TOKEN_ENV}.
|
|
28
|
+
--allow-insecure-registry Allow http only for localhost/private test registries.
|
|
29
|
+
--no-backup Do not create a timestamped backup.
|
|
30
|
+
--dry-run Print a redacted preview without writing.
|
|
31
|
+
--validate Run npm view after writing.
|
|
32
|
+
--package <name> Package used for validation.
|
|
33
|
+
--help Show this help.
|
|
34
|
+
`);
|
|
35
|
+
}
|
|
36
|
+
|
|
12
37
|
function parseArgs(argv) {
|
|
13
38
|
const parsed = {
|
|
14
39
|
npmrc: resolve(process.env.USERPROFILE ?? process.env.HOME ?? '.', '.npmrc'),
|
|
@@ -19,6 +44,9 @@ function parseArgs(argv) {
|
|
|
19
44
|
dryRun: false,
|
|
20
45
|
validate: false,
|
|
21
46
|
packageName: DEFAULT_PACKAGE_NAME,
|
|
47
|
+
storeToken: false,
|
|
48
|
+
allowInsecureRegistry: false,
|
|
49
|
+
help: false,
|
|
22
50
|
};
|
|
23
51
|
|
|
24
52
|
for (let i = 0; i < argv.length; i += 1) {
|
|
@@ -36,9 +64,21 @@ function parseArgs(argv) {
|
|
|
36
64
|
case '--token':
|
|
37
65
|
parsed.token = argv[++i];
|
|
38
66
|
break;
|
|
67
|
+
case '--token-file':
|
|
68
|
+
parsed.tokenFile = resolve(argv[++i]);
|
|
69
|
+
break;
|
|
70
|
+
case '--token-stdin':
|
|
71
|
+
parsed.tokenStdin = true;
|
|
72
|
+
break;
|
|
39
73
|
case '--token-env':
|
|
40
74
|
parsed.tokenEnv = argv[++i];
|
|
41
75
|
break;
|
|
76
|
+
case '--store-token':
|
|
77
|
+
parsed.storeToken = true;
|
|
78
|
+
break;
|
|
79
|
+
case '--allow-insecure-registry':
|
|
80
|
+
parsed.allowInsecureRegistry = true;
|
|
81
|
+
break;
|
|
42
82
|
case '--no-backup':
|
|
43
83
|
parsed.backup = false;
|
|
44
84
|
break;
|
|
@@ -51,6 +91,10 @@ function parseArgs(argv) {
|
|
|
51
91
|
case '--package':
|
|
52
92
|
parsed.packageName = argv[++i];
|
|
53
93
|
break;
|
|
94
|
+
case '--help':
|
|
95
|
+
case '-h':
|
|
96
|
+
parsed.help = true;
|
|
97
|
+
break;
|
|
54
98
|
default:
|
|
55
99
|
throw new Error(`Unknown argument: ${arg}`);
|
|
56
100
|
}
|
|
@@ -59,12 +103,39 @@ function parseArgs(argv) {
|
|
|
59
103
|
return parsed;
|
|
60
104
|
}
|
|
61
105
|
|
|
106
|
+
function readToken(options) {
|
|
107
|
+
if (options.token) {
|
|
108
|
+
return options.token;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (options.tokenFile) {
|
|
112
|
+
return readFileSync(options.tokenFile, 'utf8').trim();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (options.tokenStdin) {
|
|
116
|
+
return readFileSync(0, 'utf8').trim();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return process.env[options.tokenEnv];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function buildBackupPath(npmrcPath) {
|
|
123
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
124
|
+
return `${npmrcPath}.bak.${timestamp}`;
|
|
125
|
+
}
|
|
126
|
+
|
|
62
127
|
function main() {
|
|
63
128
|
const options = parseArgs(process.argv.slice(2));
|
|
64
|
-
const token = options.token ?? process.env[options.tokenEnv];
|
|
65
129
|
|
|
66
|
-
if (
|
|
67
|
-
|
|
130
|
+
if (options.help) {
|
|
131
|
+
printHelp();
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const token = readToken(options);
|
|
136
|
+
const needsToken = options.storeToken || options.validate;
|
|
137
|
+
if (needsToken && !token) {
|
|
138
|
+
throw new Error(`Token not provided. Use --token-file, --token-stdin, --token, or define ${options.tokenEnv}.`);
|
|
68
139
|
}
|
|
69
140
|
|
|
70
141
|
const currentContent = existsSync(options.npmrc)
|
|
@@ -75,16 +146,24 @@ function main() {
|
|
|
75
146
|
scope: options.scope,
|
|
76
147
|
registryUrl: options.registry,
|
|
77
148
|
tokenValue: token,
|
|
149
|
+
tokenEnv: options.tokenEnv,
|
|
150
|
+
storeToken: options.storeToken,
|
|
151
|
+
allowInsecureRegistry: options.allowInsecureRegistry,
|
|
78
152
|
});
|
|
79
153
|
|
|
80
154
|
const changed = nextContent !== currentContent.replace(/\r\n/g, '\n');
|
|
81
155
|
|
|
82
|
-
if (
|
|
156
|
+
if (options.dryRun) {
|
|
157
|
+
process.stdout.write(redactSensitiveNpmrc(nextContent));
|
|
158
|
+
} else {
|
|
83
159
|
mkdirSync(dirname(options.npmrc), { recursive: true });
|
|
84
160
|
if (changed && options.backup && existsSync(options.npmrc)) {
|
|
85
|
-
copyFileSync(options.npmrc,
|
|
161
|
+
copyFileSync(options.npmrc, buildBackupPath(options.npmrc));
|
|
86
162
|
}
|
|
87
163
|
writeFileSync(options.npmrc, nextContent, 'utf8');
|
|
164
|
+
if (process.platform !== 'win32') {
|
|
165
|
+
chmodSync(options.npmrc, 0o600);
|
|
166
|
+
}
|
|
88
167
|
}
|
|
89
168
|
|
|
90
169
|
if (options.validate) {
|
|
@@ -95,7 +174,11 @@ function main() {
|
|
|
95
174
|
const result = spawnSync(
|
|
96
175
|
'npm',
|
|
97
176
|
['view', options.packageName, `--registry=${options.registry}`],
|
|
98
|
-
{
|
|
177
|
+
{
|
|
178
|
+
env: { ...process.env, [options.tokenEnv]: token },
|
|
179
|
+
stdio: 'inherit',
|
|
180
|
+
shell: process.platform === 'win32',
|
|
181
|
+
},
|
|
99
182
|
);
|
|
100
183
|
|
|
101
184
|
if (result.status !== 0) {
|
|
@@ -108,6 +191,7 @@ function main() {
|
|
|
108
191
|
`[skill_master] ${options.dryRun ? 'Preview ready for' : 'Configured'} ${options.npmrc}`,
|
|
109
192
|
`[skill_master] Scope: ${options.scope}`,
|
|
110
193
|
`[skill_master] Registry: ${options.registry}`,
|
|
194
|
+
`[skill_master] Token storage: ${options.storeToken ? 'literal token in .npmrc' : `environment reference \${${options.tokenEnv}}`}`,
|
|
111
195
|
`[skill_master] Changed: ${changed ? 'yes' : 'no'}`,
|
|
112
196
|
`[skill_master] Validate with: npm view ${options.packageName} --registry=${options.registry}`,
|
|
113
197
|
`[skill_master] Run with: npx -y ${options.packageName}@latest`,
|
|
@@ -3,30 +3,68 @@ export const DEFAULT_PACKAGE_NAME = '@fprad0/skill-master-mcp';
|
|
|
3
3
|
export const DEFAULT_REGISTRY_URL = 'https://npm.pkg.github.com';
|
|
4
4
|
export const DEFAULT_TOKEN_ENV = 'GITHUB_PACKAGES_TOKEN';
|
|
5
5
|
|
|
6
|
-
export function
|
|
7
|
-
|
|
6
|
+
export function normalizeRegistryUrl(registryUrl = DEFAULT_REGISTRY_URL, { allowInsecure = false } = {}) {
|
|
7
|
+
const parsed = new URL(registryUrl);
|
|
8
|
+
const isLocalhost = ['localhost', '127.0.0.1', '::1'].includes(parsed.hostname);
|
|
9
|
+
|
|
10
|
+
if (parsed.protocol !== 'https:' && !(allowInsecure && isLocalhost)) {
|
|
11
|
+
throw new Error(`Registry must use HTTPS: ${registryUrl}`);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
parsed.hash = '';
|
|
15
|
+
parsed.search = '';
|
|
16
|
+
return parsed.toString().replace(/\/$/, '');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function registryHostFromUrl(registryUrl = DEFAULT_REGISTRY_URL, { allowInsecure = false } = {}) {
|
|
20
|
+
return new URL(normalizeRegistryUrl(registryUrl, { allowInsecure })).host;
|
|
8
21
|
}
|
|
9
22
|
|
|
10
23
|
export function buildRegistryLine(scope = DEFAULT_SCOPE, registryUrl = DEFAULT_REGISTRY_URL) {
|
|
11
|
-
return `${scope}:registry=${registryUrl}`;
|
|
24
|
+
return `${scope}:registry=${normalizeRegistryUrl(registryUrl)}`;
|
|
12
25
|
}
|
|
13
26
|
|
|
14
27
|
export function buildTokenLine(registryUrl = DEFAULT_REGISTRY_URL, tokenValue) {
|
|
15
28
|
return `//${registryHostFromUrl(registryUrl)}/:_authToken=${tokenValue}`;
|
|
16
29
|
}
|
|
17
30
|
|
|
31
|
+
export function buildTokenEnvReference(tokenEnv = DEFAULT_TOKEN_ENV) {
|
|
32
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(tokenEnv)) {
|
|
33
|
+
throw new Error(`Invalid token environment variable name: ${tokenEnv}`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return '${' + tokenEnv + '}';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function validateScope(scope = DEFAULT_SCOPE) {
|
|
40
|
+
if (!/^@[a-z0-9][a-z0-9._-]*$/i.test(scope)) {
|
|
41
|
+
throw new Error(`Invalid npm scope: ${scope}`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return scope;
|
|
45
|
+
}
|
|
46
|
+
|
|
18
47
|
export function upsertNpmrc(content, {
|
|
19
48
|
scope = DEFAULT_SCOPE,
|
|
20
49
|
registryUrl = DEFAULT_REGISTRY_URL,
|
|
21
50
|
tokenValue,
|
|
51
|
+
tokenEnv = DEFAULT_TOKEN_ENV,
|
|
52
|
+
storeToken = false,
|
|
53
|
+
allowInsecureRegistry = false,
|
|
22
54
|
} = {}) {
|
|
23
|
-
|
|
24
|
-
|
|
55
|
+
const normalizedScope = validateScope(scope);
|
|
56
|
+
const normalizedRegistryUrl = normalizeRegistryUrl(registryUrl, { allowInsecure: allowInsecureRegistry });
|
|
57
|
+
const tokenValueForFile = storeToken
|
|
58
|
+
? tokenValue
|
|
59
|
+
: buildTokenEnvReference(tokenEnv);
|
|
60
|
+
|
|
61
|
+
if (!tokenValueForFile) {
|
|
62
|
+
throw new Error(storeToken ? 'tokenValue is required when storeToken is enabled' : 'tokenEnv is required');
|
|
25
63
|
}
|
|
26
64
|
|
|
27
65
|
const existing = (content ?? '').replace(/\r\n/g, '\n');
|
|
28
|
-
const registryLinePrefix = `${
|
|
29
|
-
const tokenLinePrefix = `//${registryHostFromUrl(
|
|
66
|
+
const registryLinePrefix = `${normalizedScope}:registry=`;
|
|
67
|
+
const tokenLinePrefix = `//${registryHostFromUrl(normalizedRegistryUrl, { allowInsecure: allowInsecureRegistry })}/:_authToken=`;
|
|
30
68
|
|
|
31
69
|
const filteredLines = existing
|
|
32
70
|
.split('\n')
|
|
@@ -40,8 +78,8 @@ export function upsertNpmrc(content, {
|
|
|
40
78
|
filteredLines.push('');
|
|
41
79
|
}
|
|
42
80
|
|
|
43
|
-
filteredLines.push(buildRegistryLine(
|
|
44
|
-
filteredLines.push(buildTokenLine(
|
|
81
|
+
filteredLines.push(buildRegistryLine(normalizedScope, normalizedRegistryUrl));
|
|
82
|
+
filteredLines.push(buildTokenLine(normalizedRegistryUrl, tokenValueForFile));
|
|
45
83
|
|
|
46
84
|
return `${filteredLines.join('\n')}\n`;
|
|
47
85
|
}
|
|
@@ -54,3 +92,6 @@ export function hasRegistryConfig(content, {
|
|
|
54
92
|
return normalized.includes(buildRegistryLine(scope, registryUrl));
|
|
55
93
|
}
|
|
56
94
|
|
|
95
|
+
export function redactSensitiveNpmrc(content) {
|
|
96
|
+
return (content ?? '').replace(/(:_authToken=)(.+)$/gm, '$1<redacted>');
|
|
97
|
+
}
|