@promovaweb/brandfy 1.1.0 → 1.3.1

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/README.md CHANGED
@@ -1,9 +1,8 @@
1
1
  # CLI do Brandfy
2
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`.
3
+ O pacote `@promovaweb/brandfy` instala e mantém o sistema de skills no projeto
4
+ consumidor. A conversa sobre a marca acontece exclusivamente na skill
5
+ `$brandfy`; o CLI não expõe atalhos para especialistas, geradores ou auditorias.
7
6
 
8
7
  ## Instalação
9
8
 
@@ -11,57 +10,43 @@ Instale o executável globalmente:
11
10
 
12
11
  ```bash
13
12
  npm install --global @promovaweb/brandfy
14
- brandfy init .
13
+ brandfy install .
15
14
  ```
16
15
 
17
- Também é possível executar sem instalação global:
16
+ O comando instala o catálogo completo pelo gerenciador `skills`, prepara
17
+ `.brandfy/`, cria ou confere `BRAND.md`, mantém `brand/README.md` como índice e
18
+ atualiza o bloco gerenciado do `AGENTS.md`.
18
19
 
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
20
+ ## Comandos públicos
28
21
 
29
22
  | Comando | Resultado |
30
23
  | --- | --- |
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:
24
+ | `brandfy install .` | Instala todas as skills e prepara o projeto consumidor. |
25
+ | `brandfy update` | Atualiza as skills, reconcilia a estrutura e confere o setup. |
26
+ | `brandfy doctor` | Diagnostica Node.js, as 19 skills, lock, configuração e arquivos da marca. |
46
27
 
47
- ```bash
48
- brandfy pdf . -- --output brand/manual-da-marca.pdf
49
- ```
28
+ O diretório usado por `update` e `doctor` é o projeto atual. Para instalar em
29
+ outro local, informe esse caminho somente no comando `install`, como em
30
+ `brandfy install ../meu-projeto`.
31
+
32
+ Depois de `brandfy install .`, abra o agente e use apenas `$brandfy`. A skill
33
+ orquestradora chama `brandfy-setup`, `brandfy-mvp` e as demais especialistas na
34
+ ordem adequada.
50
35
 
51
36
  ## Atualizações
52
37
 
53
38
  O framework e o CLI compartilham a mesma SemVer. Atualize o executável pelo npm
54
- e, dentro do projeto, atualize as skills:
39
+ e, dentro do projeto, execute:
55
40
 
56
41
  ```bash
57
42
  npm install --global @promovaweb/brandfy@latest
58
- brandfy update .
59
- brandfy doctor .
43
+ brandfy update
44
+ brandfy doctor
60
45
  ```
61
46
 
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.
47
+ O `brandfy update` preserva arquivos autorais e reaplica somente a estrutura
48
+ gerenciada. O diagnóstico retorna código diferente de zero quando faltar uma
49
+ skill, configuração, arquivo da marca ou bloco de instruções.
65
50
 
66
51
  ## Desenvolvimento
67
52
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@promovaweb/brandfy",
3
- "version": "1.1.0",
3
+ "version": "1.3.1",
4
4
  "description": "CLI do Brandfy para instalar skills e manter sistemas de marca em projetos com agentes.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.mjs CHANGED
@@ -1,18 +1,15 @@
1
1
  /**
2
- * Implementa a interface de linha de comando do Brandfy.
2
+ * Implementa a interface pública mínima do CLI do Brandfy.
3
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.
4
+ * A conversa de marca acontece na skill `$brandfy`. O CLI somente instala,
5
+ * atualiza e diagnostica o sistema no projeto consumidor.
6
6
  */
7
7
 
8
8
  import path from "node:path";
9
9
  import { readFile } from "node:fs/promises";
10
10
  import { fileURLToPath } from "node:url";
11
11
  import { createProcessRunner } from "./process-runner.mjs";
12
- import {
13
- inspectProject,
14
- locateSkillScript,
15
- } from "./project.mjs";
12
+ import { inspectProject, locateSkillScript } from "./project.mjs";
16
13
 
17
14
  const packageRoot = path.resolve(
18
15
  path.dirname(fileURLToPath(import.meta.url)),
@@ -27,50 +24,27 @@ export const DEFAULT_AGENT = "codex";
27
24
 
28
25
  const HELP = `Brandfy ${packageManifest.version}
29
26
 
30
- Instala as skills do Brandfy e executa os geradores no projeto atual.
27
+ Instala e mantém o sistema de skills do Brandfy no projeto atual.
31
28
 
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
29
+ Comandos:
30
+ brandfy install . Instala as skills e prepara o projeto
31
+ brandfy update Atualiza as skills e reconcilia o projeto
32
+ brandfy doctor Diagnostica skills e arquivos
33
+ brandfy --version Mostra a versão do CLI
42
34
 
43
35
  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
36
+ --json Emite o diagnóstico em JSON
37
+ --help Mostra esta ajuda
48
38
 
49
- Os argumentos escritos depois de -- seguem para o gerador de PDF ou auditoria.
39
+ Depois da instalação, converse somente com a skill $brandfy.
50
40
  `;
51
41
 
52
42
  function parseArguments(argv) {
53
43
  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") {
44
+ const options = { json: false };
45
+
46
+ for (const argument of argv) {
47
+ if (argument === "--json") {
74
48
  options.json = true;
75
49
  } else if (argument.startsWith("-")) {
76
50
  throw new Error(`Opção desconhecida: ${argument}`);
@@ -79,7 +53,7 @@ function parseArguments(argv) {
79
53
  }
80
54
  }
81
55
 
82
- return { positional, passthrough, options };
56
+ return { positional, options };
83
57
  }
84
58
 
85
59
  function projectFrom(value, cwd) {
@@ -93,13 +67,13 @@ async function ensureSuccess(result, action) {
93
67
  }
94
68
  }
95
69
 
96
- async function installSkills(runner, project, options, label) {
70
+ async function installSkills(runner, project, label) {
97
71
  console.log(`${label} as skills do Brandfy em ${project}...`);
98
72
  const result = await runner.runSkills([
99
73
  "add",
100
- options.source,
74
+ DEFAULT_SOURCE,
101
75
  "--agent",
102
- options.agent,
76
+ DEFAULT_AGENT,
103
77
  "--skill",
104
78
  "*",
105
79
  "-y",
@@ -108,25 +82,31 @@ async function installSkills(runner, project, options, label) {
108
82
  await ensureSuccess(result, "O gerenciador skills não concluiu a instalação");
109
83
  }
110
84
 
111
- async function runInstalledScript(
112
- runner,
113
- project,
114
- skill,
115
- relativeScript,
116
- args,
117
- ) {
118
- const script = await locateSkillScript(project, skill, relativeScript);
85
+ async function runSetup(runner, project, check = false) {
86
+ const script = await locateSkillScript(
87
+ project,
88
+ "brandfy-setup",
89
+ "scripts/setup.mjs",
90
+ );
119
91
  if (!script) {
120
92
  throw new Error(
121
- `A skill ${skill} não está instalada. Execute brandfy init primeiro.`,
93
+ "A skill brandfy-setup não está instalada. Execute brandfy install . primeiro.",
122
94
  );
123
95
  }
96
+ const args = ["--project", project];
97
+ if (check) args.push("--check");
124
98
  const result = await runner.runScript(script, args, project);
125
- await ensureSuccess(result, `O script da skill ${skill} falhou`);
99
+ await ensureSuccess(result, "O brandfy-setup não concluiu a operação");
100
+ }
101
+
102
+ function assertCommandShape(positional) {
103
+ if (positional.length > 2) {
104
+ throw new Error("Use somente um comando e, opcionalmente, o diretório do projeto.");
105
+ }
126
106
  }
127
107
 
128
108
  /**
129
- * Executa um comando com dependências injetáveis para permitir testes isolados.
109
+ * Executa a interface pública com dependências injetáveis para testes.
130
110
  *
131
111
  * @param {string[]} argv Argumentos sem `node` e sem o nome do binário.
132
112
  * @param {{cwd?: string, runner?: object, stdout?: Function}} dependencies
@@ -142,100 +122,53 @@ export async function execute(argv, dependencies = {}) {
142
122
  stdout(HELP.trimEnd());
143
123
  return 0;
144
124
  }
145
- if (argv[0] === "--version" || argv[0] === "-v" || argv[0] === "version") {
125
+ if (argv[0] === "--version" || argv[0] === "-v") {
146
126
  stdout(packageManifest.version);
147
127
  return 0;
148
128
  }
149
129
 
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
- }
130
+ const { positional, options } = parseArguments(argv);
131
+ const [command, possibleProject] = positional;
132
+ assertCommandShape(positional);
189
133
 
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;
134
+ if (![
135
+ "install",
136
+ "update",
137
+ "doctor",
138
+ ].includes(command)) {
139
+ throw new Error(`Comando desconhecido: ${command || "(vazio)"}`);
201
140
  }
202
141
 
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;
142
+ const project = projectFrom(possibleProject, cwd);
143
+ if (options.json && command !== "doctor") {
144
+ throw new Error("A opção --json só pode ser usada com brandfy doctor.");
212
145
  }
213
146
 
214
- if (command === "audit") {
215
- await runInstalledScript(
216
- runner,
217
- project,
218
- "brandfy-auditoria",
219
- "scripts/audit-brand.mjs",
220
- ["--project", project, ...passthrough],
221
- );
147
+ if (command === "install") {
148
+ await installSkills(runner, project, "Instalando");
149
+ await runSetup(runner, project);
150
+ stdout(`Brandfy instalado e preparado em ${project}.`);
222
151
  return 0;
223
152
  }
224
153
 
225
154
  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}.`);
155
+ await installSkills(runner, project, "Atualizando");
156
+ await runSetup(runner, project);
157
+ await runSetup(runner, project, true);
158
+ stdout(`Brandfy atualizado e conferido em ${project}.`);
235
159
  return 0;
236
160
  }
237
161
 
238
- throw new Error(`Comando desconhecido: ${command || "(vazio)"}`);
162
+ const checks = await inspectProject(project);
163
+ if (options.json) {
164
+ stdout(JSON.stringify({ project, checks }, null, 2));
165
+ } else {
166
+ stdout(`Diagnóstico do Brandfy em ${project}`);
167
+ for (const check of checks) {
168
+ stdout(`${check.ok ? "✓" : "✗"} ${check.name}: ${check.detail}`);
169
+ }
170
+ }
171
+ return checks.every((check) => check.ok) ? 0 : 1;
239
172
  }
240
173
 
241
174
  /**
package/src/project.mjs CHANGED
@@ -14,6 +14,28 @@ export const SKILL_DIRECTORIES = [
14
14
  ".claude/skills",
15
15
  ];
16
16
 
17
+ export const REQUIRED_SKILLS = [
18
+ "brandfy",
19
+ "brandfy-setup",
20
+ "brandfy-mvp",
21
+ "brandfy-entrevista",
22
+ "brandfy-diagnostico",
23
+ "brandfy-estrategia",
24
+ "brandfy-naming",
25
+ "brandfy-slogan",
26
+ "brandfy-voz",
27
+ "brandfy-identidade-visual",
28
+ "brandfy-tipografia-web",
29
+ "brandfy-logo",
30
+ "brandfy-ativos-logo",
31
+ "brandfy-design-tokens",
32
+ "brandfy-aplicacoes",
33
+ "brandfy-templates-canais",
34
+ "brandfy-manual",
35
+ "brandfy-guia-pdf",
36
+ "brandfy-auditoria",
37
+ ];
38
+
17
39
  export async function exists(target) {
18
40
  try {
19
41
  await access(target);
@@ -51,15 +73,19 @@ function supportsRequiredNode(version) {
51
73
  * @returns {Promise<Array<{name: string, ok: boolean, detail: string}>>}
52
74
  */
53
75
  export async function inspectProject(project) {
54
- const setup = await locateSkillScript(
55
- project,
56
- "brandfy-setup",
57
- "scripts/setup.mjs",
58
- );
59
76
  const agentsPath = path.join(project, "AGENTS.md");
60
77
  const agents = await exists(agentsPath)
61
78
  ? await readFile(agentsPath, "utf8")
62
79
  : "";
80
+ const installedSkills = await Promise.all(
81
+ REQUIRED_SKILLS.map(async (skill) => ({
82
+ skill,
83
+ path: await locateSkillScript(project, skill, "SKILL.md"),
84
+ })),
85
+ );
86
+ const missingSkills = installedSkills
87
+ .filter(({ path: skillPath }) => !skillPath)
88
+ .map(({ skill }) => skill);
63
89
 
64
90
  return [
65
91
  {
@@ -68,11 +94,11 @@ export async function inspectProject(project) {
68
94
  detail: `versão ${process.versions.node}`,
69
95
  },
70
96
  {
71
- name: "Skills do Brandfy",
72
- ok: Boolean(setup),
73
- detail: setup
74
- ? path.relative(project, setup)
75
- : "brandfy-setup não foi encontrada",
97
+ name: "Biblioteca do Brandfy",
98
+ ok: missingSkills.length === 0,
99
+ detail: missingSkills.length === 0
100
+ ? `${REQUIRED_SKILLS.length} skills instaladas`
101
+ : `ausentes: ${missingSkills.join(", ")}`,
76
102
  },
77
103
  {
78
104
  name: "Lock das skills",
@@ -85,7 +111,12 @@ export async function inspectProject(project) {
85
111
  detail: ".brandfy/config.yaml",
86
112
  },
87
113
  {
88
- name: "Manual editável",
114
+ name: "Guia da marca",
115
+ ok: await exists(path.join(project, "BRAND.md")),
116
+ detail: "BRAND.md",
117
+ },
118
+ {
119
+ name: "Índice da marca",
89
120
  ok: await exists(path.join(project, "brand", "README.md")),
90
121
  detail: "brand/README.md",
91
122
  },
@@ -95,5 +126,12 @@ export async function inspectProject(project) {
95
126
  && agents.includes("<!-- brandfy:consumer:end -->"),
96
127
  detail: "bloco gerenciado no AGENTS.md",
97
128
  },
129
+ {
130
+ name: "MVP.md",
131
+ ok: true,
132
+ detail: await exists(path.join(project, "MVP.md"))
133
+ ? "detectado; será lido pela skill brandfy-mvp"
134
+ : "não presente; entrada opcional",
135
+ },
98
136
  ];
99
137
  }