@h1veframework/cli 0.1.0 → 0.3.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.
Files changed (3) hide show
  1. package/README.md +52 -58
  2. package/dist/index.js +30 -16
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,87 +1,81 @@
1
- # @h1veframework/cli — `nf`
1
+ # @h1veframework/cli
2
2
 
3
- CLI de terminal do **H1VE Flow**. Opera o fluxo da feature **da própria branch**, sem abrir o painel: ver estado, mover stage, abrir blocker, ler a spec, enviar a AI declaration e consultar a saúde técnica.
3
+ CLI `nf` do **H1VE Flow** opera o fluxo da feature da sua branch direto do terminal
4
+ (status, start, move, spec, done, blocker, health, connect), autenticado por um token pessoal.
4
5
 
5
- É um **cliente fino**: toda regra (transições, ownership, papéis) é decidida no servidor. O `nf` resolve a branch (via `git`), chama a API e formata a saída. O bundle é **auto-contido** (sem dependências de runtime além do Node).
6
+ Parte do tooling do H1VE junto com o **[servidor MCP](https://www.npmjs.com/package/@h1veframework/mcp)**
7
+ (para o Claude Code).
6
8
 
7
- ## Instalação
9
+ ## Pré-requisitos
8
10
 
9
- ```bash
10
- npm install -g @h1veframework/cli # disponibiliza o comando `nf` globalmente
11
- ```
11
+ - **Node.js 18.18+** (`node --version`).
12
+ - Uma conta no H1VE Flow e um **PAT** (`nf_pat_…`), criado em **`app.h1ve.org/api-tokens`**.
13
+ - PAT autentica **como você**: leitura **e escrita**. A chave de serviço é **só leitura**.
14
+ - Guarde o PAT com cuidado — é um segredo, mostrado uma vez.
12
15
 
13
- > Requer Node ≥ 18.18 e `git` no PATH.
14
-
15
- <details><summary>A partir do código-fonte (contribuidores)</summary>
16
+ ## Instalação
16
17
 
17
18
  ```bash
18
- npm install # na raiz do monorepo
19
- npm run build -w @h1veframework/cli
20
- node packages/cli/dist/index.js --help # ou: npm i -g ./packages/cli
19
+ npm i -g @h1veframework/cli
20
+ nf --version
21
21
  ```
22
- </details>
23
22
 
24
23
  ## Configuração
25
24
 
26
- Duas variáveis de ambiente:
27
-
28
- | Variável | Descrição |
29
- |---|---|
30
- | `NEXUS_FLOW_API_URL` | URL do H1VE Flow (ex.: `https://app.h1ve.org`) |
31
- | `NEXUS_FLOW_API_KEY` | Um **PAT** (`nf_pat_…`, criado em `/api-tokens`) **ou** a chave de serviço |
32
-
33
25
  ```bash
34
- export NEXUS_FLOW_API_URL=https://app.h1ve.org
35
- export NEXUS_FLOW_API_KEY=nf_pat_xxxxxxxx
26
+ export H1VE_API_URL="https://app.h1ve.org"
27
+ export H1VE_API_KEY="nf_pat_..." # seu PAT
28
+ export H1VE_PROJECT_ID="..." # OBRIGATÓRIO só se você tem 2+ projetos (o id do projeto)
29
+ nf health # testa a conexão
36
30
  ```
37
31
 
38
- > Nota: os nomes das env vars ainda usam o prefixo `NEXUS_FLOW_` (nome interno original). O rebrand desses nomes p/ `H1VE_` toca código de servidor + config de deploy — fica p/ uma fatia futura.
32
+ > **`H1VE_PROJECT_ID`** (SPEC-076): com **um** projeto, não precisa o servidor resolve sozinho.
33
+ > Com **2+ projetos** (plano pago), defina o id do projeto — senão o `nf start`/`nf health` responde
34
+ > *"Você tem mais de um projeto. Defina H1VE_PROJECT_ID."* (o id fica na app, no projeto).
39
35
 
40
- - **PAT** (`nf_pat_…`): age com a **sua identidade e papel** habilita escrita (`move`, `blocker`, `done`).
41
- - **Chave de serviço**: **só leitura** `move`/`blocker`/`done` respondem `SERVICE_CANNOT_WRITE`. `status`/`spec`/`health` funcionam.
36
+ > Coloque os `export` no seu `~/.zshrc` / `~/.bashrc` para não repetir a cada sessão.
37
+ > "Nenhum snapshot registrado" no `nf health` é **sucesso** (conectou; projeto sem métricas ainda).
42
38
 
43
39
  ## Comandos
44
40
 
45
- ```bash
46
- nf status # estado da feature da branch (stage, dias ativos, blockers, sign-offs)
47
- nf spec # imprime a spec (spec_content) da feature
48
- nf move <stage> [--note "..."] # move a feature de stage
49
- nf blocker "<descrição>" # abre um blocker
50
- nf done [--from <arq>] [--no-move] # envia a AI declaration (JSON) e move dev pr
51
- nf health # últimos snapshots de saúde técnica (founder/architect, ou serviço)
52
- ```
53
-
54
- Flags globais: `--json` (saída crua), `-h/--help`, `-v/--version`.
41
+ | Comando | O que faz |
42
+ |---|---|
43
+ | `nf health` | Últimos snapshots de saúde técnica do projeto |
44
+ | `nf status` | Estado da feature da branch atual (stage, dias ativo, blockers, sign-offs) |
45
+ | `nf start [<nº\|id>] [--slug <s>]` | Inicia uma feature atribuída: cria a branch `feat/{você}/{slug}` e grava o slug |
46
+ | `nf spec` | Imprime a spec (markdown) da feature da branch |
47
+ | `nf move <stage> [--note]` | Move a feature para outro stage |
48
+ | `nf done [--from <arq>] [--no-move]` | Envia a AI declaration (JSON) e move `dev → pr` |
49
+ | `nf blocker "<desc>"` | Abre um blocker na feature (você vira o dono) |
50
+ | `nf connect --kind <k> --label <l> --env KEY=VAL` | Aplica uma credencial no `.env.local` **local** (nunca ao servidor) e registra o inventário (Jeito B) |
51
+ | `nf serve [--port 7391]` | Sobe o agente local (`127.0.0.1`) p/ o menu visual do painel aplicar credenciais pelo navegador |
55
52
 
56
- ### `nf done`
53
+ Flags: `--json` (saída crua p/ scripts) · `--project <nome\|id>` (se você é membro de +1 projeto) · `-h` (ajuda completa).
57
54
 
58
- Recebe a AI declaration como **JSON** — de um arquivo ou do stdin:
55
+ ## Exemplo de uso
59
56
 
60
57
  ```bash
61
- nf done --from ai-declaration.json
62
- nf done < ai-declaration.json
58
+ nf start # inicia a feature atribuída (cria a branch)
59
+ # ... trabalha (git, código, commits) ...
60
+ nf status # estado a qualquer momento
61
+ nf blocker "aguardando credencial do Neon" # trava? abre blocker
62
+ nf done --from ai-declaration.json # envia a AI declaration + move dev → pr
63
63
  ```
64
64
 
65
- Formato do JSON (validado no servidor):
66
-
67
- ```json
68
- {
69
- "generated_files": [{ "file": "src/x.ts", "pct_generated": 80 }],
70
- "reviewed_files": [{ "file": "src/x.ts", "reviewed_by_human": true }],
71
- "github_pr_number": 42,
72
- "out_of_scope": "nenhum"
73
- }
74
- ```
65
+ ## Troubleshooting
75
66
 
76
- `nf done` **envia a declaration e depois move `dev → pr`**. Com `--no-move`, só envia. Se o move falhar (ex.: a feature não está em `dev`), a declaration **já foi enviada** — o CLI reporta o parcial e sai com código ≠ 0.
67
+ | Sintoma | Solução |
68
+ |---|---|
69
+ | `nf: command not found` | Node ausente ou terminal errado. Confira `node --version`. No Windows, o PowerShell pode não ter o bin do npm no PATH — use o terminal do VS Code ou reabra o shell. |
70
+ | `404` ao instalar | Propagação do npm logo após publicação. Espere alguns minutos. |
71
+ | `NO_PROJECT` | Você é membro de +1 projeto — passe `--project <nome\|id>`. |
72
+ | `401` / `403` | Token errado/ausente/sem permissão. Confira que `H1VE_API_KEY` é um PAT (`nf_pat_…`). `403 SERVICE_CANNOT_WRITE` = chave de serviço (só leitura) numa escrita → use um PAT. |
77
73
 
78
- ## Saída e exit codes
74
+ ## Compatibilidade de nomes
79
75
 
80
- - Sucesso **stdout**, exit `0`.
81
- - Erro **stderr** (mensagem amigável, sem stack), exit `≠ 0` (config faltando = `2`).
76
+ Use **`H1VE_API_URL`** e **`H1VE_API_KEY`**. Os nomes legados `NEXUS_FLOW_API_URL` / `NEXUS_FLOW_API_KEY`
77
+ **ainda são aceitos** por compatibilidade se você configurou com eles, não precisa mudar nada.
82
78
 
83
- Compõe bem em scripts/CI:
79
+ ---
84
80
 
85
- ```bash
86
- nf status --json | jq .stage
87
- ```
81
+ MIT · [H1VE Flow](https://app.h1ve.org)
package/dist/index.js CHANGED
@@ -13,10 +13,16 @@ var NexusApiError = class extends Error {
13
13
  }
14
14
  code;
15
15
  };
16
- function errorForStatus(status2) {
16
+ function errorForStatus(status2, code) {
17
+ if (code === "NO_PROJECT") {
18
+ return new NexusApiError(
19
+ "NO_PROJECT",
20
+ "Voc\xEA tem mais de um projeto. Defina H1VE_PROJECT_ID (o id do projeto) na config do CLI/MCP."
21
+ );
22
+ }
17
23
  switch (status2) {
18
24
  case 401:
19
- return new NexusApiError("UNAUTHENTICATED", "NEXUS_FLOW_API_KEY ausente ou inv\xE1lida.");
25
+ return new NexusApiError("UNAUTHENTICATED", "H1VE_API_KEY ausente ou inv\xE1lida.");
20
26
  case 403:
21
27
  return new NexusApiError("FORBIDDEN", "Sem permiss\xE3o para ver esta feature.");
22
28
  case 404:
@@ -45,7 +51,7 @@ function mapWriteError(status2, code) {
45
51
  }
46
52
  switch (status2) {
47
53
  case 401:
48
- return new NexusApiError("UNAUTHENTICATED", "NEXUS_FLOW_API_KEY ausente ou inv\xE1lida.");
54
+ return new NexusApiError("UNAUTHENTICATED", "H1VE_API_KEY ausente ou inv\xE1lida.");
49
55
  case 403:
50
56
  return new NexusApiError(code ?? "FORBIDDEN", "Sem permiss\xE3o para esta a\xE7\xE3o (papel ou ownership).");
51
57
  case 404:
@@ -68,7 +74,13 @@ function rootUrl(baseUrl) {
68
74
  }
69
75
  function createClient(config) {
70
76
  const doFetch = config.fetchImpl ?? fetch;
71
- const authHeaders = { "x-api-key": config.apiKey, accept: "application/json" };
77
+ const authHeaders = {
78
+ "x-api-key": config.apiKey,
79
+ accept: "application/json",
80
+ // SPEC-076: envia o projeto ativo quando configurado. O servidor valida a membership
81
+ // do id e ignora o header onde não escopa — então mandar sempre é simples e seguro.
82
+ ...config.projectId ? { "x-project-id": config.projectId } : {}
83
+ };
72
84
  async function postAction(featureId, action, body) {
73
85
  const url = `${rootUrl(config.baseUrl)}/api/features/${encodeURIComponent(featureId)}/${action}`;
74
86
  const res = await doFetch(url, {
@@ -83,19 +95,19 @@ function createClient(config) {
83
95
  async getContext(branch) {
84
96
  const url = `${rootUrl(config.baseUrl)}/api/context?branch=${encodeURIComponent(branch)}`;
85
97
  const res = await doFetch(url, { headers: authHeaders });
86
- if (!res.ok) throw errorForStatus(res.status);
98
+ if (!res.ok) throw errorForStatus(res.status, await readErrorCode(res));
87
99
  return await res.json();
88
100
  },
89
101
  async listHealth() {
90
102
  const url = `${rootUrl(config.baseUrl)}/api/health`;
91
103
  const res = await doFetch(url, { headers: authHeaders });
92
- if (!res.ok) throw errorForStatus(res.status);
104
+ if (!res.ok) throw errorForStatus(res.status, await readErrorCode(res));
93
105
  return await res.json();
94
106
  },
95
107
  async listFeatures() {
96
108
  const url = `${rootUrl(config.baseUrl)}/api/features`;
97
109
  const res = await doFetch(url, { headers: authHeaders });
98
- if (!res.ok) throw errorForStatus(res.status);
110
+ if (!res.ok) throw errorForStatus(res.status, await readErrorCode(res));
99
111
  return await res.json();
100
112
  },
101
113
  startFeature(featureId, slug) {
@@ -113,7 +125,7 @@ function createClient(config) {
113
125
  async listProjects() {
114
126
  const url = `${rootUrl(config.baseUrl)}/api/projects`;
115
127
  const res = await doFetch(url, { headers: authHeaders });
116
- if (!res.ok) throw errorForStatus(res.status);
128
+ if (!res.ok) throw errorForStatus(res.status, await readErrorCode(res));
117
129
  return await res.json();
118
130
  },
119
131
  async createConnection(projectId, input) {
@@ -258,18 +270,19 @@ var CliError = class extends Error {
258
270
 
259
271
  // src/config.ts
260
272
  function readConfig(env = process.env) {
261
- const baseUrl = env.NEXUS_FLOW_API_URL?.trim();
262
- const apiKey = env.NEXUS_FLOW_API_KEY?.trim();
273
+ const baseUrl = env.H1VE_API_URL?.trim() || env.NEXUS_FLOW_API_URL?.trim();
274
+ const apiKey = env.H1VE_API_KEY?.trim() || env.NEXUS_FLOW_API_KEY?.trim();
275
+ const projectId = env.H1VE_PROJECT_ID?.trim() || env.NEXUS_FLOW_PROJECT_ID?.trim();
263
276
  if (!baseUrl || !apiKey) {
264
277
  throw new CliError(
265
- "Defina NEXUS_FLOW_API_URL e NEXUS_FLOW_API_KEY no ambiente.\n export NEXUS_FLOW_API_URL=https://seu-nexus.example.com\n export NEXUS_FLOW_API_KEY=nf_pat_... (crie um token em /api-tokens)",
278
+ "Defina H1VE_API_URL e H1VE_API_KEY no ambiente.\n export H1VE_API_URL=https://app.h1ve.org\n export H1VE_API_KEY=nf_pat_... (crie um token em /api-tokens)\n (os nomes legados NEXUS_FLOW_API_URL/KEY ainda s\xE3o aceitos)",
266
279
  2
267
280
  );
268
281
  }
269
- return { baseUrl, apiKey };
282
+ return { baseUrl, apiKey, projectId };
270
283
  }
271
284
  function clientFromConfig(cfg) {
272
- return createClient({ baseUrl: cfg.baseUrl, apiKey: cfg.apiKey });
285
+ return createClient({ baseUrl: cfg.baseUrl, apiKey: cfg.apiKey, projectId: cfg.projectId });
273
286
  }
274
287
 
275
288
  // src/format.ts
@@ -993,7 +1006,7 @@ Cole o c\xF3digo no painel /connections. Ctrl+C para parar.
993
1006
  }
994
1007
 
995
1008
  // src/index.ts
996
- var VERSION = "0.1.0";
1009
+ var VERSION = "0.2.0";
997
1010
  var HELP = `nf \u2014 CLI do Nexus Flow
998
1011
 
999
1012
  Uso:
@@ -1025,8 +1038,9 @@ Flags:
1025
1038
  -v, --version vers\xE3o do CLI
1026
1039
 
1027
1040
  Ambiente:
1028
- NEXUS_FLOW_API_URL URL do Nexus Flow
1029
- NEXUS_FLOW_API_KEY PAT (nf_pat_\u2026) p/ escrever, ou a chave de servi\xE7o (s\xF3 leitura)
1041
+ H1VE_API_URL URL do H1VE Flow (ex.: https://app.h1ve.org)
1042
+ H1VE_API_KEY PAT (nf_pat_\u2026) p/ escrever, ou a chave de servi\xE7o (s\xF3 leitura)
1043
+ (nomes legados aceitos: NEXUS_FLOW_API_URL / NEXUS_FLOW_API_KEY)
1030
1044
  `;
1031
1045
  var COMMANDS = { start, status, spec, move, blocker, done, health, connect, serve };
1032
1046
  async function readStdin() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@h1veframework/cli",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "CLI `nf` do H1VE Flow: opera o fluxo da feature (status/move/blocker/spec/done/health/start) da própria branch, via PAT.",
5
5
  "license": "MIT",
6
6
  "type": "module",