@h1veframework/cli 0.5.1 → 0.6.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 +48 -62
- package/dist/index.js +78 -22
- package/package.json +15 -5
package/README.md
CHANGED
|
@@ -1,93 +1,79 @@
|
|
|
1
|
-
# @h1veframework/cli
|
|
1
|
+
# @h1veframework/cli · `nf`
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
(status, start, move, spec, done, blocker, health, connect), autenticado por um token pessoal.
|
|
3
|
+
**The command-line companion for [H1VE](https://h1ve.org) — the governance & memory layer for teams building with AI.**
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
Your team ships with AI (Claude Code, Cursor, Copilot). As the codebase grows, the AI and your teammates start
|
|
6
|
+
**losing context and drifting from decisions you already made**. H1VE keeps every feature aligned — a shared,
|
|
7
|
+
**eternal project memory** plus an **anti-drift engine** that checks each change against what's been decided.
|
|
8
|
+
`nf` drives that workflow straight from your terminal.
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
Part of the H1VE toolchain, alongside the **[MCP server](https://www.npmjs.com/package/@h1veframework/mcp)** for
|
|
11
|
+
Claude Code / Cursor.
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
- Uma conta no H1VE Flow. **Não precisa criar/colar token** — o `nf login` cuida disso.
|
|
13
|
+
→ **[h1ve.org](https://h1ve.org)** · **[app.h1ve.org](https://app.h1ve.org)**
|
|
13
14
|
|
|
14
|
-
##
|
|
15
|
+
## Quickstart (zero paste)
|
|
15
16
|
|
|
16
17
|
```bash
|
|
17
18
|
npm i -g @h1veframework/cli
|
|
18
|
-
nf
|
|
19
|
+
nf login # opens your browser → you click Authorize → done
|
|
20
|
+
nf status # state of the current branch's feature
|
|
19
21
|
```
|
|
20
22
|
|
|
21
|
-
|
|
23
|
+
`nf login` stores the credential locally (`~/.config/h1ve/credentials.json`, `600`) — the MCP server reads the
|
|
24
|
+
same one, so **one login sets up both**. The project **auto-resolves from your git remote**, so there's nothing
|
|
25
|
+
to paste and no `H1VE_PROJECT_ID` to set. `nf logout` removes it.
|
|
22
26
|
|
|
23
|
-
|
|
24
|
-
nf login # abre o navegador → você autoriza → pronto
|
|
25
|
-
nf health # já funciona
|
|
26
|
-
```
|
|
27
|
+
> `nf health` saying "no snapshots yet" is **success** (you're connected; the project just has no metrics yet).
|
|
27
28
|
|
|
28
|
-
|
|
29
|
-
a credencial sozinho** em `~/.config/h1ve/credentials.json` (permissão 600). O **[MCP](https://www.npmjs.com/package/@h1veframework/mcp)**
|
|
30
|
-
lê a mesma credencial — um `nf login` configura os dois. `nf logout` remove. **100% automático** (SPEC-078):
|
|
31
|
-
o projeto também é resolvido sozinho pelo repo (SPEC-077), então nada de `H1VE_PROJECT_ID`.
|
|
29
|
+
## Commands
|
|
32
30
|
|
|
33
|
-
|
|
31
|
+
| Command | What it does |
|
|
32
|
+
|---|---|
|
|
33
|
+
| `nf login` / `nf logout` | Authorize in the browser and store the credential locally / remove it |
|
|
34
|
+
| `nf status` | State of the current branch's feature (stage, days active, blockers, sign-offs) |
|
|
35
|
+
| `nf start [<#\|id>] [--slug <s>]` | Start an assigned feature: create the `feat/{you}/{slug}` branch and record the slug |
|
|
36
|
+
| `nf spec` | Print the feature's spec (markdown) |
|
|
37
|
+
| `nf move <stage> [--note]` | Move the feature to another stage |
|
|
38
|
+
| `nf done [--from <file>] [--no-move]` | Submit the AI declaration (JSON) and move `dev → pr` |
|
|
39
|
+
| `nf blocker "<desc>"` | Open a blocker on the feature (you become its owner) |
|
|
40
|
+
| `nf health` | Latest technical-health snapshots for the project |
|
|
41
|
+
| `nf connect --kind <k> --label <l> --env KEY=VAL` | Write a credential to your **local** `.env.local` and register the inventory (never sent to the server) |
|
|
42
|
+
| `nf serve [--port 7391]` | Run the local agent (`127.0.0.1`) so the dashboard can apply credentials from the browser |
|
|
34
43
|
|
|
35
|
-
|
|
44
|
+
Flags: `--json` (raw output for scripts) · `--project <name\|id>` (if you belong to more than one project) · `-h`.
|
|
36
45
|
|
|
37
|
-
|
|
46
|
+
## Example
|
|
38
47
|
|
|
39
48
|
```bash
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
49
|
+
nf start # start your assigned feature (creates the branch)
|
|
50
|
+
# ... work: git, code, commits ...
|
|
51
|
+
nf status # check state anytime
|
|
52
|
+
nf blocker "waiting on the Neon credential" # blocked? open a blocker
|
|
53
|
+
nf done --from ai-declaration.json # submit the AI declaration + move dev → pr
|
|
43
54
|
```
|
|
44
55
|
|
|
45
|
-
|
|
46
|
-
> CLI **resolve o projeto sozinho** pelo `git remote` — zero config, mesmo com 2+ projetos. Use só pra
|
|
47
|
-
> **forçar** um projeto (override).
|
|
56
|
+
## CI / headless (env vars)
|
|
48
57
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
| Comando | O que faz |
|
|
52
|
-
|---|---|
|
|
53
|
-
| `nf login` | Autoriza no navegador e salva a credencial local (sem colar nada) |
|
|
54
|
-
| `nf logout` | Remove a credencial local |
|
|
55
|
-
| `nf health` | Últimos snapshots de saúde técnica do projeto |
|
|
56
|
-
| `nf status` | Estado da feature da branch atual (stage, dias ativo, blockers, sign-offs) |
|
|
57
|
-
| `nf start [<nº\|id>] [--slug <s>]` | Inicia uma feature atribuída: cria a branch `feat/{você}/{slug}` e grava o slug |
|
|
58
|
-
| `nf spec` | Imprime a spec (markdown) da feature da branch |
|
|
59
|
-
| `nf move <stage> [--note]` | Move a feature para outro stage |
|
|
60
|
-
| `nf done [--from <arq>] [--no-move]` | Envia a AI declaration (JSON) e move `dev → pr` |
|
|
61
|
-
| `nf blocker "<desc>"` | Abre um blocker na feature (você vira o dono) |
|
|
62
|
-
| `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) |
|
|
63
|
-
| `nf serve [--port 7391]` | Sobe o agente local (`127.0.0.1`) p/ o menu visual do painel aplicar credenciais pelo navegador |
|
|
64
|
-
|
|
65
|
-
Flags: `--json` (saída crua p/ scripts) · `--project <nome\|id>` (se você é membro de +1 projeto) · `-h` (ajuda completa).
|
|
66
|
-
|
|
67
|
-
## Exemplo de uso
|
|
58
|
+
No browser? Configure by env (takes **precedence** over `nf login`):
|
|
68
59
|
|
|
69
60
|
```bash
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
nf blocker "aguardando credencial do Neon" # trava? abre blocker
|
|
74
|
-
nf done --from ai-declaration.json # envia a AI declaration + move dev → pr
|
|
61
|
+
export H1VE_API_URL="https://app.h1ve.org" # optional (this is the default)
|
|
62
|
+
export H1VE_API_KEY="nf_pat_..." # a PAT from app.h1ve.org/api-tokens
|
|
63
|
+
# export H1VE_PROJECT_ID="..." # OPTIONAL — the CLI resolves the project from the repo
|
|
75
64
|
```
|
|
76
65
|
|
|
77
66
|
## Troubleshooting
|
|
78
67
|
|
|
79
|
-
|
|
|
68
|
+
| Symptom | Fix |
|
|
80
69
|
|---|---|
|
|
81
|
-
| `nf: command not found` | Node
|
|
82
|
-
| `404`
|
|
83
|
-
| `NO_PROJECT` |
|
|
84
|
-
| `401` / `403` |
|
|
85
|
-
|
|
86
|
-
## Compatibilidade de nomes
|
|
70
|
+
| `nf: command not found` | Node missing or wrong terminal. Check `node --version`. On Windows, reopen the shell or use the VS Code terminal. |
|
|
71
|
+
| `404` on install | npm propagation right after a publish — wait a few minutes. |
|
|
72
|
+
| `NO_PROJECT` | You belong to more than one project — pass `--project <name\|id>`. |
|
|
73
|
+
| `401` / `403` | Wrong/missing token. Make sure `H1VE_API_KEY` is a PAT (`nf_pat_…`). `403 SERVICE_CANNOT_WRITE` = a read-only service key used on a write → use a PAT. |
|
|
87
74
|
|
|
88
|
-
|
|
89
|
-
**ainda são aceitos** por compatibilidade — se você já configurou com eles, não precisa mudar nada.
|
|
75
|
+
Requires **Node.js 18.18+**. Legacy env names `NEXUS_FLOW_API_URL` / `NEXUS_FLOW_API_KEY` are still accepted.
|
|
90
76
|
|
|
91
77
|
---
|
|
92
78
|
|
|
93
|
-
MIT · [H1VE
|
|
79
|
+
MIT · built with H1VE, by [H1VE](https://h1ve.org) 🐝
|
package/dist/index.js
CHANGED
|
@@ -50,6 +50,24 @@ async function createBranch(name, runner = defaultGitRunner) {
|
|
|
50
50
|
throw new Error("GIT_BRANCH_FAILED");
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
|
+
async function switchBranch(name, runner = defaultGitRunner) {
|
|
54
|
+
try {
|
|
55
|
+
await runner(["switch", name]);
|
|
56
|
+
} catch (err) {
|
|
57
|
+
const text = gitErrText(err);
|
|
58
|
+
if (/not a git repository/i.test(text)) throw new Error("NOT_A_GIT_REPO");
|
|
59
|
+
throw new Error("GIT_SWITCH_FAILED");
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
async function listLocalBranches(runner = defaultGitRunner) {
|
|
63
|
+
let out;
|
|
64
|
+
try {
|
|
65
|
+
out = await runner(["for-each-ref", "--format=%(refname:short)", "refs/heads"]);
|
|
66
|
+
} catch {
|
|
67
|
+
return [];
|
|
68
|
+
}
|
|
69
|
+
return out.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
70
|
+
}
|
|
53
71
|
|
|
54
72
|
// ../core/src/client.ts
|
|
55
73
|
var NexusApiError = class extends Error {
|
|
@@ -128,16 +146,17 @@ function createClient(config) {
|
|
|
128
146
|
// do id e ignora o header onde não escopa — então mandar sempre é simples e seguro.
|
|
129
147
|
...config.projectId ? { "x-project-id": config.projectId } : {}
|
|
130
148
|
};
|
|
131
|
-
async function
|
|
149
|
+
async function writeAction(method, featureId, action, body) {
|
|
132
150
|
const url = `${rootUrl(config.baseUrl)}/api/features/${encodeURIComponent(featureId)}/${action}`;
|
|
133
151
|
const res = await doFetch(url, {
|
|
134
|
-
method
|
|
152
|
+
method,
|
|
135
153
|
headers: { ...authHeaders, "content-type": "application/json" },
|
|
136
154
|
body: JSON.stringify(body)
|
|
137
155
|
});
|
|
138
156
|
if (!res.ok) throw mapWriteError(res.status, await readErrorCode(res));
|
|
139
157
|
return await res.json();
|
|
140
158
|
}
|
|
159
|
+
const postAction = (featureId, action, body) => writeAction("POST", featureId, action, body);
|
|
141
160
|
return {
|
|
142
161
|
async getContext(branch) {
|
|
143
162
|
const url = `${rootUrl(config.baseUrl)}/api/context?branch=${encodeURIComponent(branch)}`;
|
|
@@ -157,9 +176,38 @@ function createClient(config) {
|
|
|
157
176
|
if (!res.ok) throw errorForStatus(res.status, await readErrorCode(res));
|
|
158
177
|
return await res.json();
|
|
159
178
|
},
|
|
179
|
+
async recallMemory(query) {
|
|
180
|
+
const params = new URLSearchParams();
|
|
181
|
+
if (query.entities?.length) params.set("entities", query.entities.join(","));
|
|
182
|
+
if (query.text) params.set("q", query.text);
|
|
183
|
+
if (query.limit) params.set("limit", String(query.limit));
|
|
184
|
+
const url = `${rootUrl(config.baseUrl)}/api/memory/recall?${params.toString()}`;
|
|
185
|
+
const res = await doFetch(url, { headers: authHeaders });
|
|
186
|
+
if (!res.ok) throw errorForStatus(res.status, await readErrorCode(res));
|
|
187
|
+
return await res.json();
|
|
188
|
+
},
|
|
160
189
|
startFeature(featureId, slug) {
|
|
161
190
|
return postAction(featureId, "start", slug ? { slug } : {});
|
|
162
191
|
},
|
|
192
|
+
setSpec(featureId, specContent) {
|
|
193
|
+
return writeAction("PATCH", featureId, "spec", { spec_content: specContent });
|
|
194
|
+
},
|
|
195
|
+
// SPEC-100 (B2): best-effort — o juiz de drift é ADVISORY; qualquer falha → {checked:false}
|
|
196
|
+
// (nunca lança, pra não estragar o fluxo de gravar a SPEC no `set_spec` do MCP).
|
|
197
|
+
async checkSpecAlignment(featureId) {
|
|
198
|
+
try {
|
|
199
|
+
const url = `${rootUrl(config.baseUrl)}/api/features/${encodeURIComponent(featureId)}/spec/alignment`;
|
|
200
|
+
const res = await doFetch(url, {
|
|
201
|
+
method: "POST",
|
|
202
|
+
headers: { ...authHeaders, "content-type": "application/json" },
|
|
203
|
+
body: "{}"
|
|
204
|
+
});
|
|
205
|
+
if (!res.ok) return { checked: false };
|
|
206
|
+
return await res.json();
|
|
207
|
+
} catch {
|
|
208
|
+
return { checked: false };
|
|
209
|
+
}
|
|
210
|
+
},
|
|
163
211
|
moveStage(featureId, toStage, note) {
|
|
164
212
|
return postAction(featureId, "move", { to_stage: toStage, note });
|
|
165
213
|
},
|
|
@@ -442,16 +490,17 @@ function formatBlocker(r) {
|
|
|
442
490
|
return `\u2713 Blocker aberto: ${r.description}`;
|
|
443
491
|
}
|
|
444
492
|
function formatStartList(list) {
|
|
445
|
-
const header = "Features
|
|
446
|
-
const rows = list.map(
|
|
447
|
-
|
|
448
|
-
)
|
|
493
|
+
const header = "Features para iniciar ou retomar \u2014 escolha: nf start <n\xBA>";
|
|
494
|
+
const rows = list.map((f, i) => {
|
|
495
|
+
const tag = f.branch_slug !== null ? " (retomar)" : "";
|
|
496
|
+
return ` ${String(i + 1).padStart(2)}. ${f.name} [${stageLabel(f.stage)}]${tag}`;
|
|
497
|
+
});
|
|
449
498
|
return [header, ...rows].join("\n");
|
|
450
499
|
}
|
|
451
|
-
function formatStart(r) {
|
|
500
|
+
function formatStart(r, opts) {
|
|
452
501
|
return [
|
|
453
|
-
`\u2713 Iniciada: ${r.feature.name}`,
|
|
454
|
-
`Branch: ${r.branch_name} (criada e ativa)`,
|
|
502
|
+
`\u2713 ${opts?.resumed ? "Retomada" : "Iniciada"}: ${r.feature.name}`,
|
|
503
|
+
`Branch: ${r.branch_name} (${opts?.resumed ? "pronta e ativa" : "criada e ativa"})`,
|
|
455
504
|
"Pr\xF3ximo: codar \u2192 'nf status' \u2192 'nf done' (move dev \u2192 pr)"
|
|
456
505
|
].join("\n");
|
|
457
506
|
}
|
|
@@ -573,8 +622,15 @@ async function health(io) {
|
|
|
573
622
|
}
|
|
574
623
|
|
|
575
624
|
// src/commands/start.ts
|
|
576
|
-
function
|
|
577
|
-
return
|
|
625
|
+
function branchSlugOf(branch) {
|
|
626
|
+
return branch.match(/^feat\/[^/]+\/(.+)$/)?.[1] ?? null;
|
|
627
|
+
}
|
|
628
|
+
function startable(features, localBranches) {
|
|
629
|
+
const localSlugs = new Set(
|
|
630
|
+
localBranches.map(branchSlugOf).filter((s) => s !== null)
|
|
631
|
+
);
|
|
632
|
+
const hasLocalBranch = (f) => f.branch_slug !== null && localSlugs.has(f.branch_slug);
|
|
633
|
+
return features.filter((f) => f.stage !== "main" && !hasLocalBranch(f));
|
|
578
634
|
}
|
|
579
635
|
function pick(list, arg) {
|
|
580
636
|
if (!arg) return list.length === 1 ? list[0] ?? null : null;
|
|
@@ -585,27 +641,25 @@ function pick(list, arg) {
|
|
|
585
641
|
throw new CliError(`Feature n\xE3o encontrada: ${arg}. Rode 'nf start' para ver a lista.`);
|
|
586
642
|
}
|
|
587
643
|
async function start(io, args) {
|
|
588
|
-
const
|
|
644
|
+
const [features, localBranches] = await Promise.all([io.client.listFeatures(), io.listBranches()]);
|
|
645
|
+
const list = startable(features, localBranches);
|
|
589
646
|
if (list.length === 0) {
|
|
590
|
-
throw new CliError("Nenhuma feature
|
|
647
|
+
throw new CliError("Nenhuma feature para iniciar ou retomar (sem branch local e fora de main).");
|
|
591
648
|
}
|
|
592
649
|
const selected = pick(list, args.positional);
|
|
593
650
|
if (!selected) {
|
|
594
651
|
return { human: formatStartList(list), data: { features: list } };
|
|
595
652
|
}
|
|
653
|
+
const resuming = selected.branch_slug !== null;
|
|
596
654
|
const result = await io.client.startFeature(selected.id, args.slug);
|
|
597
655
|
try {
|
|
598
656
|
await io.createBranch(result.branch_name);
|
|
599
657
|
} catch (err) {
|
|
600
658
|
const code = err instanceof Error ? err.message : "";
|
|
601
|
-
if (code === "BRANCH_EXISTS")
|
|
602
|
-
|
|
603
|
-
`branch_slug gravado, mas a branch '${result.branch_name}' j\xE1 existe. Rode: git switch ${result.branch_name}`
|
|
604
|
-
);
|
|
605
|
-
}
|
|
606
|
-
throw err;
|
|
659
|
+
if (code === "BRANCH_EXISTS") await io.switchBranch(result.branch_name);
|
|
660
|
+
else throw err;
|
|
607
661
|
}
|
|
608
|
-
return { human: formatStart(result), data: result };
|
|
662
|
+
return { human: formatStart(result, { resumed: resuming }), data: result };
|
|
609
663
|
}
|
|
610
664
|
|
|
611
665
|
// src/commands/connect.ts
|
|
@@ -1232,14 +1286,14 @@ async function logout(io, _args) {
|
|
|
1232
1286
|
}
|
|
1233
1287
|
|
|
1234
1288
|
// src/index.ts
|
|
1235
|
-
var VERSION = "0.
|
|
1289
|
+
var VERSION = "0.6.0";
|
|
1236
1290
|
var HELP = `nf \u2014 CLI do Nexus Flow
|
|
1237
1291
|
|
|
1238
1292
|
Uso:
|
|
1239
1293
|
nf login autoriza no navegador e salva a credencial local (sem colar nada)
|
|
1240
1294
|
nf logout remove a credencial local
|
|
1241
1295
|
nf start [<n\xBA|id>] [--slug <s>]
|
|
1242
|
-
inicia uma feature
|
|
1296
|
+
inicia (ou retoma) uma feature: cria/entra na branch e grava o slug
|
|
1243
1297
|
nf status estado da feature da branch atual
|
|
1244
1298
|
nf spec imprime a spec da feature
|
|
1245
1299
|
nf move <stage> [--note] move a feature de stage
|
|
@@ -1333,6 +1387,8 @@ ${HELP}`);
|
|
|
1333
1387
|
client,
|
|
1334
1388
|
resolveBranch: () => currentBranch(defaultGitRunner),
|
|
1335
1389
|
createBranch: (name) => createBranch(name, defaultGitRunner),
|
|
1390
|
+
switchBranch: (name) => switchBranch(name, defaultGitRunner),
|
|
1391
|
+
listBranches: () => listLocalBranches(defaultGitRunner),
|
|
1336
1392
|
readFile: (path) => readFile(path, "utf8"),
|
|
1337
1393
|
readStdin,
|
|
1338
1394
|
writeEnvVars: (pairs, opts) => writeEnvVars(pairs, { file: opts?.file }),
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@h1veframework/cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "CLI
|
|
3
|
+
"version": "0.6.0",
|
|
4
|
+
"description": "The CLI for H1VE — the governance & memory layer for teams building with AI. Drive your feature flow (start, status, move, spec, done) from the terminal. Works with Claude Code, Cursor and Copilot.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
@@ -19,17 +19,27 @@
|
|
|
19
19
|
"url": "git+https://github.com/GusHartz/nexus-flow.git",
|
|
20
20
|
"directory": "packages/cli"
|
|
21
21
|
},
|
|
22
|
-
"homepage": "https://
|
|
22
|
+
"homepage": "https://h1ve.org",
|
|
23
23
|
"bugs": {
|
|
24
24
|
"url": "https://github.com/GusHartz/nexus-flow/issues"
|
|
25
25
|
},
|
|
26
26
|
"keywords": [
|
|
27
27
|
"h1ve",
|
|
28
|
-
"h1ve-flow",
|
|
29
28
|
"cli",
|
|
30
29
|
"nf",
|
|
30
|
+
"ai",
|
|
31
|
+
"claude-code",
|
|
32
|
+
"cursor",
|
|
33
|
+
"copilot",
|
|
34
|
+
"ai-coding",
|
|
35
|
+
"ai-agents",
|
|
36
|
+
"memory",
|
|
37
|
+
"anti-drift",
|
|
38
|
+
"context",
|
|
39
|
+
"governance",
|
|
40
|
+
"developer-tools",
|
|
31
41
|
"dev-workflow",
|
|
32
|
-
"
|
|
42
|
+
"mcp"
|
|
33
43
|
],
|
|
34
44
|
"publishConfig": {
|
|
35
45
|
"access": "public"
|