@h1veframework/cli 0.2.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 CHANGED
@@ -25,9 +25,14 @@ nf --version
25
25
  ```bash
26
26
  export H1VE_API_URL="https://app.h1ve.org"
27
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)
28
29
  nf health # testa a conexão
29
30
  ```
30
31
 
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).
35
+
31
36
  > Coloque os `export` no seu `~/.zshrc` / `~/.bashrc` para não repetir a cada sessão.
32
37
  > "Nenhum snapshot registrado" no `nf health` é **sucesso** (conectou; projeto sem métricas ainda).
33
38
 
package/dist/index.js CHANGED
@@ -13,7 +13,13 @@ 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
25
  return new NexusApiError("UNAUTHENTICATED", "H1VE_API_KEY ausente ou inv\xE1lida.");
@@ -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) {
@@ -260,16 +272,17 @@ var CliError = class extends Error {
260
272
  function readConfig(env = process.env) {
261
273
  const baseUrl = env.H1VE_API_URL?.trim() || env.NEXUS_FLOW_API_URL?.trim();
262
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
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@h1veframework/cli",
3
- "version": "0.2.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",