@h1veframework/cli 0.6.0 → 0.7.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 +26 -24
- package/dist/index.js +57 -29
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
# @h1veframework/cli · `
|
|
1
|
+
# @h1veframework/cli · `h1ve`
|
|
2
2
|
|
|
3
3
|
**The command-line companion for [H1VE](https://h1ve.org) — the governance & memory layer for teams building with AI.**
|
|
4
4
|
|
|
5
5
|
Your team ships with AI (Claude Code, Cursor, Copilot). As the codebase grows, the AI and your teammates start
|
|
6
6
|
**losing context and drifting from decisions you already made**. H1VE keeps every feature aligned — a shared,
|
|
7
7
|
**eternal project memory** plus an **anti-drift engine** that checks each change against what's been decided.
|
|
8
|
-
`
|
|
8
|
+
`h1ve` drives that workflow straight from your terminal.
|
|
9
9
|
|
|
10
10
|
Part of the H1VE toolchain, alongside the **[MCP server](https://www.npmjs.com/package/@h1veframework/mcp)** for
|
|
11
11
|
Claude Code / Cursor.
|
|
@@ -16,46 +16,48 @@ Claude Code / Cursor.
|
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
18
|
npm i -g @h1veframework/cli
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
h1ve login # opens your browser → you click Authorize → done
|
|
20
|
+
h1ve status # state of the current branch's feature
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
`
|
|
23
|
+
`h1ve login` stores the credential locally (`~/.config/h1ve/credentials.json`, `600`) — the MCP server reads the
|
|
24
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. `
|
|
25
|
+
to paste and no `H1VE_PROJECT_ID` to set. `h1ve logout` removes it.
|
|
26
26
|
|
|
27
|
-
> `nf
|
|
27
|
+
> The legacy `nf` command still works as an alias — every `h1ve <cmd>` below can be run as `nf <cmd>`.
|
|
28
|
+
|
|
29
|
+
> `h1ve health` saying "no snapshots yet" is **success** (you're connected; the project just has no metrics yet).
|
|
28
30
|
|
|
29
31
|
## Commands
|
|
30
32
|
|
|
31
33
|
| Command | What it does |
|
|
32
34
|
|---|---|
|
|
33
|
-
| `
|
|
34
|
-
| `
|
|
35
|
-
| `
|
|
36
|
-
| `
|
|
37
|
-
| `
|
|
38
|
-
| `
|
|
39
|
-
| `
|
|
40
|
-
| `
|
|
41
|
-
| `
|
|
42
|
-
| `
|
|
35
|
+
| `h1ve login` / `h1ve logout` | Authorize in the browser and store the credential locally / remove it |
|
|
36
|
+
| `h1ve status` | State of the current branch's feature (stage, days active, blockers, sign-offs) |
|
|
37
|
+
| `h1ve start [<#\|id>] [--slug <s>]` | Start an assigned feature: create the `feat/{you}/{slug}` branch and record the slug |
|
|
38
|
+
| `h1ve spec` | Print the feature's spec (markdown) |
|
|
39
|
+
| `h1ve move <stage> [--note]` | Move the feature to another stage |
|
|
40
|
+
| `h1ve done [--from <file>] [--no-move]` | Submit the AI declaration (JSON) and move `dev → pr` |
|
|
41
|
+
| `h1ve blocker "<desc>"` | Open a blocker on the feature (you become its owner) |
|
|
42
|
+
| `h1ve health` | Latest technical-health snapshots for the project |
|
|
43
|
+
| `h1ve 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) |
|
|
44
|
+
| `h1ve serve [--port 7391]` | Run the local agent (`127.0.0.1`) so the dashboard can apply credentials from the browser |
|
|
43
45
|
|
|
44
46
|
Flags: `--json` (raw output for scripts) · `--project <name\|id>` (if you belong to more than one project) · `-h`.
|
|
45
47
|
|
|
46
48
|
## Example
|
|
47
49
|
|
|
48
50
|
```bash
|
|
49
|
-
|
|
51
|
+
h1ve start # start your assigned feature (creates the branch)
|
|
50
52
|
# ... work: git, code, commits ...
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
53
|
+
h1ve status # check state anytime
|
|
54
|
+
h1ve blocker "waiting on the Neon credential" # blocked? open a blocker
|
|
55
|
+
h1ve done --from ai-declaration.json # submit the AI declaration + move dev → pr
|
|
54
56
|
```
|
|
55
57
|
|
|
56
58
|
## CI / headless (env vars)
|
|
57
59
|
|
|
58
|
-
No browser? Configure by env (takes **precedence** over `
|
|
60
|
+
No browser? Configure by env (takes **precedence** over `h1ve login`):
|
|
59
61
|
|
|
60
62
|
```bash
|
|
61
63
|
export H1VE_API_URL="https://app.h1ve.org" # optional (this is the default)
|
|
@@ -67,12 +69,12 @@ export H1VE_API_KEY="nf_pat_..." # a PAT from app.h1ve.org/api-token
|
|
|
67
69
|
|
|
68
70
|
| Symptom | Fix |
|
|
69
71
|
|---|---|
|
|
70
|
-
| `
|
|
72
|
+
| `h1ve: command not found` | Node missing or wrong terminal. Check `node --version`. On Windows, reopen the shell or use the VS Code terminal. |
|
|
71
73
|
| `404` on install | npm propagation right after a publish — wait a few minutes. |
|
|
72
74
|
| `NO_PROJECT` | You belong to more than one project — pass `--project <name\|id>`. |
|
|
73
75
|
| `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. |
|
|
74
76
|
|
|
75
|
-
Requires **Node.js 18.18+**.
|
|
77
|
+
Requires **Node.js 18.18+**. The legacy `nf` command and env names `NEXUS_FLOW_API_URL` / `NEXUS_FLOW_API_KEY` are still accepted.
|
|
76
78
|
|
|
77
79
|
---
|
|
78
80
|
|
package/dist/index.js
CHANGED
|
@@ -95,7 +95,7 @@ function errorForStatus(status2, code) {
|
|
|
95
95
|
case 422:
|
|
96
96
|
return new NexusApiError("INVALID_INPUT", "Branch inv\xE1lida.");
|
|
97
97
|
default:
|
|
98
|
-
return new NexusApiError("API_ERROR", `Falha ao consultar o
|
|
98
|
+
return new NexusApiError("API_ERROR", `Falha ao consultar o H1VE (HTTP ${status2}).`);
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
101
|
function mapWriteError(status2, code) {
|
|
@@ -124,7 +124,7 @@ function mapWriteError(status2, code) {
|
|
|
124
124
|
case 422:
|
|
125
125
|
return new NexusApiError(code ?? "INVALID_INPUT", "Entrada inv\xE1lida para esta a\xE7\xE3o.");
|
|
126
126
|
default:
|
|
127
|
-
return new NexusApiError("API_ERROR", `Falha ao escrever no
|
|
127
|
+
return new NexusApiError("API_ERROR", `Falha ao escrever no H1VE (HTTP ${status2}).`);
|
|
128
128
|
}
|
|
129
129
|
}
|
|
130
130
|
async function readErrorCode(res) {
|
|
@@ -434,7 +434,7 @@ async function resolveConfig(env = process.env, store = defaultCredentialStore)
|
|
|
434
434
|
}
|
|
435
435
|
if (!apiKey) {
|
|
436
436
|
throw new CliError(
|
|
437
|
-
"Voc\xEA n\xE3o est\xE1 logado. Rode `
|
|
437
|
+
"Voc\xEA n\xE3o est\xE1 logado. Rode `h1ve login` (abre o navegador) \u2014 ou defina H1VE_API_KEY.\n (o nome legado NEXUS_FLOW_API_KEY tamb\xE9m \xE9 aceito)",
|
|
438
438
|
2
|
|
439
439
|
);
|
|
440
440
|
}
|
|
@@ -490,18 +490,45 @@ function formatBlocker(r) {
|
|
|
490
490
|
return `\u2713 Blocker aberto: ${r.description}`;
|
|
491
491
|
}
|
|
492
492
|
function formatStartList(list) {
|
|
493
|
-
const header = "Features para iniciar ou retomar \u2014 escolha:
|
|
493
|
+
const header = "Features para iniciar ou retomar \u2014 escolha: h1ve start <n\xBA>";
|
|
494
494
|
const rows = list.map((f, i) => {
|
|
495
495
|
const tag = f.branch_slug !== null ? " (retomar)" : "";
|
|
496
496
|
return ` ${String(i + 1).padStart(2)}. ${f.name} [${stageLabel(f.stage)}]${tag}`;
|
|
497
497
|
});
|
|
498
498
|
return [header, ...rows].join("\n");
|
|
499
499
|
}
|
|
500
|
+
var PRE_DEV_STAGES = /* @__PURE__ */ new Set(["backlog", "spec", "blocked"]);
|
|
501
|
+
function startFlowLines(r) {
|
|
502
|
+
const stage = r.landed_stage ?? r.feature.stage;
|
|
503
|
+
if (!PRE_DEV_STAGES.has(stage)) {
|
|
504
|
+
return [
|
|
505
|
+
`Fluxo: card em ${stageLabel(stage)} \u2014 pronto para codar.`,
|
|
506
|
+
"Pr\xF3ximo: codar \u2192 'h1ve status' \u2192 'h1ve done' (move dev \u2192 pr)"
|
|
507
|
+
];
|
|
508
|
+
}
|
|
509
|
+
const blocked = r.advance?.blocked_by;
|
|
510
|
+
if (blocked === "SPEC_REQUIRED" || blocked === "SPEC_MALFORMED") {
|
|
511
|
+
return [
|
|
512
|
+
"Fluxo: card em Spec (aguarda a SPEC \u2014 Modo Estrito).",
|
|
513
|
+
"Pr\xF3ximo: escreva a SPEC no card \u2192 'h1ve start' de novo para avan\xE7ar at\xE9 Dev."
|
|
514
|
+
];
|
|
515
|
+
}
|
|
516
|
+
if (blocked === "INVALID_TRANSITION") {
|
|
517
|
+
return [
|
|
518
|
+
`Fluxo: card em ${stageLabel(stage)}.`,
|
|
519
|
+
"Pr\xF3ximo: pe\xE7a ao arquiteto para mover o card at\xE9 Dev (voc\xEA n\xE3o tem permiss\xE3o)."
|
|
520
|
+
];
|
|
521
|
+
}
|
|
522
|
+
return [
|
|
523
|
+
`Fluxo: card em ${stageLabel(stage)}.`,
|
|
524
|
+
"Pr\xF3ximo: mova o card at\xE9 Dev no board para come\xE7ar."
|
|
525
|
+
];
|
|
526
|
+
}
|
|
500
527
|
function formatStart(r, opts) {
|
|
501
528
|
return [
|
|
502
529
|
`\u2713 ${opts?.resumed ? "Retomada" : "Iniciada"}: ${r.feature.name}`,
|
|
503
530
|
`Branch: ${r.branch_name} (${opts?.resumed ? "pronta e ativa" : "criada e ativa"})`,
|
|
504
|
-
|
|
531
|
+
...startFlowLines(r)
|
|
505
532
|
].join("\n");
|
|
506
533
|
}
|
|
507
534
|
function formatHealth(snaps) {
|
|
@@ -557,7 +584,7 @@ function isStage(s) {
|
|
|
557
584
|
}
|
|
558
585
|
async function move(io, args) {
|
|
559
586
|
const stage = args.positional;
|
|
560
|
-
if (!stage) throw new CliError("Uso:
|
|
587
|
+
if (!stage) throw new CliError("Uso: h1ve move <stage>");
|
|
561
588
|
if (!isStage(stage)) {
|
|
562
589
|
throw new CliError(`Stage inv\xE1lido: ${stage}. V\xE1lidos: ${STAGES.join(", ")}`);
|
|
563
590
|
}
|
|
@@ -570,7 +597,7 @@ async function move(io, args) {
|
|
|
570
597
|
// src/commands/blocker.ts
|
|
571
598
|
async function blocker(io, args) {
|
|
572
599
|
const description = args.positional;
|
|
573
|
-
if (!description) throw new CliError('Uso:
|
|
600
|
+
if (!description) throw new CliError('Uso: h1ve blocker "<descri\xE7\xE3o>"');
|
|
574
601
|
const branch = await io.resolveBranch();
|
|
575
602
|
const digest = await io.client.getContext(branch);
|
|
576
603
|
const result = await io.client.createBlocker(digest.feature.id, description);
|
|
@@ -610,7 +637,7 @@ ${movedLine}`, data: { declaration, move: moved } };
|
|
|
610
637
|
throw new CliError(
|
|
611
638
|
`${sent}
|
|
612
639
|
\u2717 Move dev \u2192 pr falhou: ${reason}
|
|
613
|
-
(a declaration J\xC1 foi enviada \u2014 rode \`
|
|
640
|
+
(a declaration J\xC1 foi enviada \u2014 rode \`h1ve move pr\` para concluir; N\xC3O repita \`h1ve done\`, pois enviaria a declaration de novo.)`
|
|
614
641
|
);
|
|
615
642
|
}
|
|
616
643
|
}
|
|
@@ -638,7 +665,7 @@ function pick(list, arg) {
|
|
|
638
665
|
if (byId) return byId;
|
|
639
666
|
const idx = Number(arg);
|
|
640
667
|
if (Number.isInteger(idx) && idx >= 1 && idx <= list.length) return list[idx - 1] ?? null;
|
|
641
|
-
throw new CliError(`Feature n\xE3o encontrada: ${arg}. Rode '
|
|
668
|
+
throw new CliError(`Feature n\xE3o encontrada: ${arg}. Rode 'h1ve start' para ver a lista.`);
|
|
642
669
|
}
|
|
643
670
|
async function start(io, args) {
|
|
644
671
|
const [features, localBranches] = await Promise.all([io.client.listFeatures(), io.listBranches()]);
|
|
@@ -1236,7 +1263,7 @@ async function startCallbackServer(expectedState) {
|
|
|
1236
1263
|
const waitForCode = () => new Promise((resolve, reject) => {
|
|
1237
1264
|
const timer = setTimeout(() => {
|
|
1238
1265
|
server.close();
|
|
1239
|
-
reject(new CliError("Tempo esgotado esperando a autoriza\xE7\xE3o. Rode `
|
|
1266
|
+
reject(new CliError("Tempo esgotado esperando a autoriza\xE7\xE3o. Rode `h1ve login` de novo."));
|
|
1240
1267
|
}, LOGIN_TIMEOUT_MS);
|
|
1241
1268
|
void codePromise.then((code) => {
|
|
1242
1269
|
clearTimeout(timer);
|
|
@@ -1286,31 +1313,32 @@ async function logout(io, _args) {
|
|
|
1286
1313
|
}
|
|
1287
1314
|
|
|
1288
1315
|
// src/index.ts
|
|
1289
|
-
var VERSION = "0.
|
|
1290
|
-
var HELP = `
|
|
1316
|
+
var VERSION = "0.7.0";
|
|
1317
|
+
var HELP = `h1ve \u2014 CLI do H1VE
|
|
1318
|
+
(o comando legado \`nf\` continua funcionando como alias)
|
|
1291
1319
|
|
|
1292
1320
|
Uso:
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1321
|
+
h1ve login autoriza no navegador e salva a credencial local (sem colar nada)
|
|
1322
|
+
h1ve logout remove a credencial local
|
|
1323
|
+
h1ve start [<n\xBA|id>] [--slug <s>]
|
|
1296
1324
|
inicia (ou retoma) uma feature: cria/entra na branch e grava o slug
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1325
|
+
h1ve status estado da feature da branch atual
|
|
1326
|
+
h1ve spec imprime a spec da feature
|
|
1327
|
+
h1ve move <stage> [--note] move a feature de stage
|
|
1328
|
+
h1ve blocker "<desc>" abre um blocker na feature
|
|
1329
|
+
h1ve done [--from <arq>] [--no-move]
|
|
1302
1330
|
envia a AI declaration (JSON) e move dev \u2192 pr
|
|
1303
|
-
|
|
1304
|
-
|
|
1331
|
+
h1ve health \xFAltimos snapshots de sa\xFAde t\xE9cnica
|
|
1332
|
+
h1ve connect --kind <k> --label <l> --env KEY=VAL [...]
|
|
1305
1333
|
aplica a credencial no .env.local LOCAL (nunca ao servidor)
|
|
1306
1334
|
e registra o invent\xE1rio da conex\xE3o (Jeito B)
|
|
1307
|
-
|
|
1335
|
+
h1ve serve [--port 7391] [--origin http://localhost:3000]
|
|
1308
1336
|
sobe o agente local (127.0.0.1) p/ o menu visual aplicar
|
|
1309
1337
|
credenciais pelo navegador \u2014 imprime o c\xF3digo de pareamento
|
|
1310
1338
|
|
|
1311
1339
|
Flags:
|
|
1312
1340
|
--json sa\xEDda em JSON cru (comp\xF5e em scripts)
|
|
1313
|
-
--slug <s> slug custom para '
|
|
1341
|
+
--slug <s> slug custom para 'h1ve start' (default = derivado do nome)
|
|
1314
1342
|
--kind <k> tipo da conex\xE3o (database|deploy|observability|api|auth|other)
|
|
1315
1343
|
--label <l> nome da conex\xE3o (ex.: "Neon (prod)")
|
|
1316
1344
|
--env KEY=VAL vari\xE1vel a escrever no .env.local (repet\xEDvel)
|
|
@@ -1319,7 +1347,7 @@ Flags:
|
|
|
1319
1347
|
-h, --help esta ajuda
|
|
1320
1348
|
-v, --version vers\xE3o do CLI
|
|
1321
1349
|
|
|
1322
|
-
Ambiente (opcional \u2014 '
|
|
1350
|
+
Ambiente (opcional \u2014 'h1ve login' dispensa):
|
|
1323
1351
|
H1VE_API_URL URL do H1VE Flow (default https://app.h1ve.org)
|
|
1324
1352
|
H1VE_API_KEY PAT (nf_pat_\u2026) p/ escrever, ou a chave de servi\xE7o (s\xF3 leitura)
|
|
1325
1353
|
(nomes legados aceitos: NEXUS_FLOW_API_URL / NEXUS_FLOW_API_KEY)
|
|
@@ -1345,7 +1373,7 @@ function parse(argv) {
|
|
|
1345
1373
|
json: { type: "boolean" },
|
|
1346
1374
|
help: { type: "boolean", short: "h" },
|
|
1347
1375
|
version: { type: "boolean", short: "v" },
|
|
1348
|
-
//
|
|
1376
|
+
// h1ve connect (SPEC-050)
|
|
1349
1377
|
kind: { type: "string" },
|
|
1350
1378
|
label: { type: "string" },
|
|
1351
1379
|
url: { type: "string" },
|
|
@@ -1353,13 +1381,13 @@ function parse(argv) {
|
|
|
1353
1381
|
project: { type: "string" },
|
|
1354
1382
|
file: { type: "string" },
|
|
1355
1383
|
env: { type: "string", multiple: true },
|
|
1356
|
-
//
|
|
1384
|
+
// h1ve serve (SPEC-051)
|
|
1357
1385
|
port: { type: "string" },
|
|
1358
1386
|
origin: { type: "string" }
|
|
1359
1387
|
}
|
|
1360
1388
|
});
|
|
1361
1389
|
} catch {
|
|
1362
|
-
throw new CliError("Argumentos inv\xE1lidos. Veja `
|
|
1390
|
+
throw new CliError("Argumentos inv\xE1lidos. Veja `h1ve --help`.", 2);
|
|
1363
1391
|
}
|
|
1364
1392
|
}
|
|
1365
1393
|
async function main(argv) {
|
|
@@ -1423,7 +1451,7 @@ function messageFor(err) {
|
|
|
1423
1451
|
if (err instanceof Error && (err.message === "NOT_A_GIT_REPO" || err.message === "GIT_BRANCH_FAILED")) {
|
|
1424
1452
|
return "N\xE3o consegui criar a branch git. Rode dentro do reposit\xF3rio (git) da feature.";
|
|
1425
1453
|
}
|
|
1426
|
-
return "Erro inesperado ao falar com o
|
|
1454
|
+
return "Erro inesperado ao falar com o H1VE.";
|
|
1427
1455
|
}
|
|
1428
1456
|
main(process.argv.slice(2)).then((code) => {
|
|
1429
1457
|
process.exitCode = code;
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@h1veframework/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
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": {
|
|
8
|
+
"h1ve": "dist/index.js",
|
|
8
9
|
"nf": "dist/index.js"
|
|
9
10
|
},
|
|
10
11
|
"files": [
|