Metadata-Version: 2.5
Name: shade-cli
Version: 0.1.5
Summary: O comando `shade`: cria, ensaia, testa e empacota robôs da ShadeOne escritos com o shade-sdk.
Author: ShadeOne
License-Expression: Apache-2.0
License-File: LICENSE
Requires-Python: >=3.11
Requires-Dist: keyring>=25.6
Requires-Dist: pathspec>=1.1
Description-Content-Type: text/markdown

# shade-cli

O comando `shade`: cria, ensaia, testa, empacota e envia robôs da ShadeOne escritos com o
`shade-sdk`. O ciclo local roda sem conta; `auth`, `deploy` e `releases` falam com a plataforma
pela chave de API. Nunca importa o runtime no
próprio processo: o projeto roda em subprocesso, no venv dele (`uv run` na pasta), com o
`shade-sdk` que ele fixa. Spec: `docs/specs/sdk-python-cli-e-mcp.md` (§6 e §8); ADR 019 (D3,
D4, D11 e D15).

```bash
uv tool install shade-cli
shade init cadastro-erp          # starter de produção (--minimo para o enxuto)
cd cadastro-erp
uv run playwright install chromium
shade test                       # lint de contrato + pytest do projeto
shade run                        # ensaio: para antes de cada efetivar()
shade build                      # artefato determinístico em .shade/build/
```

## Comandos

| Comando | Faz |
|---|---|
| `shade init <pasta> [--minimo] [--modelo lista\|tarefa] [--editor vscode] [--id ID] [--nome NOME] [--sem-sync]` | o starter (produção por padrão) e o `uv sync`; nunca pergunta |
| `shade run [pasta] [--real [--sim]] [--entrada ARQ] [--item CHAVE] [--debug] [--trace] [--como-plataforma] [--headless]` | ensaio por padrão; `--real` pede TTY ou `--sim` |
| `shade test [pasta]` | lint (L1 a L8) e o pytest do projeto |
| `shade build [pasta]` | `projeto.tar.gz` (formato da HU-22.1) e `manifesto.json` em `.shade/build/` |
| `shade doctor [pasta]` | Python, uv, Chromium do Playwright, Docker e o `shade-sdk` do projeto |
| `shade docs [tópico] [--pasta P]` | a referência da versão do `shade-sdk` do projeto |
| `shade inspecionar <url> [--pagina Nome] [--pasta P]` | abre a página no Chromium (contexto efêmero, rede só em `[hosts] permitidos`), grava o que você clica e preenche, e propõe `Pagina` com `Alvo`s já conferidos pelo diagnóstico do SDK; Enter no terminal encerra (`--headless` varre a página sem janela) |

### Com a chave de API

| Comando | Faz |
|---|---|
| `shade auth login [--url URL]` | lê a chave sem eco (ou de uma linha do stdin, sem terminal), confere com a plataforma e guarda URL, chave e workspace no keyring do sistema |
| `shade auth status` | de onde vem a conta, o workspace e os escopos da chave, conferidos agora |
| `shade auth logout` | apaga a conta do keyring |
| `shade deploy [pasta] [--criar] [--nao-esperar] [--prazo S]` | confere `/sdk/compat`, monta o artefato em memória, envia e acompanha `recebida → conferida → em ensaio → aguardando aprovação`; o `robo.id` novo pede `--criar` sem terminal |
| `shade releases list [pasta] [--robo ID] [--limite N]` | as versões do robô |
| `shade releases show <id> [--esperar] [--prazo S]` | uma versão; com `--esperar`, acompanha como o `deploy` |
| `shade releases ensaiar <id> [--esperar] [--prazo S]` | repete o ensaio que caiu por infraestrutura ou esperava credencial |

`SHADE_API_KEY` e `SHADE_URL` vencem o keyring (o caminho do CI). A chave nunca é argumento,
nunca vai para arquivo e sai redigida de todo texto. `http://` só para `localhost`.

O deploy é idempotente: o mesmo código dá o mesmo `sha256`, e o servidor devolve a release que
já existe. Se a CLI cair no meio, repetir `shade deploy` (ou `shade releases show <id>
--esperar`) retoma a mesma versão. O prazo da espera (10 min) sai 6 com esse comando.

Tudo o que a CLI escreve num projeto fica em `.shade/` (que se ignora sozinho no git): o
`git status` do projeto continua limpo depois de qualquer comando, menos o `init`.

## `--json` e códigos de saída

Com `--json`, um documento no stdout; progresso e logs no stderr, redigidos:

```json
{"versao": 1, "comando": "build", "ok": false, "dados": {},
 "erro": {"codigo": "lint_reprovado", "mensagem": "L3 robo.py:1 import fora do lock: yaml",
          "recuperacao": "corrija o que o lint aponta (shade test mostra a lista inteira)"}}
```

| Código | Significado |
|---|---|
| 0 | ok (inclusive o deploy que acha a release existente, e a release que espera uma pessoa: aprovar ou preencher o cofre) |
| 1 | gate, lint ou teste reprovou; release reprovada na conferência ou no ensaio (e o defeito da própria CLI, com `erro.codigo = "erro_interno"`) |
| 2 | uso inválido ou `shade.toml` inválido; robô novo sem `--criar` |
| 3 | chave ausente, recusada ou sem o escopo (401/403) |
| 4 | conflito de estado (409): ensaiar uma release reprovada, a release arquivada |
| 5 | incompatibilidade de versão: o `shade-sdk` do projeto não é o da imagem, ou o `/sdk/compat` não casa |
| 6 | o prazo da espera acabou; a release segue no servidor (`erro.recuperacao` traz a retomada) |
| 7 | rede ou servidor indisponível (5xx, 429) |
| 130 | interrompido (Ctrl-C, SIGTERM ou SIGHUP: o robô em andamento morre junto) |

## Lint de contrato

| Regra | Efeito | O quê |
|---|---|---|
| L3 | reprova | import de pacote fora do lock da imagem e dos plugins aprovados |
| L7 | reprova (avisa no `run`) | credencial lida sem declaração, ou o valor dela chegando a `goto`, ao HTTP do Playwright, a `shade_sdk.http` ou a arquivo |
| L8 | reprova | entrada ausente ou inválida (`main.py`, `[robo] classe`, arquivo que não compila) |
| L1 | avisa | `subprocess`, `os.system`, `os.exec*`, `os.fork`, `ctypes`, `multiprocessing` |
| L2 | avisa | `socket`, `http.client`, `urllib.request`, `requests`, `httpx`, `aiohttp` |
| L4 | avisa | `eval`, `exec`, `compile`, import dinâmico |
| L5 | avisa | caminho literal fora do workdir |
| L6 | avisa | clique ou envio em alvo com cara de submissão fora de `efetivar()` |

O lint não é controle de segurança (ADR 019, D3): a fronteira é o isolamento do executor.

## Desenvolvimento

```bash
cd cli && uv sync
uv run pytest -q -m "not starter"   # rápido
uv run pytest -q -m starter         # os starters de verdade: uv sync, Chromium, ~45 s
```

`src/shade_cli/imagem.py` é a cópia do `requirements.lock` da imagem do run:
`backend/tests/test_hu294_artefato_da_cli.py` a confere, e confere que o artefato do
`shade build` passa no `ler()` do backend.
