theforge-cli 0.1.0 → 0.2.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.
@@ -7,32 +7,34 @@ vive em módulos especializados.**
7
7
 
8
8
  ## Mapa de módulos
9
9
 
10
- | Módulo | Responsabilidade |
11
- | ------------------------ | ------------------------------------------------------------------------------------------------------ |
12
- | `src/index.ts` | Só despacha: parse de args, menu, resolução do subcomando, SIGINT e erros fatais. |
13
- | `src/cmd/shared.ts` | Banner, preflight, catálogo efetivo, `--json`, seleção de Forjas por nome. |
14
- | `src/cmd/batch.ts` | Execução sequencial com spinner/taskLog por Forja. |
15
- | `src/cmd/query.ts` | `doctor`, `list`, `status`, `update --check`, `versions` (com `--json`). |
16
- | `src/cmd/manage.ts` | `pin`, `unpin`, `rollback`, `uninstall <forjas>`, `self update`, PATH. |
17
- | `src/cmd/run.ts` | Fluxo de instalação/clone/update/reinstall e resumo de execução com aprovação. |
18
- | `src/prompts.ts` | Prompts interativos (diretório, seleção, confirmação) e tratamento de cancelamento via `isCancel()`. |
19
- | `src/preflight.ts` | Verificações de ambiente antes de qualquer prompt: `git`, Node.js >= 20, `powershell.exe` no Windows. |
20
- | `src/runner.ts` | Pipeline por repositório com cronômetro e fases: sincronizar → `.env` → script de instalação. |
21
- | `src/env.ts` | Provisionamento do `.env` (`planEnvFile` decide, `ensureEnvFile` executa). |
22
- | `src/report.ts` | Badges de status, linha por repositório e relatório consolidado. |
23
- | `src/ui/theme.ts` | Paleta de cores (picocolors) e ícones emoji. Único ponto de contato com cores. |
24
- | `src/ui/strings.ts` | Todas as strings visíveis ao usuário, em PT-BR. |
25
- | `src/constants/repos.ts` | Catálogo fixo dos oito repositórios Forge. |
26
- | `src/utils/system.ts` | Primitivas: resolução de caminho, `.git`, seleção de script por SO, `execa`, detalhe de erro. |
27
- | `src/commands.ts` | Resolve o subcomando (`install`, `clone`, `update`, `pin`, `self ...`) a partir dos posicionais. |
28
- | `src/menu.ts` | Menu principal; só aparece com TTY e sem comando/flags de seleção. |
29
- | `src/safety.ts` | Nomes/URLs de repositório válidos, bloqueio de symlink/junction, redação de segredos, escrita atômica. |
30
- | `src/git-revs.ts` | Tags, checkout destacado por ref/versão, verificação de atualização via fetch. |
31
- | `src/lifecycle.ts` | Plano e execução de uninstall (com backup) e rollback de checkout. |
32
- | `src/native-install.ts` | Monta o `<cli> install` nativo da Forja com as flags que ela documenta. |
33
- | `src/status.ts` | Fase local (não clonado, clonado, instalado, falha), versão e alterações. |
34
- | `src/self.ts` | Detecta o método de instalação do CLI e planeja o `self update`. |
35
- | `src/types.ts` | Tipos compartilhados (`ForgeRepo`, `RepoResult`, `InstallScript`, `PreflightCheck`). |
10
+ | Módulo | Responsabilidade |
11
+ | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
12
+ | `src/index.ts` | Só despacha: parse de args, menu, resolução do subcomando, SIGINT e erros fatais. |
13
+ | `src/cmd/shared.ts` | Banner, preflight, catálogo efetivo, `--json`, seleção de Forjas por nome. |
14
+ | `src/cmd/batch.ts` | Execução sequencial com spinner/taskLog por Forja. |
15
+ | `src/cmd/query.ts` | `doctor`, `list`, `status`, `update --check`, `versions` (com `--json`). |
16
+ | `src/cmd/manage.ts` | `pin`, `unpin`, `rollback`, `uninstall <forjas>`, `self update`, PATH. |
17
+ | `src/cmd/run.ts` | Fluxo de instalação/clone/update/reinstall e resumo de execução com aprovação. |
18
+ | `src/cmd/registry.ts` | `install/uninstall --method registry` e `registry`: plano, confirmação, `--json`/`--report`, probes. |
19
+ | `src/registry/` | Método registry-first: `catalog` (dists PyPI), `pypi` (resolve + cache), `versions` (PEP 440 subset), `install` (venv isolada, sha256, receipt/state). |
20
+ | `src/prompts.ts` | Prompts interativos (diretório, seleção, confirmação) e tratamento de cancelamento via `isCancel()`. |
21
+ | `src/preflight.ts` | Verificações de ambiente antes de qualquer prompt: `git`, Node.js >= 20, `powershell.exe` no Windows. |
22
+ | `src/runner.ts` | Pipeline por repositório com cronômetro e fases: sincronizar → `.env` → script de instalação. |
23
+ | `src/env.ts` | Provisionamento do `.env` (`planEnvFile` decide, `ensureEnvFile` executa). |
24
+ | `src/report.ts` | Badges de status, linha por repositório e relatório consolidado. |
25
+ | `src/ui/theme.ts` | Paleta de cores (picocolors) e ícones emoji. Único ponto de contato com cores. |
26
+ | `src/ui/strings.ts` | Todas as strings visíveis ao usuário, em PT-BR. |
27
+ | `src/constants/repos.ts` | Catálogo fixo dos oito repositórios Forge. |
28
+ | `src/utils/system.ts` | Primitivas: resolução de caminho, `.git`, seleção de script por SO, `execa`, detalhe de erro. |
29
+ | `src/commands.ts` | Resolve o subcomando (`install`, `clone`, `update`, `pin`, `self ...`) a partir dos posicionais. |
30
+ | `src/menu.ts` | Menu principal; só aparece com TTY e sem comando/flags de seleção. |
31
+ | `src/safety.ts` | Nomes/URLs de repositório válidos, bloqueio de symlink/junction, redação de segredos, escrita atômica. |
32
+ | `src/git-revs.ts` | Tags, checkout destacado por ref/versão, verificação de atualização via fetch. |
33
+ | `src/lifecycle.ts` | Plano e execução de uninstall (com backup) e rollback de checkout. |
34
+ | `src/native-install.ts` | Monta o `<cli> install` nativo da Forja com as flags que ela documenta. |
35
+ | `src/status.ts` | Fase local (não clonado, clonado, instalado, falha), versão e alterações. |
36
+ | `src/self.ts` | Detecta o método de instalação do CLI e planeja o `self update`. |
37
+ | `src/types.ts` | Tipos compartilhados (`ForgeRepo`, `RepoResult`, `InstallScript`, `PreflightCheck`). |
36
38
 
37
39
  ## Fluxo de execução
38
40
 
@@ -17,6 +17,53 @@ Revisões inspecionadas (HEAD de `main` em 2026-10-12):
17
17
  | forge-doctor-data | `3ac1cae` |
18
18
  | forge-portal | `d0b6ba9` |
19
19
 
20
+ ## Métodos de instalação do forge-cli
21
+
22
+ - **git** (default) — clone + bootstrap `setup.sh`/`setup.ps1` +
23
+ instalador nativo sob opt-in (`--native`, `--profile`, `--scope`,
24
+ `--components`). É o fluxo descrito na matriz abaixo.
25
+ - **registry** — `install <forja> --method registry`: resolve a dist no
26
+ PyPI, cria venv isolada em `~/.forge-cli/forges/<nome>/venv`, baixa a
27
+ wheel para `~/.forge-cli/cache/wheels/`, confere sha256 contra o
28
+ digest publicado e instala pelo caminho do artefato verificado.
29
+ `uninstall --method registry` remove venv + wheel cache + receipt com
30
+ confirmação; `--plan`, `--offline`, `--clean`, `--version` pinado,
31
+ `--json` e `--report` são honrados. `--native` delega ao instalador
32
+ nativo **da venv** (dry-run sem `--yes`; `--profile`/`--scope`/
33
+ `--components`/`--host`/`--gateway-profile` passam adiante);
34
+ Forjas cujo instalador é script de repositório (forge-doctor-api)
35
+ recebem skip honesto — a wheel não carrega o script.
36
+ - **lifecycle registry** — `update` (mais nova no PyPI; `--check` só
37
+ planeja; respeita pin), `reinstall` (mesma versão do receipt — reparo
38
+ de drift), `rollback` (desempilha `history` do receipt; rollback de
39
+ rollback funciona), `pin`/`unpin` (trava a versão instalada no state).
40
+ Tudo com o mesmo contrato: receipts atômicos, `--json`/`--report`,
41
+ flags git ignoradas avisadas, nomes desconhecidos → exit 2, falhas → 1.
42
+ - **hosts/MCP sem clone** — `forge-cli mcp <forja>` resolve o argv MCP
43
+ (forge.json do clone ou template `mcp` do catálogo + receipt:
44
+ `{bin}` = bin da venv, `{binDir}/x` = script irmão); `--verify` roda
45
+ `--version` como handshake. `hosts plan|register|validate|rollback`
46
+ usam o mesmo descritor — `hosts register` grava `mcpServers` apontando
47
+ para o binário da venv registry.
48
+ - **portal** — `GET /api/v1/registry/state` no forge-portal projeta
49
+ `~/.forge-cli/forges/state.json` (contrato
50
+ `docs/registry-state-contract.md`).
51
+
52
+ ## Matriz registry (PyPI)
53
+
54
+ | Forja | Dist PyPI | Piso Python | MCP (venv) | Nativo `--native` |
55
+ | ----------------- | ------------------- | ----------- | ---------------------------- | --------------------------- |
56
+ | the-forge | `theforge-core` | >=3.11 | — (sem mcp_command) | `theforge install apply` |
57
+ | api-forge | `apiforge-af` | >=3.12 | `apiforge-mcp` | `apiforge install` |
58
+ | platform-forge | `platformforge` | >=3.10 | `platformforge-mcp` | `platformforge install` |
59
+ | spark-forge-aws | `sparkforge-aws` | >=3.10 | `sparkforge-aws mcp serve` | `sparkforge-aws install` |
60
+ | spark-forge-azure | `sparkforge-azure` | >=3.10 | `sparkforge-azure mcp serve` | `sparkforge-azure install` |
61
+ | forge-doctor-data | `forge-doctor-data` | >=3.11 | `forge-doctor-data mcp` | `forge-doctor-data install` |
62
+ | forge-doctor-api | `forge-doctor-api` | >=3.11 | `forge-doctor-api mcp` | **não** (script de repo) |
63
+
64
+ `requires_python` publicado no PyPI é autoritativo por release; o piso da
65
+ tabela compõe com ele (o mais restritivo vence).
66
+
20
67
  ## Modelo de instalação em duas fases
21
68
 
22
69
  1. **Bootstrap** — `setup.sh` / `setup.ps1` (todas as Forjas Python) chamam
@@ -0,0 +1,123 @@
1
+ # Contrato de estado registry — `forge-cli/RegistryState/v1`
2
+
3
+ Fonte única do formato que `forge-cli` escreve e que leitores externos
4
+ (forge-portal, scripts, hosts) podem consumir. O CLI é o único escritor;
5
+ leitores são somente-leitura e nunca inventam campos.
6
+
7
+ ## Arquivos
8
+
9
+ | Arquivo | Conteúdo | Escrita |
10
+ | ----------------------------------------- | ----------------------------------------------------- | --------------------------- |
11
+ | `~/.forge-cli/forges/state.json` | Índice agregado `{ "forges": { <nome>: <receipt> } }` | atômica (`writeFileAtomic`) |
12
+ | `~/.forge-cli/forges/<nome>/receipt.json` | Receipt por Forja | atômica |
13
+ | `~/.forge-cli/forges/<nome>/install.json` | Metadados da instalação ativa | atômica |
14
+ | `~/.forge-cli/forges/state.json.corrupt` | Backup quando o state não parseia | rename |
15
+
16
+ `~` = `os.homedir()` do processo CLI (override de testes: `home` explícito
17
+ nas APIs; o portal honra `FORGE_HOME_OVERRIDE`).
18
+
19
+ ## `state.json`
20
+
21
+ ```json
22
+ {
23
+ "forges": {
24
+ "<nome-da-forja>": { "<receipt>" }
25
+ }
26
+ }
27
+ ```
28
+
29
+ - Chaves são nomes de Forja validados por `isSafeRepoName`
30
+ (`[a-z0-9][a-z0-9-]*`, sem `..`, `/`, `\`) — leitores podem confiar que
31
+ chaves não carregam traversal.
32
+ - Valores não-objeto são filtrados na leitura (state poisoned degrada
33
+ para ausência, nunca crash).
34
+
35
+ ## Receipt (por Forja)
36
+
37
+ | Campo | Tipo | Semântica |
38
+ | ---------------- | --------------------------- | ------------------------------------ |
39
+ | `forge` | string | nome da Forja |
40
+ | `method` | `"registry"` | método de instalação |
41
+ | `dist` | string | distribuição PyPI instalada |
42
+ | `version` | string | versão resolvida (PEP 440) |
43
+ | `requiresPython` | string? | spec do release (PyPI) |
44
+ | `python` | string | interpretador usado na venv |
45
+ | `source` | string | `pypi` ou `offline-cache` |
46
+ | `venv` | string | path absoluto da venv |
47
+ | `bin` | string | path absoluto do console script |
48
+ | `installedAt` | string | ISO 8601 |
49
+ | `verifiedHash` | string | `sha256:<hex>` do artefato instalado |
50
+ | `verify.ok` | bool | pós-install `<bin> --help` passou |
51
+ | `files` | string[] | artefatos gravados (bounded) |
52
+ | `yanked` | bool | release marcada yanked no PyPI |
53
+ | `staleCache` | bool | metadados vieram de cache expirado |
54
+ | `pinned` | string? | pin ativo (`pin --method registry`) |
55
+ | `history` | `{version, installedAt}[]`? | versões anteriores (rollback) |
56
+
57
+ Campos novos podem aparecer em versões futuras do CLI — leitores devem
58
+ tolerar campos desconhecidos e tratar campos conhecidos ausentes como
59
+ `null`/não-presentes.
60
+
61
+ ## Leitura (forge-portal)
62
+
63
+ `portal_core.forge_portal.read_registry_state()` projeta o state em
64
+ `forge-cli/RegistryState/v1`:
65
+
66
+ ```json
67
+ {
68
+ "schema": "forge-cli/RegistryState/v1",
69
+ "state_path": "...",
70
+ "present": true,
71
+ "forges": { "<nome>": { "<receipt projetado>" } }
72
+ }
73
+ ```
74
+
75
+ - `present: false` (+ `error: "malformed"` quando aplicável) se o
76
+ arquivo não existir, não parsear ou `forges` não for objeto —
77
+ o portal renderiza "não instalado", nunca um receipt inventado.
78
+ - `GET /api/v1/registry/state` expõe a mesma projeção (somente-leitura,
79
+ host loopback + sessão, como toda a API do portal).
80
+ - Projeção é whitelist de escalares/containers pequenos — nada de
81
+ objetos arbitrários vindos do disco.
82
+
83
+ ## Consistência e recuperação
84
+
85
+ O CLI trata `state.json` + `install.json` + venv como um único fato.
86
+ `installConsistency(home, name)` compara os três e classifica:
87
+
88
+ - `ok` — as três fontes concordam;
89
+ - `not-installed` — nada no state, nada no disco;
90
+ - `orphaned` — diretório/venv presente sem entrada no state
91
+ (típico de kill entre "venv criada" e "state commitado");
92
+ - `missing-receipt` — entrada no state sem `install.json`;
93
+ - `unreadable-receipt` — `install.json` existe mas não parseia;
94
+ - `forge-mismatch` / `version-mismatch` — receipt diverge do state;
95
+ - `missing-venv` — registro coerente, venv ausente.
96
+
97
+ Regras do contrato de consistência:
98
+
99
+ 1. Qualquer status diferente de `ok`/`not-installed` é exposto por
100
+ `forge-cli registry` (`consistent: false` + `consistency`) — uma
101
+ instalação pela metade nunca aparece saudável.
102
+ 2. `install` recusa com `stage: "incomplete"` sobre estado
103
+ inconsistente; `--clean` é o caminho de reparo (backup da instalação
104
+ anterior até o commit, restauração em qualquer falha).
105
+ 3. `uninstall --method registry` remove também órfãs (disco sem state),
106
+ senão o reparo ficaria sem saída.
107
+ 4. O commit é a última fronteira: receipt + state são gravados juntos
108
+ sob o lifecycle lock; falha nesse ponto é `stage: "persist"` com o
109
+ errno (EACCES/ENOSPC) no detalhe — o disco pode conter a venv, mas
110
+ a instalação permanece "não saudável" até o reparo.
111
+ 5. Escritas são atômicas (tmp + fsync + rename) e varrem `*.tmp`
112
+ órfãos de runs mortos com SIGKILL.
113
+
114
+ ## Lifecycle lock
115
+
116
+ `~/.forge-cli/forges/.forge-lifecycle.lock` é um diretório criado com
117
+ `mkdir` (atômico por construção de fs) contendo `info.json`
118
+ (`pid`, `at`). Titular com PID morto é quebrado na hora; lock sem
119
+ metadata legível só é stale após o threshold de idade; a liberação é
120
+ best-effort. O lock serializa install/uninstall/pin/update — quem
121
+ perde a corrida re-checa o state sob o lock antes de recusar, então
122
+ `already-installed`/`incomplete` refletem o estado pós-serialização,
123
+ nunca uma corrida de observação.
@@ -83,3 +83,49 @@ erro de binário esbuild, rode
83
83
  spec nova.
84
84
  - `vitest` é `^5` para evitar vulnerabilidades do `tinypool` (ver
85
85
  `npm audit`).
86
+
87
+ ## Instalação registry incompleta ou interrompida
88
+
89
+ Uma instalação morta no meio (SIGKILL, queda de energia, disco cheio)
90
+ nunca se apresenta como saudável: `forge-cli registry` marca a linha
91
+ com `incompleta` e o JSON carrega `consistency` com o motivo exato:
92
+
93
+ | `consistency` | Significado |
94
+ | -------------------- | -------------------------------------------------- |
95
+ | `orphaned` | venv/arquivos no disco sem entrada no `state.json` |
96
+ | `missing-receipt` | entrada no state, mas `install.json` ausente |
97
+ | `unreadable-receipt` | `install.json` não parseia |
98
+ | `forge-mismatch` | `install.json` registra outra Forja |
99
+ | `version-mismatch` | `install.json` e `state.json` divergem na versão |
100
+ | `missing-venv` | state+receipt coerentes, mas a venv sumiu |
101
+
102
+ Reparo documentado (as duas únicas portas de saída honestas):
103
+
104
+ ```bash
105
+ forge-cli reinstall --method registry <forja> --clean # regrava tudo com backup da anterior
106
+ forge-cli uninstall --method registry <forja> # remove venv + state + receipt (inclui órfãs)
107
+ ```
108
+
109
+ `install` sem `--clean` recusa com `incomplete` — ela nunca sobrescreve
110
+ uma instalação pela metade fingindo que já existia.
111
+
112
+ ## Lifecycle lock preso (`busy`)
113
+
114
+ Operações registry (install/uninstall/pin) serializam por
115
+ `~/.forge-cli/forges/.forge-lifecycle.lock`. Se o erro diz `busy`:
116
+
117
+ - **Outra execução em andamento** — espere terminar; o titular é o PID
118
+ gravado em `.forge-lifecycle.lock/info.json`.
119
+ - **Titular morto** (o PID não existe mais) — o próprio CLI detecta e
120
+ quebra o lock automaticamente na próxima execução; nada a fazer.
121
+ - **Lock órfão sem metadata legível** — só é considerado stale depois
122
+ de expirar o threshold de idade; se persistir além disso, remova o
123
+ diretório `.forge-lifecycle.lock` manualmente (seguro: quem cria o
124
+ lock nunca tem trabalho a perder fora dele).
125
+
126
+ ## `state.json` corrompido
127
+
128
+ Leitura nunca escreve: um `state.json` que não parseia é tratado como
129
+ vazio (nada é removido nem sobrescrito) e a evidência só vai para
130
+ `state.json.corrupt` no momento em que uma escrita real acontecer —
131
+ assim `--plan`/`--check`/`registry` (list) não têm efeito colateral.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "theforge-cli",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "CLI interativa para clonar e configurar o ecossistema Forge",
5
5
  "type": "module",
6
6
  "bin": {