@promovaweb/brandfy 1.1.0
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 +13 -0
- package/LICENSE +21 -0
- package/README.md +77 -0
- package/bin/brandfy.mjs +12 -0
- package/package.json +46 -0
- package/src/cli.mjs +254 -0
- package/src/process-runner.mjs +86 -0
- package/src/project.mjs +99 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Changelog do pacote npm
|
|
2
|
+
|
|
3
|
+
O changelog canônico da release fica em
|
|
4
|
+
[`../CHANGELOG.md`](../CHANGELOG.md), pois o CLI, o framework e a documentação
|
|
5
|
+
usam a mesma SemVer.
|
|
6
|
+
|
|
7
|
+
## [1.1.0] - 2026-07-30
|
|
8
|
+
|
|
9
|
+
- Primeira publicação do comando `brandfy`.
|
|
10
|
+
- Instalação e atualização das skills por meio do pacote `skills`.
|
|
11
|
+
- Comandos de setup, diagnóstico, PDF e auditoria.
|
|
12
|
+
|
|
13
|
+
[1.1.0]: https://github.com/promovaweb/brandfy/releases/tag/v1.1.0
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Promovaweb
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# CLI do Brandfy
|
|
2
|
+
|
|
3
|
+
O pacote `@promovaweb/brandfy` instala as skills oficiais e oferece atalhos
|
|
4
|
+
para preparar, conferir e compilar a marca de um projeto. O executável usa o
|
|
5
|
+
pacote `skills` como dependência fixa, portanto a instalação e a atualização
|
|
6
|
+
continuam compatíveis com `skills-lock.json`.
|
|
7
|
+
|
|
8
|
+
## Instalação
|
|
9
|
+
|
|
10
|
+
Instale o executável globalmente:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install --global @promovaweb/brandfy
|
|
14
|
+
brandfy init .
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Também é possível executar sem instalação global:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npx @promovaweb/brandfy init .
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
O comando `init` instala todas as skills no projeto, prepara `.brandfy/`,
|
|
24
|
+
cria a estrutura inicial em `brand/` e inclui o bloco gerenciado no
|
|
25
|
+
`AGENTS.md`.
|
|
26
|
+
|
|
27
|
+
## Comandos
|
|
28
|
+
|
|
29
|
+
| Comando | Resultado |
|
|
30
|
+
| --- | --- |
|
|
31
|
+
| `brandfy init [diretório]` | Instala as skills e executa o setup. |
|
|
32
|
+
| `brandfy doctor [diretório]` | Confere Node.js, skills, lock, configuração, manual e instruções. |
|
|
33
|
+
| `brandfy skills list` | Lista o catálogo disponível na origem configurada. |
|
|
34
|
+
| `brandfy skills install [diretório]` | Instala todas as skills pelo `skills add`. |
|
|
35
|
+
| `brandfy skills update [diretório]` | Reinstala somente as skills do Brandfy a partir da origem. |
|
|
36
|
+
| `brandfy pdf [diretório]` | Compila `brand/README.md` em `brand/brand-guide.pdf`. |
|
|
37
|
+
| `brandfy audit [diretório]` | Executa a auditoria e atualiza `.brandfy/audit.md`. |
|
|
38
|
+
| `brandfy update [diretório]` | Atualiza as skills e executa o setup em modo de conferência. |
|
|
39
|
+
|
|
40
|
+
Use `--source <origem>` para testar um checkout local ou outra origem aceita
|
|
41
|
+
pelo gerenciador. O destino padrão é `promovaweb/brandfy`, e o agente padrão é
|
|
42
|
+
o Codex.
|
|
43
|
+
|
|
44
|
+
Os argumentos escritos depois de `--` seguem para o script da skill. Este
|
|
45
|
+
exemplo escolhe outro nome para o PDF:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
brandfy pdf . -- --output brand/manual-da-marca.pdf
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Atualizações
|
|
52
|
+
|
|
53
|
+
O framework e o CLI compartilham a mesma SemVer. Atualize o executável pelo npm
|
|
54
|
+
e, dentro do projeto, atualize as skills:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
npm install --global @promovaweb/brandfy@latest
|
|
58
|
+
brandfy update .
|
|
59
|
+
brandfy doctor .
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
O comando `brandfy update` não substitui arquivos da marca que pertencem ao
|
|
63
|
+
usuário. O setup executado na sequência confere a estrutura e informa qualquer
|
|
64
|
+
arquivo ausente.
|
|
65
|
+
|
|
66
|
+
## Desenvolvimento
|
|
67
|
+
|
|
68
|
+
Na raiz do repositório Brandfy, instale as dependências e execute os testes:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
npm --prefix cli ci
|
|
72
|
+
npm run cli:test
|
|
73
|
+
npm run release:check
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Consulte [`RELEASING.md`](../RELEASING.md) para atualizar a versão, gerar a
|
|
77
|
+
documentação, publicar no npm e criar a tag e a GitHub Release.
|
package/bin/brandfy.mjs
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Expõe o executável público do Brandfy.
|
|
5
|
+
*
|
|
6
|
+
* O arquivo permanece pequeno para que toda a lógica possa ser importada e
|
|
7
|
+
* exercitada pela suíte sem iniciar outro processo.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { main } from "../src/cli.mjs";
|
|
11
|
+
|
|
12
|
+
await main(process.argv.slice(2));
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@promovaweb/brandfy",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "CLI do Brandfy para instalar skills e manter sistemas de marca em projetos com agentes.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"brandfy": "bin/brandfy.mjs"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin",
|
|
11
|
+
"src",
|
|
12
|
+
"CHANGELOG.md",
|
|
13
|
+
"LICENSE",
|
|
14
|
+
"README.md"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"test": "node --test tests/*.test.mjs",
|
|
18
|
+
"prepack": "npm test"
|
|
19
|
+
},
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=22.20.0"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"agents",
|
|
25
|
+
"branding",
|
|
26
|
+
"brand",
|
|
27
|
+
"cli",
|
|
28
|
+
"skills"
|
|
29
|
+
],
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "git+https://github.com/promovaweb/brandfy.git",
|
|
34
|
+
"directory": "cli"
|
|
35
|
+
},
|
|
36
|
+
"bugs": {
|
|
37
|
+
"url": "https://github.com/promovaweb/brandfy/issues"
|
|
38
|
+
},
|
|
39
|
+
"homepage": "https://github.com/promovaweb/brandfy#readme",
|
|
40
|
+
"publishConfig": {
|
|
41
|
+
"access": "public"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"skills": "1.5.21"
|
|
45
|
+
}
|
|
46
|
+
}
|
package/src/cli.mjs
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Implementa a interface de linha de comando do Brandfy.
|
|
3
|
+
*
|
|
4
|
+
* O CLI delega instalação e atualização ao pacote `skills`. Os comandos de
|
|
5
|
+
* geração executam os scripts das skills instaladas no projeto consumidor.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import { readFile } from "node:fs/promises";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
11
|
+
import { createProcessRunner } from "./process-runner.mjs";
|
|
12
|
+
import {
|
|
13
|
+
inspectProject,
|
|
14
|
+
locateSkillScript,
|
|
15
|
+
} from "./project.mjs";
|
|
16
|
+
|
|
17
|
+
const packageRoot = path.resolve(
|
|
18
|
+
path.dirname(fileURLToPath(import.meta.url)),
|
|
19
|
+
"..",
|
|
20
|
+
);
|
|
21
|
+
const packageManifest = JSON.parse(
|
|
22
|
+
await readFile(path.join(packageRoot, "package.json"), "utf8"),
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
export const DEFAULT_SOURCE = "promovaweb/brandfy";
|
|
26
|
+
export const DEFAULT_AGENT = "codex";
|
|
27
|
+
|
|
28
|
+
const HELP = `Brandfy ${packageManifest.version}
|
|
29
|
+
|
|
30
|
+
Instala as skills do Brandfy e executa os geradores no projeto atual.
|
|
31
|
+
|
|
32
|
+
Uso:
|
|
33
|
+
brandfy init [diretório] Instala as skills e prepara a marca
|
|
34
|
+
brandfy doctor [diretório] [--json] Confere a instalação e os arquivos
|
|
35
|
+
brandfy skills list Lista as skills disponíveis
|
|
36
|
+
brandfy skills install [diretório] Instala todas as skills
|
|
37
|
+
brandfy skills update [diretório] Atualiza somente as skills do Brandfy
|
|
38
|
+
brandfy pdf [diretório] [-- ...] Compila brand/README.md em PDF
|
|
39
|
+
brandfy audit [diretório] [-- ...] Audita a estrutura da marca
|
|
40
|
+
brandfy update [diretório] Atualiza as skills e confere o setup
|
|
41
|
+
brandfy --version Mostra a versão do CLI
|
|
42
|
+
|
|
43
|
+
Opções:
|
|
44
|
+
--source <origem> Repositório ou caminho aceito pelo skills add
|
|
45
|
+
--agent <agente> Agente de destino (padrão: codex)
|
|
46
|
+
--json Emite o diagnóstico como JSON
|
|
47
|
+
--help Mostra esta ajuda
|
|
48
|
+
|
|
49
|
+
Os argumentos escritos depois de -- seguem para o gerador de PDF ou auditoria.
|
|
50
|
+
`;
|
|
51
|
+
|
|
52
|
+
function parseArguments(argv) {
|
|
53
|
+
const positional = [];
|
|
54
|
+
const passthrough = [];
|
|
55
|
+
const options = {
|
|
56
|
+
source: DEFAULT_SOURCE,
|
|
57
|
+
agent: DEFAULT_AGENT,
|
|
58
|
+
json: false,
|
|
59
|
+
};
|
|
60
|
+
let forwarding = false;
|
|
61
|
+
|
|
62
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
63
|
+
const argument = argv[index];
|
|
64
|
+
if (forwarding) {
|
|
65
|
+
passthrough.push(argument);
|
|
66
|
+
} else if (argument === "--") {
|
|
67
|
+
forwarding = true;
|
|
68
|
+
} else if (argument === "--source" || argument === "--agent") {
|
|
69
|
+
const value = argv[index + 1];
|
|
70
|
+
if (!value) throw new Error(`${argument} exige um valor.`);
|
|
71
|
+
options[argument.slice(2)] = value;
|
|
72
|
+
index += 1;
|
|
73
|
+
} else if (argument === "--json") {
|
|
74
|
+
options.json = true;
|
|
75
|
+
} else if (argument.startsWith("-")) {
|
|
76
|
+
throw new Error(`Opção desconhecida: ${argument}`);
|
|
77
|
+
} else {
|
|
78
|
+
positional.push(argument);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return { positional, passthrough, options };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function projectFrom(value, cwd) {
|
|
86
|
+
return path.resolve(cwd, value || ".");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function ensureSuccess(result, action) {
|
|
90
|
+
if (result.code !== 0) {
|
|
91
|
+
const detail = result.stderr?.trim() || result.stdout?.trim();
|
|
92
|
+
throw new Error(detail ? `${action}: ${detail}` : `${action}.`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function installSkills(runner, project, options, label) {
|
|
97
|
+
console.log(`${label} as skills do Brandfy em ${project}...`);
|
|
98
|
+
const result = await runner.runSkills([
|
|
99
|
+
"add",
|
|
100
|
+
options.source,
|
|
101
|
+
"--agent",
|
|
102
|
+
options.agent,
|
|
103
|
+
"--skill",
|
|
104
|
+
"*",
|
|
105
|
+
"-y",
|
|
106
|
+
"--copy",
|
|
107
|
+
], project);
|
|
108
|
+
await ensureSuccess(result, "O gerenciador skills não concluiu a instalação");
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function runInstalledScript(
|
|
112
|
+
runner,
|
|
113
|
+
project,
|
|
114
|
+
skill,
|
|
115
|
+
relativeScript,
|
|
116
|
+
args,
|
|
117
|
+
) {
|
|
118
|
+
const script = await locateSkillScript(project, skill, relativeScript);
|
|
119
|
+
if (!script) {
|
|
120
|
+
throw new Error(
|
|
121
|
+
`A skill ${skill} não está instalada. Execute brandfy init primeiro.`,
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
const result = await runner.runScript(script, args, project);
|
|
125
|
+
await ensureSuccess(result, `O script da skill ${skill} falhou`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Executa um comando com dependências injetáveis para permitir testes isolados.
|
|
130
|
+
*
|
|
131
|
+
* @param {string[]} argv Argumentos sem `node` e sem o nome do binário.
|
|
132
|
+
* @param {{cwd?: string, runner?: object, stdout?: Function}} dependencies
|
|
133
|
+
* Dependências substituíveis pela suíte.
|
|
134
|
+
* @returns {Promise<number>} Código de saída.
|
|
135
|
+
*/
|
|
136
|
+
export async function execute(argv, dependencies = {}) {
|
|
137
|
+
const cwd = dependencies.cwd || process.cwd();
|
|
138
|
+
const runner = dependencies.runner || createProcessRunner();
|
|
139
|
+
const stdout = dependencies.stdout || console.log;
|
|
140
|
+
|
|
141
|
+
if (argv.length === 0 || argv.includes("--help") || argv.includes("-h")) {
|
|
142
|
+
stdout(HELP.trimEnd());
|
|
143
|
+
return 0;
|
|
144
|
+
}
|
|
145
|
+
if (argv[0] === "--version" || argv[0] === "-v" || argv[0] === "version") {
|
|
146
|
+
stdout(packageManifest.version);
|
|
147
|
+
return 0;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const { positional, passthrough, options } = parseArguments(argv);
|
|
151
|
+
const [command, subcommand, possibleProject] = positional;
|
|
152
|
+
|
|
153
|
+
if (command === "skills" && subcommand === "list") {
|
|
154
|
+
const result = await runner.runSkills(
|
|
155
|
+
["add", options.source, "--list"],
|
|
156
|
+
cwd,
|
|
157
|
+
);
|
|
158
|
+
await ensureSuccess(result, "Não foi possível listar as skills");
|
|
159
|
+
return 0;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (
|
|
163
|
+
command === "skills"
|
|
164
|
+
&& (subcommand === "install" || subcommand === "update")
|
|
165
|
+
) {
|
|
166
|
+
const project = projectFrom(possibleProject, cwd);
|
|
167
|
+
await installSkills(
|
|
168
|
+
runner,
|
|
169
|
+
project,
|
|
170
|
+
options,
|
|
171
|
+
subcommand === "install" ? "Instalando" : "Atualizando",
|
|
172
|
+
);
|
|
173
|
+
return 0;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const project = projectFrom(subcommand, cwd);
|
|
177
|
+
if (command === "init") {
|
|
178
|
+
await installSkills(runner, project, options, "Instalando");
|
|
179
|
+
await runInstalledScript(
|
|
180
|
+
runner,
|
|
181
|
+
project,
|
|
182
|
+
"brandfy-setup",
|
|
183
|
+
"scripts/setup.mjs",
|
|
184
|
+
["--project", project],
|
|
185
|
+
);
|
|
186
|
+
stdout(`Brandfy preparado em ${project}.`);
|
|
187
|
+
return 0;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (command === "doctor") {
|
|
191
|
+
const checks = await inspectProject(project);
|
|
192
|
+
if (options.json) {
|
|
193
|
+
stdout(JSON.stringify({ project, checks }, null, 2));
|
|
194
|
+
} else {
|
|
195
|
+
stdout(`Diagnóstico do Brandfy em ${project}`);
|
|
196
|
+
for (const check of checks) {
|
|
197
|
+
stdout(`${check.ok ? "✓" : "✗"} ${check.name}: ${check.detail}`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return checks.every((check) => check.ok) ? 0 : 1;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (command === "pdf") {
|
|
204
|
+
await runInstalledScript(
|
|
205
|
+
runner,
|
|
206
|
+
project,
|
|
207
|
+
"brandfy-guia-pdf",
|
|
208
|
+
"scripts/build-brand-guide.mjs",
|
|
209
|
+
["--project", project, ...passthrough],
|
|
210
|
+
);
|
|
211
|
+
return 0;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (command === "audit") {
|
|
215
|
+
await runInstalledScript(
|
|
216
|
+
runner,
|
|
217
|
+
project,
|
|
218
|
+
"brandfy-auditoria",
|
|
219
|
+
"scripts/audit-brand.mjs",
|
|
220
|
+
["--project", project, ...passthrough],
|
|
221
|
+
);
|
|
222
|
+
return 0;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (command === "update") {
|
|
226
|
+
await installSkills(runner, project, options, "Atualizando");
|
|
227
|
+
await runInstalledScript(
|
|
228
|
+
runner,
|
|
229
|
+
project,
|
|
230
|
+
"brandfy-setup",
|
|
231
|
+
"scripts/setup.mjs",
|
|
232
|
+
["--project", project, "--check"],
|
|
233
|
+
);
|
|
234
|
+
stdout(`Skills atualizadas e setup conferido em ${project}.`);
|
|
235
|
+
return 0;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
throw new Error(`Comando desconhecido: ${command || "(vazio)"}`);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Converte falhas esperadas em uma mensagem curta de terminal.
|
|
243
|
+
*
|
|
244
|
+
* @param {string[]} argv Argumentos do processo.
|
|
245
|
+
* @returns {Promise<void>}
|
|
246
|
+
*/
|
|
247
|
+
export async function main(argv) {
|
|
248
|
+
try {
|
|
249
|
+
process.exitCode = await execute(argv);
|
|
250
|
+
} catch (error) {
|
|
251
|
+
console.error(`Erro: ${error.message}`);
|
|
252
|
+
process.exitCode = 1;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Executa o gerenciador `skills` e os scripts instalados pelo Brandfy.
|
|
3
|
+
*
|
|
4
|
+
* A resolução parte da dependência empacotada, portanto o CLI não depende de
|
|
5
|
+
* um comando `skills` global nem baixa uma versão diferente a cada execução.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { spawn } from "node:child_process";
|
|
9
|
+
import { readFile } from "node:fs/promises";
|
|
10
|
+
import { createRequire } from "node:module";
|
|
11
|
+
import path from "node:path";
|
|
12
|
+
|
|
13
|
+
const require = createRequire(import.meta.url);
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Executa um processo filho com o terminal herdado.
|
|
17
|
+
*
|
|
18
|
+
* @param {string} command Executável que será iniciado.
|
|
19
|
+
* @param {string[]} args Argumentos entregues ao executável.
|
|
20
|
+
* @param {string} cwd Diretório usado durante a execução.
|
|
21
|
+
* @param {"inherit" | "pipe"} stdio Forma de encaminhar a saída.
|
|
22
|
+
* @returns {Promise<{code: number, stdout: string, stderr: string}>}
|
|
23
|
+
*/
|
|
24
|
+
export function runProcess(command, args, cwd, stdio = "inherit") {
|
|
25
|
+
return new Promise((resolve, reject) => {
|
|
26
|
+
const child = spawn(command, args, {
|
|
27
|
+
cwd,
|
|
28
|
+
stdio: stdio === "inherit" ? "inherit" : ["ignore", "pipe", "pipe"],
|
|
29
|
+
env: process.env,
|
|
30
|
+
});
|
|
31
|
+
let stdout = "";
|
|
32
|
+
let stderr = "";
|
|
33
|
+
|
|
34
|
+
if (stdio === "pipe") {
|
|
35
|
+
child.stdout.setEncoding("utf8");
|
|
36
|
+
child.stderr.setEncoding("utf8");
|
|
37
|
+
child.stdout.on("data", (chunk) => {
|
|
38
|
+
stdout += chunk;
|
|
39
|
+
});
|
|
40
|
+
child.stderr.on("data", (chunk) => {
|
|
41
|
+
stderr += chunk;
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
child.once("error", reject);
|
|
46
|
+
child.once("close", (code) => {
|
|
47
|
+
resolve({ code: code ?? 1, stdout, stderr });
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Localiza o binário distribuído pela dependência npm `skills`.
|
|
54
|
+
*
|
|
55
|
+
* @returns {Promise<string>} Caminho absoluto do arquivo executável.
|
|
56
|
+
*/
|
|
57
|
+
export async function resolveSkillsCli() {
|
|
58
|
+
const packagePath = require.resolve("skills/package.json");
|
|
59
|
+
const manifest = JSON.parse(await readFile(packagePath, "utf8"));
|
|
60
|
+
const relativeBin = typeof manifest.bin === "string"
|
|
61
|
+
? manifest.bin
|
|
62
|
+
: manifest.bin?.skills;
|
|
63
|
+
|
|
64
|
+
if (!relativeBin) {
|
|
65
|
+
throw new Error("A dependência skills não expõe o executável esperado.");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return path.resolve(path.dirname(packagePath), relativeBin);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Cria o adaptador usado pelos comandos do Brandfy.
|
|
73
|
+
*
|
|
74
|
+
* @returns {{runSkills: Function, runScript: Function}} Adaptador de processos.
|
|
75
|
+
*/
|
|
76
|
+
export function createProcessRunner() {
|
|
77
|
+
return {
|
|
78
|
+
async runSkills(args, cwd, stdio = "inherit") {
|
|
79
|
+
const executable = await resolveSkillsCli();
|
|
80
|
+
return runProcess(process.execPath, [executable, ...args], cwd, stdio);
|
|
81
|
+
},
|
|
82
|
+
runScript(script, args, cwd, stdio = "inherit") {
|
|
83
|
+
return runProcess(process.execPath, [script, ...args], cwd, stdio);
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
}
|
package/src/project.mjs
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve a instalação do Brandfy e confere o estado do projeto consumidor.
|
|
3
|
+
*
|
|
4
|
+
* A busca aceita os diretórios usados pelos agentes mais comuns, mas o CLI
|
|
5
|
+
* instala no escopo do projeto para manter o lock junto do repositório.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { access, readFile } from "node:fs/promises";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
|
|
11
|
+
export const SKILL_DIRECTORIES = [
|
|
12
|
+
".agents/skills",
|
|
13
|
+
".codex/skills",
|
|
14
|
+
".claude/skills",
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
export async function exists(target) {
|
|
18
|
+
try {
|
|
19
|
+
await access(target);
|
|
20
|
+
return true;
|
|
21
|
+
} catch {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Localiza um script pertencente a uma skill instalada.
|
|
28
|
+
*
|
|
29
|
+
* @param {string} project Diretório absoluto do projeto consumidor.
|
|
30
|
+
* @param {string} skill Nome da skill.
|
|
31
|
+
* @param {string} script Caminho relativo dentro da skill.
|
|
32
|
+
* @returns {Promise<string | null>} Caminho encontrado ou `null`.
|
|
33
|
+
*/
|
|
34
|
+
export async function locateSkillScript(project, skill, script) {
|
|
35
|
+
for (const directory of SKILL_DIRECTORIES) {
|
|
36
|
+
const candidate = path.join(project, directory, skill, script);
|
|
37
|
+
if (await exists(candidate)) return candidate;
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function supportsRequiredNode(version) {
|
|
43
|
+
const [major, minor] = version.split(".").map(Number);
|
|
44
|
+
return major > 22 || (major === 22 && minor >= 20);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Produz as verificações exibidas por `brandfy doctor`.
|
|
49
|
+
*
|
|
50
|
+
* @param {string} project Diretório absoluto do projeto.
|
|
51
|
+
* @returns {Promise<Array<{name: string, ok: boolean, detail: string}>>}
|
|
52
|
+
*/
|
|
53
|
+
export async function inspectProject(project) {
|
|
54
|
+
const setup = await locateSkillScript(
|
|
55
|
+
project,
|
|
56
|
+
"brandfy-setup",
|
|
57
|
+
"scripts/setup.mjs",
|
|
58
|
+
);
|
|
59
|
+
const agentsPath = path.join(project, "AGENTS.md");
|
|
60
|
+
const agents = await exists(agentsPath)
|
|
61
|
+
? await readFile(agentsPath, "utf8")
|
|
62
|
+
: "";
|
|
63
|
+
|
|
64
|
+
return [
|
|
65
|
+
{
|
|
66
|
+
name: "Node.js",
|
|
67
|
+
ok: supportsRequiredNode(process.versions.node),
|
|
68
|
+
detail: `versão ${process.versions.node}`,
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
name: "Skills do Brandfy",
|
|
72
|
+
ok: Boolean(setup),
|
|
73
|
+
detail: setup
|
|
74
|
+
? path.relative(project, setup)
|
|
75
|
+
: "brandfy-setup não foi encontrada",
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
name: "Lock das skills",
|
|
79
|
+
ok: await exists(path.join(project, "skills-lock.json")),
|
|
80
|
+
detail: "skills-lock.json",
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
name: "Configuração",
|
|
84
|
+
ok: await exists(path.join(project, ".brandfy", "config.yaml")),
|
|
85
|
+
detail: ".brandfy/config.yaml",
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
name: "Manual editável",
|
|
89
|
+
ok: await exists(path.join(project, "brand", "README.md")),
|
|
90
|
+
detail: "brand/README.md",
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: "Instruções do agente",
|
|
94
|
+
ok: agents.includes("<!-- brandfy:consumer:start -->")
|
|
95
|
+
&& agents.includes("<!-- brandfy:consumer:end -->"),
|
|
96
|
+
detail: "bloco gerenciado no AGENTS.md",
|
|
97
|
+
},
|
|
98
|
+
];
|
|
99
|
+
}
|