@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.
- package/README.md +52 -58
- package/dist/index.js +30 -16
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,87 +1,81 @@
|
|
|
1
|
-
# @h1veframework/cli
|
|
1
|
+
# @h1veframework/cli
|
|
2
2
|
|
|
3
|
-
CLI
|
|
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
|
-
|
|
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
|
-
##
|
|
9
|
+
## Pré-requisitos
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
<details><summary>A partir do código-fonte (contribuidores)</summary>
|
|
16
|
+
## Instalação
|
|
16
17
|
|
|
17
18
|
```bash
|
|
18
|
-
npm
|
|
19
|
-
|
|
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
|
|
35
|
-
export
|
|
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
|
-
>
|
|
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
|
-
|
|
41
|
-
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
nf
|
|
48
|
-
nf
|
|
49
|
-
nf
|
|
50
|
-
nf
|
|
51
|
-
nf
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
53
|
+
Flags: `--json` (saída crua p/ scripts) · `--project <nome\|id>` (se você é membro de +1 projeto) · `-h` (ajuda completa).
|
|
57
54
|
|
|
58
|
-
|
|
55
|
+
## Exemplo de uso
|
|
59
56
|
|
|
60
57
|
```bash
|
|
61
|
-
nf
|
|
62
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
74
|
+
## Compatibilidade de nomes
|
|
79
75
|
|
|
80
|
-
|
|
81
|
-
|
|
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ê já configurou com eles, não precisa mudar nada.
|
|
82
78
|
|
|
83
|
-
|
|
79
|
+
---
|
|
84
80
|
|
|
85
|
-
|
|
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", "
|
|
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", "
|
|
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 = {
|
|
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
|
|
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.
|
|
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
|
-
|
|
1029
|
-
|
|
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