@aksp/opencrew 1.4.1 → 1.5.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 +116 -0
- package/README.md +38 -17
- package/bin/opencrew.js +4 -4
- package/package.json +4 -3
- package/src/cli.js +93 -43
- package/src/commands/init.js +79 -53
- package/src/commands/update.js +44 -17
- package/src/lib/errors.js +12 -0
- package/src/lib/fsx.js +18 -9
- package/src/lib/ides.js +9 -34
- package/templates/AGENTS.md +42 -63
- package/templates/_opencrew/.opencrew-version +1 -1
- package/templates/_opencrew/core/best-practices/copywriting.md +4 -1
- package/templates/_opencrew/core/best-practices/image-design.md +5 -5
- package/templates/_opencrew/core/best-practices/instagram-feed.md +4 -4
- package/templates/_opencrew/core/best-practices/instagram-reels.md +1 -1
- package/templates/_opencrew/core/best-practices/review.md +7 -0
- package/templates/_opencrew/core/best-practices/social-networks-publishing.md +8 -8
- package/templates/_opencrew/core/prompts/build.prompt.md +25 -0
- package/templates/_opencrew/core/prompts/design.prompt.md +9 -5
- package/templates/_opencrew/core/runner.pipeline.md +56 -7
- package/templates/_opencrew/core/scripts/verificar/leitura.mjs +99 -0
- package/templates/_opencrew/core/scripts/verificar/regras.mjs +127 -0
- package/templates/_opencrew/core/scripts/verificar.mjs +118 -0
- package/templates/gitignore +3 -1
- package/templates/skills/image-ai-generator/SKILL.md +9 -5
- package/templates/skills/image-creator/SKILL.md +5 -3
- package/templates/skills/image-fetcher/SKILL.md +1 -1
- package/templates/skills/instagram-publisher/SKILL.md +34 -16
- package/templates/skills/instagram-publisher/scripts/publish.js +58 -27
- package/templates/skills/template-designer/SKILL.md +3 -3
- package/templates/skills/template-designer/base-templates/model-a.html +1 -1
- package/templates/skills/template-designer/base-templates/model-b.html +1 -1
- package/templates/skills/template-designer/base-templates/model-c.html +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,111 @@
|
|
|
3
3
|
All notable changes to opencrew are documented here.
|
|
4
4
|
The format is based on [Keep a Changelog](https://keepachangelog.com/).
|
|
5
5
|
|
|
6
|
+
## [1.5.0] — 2026-10-02
|
|
7
|
+
|
|
8
|
+
Trilha U1 "Revisor com dentes" — primeira melhoria vinda do uso real
|
|
9
|
+
(`docs/jornada/2026-10-02-uso-real.md`, `specs/fase-u1-revisor-com-dentes.md`).
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- **Verificador automático** (`_opencrew/core/scripts/verificar.mjs`, Node puro): mede o texto
|
|
13
|
+
ANTES do revisor usando os limites `constraints:` dos best-practices — título e meta description
|
|
14
|
+
do blog, legenda e hashtags do Instagram, slides do carrossel, post do LinkedIn, tweets, links
|
|
15
|
+
(alerta quando abaixo do mínimo). Bloqueia placeholders (`wa.me/55…9999…`, `[Empresa X]`,
|
|
16
|
+
`lorem ipsum`…), termos entre aspas em `## Proibições Explícitas` da memória da crew e
|
|
17
|
+
`[PREENCHER: …]`; alerta afirmações em 1ª pessoa com dado concreto (R$, %, ano passado,
|
|
18
|
+
"N clientes"). Relatório em PT-BR com valor medido × limite; última linha
|
|
19
|
+
`VERIFICACAO:OK | BLOQUEADA | AGUARDANDO_USUARIO`.
|
|
20
|
+
- **Regras de veracidade** injetadas em todo passo de criação: nunca inventar casos, depoimentos,
|
|
21
|
+
números ou histórias em 1ª pessoa — usar `[PREENCHER: o que falta]`.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
- **Revisão com trava**: antes de todo passo com `on_reject`, o runner verifica **todas** as saídas
|
|
25
|
+
desde o redator (não só a entrada do revisor — no uso real as legendas nunca eram revisadas);
|
|
26
|
+
`VERIFICACAO:BLOQUEADA` força REJECT seja qual for a nota; no limite de ciclos o usuário escolhe
|
|
27
|
+
corrigir, aceitar (registrado) ou abortar; a aprovação final mostra o resumo e pede os
|
|
28
|
+
`[PREENCHER]`.
|
|
29
|
+
- `review.md`: o revisor copia os números do relatório (nunca estima), não aprova com bloqueio e
|
|
30
|
+
tem nota máxima 7/10 com alerta não resolvido. `copywriting.md` e o build: regra de não inventar.
|
|
31
|
+
- **Instagram em 4:5**: carrossel/feed agora 1080×1350 (a API do Instagram só publica de 4:5 a
|
|
32
|
+
1,91:1), no máximo 10 slides; presets do `image-creator`, `template-designer` (e modelos-base),
|
|
33
|
+
`image-fetcher` e best-practices atualizados. Limites com nomes canônicos (`hashtags_max`).
|
|
34
|
+
|
|
35
|
+
### Internal
|
|
36
|
+
- Docs de jornada (`docs/jornada/`: uso real, roteiro de teste U0, medições) e roadmap de trilhas U.
|
|
37
|
+
- Regras de dev 12 (limite só vale se medido) e 13 (PT-BR para o usuário); alerta de tamanho e
|
|
38
|
+
lint cobrem os scripts do runtime.
|
|
39
|
+
|
|
40
|
+
## [1.4.2] — 2026-10-02
|
|
41
|
+
|
|
42
|
+
Hotfix "parar de causar dano" (Fase 1 da auditoria — `specs/fase-1-hotfix.md`).
|
|
43
|
+
|
|
44
|
+
### Fixed
|
|
45
|
+
- **`CLAUDE.md` gerado não traz mais o fluxo STATUS.md do mantenedor** (vazado na 1.4.0/1.4.1);
|
|
46
|
+
o `update` remove a seção de instalações existentes, sem tocar no texto do usuário.
|
|
47
|
+
`STATUS.md` saiu do `.gitignore` do template.
|
|
48
|
+
- **`--help` nunca executa comando**: `update --help` / `-h` e `init --help` só mostram a ajuda.
|
|
49
|
+
Parser estrito (`node:util.parseArgs`): opção desconhecida, `--ide` sem id válido ou
|
|
50
|
+
argumento solto (`init minha-pasta`) falham com exit 1 **antes** de escrever qualquer arquivo.
|
|
51
|
+
`--ide claude-code` (com espaço) e `-yv` funcionam. `update --dry-run` = `--check`.
|
|
52
|
+
- **`update` sem `AGENTS.md`** cria a ponte em vez de quebrar com ENOENT.
|
|
53
|
+
- **Migração do `AGENTS.md` legado faz backup** em `AGENTS.md.bak` (ou `.bak-<timestamp>`).
|
|
54
|
+
- **`.env.example` e `.gitignore` do usuário não são mais sobrescritos/ignorados**: recebem um
|
|
55
|
+
bloco `# opencrew:start … # opencrew:end` no fim; as linhas do usuário ficam intactas.
|
|
56
|
+
O bloco do `.gitignore` agora cobre `.claude/settings.local.json`, `crews/*/state.json` e
|
|
57
|
+
`crews/*/_investigations/`.
|
|
58
|
+
- **Ctrl+C no prompt de IDEs não deixa instalação pela metade**: as IDEs são escolhidas antes
|
|
59
|
+
da primeira escrita; cancelamento sai com 130 e "Cancelled — nothing was written.". Uma
|
|
60
|
+
instalação interrompida (core sem stamp de versão) é **retomada** pelo próximo `init`.
|
|
61
|
+
- Erros inesperados mostram uma linha `✗ <mensagem>` (stack só com `OPENCREW_DEBUG=1`).
|
|
62
|
+
- Marcadores de bloco: um `start` órfão (fim apagado à mão) não faz mais a regravação engolir
|
|
63
|
+
linhas do usuário.
|
|
64
|
+
- Skills: caminho do `image-ai-generator` corrigido (`{skill_path}/scripts/generate.py`, nota
|
|
65
|
+
`py -3` no Windows); `instagram-publisher` lê as imagens da pasta do run atual; o
|
|
66
|
+
`image-creator` renderiza JPEG quando o destino é Instagram.
|
|
67
|
+
- Runner: o toggle do dashboard reconhece o formato `- **Dashboard:** enabled` gravado no
|
|
68
|
+
onboarding.
|
|
69
|
+
|
|
70
|
+
### Security
|
|
71
|
+
- **Publicar/enviar é sempre o último trecho do pipeline**: `… → Review → Final Approval
|
|
72
|
+
checkpoint → [Publish/Send]` (design), com o novo Gate 2c BLOCKING no build. Passos com
|
|
73
|
+
`side_effects: irreversible` rodam inline e **nunca** têm retry automático nem auto-correção
|
|
74
|
+
de veto — o runner avisa que a ação pode já ter acontecido e pergunta.
|
|
75
|
+
- **`instagram-publisher`**: legenda via `--caption-file` (nunca interpolada no shell); só
|
|
76
|
+
aceita `.jpg`/`.jpeg` dentro de `crews/*/output/`; upload no imgBB expira em 24h; token da
|
|
77
|
+
Graph API no corpo dos POST, não na URL; fluxo preview → `--dry-run` → confirmação explícita
|
|
78
|
+
("publish"/"publicar") → publicação única.
|
|
79
|
+
|
|
80
|
+
### Changed
|
|
81
|
+
- Template `templates/AGENTS.md` (o `system.md` instalado) compactado (−24%) sem mudar o roteamento.
|
|
82
|
+
|
|
83
|
+
### Internal
|
|
84
|
+
- Testes por cenário da spec (F1-01a…F1-13a); trava nova: nenhum arquivo de `templates/`
|
|
85
|
+
carrega conteúdo do mantenedor. `KNOWN_BROKEN` de referências zerado.
|
|
86
|
+
|
|
87
|
+
### Added (governança do repositório — Fase 0)
|
|
88
|
+
- Auditoria geral da v1.4.1 com roadmap por fases: `docs/auditoria/2026-10-02-auditoria-geral.md`.
|
|
89
|
+
- Regras de desenvolvimento em `AGENTS.md` (project-standards T3, tabela Regra → Trava);
|
|
90
|
+
`CLAUDE.md` da raiz vira apontador versionado.
|
|
91
|
+
- Porta de verificação única `npm run verify` (`scripts/verify.js`): lint (agora inclui
|
|
92
|
+
`bin/`), testes (descobertos automaticamente em `tests/`), version-sync e alerta de
|
|
93
|
+
tamanho (`scripts/check-size.js`). CI e publish chamam a mesma porta.
|
|
94
|
+
- Travas novas: `tests/package.test.js` (conteúdo do tarball × README),
|
|
95
|
+
`tests/template-refs.test.js` (caminhos citados nos prompts existem),
|
|
96
|
+
`tests/verify.test.js` e `tests/check-size.test.js` (provetas).
|
|
97
|
+
- `GLOSSARIO.md`; `IDEIAS.md` reformatado com triagem e `Alocação: →`.
|
|
98
|
+
|
|
99
|
+
### Changed (governança do repositório — Fase 0)
|
|
100
|
+
- Dogfood do mantenedor sai da raiz e vai para `sandbox/` (fora do git).
|
|
101
|
+
- `publish.yml` confere a tag contra a versão do `package.json` e roda `npm run verify`;
|
|
102
|
+
CI e publish usam só `npm ci` (sem fallback que esconde drift do lockfile).
|
|
103
|
+
|
|
104
|
+
### Docs (Fase 0)
|
|
105
|
+
- README: o dashboard não é instalado pelo `init`; `update` não atualiza as pontes de IDE
|
|
106
|
+
(use `init --repair-bridges`); migração do `AGENTS.md` legado perde instruções extras;
|
|
107
|
+
flags `upgrade`, `--ide`, `--all`/`-y`, `--repair-bridges` documentadas; contagens
|
|
108
|
+
corrigidas (22 guias, 13 prompts); Windsurf removido da lista; promessa "30-70% de
|
|
109
|
+
economia" sem base removida.
|
|
110
|
+
|
|
6
111
|
## [1.4.1] — 2026-08-04
|
|
7
112
|
|
|
8
113
|
### Fixed
|
|
@@ -25,6 +130,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/).
|
|
|
25
130
|
- `AGENTS.md` — step 7b na seção Loading the Pipeline Runner.
|
|
26
131
|
- 3 novos testes de contrato em `docs.test.js`.
|
|
27
132
|
|
|
133
|
+
> Itens desta versão que ficaram de fora da entrada original (acrescentados na auditoria
|
|
134
|
+
> de 2026-10-02):
|
|
135
|
+
- **Pontes `.agents/`** para Antigravity, Gemini CLI e Qwen Code
|
|
136
|
+
(`.agents/skills/opencrew/SKILL.md`, `.agents/workflows/opencrew.md`).
|
|
137
|
+
- **`init --repair-bridges`**: regrava as pontes de IDE num workspace existente.
|
|
138
|
+
- **`update` migra `AGENTS.md` legado** (pré-1.3, sistema completo) para a ponte fina.
|
|
139
|
+
- **Fix Antigravity**: frontmatter no workflow para registrar `/opencrew`.
|
|
140
|
+
- ⚠️ **Regressão**: o `CLAUDE.md` gerado passou a conter a seção "STATUS.md (gestão de
|
|
141
|
+
sessão)" do fluxo pessoal do mantenedor, e `templates/gitignore` ganhou `STATUS.md`.
|
|
142
|
+
Correção prevista na 1.4.2 (F1-01).
|
|
143
|
+
|
|
28
144
|
## [1.3.3] — 2026-08-03
|
|
29
145
|
|
|
30
146
|
### Fixed
|
package/README.md
CHANGED
|
@@ -34,7 +34,12 @@ dentro da sua IDE.**
|
|
|
34
34
|
sem abrir editor nenhum.
|
|
35
35
|
- 🎛️ **Seleção inteligente de agentes** — o sistema analisa seu pedido e
|
|
36
36
|
sugere quais agentes são necessários para aquela tarefa. Você confirma ou
|
|
37
|
-
ajusta com um clique.
|
|
37
|
+
ajusta com um clique. Agentes pulados não gastam tokens naquele run.
|
|
38
|
+
- 🔎 **Revisor com dentes** — antes da revisão, um verificador automático mede o texto
|
|
39
|
+
(tamanho de título, meta description, legenda, hashtags, slides), barra placeholders,
|
|
40
|
+
termos que você proibiu e `[PREENCHER]` pendentes, e aponta afirmações a confirmar.
|
|
41
|
+
Bloqueio não passa, seja qual for a nota do revisor. A crew não inventa casos nem números:
|
|
42
|
+
quando falta um dado real, ela pergunta na aprovação final.
|
|
38
43
|
|
|
39
44
|
---
|
|
40
45
|
|
|
@@ -126,12 +131,12 @@ enxuto — todos apontam para a mesma fonte.
|
|
|
126
131
|
|---|---|
|
|
127
132
|
| `AGENTS.md` (ponte) + `.claude/skills/opencrew/SKILL.md` + `CLAUDE.md` | Claude Code |
|
|
128
133
|
| `AGENTS.md` (ponte) + `.agents/skills/opencrew/SKILL.md` | OpenAI Codex, Codex CLI |
|
|
129
|
-
| `AGENTS.md` (ponte) + `.cursor/rules/opencrew.mdc` | Cursor
|
|
134
|
+
| `AGENTS.md` (ponte) + `.cursor/rules/opencrew.mdc` | Cursor |
|
|
130
135
|
| `AGENTS.md` (ponte) + `.github/copilot-instructions.md` | VS Code + GitHub Copilot |
|
|
131
136
|
| `AGENTS.md` (ponte) + `.opencode/commands/opencrew.md` | OpenCode |
|
|
132
137
|
| `AGENTS.md` (ponte) + `.agent/rules/opencrew.md` + `.agent/workflows/opencrew.md` + `.agents/skills/opencrew/SKILL.md` + `.agents/workflows/opencrew.md` | Google Antigravity |
|
|
133
|
-
| `GEMINI.md` (ponte) | Gemini CLI |
|
|
134
|
-
| `QWEN.md` (ponte) | Qwen Code |
|
|
138
|
+
| `GEMINI.md` (ponte) + `.agents/skills/opencrew/SKILL.md` | Gemini CLI |
|
|
139
|
+
| `QWEN.md` (ponte) + `.agents/skills/opencrew/SKILL.md` | Qwen Code |
|
|
135
140
|
| `AGENTS.md` (ponte) + `.trae/rules/opencrew.md` | Trae |
|
|
136
141
|
|
|
137
142
|
> ⚠️ **Importante:** `CLAUDE.md`, `GEMINI.md` e os demais arquivos de IDE são
|
|
@@ -150,8 +155,8 @@ meu-projeto/
|
|
|
150
155
|
├── CLAUDE.md ← ponte fina + suas instruções (merge)
|
|
151
156
|
├── GEMINI.md ← ponte fina (Gemini CLI)
|
|
152
157
|
├── .mcp.json ← servidor Playwright do OpenCrew
|
|
153
|
-
├── .gitignore
|
|
154
|
-
├── .env.example
|
|
158
|
+
├── .gitignore ← bloco `# opencrew` no fim; suas linhas são mantidas
|
|
159
|
+
├── .env.example ← idem
|
|
155
160
|
│
|
|
156
161
|
├── _opencrew/
|
|
157
162
|
│ ├── core/
|
|
@@ -159,8 +164,8 @@ meu-projeto/
|
|
|
159
164
|
│ │ ├── runner.pipeline.md ← executor de pipeline
|
|
160
165
|
│ │ ├── skills.engine.md ← gerenciador de skills
|
|
161
166
|
│ │ ├── architect.agent.yaml ← definição do Arquiteto
|
|
162
|
-
│ │ ├── best-practices/ ←
|
|
163
|
-
│ │ └── prompts/ ←
|
|
167
|
+
│ │ ├── best-practices/ ← 22 guias de melhores práticas + _catalog.yaml
|
|
168
|
+
│ │ └── prompts/ ← 13 prompts de fase (discovery, design, build, etc.)
|
|
164
169
|
│ ├── agents/ ← 5 agentes base compartilhados
|
|
165
170
|
│ │ ├── researcher.agent.md
|
|
166
171
|
│ │ ├── copywriter.agent.md
|
|
@@ -185,11 +190,12 @@ meu-projeto/
|
|
|
185
190
|
│ ├── instagram-publisher/ ← publicação no Instagram
|
|
186
191
|
│ ├── resend/ ← envio de emails
|
|
187
192
|
│ └── ...
|
|
188
|
-
│
|
|
189
|
-
└── dashboard/
|
|
190
|
-
└── index.html ← dashboard visual (opcional, offline)
|
|
191
193
|
```
|
|
192
194
|
|
|
195
|
+
> O dashboard visual (`dashboard/index.html`) **não é instalado** pelo `init` — ele vive
|
|
196
|
+
> só no repositório do OpenCrew e ainda é experimental (decisão de publicar ou remover:
|
|
197
|
+
> Fase 4 da auditoria em `docs/auditoria/`).
|
|
198
|
+
|
|
193
199
|
---
|
|
194
200
|
|
|
195
201
|
## Mantendo o OpenCrew atualizado
|
|
@@ -198,18 +204,29 @@ meu-projeto/
|
|
|
198
204
|
npx @aksp/opencrew update
|
|
199
205
|
```
|
|
200
206
|
|
|
201
|
-
O `update`
|
|
207
|
+
O `update` não toca nas suas crews nem na sua memória. Ele atualiza apenas:
|
|
202
208
|
|
|
203
209
|
| O que é atualizado | O que NUNCA é tocado |
|
|
204
210
|
|---|---|
|
|
205
|
-
| `_opencrew/core/` (framework) | `crews/` (suas crews) |
|
|
206
|
-
| Skills do catálogo | `_opencrew/_memory/` (perfil, preferências) |
|
|
211
|
+
| `_opencrew/core/` (framework — sobrescrito por inteiro) | `crews/` (suas crews) |
|
|
212
|
+
| Skills do catálogo (sobrescritas — edições locais se perdem) | `_opencrew/_memory/` (perfil, preferências) |
|
|
207
213
|
| `_opencrew/core/system.md` | `.env` (suas chaves) |
|
|
208
|
-
| Bloco `<!-- opencrew -->`
|
|
214
|
+
| Bloco `<!-- opencrew -->` no `AGENTS.md` | Pontes das IDEs (`CLAUDE.md`, `.cursor/`, `.agents/`…) |
|
|
215
|
+
|
|
216
|
+
As **pontes das IDEs não são atualizadas** pelo `update`. Para regravá-las com a versão
|
|
217
|
+
atual, use:
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
npx @aksp/opencrew init --repair-bridges --ide=claude-code
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
(troque `claude-code` pelas IDEs que você usa, separadas por vírgula; sem `--ide`, o
|
|
224
|
+
comando grava as pontes de **todas** as IDEs suportadas).
|
|
209
225
|
|
|
210
226
|
Se você está migrando de uma versão anterior a v1.3, o `update` detecta
|
|
211
227
|
AGENTS.md legados (sistema completo de 150 linhas) e os substitui pela ponte
|
|
212
|
-
fina
|
|
228
|
+
fina. Desde a v1.4.2 o arquivo original é copiado antes para `AGENTS.md.bak` (até a v1.4.1,
|
|
229
|
+
instruções suas adicionadas a esse `AGENTS.md` legado eram perdidas).
|
|
213
230
|
|
|
214
231
|
Para verificar se há atualização disponível sem aplicar:
|
|
215
232
|
|
|
@@ -245,7 +262,11 @@ npx @aksp/opencrew update --check
|
|
|
245
262
|
|---|---|
|
|
246
263
|
| `npx @aksp/opencrew init` | Instala o OpenCrew na pasta atual |
|
|
247
264
|
| `npx @aksp/opencrew update` | Atualiza o framework |
|
|
248
|
-
| `npx @aksp/opencrew update --check` | Verifica se há update disponível |
|
|
265
|
+
| `npx @aksp/opencrew update --check` (ou `--dry-run`) | Verifica se há update disponível, sem alterar nada |
|
|
266
|
+
| `npx @aksp/opencrew upgrade` | Atalho para `update` |
|
|
267
|
+
| `npx @aksp/opencrew init --ide=claude-code,cursor` | Instala só as pontes das IDEs indicadas |
|
|
268
|
+
| `npx @aksp/opencrew init --all` (ou `-y`) | Instala as pontes de todas as IDEs |
|
|
269
|
+
| `npx @aksp/opencrew init --repair-bridges` | Regrava as pontes de IDE num workspace existente |
|
|
249
270
|
| `npx @aksp/opencrew version` | Mostra a versão instalada |
|
|
250
271
|
| `npx @aksp/opencrew help` | Mostra ajuda dos comandos CLI |
|
|
251
272
|
|
package/bin/opencrew.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// opencrew — CLI entry point
|
|
3
|
-
import { run } from '../src/cli.js';
|
|
3
|
+
import { run, reportError } from '../src/cli.js';
|
|
4
4
|
|
|
5
|
-
run(
|
|
6
|
-
|
|
7
|
-
process.
|
|
5
|
+
// run() reports its own errors; this only catches a failure inside the reporter itself.
|
|
6
|
+
run(process.argv.slice(2)).catch((e) => {
|
|
7
|
+
process.exitCode = reportError(e);
|
|
8
8
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aksp/opencrew",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -19,8 +19,9 @@
|
|
|
19
19
|
],
|
|
20
20
|
"scripts": {
|
|
21
21
|
"start": "node bin/opencrew.js",
|
|
22
|
-
"test": "node
|
|
23
|
-
"lint": "
|
|
22
|
+
"test": "node scripts/verify.js test",
|
|
23
|
+
"lint": "node scripts/verify.js lint",
|
|
24
|
+
"verify": "node scripts/verify.js",
|
|
24
25
|
"version": "node scripts/stamp-version.js && git add templates/_opencrew/.opencrew-version"
|
|
25
26
|
},
|
|
26
27
|
"keywords": [
|
package/src/cli.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { parseArgs as nodeParseArgs } from 'node:util';
|
|
1
2
|
import { readJson } from './lib/fsx.js';
|
|
2
3
|
import { packageJsonPath } from './lib/paths.js';
|
|
3
4
|
import { allIdeIds } from './lib/ides.js';
|
|
5
|
+
import { UsageError, isPromptCancel } from './lib/errors.js';
|
|
4
6
|
import { init } from './commands/init.js';
|
|
5
7
|
import { update } from './commands/update.js';
|
|
6
8
|
import { c, log, err, warn, info } from './lib/ui.js';
|
|
@@ -31,27 +33,56 @@ function lt(a, b) {
|
|
|
31
33
|
return false; // equal
|
|
32
34
|
}
|
|
33
35
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
36
|
+
const OPTION_SPEC = {
|
|
37
|
+
help: { type: 'boolean', short: 'h' },
|
|
38
|
+
version: { type: 'boolean', short: 'v' },
|
|
39
|
+
ide: { type: 'string' },
|
|
40
|
+
all: { type: 'boolean' },
|
|
41
|
+
yes: { type: 'boolean', short: 'y' },
|
|
42
|
+
'repair-bridges': { type: 'boolean' },
|
|
43
|
+
check: { type: 'boolean' },
|
|
44
|
+
'dry-run': { type: 'boolean' },
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const GLOBAL_OPTIONS = ['help', 'version'];
|
|
48
|
+
const COMMAND_OPTIONS = {
|
|
49
|
+
init: ['ide', 'all', 'yes', 'repair-bridges'],
|
|
50
|
+
update: ['check', 'dry-run'],
|
|
51
|
+
upgrade: ['check', 'dry-run'],
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Strict argument parsing: unknown options, options of another command and stray
|
|
56
|
+
* positionals are UsageErrors — raised before any command can write a file.
|
|
57
|
+
*/
|
|
58
|
+
export function parseArgs(argv) {
|
|
59
|
+
let parsed;
|
|
60
|
+
try {
|
|
61
|
+
parsed = nodeParseArgs({ args: argv, options: OPTION_SPEC, allowPositionals: true, strict: true, tokens: true });
|
|
62
|
+
} catch (e) {
|
|
63
|
+
const unknown = e.code === 'ERR_PARSE_ARGS_UNKNOWN_OPTION' && e.message.match(/'([^']+)'/);
|
|
64
|
+
const cmd = argv.find((a) => !a.startsWith('-')) ?? 'init';
|
|
65
|
+
throw new UsageError(unknown
|
|
66
|
+
? `Unknown option '${unknown[1]}' for "${cmd}".`
|
|
67
|
+
: e.message.split('. ')[0].replace(/\.$/, '') + '.');
|
|
49
68
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
69
|
+
const [cmd, ...extra] = parsed.positionals;
|
|
70
|
+
const { values } = parsed;
|
|
71
|
+
const command = cmd ?? (values.version ? 'version' : values.help ? 'help' : 'init');
|
|
72
|
+
|
|
73
|
+
const allowed = new Set([...GLOBAL_OPTIONS, ...(COMMAND_OPTIONS[command] ?? [])]);
|
|
74
|
+
const foreign = parsed.tokens.find((t) => t.kind === 'option' && !allowed.has(t.name));
|
|
75
|
+
if (foreign) throw new UsageError(`Unknown option '${foreign.rawName}' for "${command}".`);
|
|
76
|
+
|
|
77
|
+
if (extra.length) {
|
|
78
|
+
throw new UsageError(command === 'init'
|
|
79
|
+
? 'init does not take a directory — cd into the project folder first.'
|
|
80
|
+
: `Unexpected argument "${extra[0]}" for "${command}".`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const opts = { ...values, _: parsed.positionals };
|
|
84
|
+
if (opts['dry-run']) opts.check = true;
|
|
85
|
+
return { command, opts };
|
|
55
86
|
}
|
|
56
87
|
|
|
57
88
|
function help(version) {
|
|
@@ -65,8 +96,8 @@ ${c.bold('Commands')}
|
|
|
65
96
|
init Scaffold an opencrew workspace in the current folder
|
|
66
97
|
update Refresh the framework (keeps your crews, memory and .env)
|
|
67
98
|
upgrade Alias for update
|
|
68
|
-
help Show this help
|
|
69
|
-
version Print the version
|
|
99
|
+
help Show this help (also: <command> --help, -h)
|
|
100
|
+
version Print the version (also: --version, -v)
|
|
70
101
|
|
|
71
102
|
${c.bold('Options for init')}
|
|
72
103
|
--ide=a,b Preselect IDEs (skip the prompt). Valid: ${allIdeIds().join(', ')}
|
|
@@ -75,7 +106,8 @@ ${c.bold('Options for init')}
|
|
|
75
106
|
--repair-bridges Regenerate IDE bridge files in an existing workspace
|
|
76
107
|
|
|
77
108
|
${c.bold('Options for update')}
|
|
78
|
-
--check
|
|
109
|
+
--check Report whether an update is available without making changes
|
|
110
|
+
--dry-run Same as --check
|
|
79
111
|
|
|
80
112
|
${c.bold('Examples')}
|
|
81
113
|
npx @aksp/opencrew init
|
|
@@ -87,8 +119,26 @@ ${c.bold('Examples')}
|
|
|
87
119
|
`);
|
|
88
120
|
}
|
|
89
121
|
|
|
90
|
-
|
|
91
|
-
|
|
122
|
+
/** Turn any error into one readable line and an exit code. Stack only with OPENCREW_DEBUG=1. */
|
|
123
|
+
export function reportError(e) {
|
|
124
|
+
if (isPromptCancel(e)) {
|
|
125
|
+
warn('Cancelled — nothing was written.');
|
|
126
|
+
return 130;
|
|
127
|
+
}
|
|
128
|
+
err(e?.message ?? String(e));
|
|
129
|
+
if (e instanceof UsageError) info(`Run ${c.cyan('npx @aksp/opencrew help')} for usage.`);
|
|
130
|
+
else if (process.env.OPENCREW_DEBUG) console.error(e?.stack);
|
|
131
|
+
return 1;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export async function run(argv, { commands = { init, update } } = {}) {
|
|
135
|
+
let command, opts;
|
|
136
|
+
try {
|
|
137
|
+
({ command, opts } = parseArgs(argv));
|
|
138
|
+
} catch (e) {
|
|
139
|
+
process.exitCode = reportError(e);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
92
142
|
|
|
93
143
|
let version = 'unknown';
|
|
94
144
|
let engines = {};
|
|
@@ -115,23 +165,23 @@ export async function run(argv) {
|
|
|
115
165
|
}
|
|
116
166
|
}
|
|
117
167
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
168
|
+
// --version / --help never run a command (they may follow any command).
|
|
169
|
+
if (opts.version || command === 'version') return log(version);
|
|
170
|
+
if (opts.help || command === 'help') return help(version);
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
switch (command) {
|
|
174
|
+
case 'init':
|
|
175
|
+
return await commands.init(opts);
|
|
176
|
+
case 'update':
|
|
177
|
+
case 'upgrade':
|
|
178
|
+
return await commands.update(opts);
|
|
179
|
+
default:
|
|
180
|
+
err(`Unknown command: ${command}`);
|
|
181
|
+
help(version);
|
|
182
|
+
process.exitCode = 1;
|
|
183
|
+
}
|
|
184
|
+
} catch (e) {
|
|
185
|
+
process.exitCode = reportError(e);
|
|
136
186
|
}
|
|
137
187
|
}
|