@bugb-technologies/bravos 0.0.0-stage → 1.0.1
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/LICENSE +70 -0
- package/README.md +225 -2
- package/dist/account.js +140 -0
- package/dist/artifacts.js +450 -0
- package/dist/ci.js +52 -0
- package/dist/cli.js +963 -0
- package/dist/codegraph.js +268 -0
- package/dist/cursor.js +70 -0
- package/dist/detect.js +26 -0
- package/dist/doctor.js +99 -0
- package/dist/global.js +95 -0
- package/dist/graphbin.js +136 -0
- package/dist/guardlink.js +123 -0
- package/dist/guide.js +255 -0
- package/dist/help.js +334 -0
- package/dist/hooks.js +167 -0
- package/dist/install.js +209 -0
- package/dist/licence.js +41 -0
- package/dist/lock.js +50 -0
- package/dist/mcp.js +139 -0
- package/dist/migrate.js +275 -0
- package/dist/omp.js +46 -0
- package/dist/opencode.js +118 -0
- package/dist/paths.js +33 -0
- package/dist/plugin.js +101 -0
- package/dist/report/check.js +252 -0
- package/dist/report/data.js +401 -0
- package/dist/report/export.js +105 -0
- package/dist/report/markdown.js +310 -0
- package/dist/report/meta.js +48 -0
- package/dist/report/pdf.js +96 -0
- package/dist/review-server.js +7 -0
- package/dist/review.js +274 -0
- package/dist/setup.js +133 -0
- package/dist/skills.js +81 -0
- package/dist/types.js +9 -0
- package/dist/uninstall.js +192 -0
- package/dist/versions.js +8 -0
- package/dist/watch-frame.js +593 -0
- package/dist/watch.js +873 -0
- package/dist/workspace.js +316 -0
- package/docs/GUIDE.md +323 -0
- package/hooks/autopilot.mjs +45 -0
- package/hooks/lib/autopilot.mjs +191 -0
- package/hooks/lib/briefing.mjs +750 -0
- package/hooks/lib/licence.mjs +452 -0
- package/hooks/lib/pre-pr.mjs +209 -0
- package/hooks/lib/sast.mjs +207 -0
- package/hooks/lib/setup.mjs +107 -0
- package/hooks/lib/workspace.mjs +78 -0
- package/hooks/pre-pr.mjs +54 -0
- package/hooks/session-start.mjs +82 -0
- package/package.json +49 -4
- package/skills/bravos/SKILL.md +63 -0
- package/skills/bravos-annotate/SKILL.md +56 -0
- package/skills/bravos-annotate/scripts/lib/pick.mjs +127 -0
- package/skills/bravos-annotate/scripts/slice.mjs +141 -0
- package/skills/bravos-campaign/SKILL.md +49 -0
- package/skills/bravos-cicd/SKILL.md +48 -0
- package/skills/bravos-codegraph/SKILL.md +57 -0
- package/skills/bravos-context/SKILL.md +51 -0
- package/skills/bravos-context/intent.example.md +48 -0
- package/skills/bravos-env/SKILL.md +48 -0
- package/skills/bravos-env/scope.example.yaml +50 -0
- package/skills/bravos-prove/SKILL.md +52 -0
- package/skills/bravos-recheck/SKILL.md +47 -0
- package/skills/bravos-report/SKILL.md +53 -0
- package/skills/bravos-report/assessment.md +54 -0
- package/skills/bravos-report/brief.md +23 -0
- package/skills/bravos-report/finding.md +74 -0
- package/skills/bravos-report/retest.md +29 -0
- package/skills/bravos-report/style.md +44 -0
- package/skills/bravos-sast/SKILL.md +48 -0
- package/skills/bravos-schedule/SKILL.md +44 -0
- package/skills/bravos-schedule/loop.md +16 -0
- package/skills/bravos-ship/SKILL.md +58 -0
- package/skills/bravos-templatize/SKILL.md +48 -0
- package/skills/bravos-templatize/operator-guidance.md +20 -0
- package/templates/omp/bravos.js +62 -0
- package/templates/opencode/bravos.js +33 -0
- package/templates/report/report.html +88 -0
- package/templates/review/review.html +327 -0
- package/templates/workflows/bravos-nightly-claude.yml +173 -0
- package/templates/workflows/bravos-nightly-codex.yml +172 -0
- package/templates/workflows/bravos-pr-gate.yml +142 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
BRAVOS SOFTWARE LICENCE
|
|
2
|
+
Version 1.0, 6 November 2026
|
|
3
|
+
|
|
4
|
+
Copyright (c) 2026 BugB Technologies. All rights reserved.
|
|
5
|
+
|
|
6
|
+
Bravos (the "Software") is proprietary software of BugB Technologies
|
|
7
|
+
("BugB"). It is licensed, not sold. By installing or using the Software you
|
|
8
|
+
agree to these terms. If you do not agree, do not install or use it.
|
|
9
|
+
|
|
10
|
+
1. Free use
|
|
11
|
+
You may install and use the Software at no charge for the features of
|
|
12
|
+
the Free plan, as described at https://account.bugb.io/pricing.
|
|
13
|
+
|
|
14
|
+
2. Paid features
|
|
15
|
+
The features the Software marks as Team features (at this version: the
|
|
16
|
+
pre-PR gate and autopilot, the CI and nightly workflows, scheduled
|
|
17
|
+
upkeep, multi-repository workspaces, campaigns, SAST and the code graph)
|
|
18
|
+
may be used only with a valid licence issued by BugB through
|
|
19
|
+
https://account.bugb.io, and only within the plan, seats and term that
|
|
20
|
+
licence states, including a trial.
|
|
21
|
+
|
|
22
|
+
3. Restrictions
|
|
23
|
+
You may not, and may not let anyone else:
|
|
24
|
+
(a) copy the Software, except as needed to install and run it under
|
|
25
|
+
these terms;
|
|
26
|
+
(b) modify, adapt or create derivative works of the Software, except
|
|
27
|
+
that you may edit the files it writes into your own repositories
|
|
28
|
+
for your own internal use;
|
|
29
|
+
(c) remove, disable, bypass or tamper with any licence check, or use a
|
|
30
|
+
Team feature without a valid licence;
|
|
31
|
+
(d) redistribute, publish, sublicense, rent, lease, sell, or offer the
|
|
32
|
+
Software or any part of it as a service to others;
|
|
33
|
+
(e) reverse engineer the Software or the binaries it downloads, except
|
|
34
|
+
to the extent applicable law allows this despite this restriction;
|
|
35
|
+
(f) remove or alter any copyright or licence notice.
|
|
36
|
+
|
|
37
|
+
4. Your content
|
|
38
|
+
The threat models, annotations, reports, templates and other output the
|
|
39
|
+
Software produces from your code belong to you. BugB claims no rights in
|
|
40
|
+
them.
|
|
41
|
+
|
|
42
|
+
5. Third-party components
|
|
43
|
+
The Software depends on, and can install, third-party software
|
|
44
|
+
(including guardlink, cert-x-gen and open-source npm packages) that is
|
|
45
|
+
licensed under its own terms. Those terms govern those components.
|
|
46
|
+
|
|
47
|
+
6. Termination
|
|
48
|
+
Your rights under these terms end automatically if you breach them. When
|
|
49
|
+
they end, you must stop using the Software and delete every copy.
|
|
50
|
+
|
|
51
|
+
7. No warranty
|
|
52
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
53
|
+
OR IMPLIED, INCLUDING THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
|
54
|
+
PARTICULAR PURPOSE AND NON-INFRINGEMENT. Security testing can disturb
|
|
55
|
+
the systems it runs against; you are responsible for running it only
|
|
56
|
+
against systems you own or are authorised to test.
|
|
57
|
+
|
|
58
|
+
8. Limitation of liability
|
|
59
|
+
TO THE MAXIMUM EXTENT PERMITTED BY LAW, BUGB IS NOT LIABLE FOR ANY
|
|
60
|
+
INDIRECT, INCIDENTAL, SPECIAL OR CONSEQUENTIAL DAMAGES, OR LOSS OF DATA,
|
|
61
|
+
PROFITS OR BUSINESS, ARISING FROM THE SOFTWARE. BUGB'S TOTAL LIABILITY
|
|
62
|
+
IS LIMITED TO THE FEES YOU PAID FOR THE SOFTWARE IN THE TWELVE MONTHS
|
|
63
|
+
BEFORE THE CLAIM.
|
|
64
|
+
|
|
65
|
+
9. Other agreements
|
|
66
|
+
If you have a separate written agreement with BugB that covers the
|
|
67
|
+
Software, such as an Enterprise order, that agreement prevails where it
|
|
68
|
+
differs from these terms.
|
|
69
|
+
|
|
70
|
+
Questions: https://account.bugb.io/contact
|
package/README.md
CHANGED
|
@@ -1,3 +1,226 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Bravos
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
bravos is a skills pack plus a thin installer that makes Claude Code and Codex power users of guardlink (threat-model-as-code), cert-x-gen (`cxg`, pentest-as-code) and the code graph, through the harnesses' own skills and hooks. No orchestrator process, no state store: `bravos init` writes skills, MCP wiring and a session-start hook into a repo; the agents do the rest with the real CLIs.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @bugb-technologies/bravos
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
The command it installs is `bravos`.
|
|
12
|
+
|
|
13
|
+
Then, in order: `bravos account activate` signs you in once in the browser and writes the licence (a 30-day Team trial when the account has no plan; skip this to stay on Free), `bravos init` in a repository writes the skills, hooks and MCP wiring, and `bravos setup` puts the analyzer and the code graph on the machine (Team plan). Node 20 or newer; guardlink and cxg are installed by `bravos setup --tools -y` or by hand.
|
|
14
|
+
|
|
15
|
+
**Used the Python `bravos` before?** Its `bravos` command usually sits in `~/.local/bin`, which most shells search before npm's folder, so it would still answer when you type `bravos`. Keep it under another name, and this CLI keeps using its code graph and analyzer until `bravos setup` installs its own:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
mv ~/.local/bin/bravos ~/.local/bin/bravos-py
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Or remove it (`pipx uninstall bravos` or `uv tool uninstall bravos`). `which -a bravos` should then show npm's copy first (its real path ends in `node_modules/@bugb-technologies/bravos/dist/cli.js`).
|
|
22
|
+
|
|
23
|
+
## Commands
|
|
24
|
+
|
|
25
|
+
- `bravos init`: write skills, MCP wiring and the session-start hook into the current repo. `--agent claude,codex` picks the harnesses instead of detecting them. `--pre-pr-hook` also registers the PreToolUse gate on `gh pr create` / `gh pr ready` and lists it under `pre_pr` in `.guardlink/config.json`, so guardlink's agent block names it after `guardlink sync`; `--ci` also writes the GitHub Actions workflows; `--scope` writes the loopback-only cxg scope starter to `.guardlink/scope.yaml` when no scope file exists (never overwritten). A repo with a `.cursor/` folder also gets `.cursor/rules/bravos.mdc`, a pointer to the skills.
|
|
26
|
+
- `bravos sync`: refresh the installed skills and wiring to match this version of bravos; it also adds the cxg and codegraph MCP servers once they are installed (bravos's own committed MCP config carries guardlink only). The codegraph server is registered by its full path with the graph store as `BRAVOS_CODEGRAPH_DIR`, so the MCP tools read the graphs `bravos graph build` writes; after a `bravos setup` that stages a new bundle, `sync` refreshes that one entry, and keeps every other existing entry as it is.
|
|
27
|
+
- `bravos setup`: puts the binaries bravos drives on this machine. The analyzer (`bravos-cli`, SAST) and the code graph (`codegraph-build`, `codegraph-mcp`) are signed bundles fetched through the account portal on the Team plan — the portal verifies the licence, the CLI verifies each file's Ed25519 signature before it unpacks, and nothing under `$BRAVOS_HOME` changes until every check has passed; `--tools -y` also installs guardlink (npm) and cxg (cargo). `--check` reports, `--dry-run` says what would happen.
|
|
28
|
+
- `bravos graph`: `graph build [DIR]` indexes a repository (`--scip no-exec` by default, so no repository code runs); `graph <question> [args] --json` asks it — the same answers as the `codegraph_` MCP tools, through a one-call MCP session on stdio; `graph --json` lists every question this machine's binary answers.
|
|
29
|
+
- `bravos sast run`: analyzes the repository with the staged analyzer into `.bravos/sast/<run>/` (not committed), which `bravos sast status` and `candidates` read. Takes the same `--agent`, `--pre-pr-hook`, `--ci` and `--scope` options as `init`, and never runs `guardlink init`.
|
|
30
|
+
- `bravos doctor`: check that the installed pieces and the underlying CLIs are present and healthy.
|
|
31
|
+
- `bravos watch`: a live, read-only view of the repo's security work for a second terminal pane beside any agent (below).
|
|
32
|
+
|
|
33
|
+
## Skills
|
|
34
|
+
|
|
35
|
+
| Skill | What it does |
|
|
36
|
+
|---|---|
|
|
37
|
+
| `bravos` | Front door: reads the session briefing and routes to the skill below. |
|
|
38
|
+
| `bravos-annotate` | Grows the threat model in budgeted slices (or deep rounds), gated by `guardlink lint`. |
|
|
39
|
+
| `bravos-codegraph` | One bounded question to the code graph instead of grepping. With the graph built, bravos-templatize targets the route and auth that reach each sink, bravos-recheck follows a fix's callers into other files, bravos-context drafts the real public surface and bravos-prove tests the most reachable exposures first; without it they read the code and say so. |
|
|
40
|
+
| `bravos-ship` | The pre-PR security gate. |
|
|
41
|
+
| `bravos-templatize` | Turns open exposures into validated cxg templates, in-session; records classes with no oracle as gaps. |
|
|
42
|
+
| `bravos-env` | Brings up a target the user owns: loopback by default, a reviewed `scope.yaml`, the human's typed authorization, a real session. |
|
|
43
|
+
| `bravos-prove` | Runs the authorized test and writes outcomes back only through guardlink's hypothesis ledger. |
|
|
44
|
+
| `bravos-recheck` | After a fix: re-verifies stale claims, replays retest kits, checks the fix added no exposure. |
|
|
45
|
+
| `bravos-sast` | Licensed static analysis (bravos-cli: Semgrep, request-to-sink taint, dependency CVEs, secrets, IaC) joined to the threat model and the code graph; says plainly when it is unavailable. Its runs land in `.bravos/sast/` (not committed), and `bravos sast candidates` hands them on: reachable findings joined to a claim give bravos-templatize and bravos-prove the route to target, findings not in the threat model are leads for bravos-annotate, and bravos-recheck rescans after a fix. A static finding is a lead; only a run confirms. |
|
|
46
|
+
| `bravos-schedule` | Recurring upkeep: an in-session `/loop`, the nightly workflow, or a cloud task you create. Never live tests. |
|
|
47
|
+
| `bravos-cicd` | Audits and writes the security workflows; fixes broken guardlink calls and script-injection risks. |
|
|
48
|
+
| `bravos-report` | Writes the assessment, one finding, a retest or an executive brief from the evidence `bravos report data` gathers, checks it with `bravos report check`, and exports it to md, txt, html and pdf. |
|
|
49
|
+
|
|
50
|
+
## Plans and the licence
|
|
51
|
+
|
|
52
|
+
Bravos is free for one person researching one repository, and licensed per developer for a team.
|
|
53
|
+
|
|
54
|
+
| Plan | What it gives |
|
|
55
|
+
|---|---|
|
|
56
|
+
| **Free (researcher)** — no licence | bravos-context, bravos-annotate, bravos-templatize, bravos-env, bravos-prove, bravos-recheck, bravos-report and `bravos watch`, in one repository |
|
|
57
|
+
| **Team** (Solo is one seat) | everything above plus the pre-PR gate and autopilot, the CI and nightly workflows, scheduled upkeep, multi-repository workspaces, campaigns, SAST and the code graph |
|
|
58
|
+
|
|
59
|
+
`bravos licence` says which plan this machine is on, where the licence is and what it unlocks; the same line opens every session's briefing (`Plan: …`), `bravos doctor`, `bravos next` and the `bravos watch` header. `bravos account activate` signs you in once in the browser and writes the licence — a 30-day Team trial with no card when the account has no plan — and `bravos account import <file>` installs one downloaded from [account.bugb.io/dashboard/license](https://account.bugb.io/dashboard/license). A paid command or flag used without a licence explains what it would do and how to start the trial, and writes nothing; a paid hook (the pre-PR gate, autopilot) stands down with one line and never blocks a pull request; a paid skill stops at its first step and says so. The licence is a signed token verified offline (`BRAVOS_LICENCE`, or `licence.jwt` under `$BRAVOS_HOME`, default `~/.bravos`); after expiry it keeps working for min(14 days, a quarter of the term) and says so. Seats are counted in the account portal, not on the machine.
|
|
60
|
+
|
|
61
|
+
## Several repositories, one app: `bravos workspace`
|
|
62
|
+
|
|
63
|
+
For microservices, clone the services side by side in one folder and link them:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
bravos workspace create platform \
|
|
67
|
+
--member ./gateway=gateway@http://127.0.0.1:8080 \
|
|
68
|
+
--member ./orders=orders@http://127.0.0.1:8081 \
|
|
69
|
+
--member ./billing=billing@http://127.0.0.1:8082
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
That one command:
|
|
73
|
+
- creates a bravos workspace, the source of truth for members, services and URLs;
|
|
74
|
+
- runs `guardlink link-project`, so each repo's `.guardlink/workspace.yaml` names its siblings;
|
|
75
|
+
- runs `bravos init` (or `sync`) in each service, and installs bravos in the shared folder, the **app folder**;
|
|
76
|
+
- writes a snapshot to `.bravos/workspace/` in each of them (gitignored, since its paths are machine-local).
|
|
77
|
+
|
|
78
|
+
`add`, `remove`, `target NAME SERVICE=URL`, `show [NAME] [--json]` and `sync` keep all three in step. Members can live anywhere, but only the app folder lets the code graph link services: `bravos graph build <app folder> --scip no-exec` connects one service's HTTP, gRPC, GraphQL or OpenAPI client call to another's handler.
|
|
79
|
+
|
|
80
|
+
**In a service repo:**
|
|
81
|
+
- the briefing names the app, the other services, and the calls into and out of this one;
|
|
82
|
+
- `bravos watch` gets a SERVICES panel, and `s` shows every service and cross-service edge.
|
|
83
|
+
|
|
84
|
+
**At the app folder:**
|
|
85
|
+
- the briefing lists each service's open, high and confirmed exposures and coverage;
|
|
86
|
+
- `bravos report data --workspace` gathers one report across services, with ids prefixed by service (`orders:EXP-3`) and cross-service calls as `EDGE-n`.
|
|
87
|
+
|
|
88
|
+
Declare each call to another service as `@flows #<this>-<x> -> #<other>-<y> via http|grpc|queue`, with plain ids prefixed by service. guardlink merge, bravos export and cxg chaining all read this form. guardlink's dotted `#repo.tag` form is not chained by cxg.
|
|
89
|
+
|
|
90
|
+
## Reports: `bravos report` and the bravos-report skill
|
|
91
|
+
|
|
92
|
+
Ask the agent to "write the assessment report" (or a single finding, a retest, an executive brief) and the bravos-report skill does it in three steps:
|
|
93
|
+
|
|
94
|
+
1. `bravos report data` (free, no model) gathers every fact a report may cite into `.bravos/reports/<id>/evidence.json`:
|
|
95
|
+
- the threat model, and the hypothesis and boundary ledgers;
|
|
96
|
+
- each cxg run under `.guardlink/pentest-findings/`;
|
|
97
|
+
- SAST, the code graph, intent, scope and proving;
|
|
98
|
+
- tool versions.
|
|
99
|
+
|
|
100
|
+
Each fact gets an id (`EXP-n`, `LED-…`, `RUN-n/F-n`, `SAST-n`). Each exposure gets an evidence level computed from that evidence: reproduced at runtime, control held at runtime, established by code or configuration review, or suspected, awaiting validation. Token-shaped strings are redacted as they are read.
|
|
101
|
+
2. **The agent writes `report.md`** from the skill's templates. The full assessment follows the eight-section reading order (executive assessment, scope and limitations, system and threat context, coverage and approach, principal attack narratives, findings, remediation roadmap, retest and appendices). Every finding is self-contained, with:
|
|
102
|
+
- prerequisites;
|
|
103
|
+
- expected and observed behaviour;
|
|
104
|
+
- reproduction;
|
|
105
|
+
- root cause;
|
|
106
|
+
- impact (demonstrated, supported, uncertain);
|
|
107
|
+
- a severity rationale and a priority;
|
|
108
|
+
- remediation and verification.
|
|
109
|
+
|
|
110
|
+
Every proof claim cites its evidence ids.
|
|
111
|
+
3. **`bravos report check` fails the report** when any of these is wrong:
|
|
112
|
+
- a section is missing or out of order, or a finding lacks a field;
|
|
113
|
+
- a label is stronger than its cited evidence supports, or a cited id is unknown;
|
|
114
|
+
- a percentage has no denominator, or a CVSS score has no vector;
|
|
115
|
+
- something looks like a secret.
|
|
116
|
+
|
|
117
|
+
`bravos report export <id> --format md,txt,html,pdf` renders the checked report beside `report.md`:
|
|
118
|
+
- **html:** a branded page with no script and no external request, and a print stylesheet;
|
|
119
|
+
- **txt:** 80 columns;
|
|
120
|
+
- **pdf:** printed by an installed Chrome, Edge, Brave or Chromium (or Playwright's Chromium) in headless mode, with a throwaway profile. Set `BRAVOS_PDF_BROWSER` to choose one.
|
|
121
|
+
|
|
122
|
+
`bravos report list` shows the reports. Reports quote evidence, so `.bravos/reports/` carries its own `.gitignore`; sharing an export is a deliberate act. `bravos watch` shows the newest report in a REPORT panel: findings by evidence level and severity, check verdict, exports, and commits since. `p` shows its findings and executive section; `o` opens the pdf or html.
|
|
123
|
+
|
|
124
|
+
## Answering bravos's questions: `bravos review`
|
|
125
|
+
|
|
126
|
+
bravos-context no longer pastes thirty questions into chat. By default it assumes you are researching software you do not own: it drafts the intent from upstream's own docs (with links) and asks only the scope decisions — configuration baseline, single organisation or multi-tenant, attackers in scope, and intended-looking behaviours upstream leaves undocumented — which `intent.md` records as Assumptions that severities are rated against; say it is your product for the owner interview. It writes its suggestions to `.guardlink/intent-draft.json` and runs `bravos review intent`, which opens a bravos-branded page served from your machine only (loopback, a random token, answers capped and validated). Every card comes pre-filled with bravos's suggestion, its reason and its source; facts are folded away; decisions are marked "needs your call" and Confirm stays off until each one is clicked, so nobody rubber-stamps them. `intent.md` records each item as accepted as suggested, edited, chosen or draft, with names. Each person's answers are saved to `.guardlink/intent-answers/<name>.json`. Teammates run `bravos review intent` after pulling, or open the page with `--no-serve` and send the downloaded file, and the answers travel through git like any other change. `bravos review intent --summary` prints who agreed and who disagreed, for the agent to settle; `intent.md` marks each item confirmed only by the people who agreed to it. On Claude Code, `--share` renders the page for a private claude.ai artifact with a shared database instead; the agent says first that this puts your security intent on claude.ai.
|
|
127
|
+
|
|
128
|
+
## Autopilot
|
|
129
|
+
|
|
130
|
+
`bravos init --autopilot` (or `sync --autopilot`) is for teams who would rather not ask. It registers an end-of-turn check, a Stop hook in Claude Code and Codex, plus the pre-PR gate. When a turn's changes leave any of these, the agent is sent back once with the skill to use:
|
|
131
|
+
- a file with no GuardLink annotation;
|
|
132
|
+
- a confirmed or stale claim on a changed file;
|
|
133
|
+
- open exposures on changed files.
|
|
134
|
+
|
|
135
|
+
It runs only guardlink's two fast calls (`unannotated` and `ci`, a few seconds even on a large repo); lint and diff stay in the pre-PR gate.
|
|
136
|
+
|
|
137
|
+
Each finding is raised once per session, and a missing tool or a timeout lets the turn finish.
|
|
138
|
+
|
|
139
|
+
How new exposures get proven is the team's call, not a fixed rule. The first time proving comes up, the agent asks once: prove automatically on your loopback target, ask each time, on a schedule, or not now; and whether cxg may generate probes through a model API. It records the person's words in `.guardlink/proving.md`, which every briefing shows and which a PR that changes it must mention. Non-loopback targets still need a reviewed scope and typed authorization each time. `BRAVOS_AUTOPILOT=off` pauses the check.
|
|
140
|
+
|
|
141
|
+
## User-level install and one copy of each skill
|
|
142
|
+
|
|
143
|
+
`bravos init --global` (and `bravos sync --global` after an upgrade) installs bravos for your user rather than a repo: the skills once in `~/.claude/skills`, linked from `~/.agents/skills` for Codex and other shared-root agents, the session-start hook in `~/.bravos/hooks` registered in `~/.claude/settings.json` and `~/.codex/hooks.json`, and guardlink MCP in `~/.codex/config.toml`. The user hook is quiet in any repo that has its own bravos hook or no `.guardlink/`, so it briefs only repos with a threat model that bravos is not installed in. It is personal: teammates get bravos through `bravos init` in the repo. Because Claude Code prefers personal skills over a repo's, `bravos sync` in any repo also brings an existing user-level install to the same version, so an older personal copy never stands in for the repo's; it never creates one, and `BRAVOS_SYNC_GLOBAL=off` keeps the two separate.
|
|
144
|
+
|
|
145
|
+
`bravos sync --link-skills` replaces a repo's `.agents/skills` with a link to `.claude/skills`, so each skill exists once; tools that read both folders, such as Warp, otherwise show every skill twice. It removes only bravos's own unchanged copies and refuses, removing nothing, when anything else is there.
|
|
146
|
+
|
|
147
|
+
## Uninstall
|
|
148
|
+
|
|
149
|
+
`bravos uninstall` removes bravos from the current repository: every file its lock records that is unchanged, its hook entries in `.claude/settings.json`, `.codex/hooks.json` and `.agents/hooks.json`, its `pre_pr` entry in `.guardlink/config.json`, and `.bravos/`. `bravos uninstall --global` does the same for the user-level install, including the `~/.agents/skills/bravos*` links. Edited files are kept and listed unless `--force`; `--dry-run` lists everything first. It never removes guardlink's own files, a reviewed `scope.yaml`, or MCP entries, which may predate bravos; it lists them.
|
|
150
|
+
|
|
151
|
+
## A live view: `bravos watch`
|
|
152
|
+
|
|
153
|
+
`bravos watch` runs in a second terminal pane beside any agent (Claude Code, Codex, omp, opencode, Antigravity) and shows the repo's security work as it changes. A row of tiles (open, high, confirmed, tested, coverage, stale, setup, graph resolution, SAST) sits under a header bar, and bordered panels below it show:
|
|
154
|
+
|
|
155
|
+
- **Setup**: the nine steps with a progress bar, and the next one with its command.
|
|
156
|
+
- **Threat model**: a severity bar (one glyph per severity, so it reads without colour), the hypothesis ledger (confirmed, refuted, retest, untested), boundary claims held or broken, coverage, annotations, stale claims, drift, orphans, accepted risks and tickets, with sparklines of open exposures and coverage over the session.
|
|
157
|
+
- **Test next**: `guardlink hypothesis next` with severity badges, asset, state and file.
|
|
158
|
+
- **Top threats and assets**, ranked with bars.
|
|
159
|
+
- **Code graph**: currency, files, callables and calls, call resolution per language (with a warning below 50%, where "not reached" is weak evidence), entry points by kind, route access levels, how many declared exposures a test reaches, hot files that change open exposures, accepted risks. A commit leaves the checkout without a graph until it is rebuilt, because bravos keys the graph to the commit; the panel says so and gives the command.
|
|
160
|
+
- **SAST**, **branch and team** (ahead and behind the base, uncommitted files and how many lack annotations, recent commits, PRs, autopilot, pre-PR and CI hooks, intent, proving, review answers, a skills-version mismatch), the **model's size** (`guardlink status`), and **activity**.
|
|
161
|
+
|
|
162
|
+
It redraws within a couple of seconds of a file change (`fs.watch`, debounced; one snapshot at a time), re-reads boundaries, the model's size and PRs every `--interval` seconds (default 60), and asks the code graph again every `--graph-interval` seconds (default 300). Read-only and free: no model, nothing written to the repo. Autopilot logs each nudge to `.bravos/watch/events.jsonl`, a local log with its own `.gitignore`, capped at 500 lines.
|
|
163
|
+
|
|
164
|
+
Three columns from 150 wide, two from 110, one column from 80 (a split pane), a compact list below that (`--compact` forces it); the panels scroll when the terminal is shorter than they are. Keys: `n` copies the next command, `r` opens the intent review page, `d` writes guardlink's dashboard to a temp file and opens it, `g` asks the code graph a question (`bravos graph <q>`), `t` shows test-next and stale claims in full, `i` shows the recorded intent (`.guardlink/intent.md`, with who answered the review), `u` refreshes everything now, `j`/`k` (or the arrows, space, `G`) scroll, `?` the guide, `q` quits. Overlays (`t`, `i`, `?`, a graph answer) wrap to the terminal and scroll with the same keys; any other key closes them. `--once` prints one frame (and is what happens when stdout is not a terminal), `--json` one snapshot (`bravos.watch/v1`). `NO_COLOR` or `BRAVOS_WATCH_COLOR=off` prints it plain.
|
|
165
|
+
|
|
166
|
+
## Getting started: `bravos next`
|
|
167
|
+
|
|
168
|
+
`bravos next` (free, in a shell) is the setup score for a repo, in the order the steps build on each other: the code graph first (a machine step every later skill reads), then what the app intends, annotation coverage, SAST (which reads the annotations), how findings are proven and a test target, then the gates (autopilot, the pre-PR gate, CI — cheap, fine to switch on early). It shows what each unlocks and the exact command for the next one; bravos assumes a licensed bravos-cli. The same line is in the banner (`Next: … → …`, and `setup n/9` on the status line) and in the briefing every harness hands the agent, together with a few coaching rules: offer a few starts once in the first reply, suggest the next bravos step at natural moments (annotate after security edits, the ship check before a PR, a recheck after a fix), plain words first, never nag. Ask "help me get started" for a guided tour. `BRAVOS_COACH=off` keeps only the Setup line.
|
|
169
|
+
|
|
170
|
+
## The guide
|
|
171
|
+
|
|
172
|
+
`bravos guide` prints how to use bravos by role (developer, security engineer, team lead, new to the repo, CI owner), by task, skill by skill, how to read a finding, the hard safety rules and what each harness gets. It runs no model. `bravos guide <topic>` prints one topic (`start`, `roles`, `tasks`, `skills`, `states`, `safety`, `harnesses`, `all`); `--harness codex|omp` shows skills in that harness's syntax. The same text is in [docs/GUIDE.md](docs/GUIDE.md).
|
|
173
|
+
|
|
174
|
+
## Session start: a briefing for the model, a banner for you
|
|
175
|
+
|
|
176
|
+
The session-start hook prints one JSON object. Its `additionalContext` is the briefing: open exposures by severity, coverage, confirmed findings, what to test next, stale claims, tickets, work in flight and the intent state, sent to the model on every start, resume, clear and compact. On a fresh start only (`source: startup`, or a manual run), its `systemMessage` carries a banner for the human, which the model does not see: the bravos logo, repo and branch, the counts, the top three threats (or the confirmed findings to fix now) beside a three-step quick start chosen from the repo's state, and a short introduction to bravos while `.guardlink/intent.md` is missing. It is plain text within 80 columns. The registered command carries `--harness claude` or `--harness codex` so the quick start prints `/bravos-…` or `$bravos-…`; `bravos sync` replaces the older entry without the flag, and Codex asks you to trust the changed hook once in `/hooks`. Set `BRAVOS_BANNER=off` to hide the banner; the briefing is unchanged. The banner is drawn in bravos's brand colours; `NO_COLOR` or `BRAVOS_BANNER_COLOR=off` prints it plain. Codex lists a hook's message as a warning (behind F2), so Codex gets a two-line summary instead of the art. opencode finds the skills through its skill tool but lists only its own `/commands` and runs no hooks, so when opencode is in use (`.opencode/`, `opencode.json`, or `opencode` on PATH) bravos also writes one `.opencode/commands/<skill>.md` per skill (so `/bravos`, `/bravos-ship` and the rest appear), `.opencode/plugins/bravos.js` (an opencode 2.x plugin that adds the briefing to the system prompt of each request), and guardlink under `mcp` in `opencode.json`. Antigravity (`agy`) reads `.agents/hooks.json`; bravos adds a `bravos` SessionStart group there that hands the briefing to the model, since Antigravity documents no way for a hook to show text to you.
|
|
177
|
+
|
|
178
|
+
omp runs neither Claude Code's nor Codex's hooks, so when omp is in use (a `.omp/` folder, or `omp` on PATH) `bravos init` and `sync` also write `.omp/extensions/bravos.js`, which omp loads by itself: it shows the same banner above the editor at session start and hands the briefing to the model, hidden, with the first prompt. In none of these harnesses do you need to type a command for the briefing; `/bravos` is for deciding the next step. Anywhere else, the bravos skill runs `node .bravos/hooks/session-start.mjs --briefing`, which prints the briefing as plain text.
|
|
179
|
+
|
|
180
|
+
## The pre-PR gate
|
|
181
|
+
|
|
182
|
+
With `--pre-pr-hook`, every `gh pr create` and `gh pr ready` an agent runs first passes the bravos-ship gate: `guardlink validate`, `guardlink lint --since <merge-base>` with no errors, every new unmitigated exposure from `guardlink diff <merge-base> --json` disclosed in the PR body by both its threat and asset id (on `gh pr ready` the body is read with `gh pr view`), and no `@confirmed` claim from `guardlink ci -f json` on a changed file unless that file carries a `@ticket` for it. An exposure paired with `@audit` stays listed by design and is reported, not blocked forever. A gate input that fails or does not parse blocks as "unknown", including `gh pr view` on `gh pr ready`; the gate allows, and says why, in exactly two cases: guardlink is not on PATH, or no merge base with the default branch is found. It runs from the repository's top level whatever directory the agent is in. Before a PR exists, run `node .bravos/hooks/pre-pr.mjs --check` (the line `--pre-pr-hook` lists under `pre_pr` for guardlink's agent block): it blocks on lint errors and confirmed claims on changed files, and prints the new exposures the PR body must disclose. The whole gate shares a 150 s deadline inside the hook's 180 s timeout. Set `BRAVOS_PRE_PR=off` in your own environment to disable it.
|
|
183
|
+
|
|
184
|
+
## Workflows (`bravos init --ci`)
|
|
185
|
+
|
|
186
|
+
- `bravos-pr-gate.yml`: the bravos-ship gate on every pull request, with the same rulings as the pre-PR hook (lint errors, undisclosed new exposure by id, a confirmed claim on a changed file without a `@ticket`; a gate input that fails is "unknown" and fails the job). guardlink and jq only, no model, no secret. Uploads guardlink's SARIF to code scanning (skipped for fork PRs).
|
|
187
|
+
- `bravos-nightly-claude.yml` / `bravos-nightly-codex.yml`: a coding agent covers the next slice of unannotated files, the lint gate checks it, and the job opens one small PR. Keep the one for your harness. They annotate only: live testing needs a human's authorization typed in a session, which a scheduled job cannot give.
|
|
188
|
+
|
|
189
|
+
## Install as a plugin (after 1.0)
|
|
190
|
+
|
|
191
|
+
The repo also renders a plugin for each harness from the same `skills/` and `hooks/`: `plugin/` for Claude Code (served by `.claude-plugin/marketplace.json`) and `plugin-codex/` for Codex (`.agents/plugins/marketplace.json`). Regenerate both with `npm run build:plugin`; a test fails when either drifts from the source. A plugin marketplace is served from a repository customers can read, and this one is private, so the marketplace route (a small public repo holding only the built plugins, then `claude plugin marketplace add …` / `claude plugin install bravos@bravos`) comes after 1.0; until then `bravos init` is the way in for every harness.
|
|
192
|
+
|
|
193
|
+
Each plugin carries the skills, the session-start briefing (from the plugin's own copy of the hook) and the guardlink MCP entry. cxg and the code-graph server are left out because they fail to start on machines without them; `bravos init` adds them per repo when they are installed. The Codex build renders skill paths repo-relative (`.agents/skills/<name>`), so the scripts a skill runs (for example bravos-annotate's slice picker) resolve once `bravos init --agent codex` has written `.agents/skills/` into the repo. The pre-PR gate and the workflows come from `bravos init` for both harnesses. Use the plugin or `bravos init`'s session-start hook, not both, or the briefing prints twice.
|
|
194
|
+
|
|
195
|
+
## How bravos works with the model
|
|
196
|
+
|
|
197
|
+
Claude Code and Codex are already strong; bravos is an add-on, never a cap. Every budget in a skill
|
|
198
|
+
(files per slice, templates per run, rounds per campaign) is a default the agent may exceed when the
|
|
199
|
+
work needs it, saying why in one line. Only the safety rules are hard: no request to a non-loopback
|
|
200
|
+
host without a committed, reviewed `scope.yaml` and your typed authorization; destructive probe
|
|
201
|
+
classes only on a disposable loopback target; never `@accepts`, `@entitles` or a hand-written
|
|
202
|
+
`@confirmed`; never launch an external agent CLI; never invent credentials; never call anything clean
|
|
203
|
+
unless a gate exited 0.
|
|
204
|
+
|
|
205
|
+
**Effort** (`low | medium | high | max`) is one dial every skill reads: from your request ("annotate at
|
|
206
|
+
high"), else a `bravos effort: <level>` line in `AGENTS.md` / `CLAUDE.md`, else medium. Run
|
|
207
|
+
`bravos --help` for what each level means per skill.
|
|
208
|
+
|
|
209
|
+
**bravos-campaign** reaches a goal over many rounds — `/bravos-campaign coverage 100% at max, rest 10m`
|
|
210
|
+
in Claude Code, `$bravos-campaign test every open exposure` in Codex — with disjoint batches, one
|
|
211
|
+
subagent per batch, a gate per batch, a rest between rounds, and a stop when the goal is met.
|
|
212
|
+
|
|
213
|
+
## Intent-aware annotation
|
|
214
|
+
|
|
215
|
+
Code patterns alone over-rate features and miss business-logic flaws. `bravos-context` reads the
|
|
216
|
+
repo, drafts answers, and asks you a short interview: what the product is for, who the roles are,
|
|
217
|
+
what data and actions matter most, what must never happen, and what looks dangerous but is
|
|
218
|
+
intended. Answers go into `.guardlink/intent.md` (each item `— confirmed by <name>` or `— draft`)
|
|
219
|
+
and into the threat model as actors, assets, boundaries and one `#inv-…` threat per invariant.
|
|
220
|
+
bravos-annotate then adds a logic pass over user journeys, rates each exposure by impact × reach
|
|
221
|
+
× ease with the reason in the description, and writes an "intended?" `@audit` for you to answer
|
|
222
|
+
rather than claiming a by-design feature as a critical flaw.
|
|
223
|
+
|
|
224
|
+
## Licence
|
|
225
|
+
|
|
226
|
+
Bravos is proprietary software of BugB Technologies, free to use on the Free plan and licensed per developer for Team features. See [LICENSE](LICENSE) for the terms, including what you may not do (copy, modify, redistribute, or bypass a licence check). Third-party components it installs keep their own licences.
|
package/dist/account.js
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// @comment -- "bravos account: how a licence gets onto this machine. `activate` is the device-code pairing with the account portal (one POST to start, the browser to approve, polling until the portal hands back a signed token); `import` takes a downloaded file. Either way the token is verified by the same offline verifier before it is written, so a token the portal signed with a key this build does not trust is refused here, not at the first gated command. The origin is fixed (BRAVOS_ACCOUNT_ORIGIN overrides it for a local portal), no proxy is read from the environment, and the token is written 0600."
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import { chmodSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { licenceLib, licenceLines } from './licence.js';
|
|
6
|
+
import { BravosError } from './types.js';
|
|
7
|
+
export const ACTIVATION_PATH = '/api/v1/cli/activation';
|
|
8
|
+
export const POLL_PATH = '/api/v1/cli/activation/poll';
|
|
9
|
+
const DEFAULT_INTERVAL = 5;
|
|
10
|
+
const MAX_WAIT_SECONDS = 15 * 60;
|
|
11
|
+
export const accountOrigin = (env, L) => {
|
|
12
|
+
const raw = (env.BRAVOS_ACCOUNT_ORIGIN || '').trim();
|
|
13
|
+
if (!raw)
|
|
14
|
+
return L.ACCOUNT_ORIGIN;
|
|
15
|
+
if (!/^https?:\/\/[^/\s]+$/.test(raw))
|
|
16
|
+
throw new BravosError(`BRAVOS_ACCOUNT_ORIGIN must be an origin like https://account.bugb.io, got ${JSON.stringify(raw)}`);
|
|
17
|
+
return raw;
|
|
18
|
+
};
|
|
19
|
+
const defaultOpen = (url) => {
|
|
20
|
+
const [cmd, args] = process.platform === 'darwin' ? ['open', [url]] : process.platform === 'win32' ? ['cmd', ['/c', 'start', '', url]] : ['xdg-open', [url]];
|
|
21
|
+
try {
|
|
22
|
+
spawn(cmd, args, { stdio: 'ignore', detached: true }).unref();
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
/* the URL is printed anyway */
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
const licenceFile = (env, L) => {
|
|
29
|
+
const home = L.bravosHome(env);
|
|
30
|
+
if (!home)
|
|
31
|
+
throw new BravosError('no home directory: set BRAVOS_HOME to where the licence should be kept');
|
|
32
|
+
return join(home, L.LICENCE_FILES[0]);
|
|
33
|
+
};
|
|
34
|
+
// @comment -- "Verifies a token with the trusted set and writes it to $BRAVOS_HOME/licence.jwt (0600, folder 0700); a token that does not verify is refused with the verifier's reason and nothing is written."
|
|
35
|
+
export async function installToken(token, env, where) {
|
|
36
|
+
const L = await licenceLib();
|
|
37
|
+
const v = L.verify(token.trim());
|
|
38
|
+
if (v.state === 'invalid')
|
|
39
|
+
throw new BravosError(`the licence from ${where} is not accepted (${v.reason}): ${v.message}`);
|
|
40
|
+
const file = licenceFile(env, L);
|
|
41
|
+
mkdirSync(join(file, '..'), { recursive: true, mode: 0o700 });
|
|
42
|
+
writeFileSync(file, token.trim() + '\n', { mode: 0o600 });
|
|
43
|
+
try {
|
|
44
|
+
chmodSync(file, 0o600);
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
/* best effort on platforms without modes */
|
|
48
|
+
}
|
|
49
|
+
return { file, status: L.status(env) };
|
|
50
|
+
}
|
|
51
|
+
export async function importLicence(path, io) {
|
|
52
|
+
let text;
|
|
53
|
+
try {
|
|
54
|
+
text = readFileSync(path, 'utf8');
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
throw new BravosError(`${path} could not be read`);
|
|
58
|
+
}
|
|
59
|
+
const { file, status } = await installToken(text, io.env, path);
|
|
60
|
+
const L = await licenceLib();
|
|
61
|
+
return [`Licence written to ${file}`, ...licenceLines(L, status)];
|
|
62
|
+
}
|
|
63
|
+
// @comment -- "The device-code flow: POST /api/v1/cli/activation, show the code and open the verification URL, then poll /api/v1/cli/activation/poll with the device code as a bearer until the portal answers 200 {license} (428 pending, 429 slow down, 410 expired or consumed). The device code never goes anywhere but that origin, and the token it yields is verified before it is written."
|
|
64
|
+
export async function activate(io) {
|
|
65
|
+
const L = await licenceLib();
|
|
66
|
+
const origin = accountOrigin(io.env, L);
|
|
67
|
+
const f = io.fetch ?? fetch;
|
|
68
|
+
const sleep = io.sleep ?? ((s) => new Promise((r) => setTimeout(r, s * 1000)));
|
|
69
|
+
const now = io.now ?? (() => Date.now() / 1000);
|
|
70
|
+
const open = io.open ?? defaultOpen;
|
|
71
|
+
const ua = { 'User-Agent': 'bravos-cli', Accept: 'application/json' };
|
|
72
|
+
let start;
|
|
73
|
+
try {
|
|
74
|
+
const r = await f(`${origin}${ACTIVATION_PATH}`, { method: 'POST', headers: { ...ua, 'Content-Type': 'application/json' }, body: '{}' });
|
|
75
|
+
if (r.status === 404 || r.status === 405)
|
|
76
|
+
throw new BravosError(`${origin} does not offer CLI sign-in yet; download the licence from ${L.ACCOUNT_LICENCES} and run bravos account import <file>`);
|
|
77
|
+
if (!r.ok)
|
|
78
|
+
throw new BravosError(`${origin} refused to start an activation (HTTP ${r.status})${r.status === 429 ? ': too many attempts from here, try again later' : ''}`);
|
|
79
|
+
start = (await r.json());
|
|
80
|
+
}
|
|
81
|
+
catch (e) {
|
|
82
|
+
if (e instanceof BravosError)
|
|
83
|
+
throw e;
|
|
84
|
+
throw new BravosError(`could not reach ${origin}: ${e.message}. Download the licence from ${L.ACCOUNT_LICENCES} instead and run bravos account import <file>`);
|
|
85
|
+
}
|
|
86
|
+
if (!start || typeof start.device_code !== 'string' || typeof start.user_code !== 'string' || typeof start.verification_url !== 'string') {
|
|
87
|
+
throw new BravosError(`${origin} answered without a device code; download the licence from ${L.ACCOUNT_LICENCES} and run bravos account import <file>`);
|
|
88
|
+
}
|
|
89
|
+
if (!start.verification_url.startsWith(origin + '/'))
|
|
90
|
+
throw new BravosError(`${origin} pointed the browser somewhere else (${start.verification_url}); refusing`);
|
|
91
|
+
const interval = Math.max(1, Number(start.interval) || DEFAULT_INTERVAL);
|
|
92
|
+
const deadline = now() + Math.min(MAX_WAIT_SECONDS, Number(start.expires_in) || MAX_WAIT_SECONDS);
|
|
93
|
+
io.stdout(`Sign in to ${origin} and enter this code: ${start.user_code}`);
|
|
94
|
+
io.stdout(`Opening ${start.verification_url} (or open it yourself). Waiting for approval…`);
|
|
95
|
+
open(start.verification_url);
|
|
96
|
+
let wait = interval;
|
|
97
|
+
for (;;) {
|
|
98
|
+
if (now() >= deadline)
|
|
99
|
+
throw new BravosError('the activation code expired before it was approved; run bravos account activate again');
|
|
100
|
+
await sleep(wait);
|
|
101
|
+
let r;
|
|
102
|
+
try {
|
|
103
|
+
r = await f(`${origin}${POLL_PATH}`, { headers: { ...ua, Authorization: `Bearer ${start.device_code}` } });
|
|
104
|
+
}
|
|
105
|
+
catch (e) {
|
|
106
|
+
throw new BravosError(`lost ${origin} while waiting: ${e.message}; run bravos account activate again`);
|
|
107
|
+
}
|
|
108
|
+
if (r.status === 428)
|
|
109
|
+
continue;
|
|
110
|
+
if (r.status === 429) {
|
|
111
|
+
wait = Math.min(wait * 2, 60);
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (r.status === 410)
|
|
115
|
+
throw new BravosError('the activation code expired or was already used; run bravos account activate again');
|
|
116
|
+
// A portal that only speaks the Python CLI's pairing answers the GET poll with 405.
|
|
117
|
+
if (r.status === 404 || r.status === 405)
|
|
118
|
+
throw new BravosError(`${origin} does not offer CLI sign-in for this version yet; download the licence from ${L.ACCOUNT_LICENCES} and run bravos account import <file>`);
|
|
119
|
+
// The portal answers 402 when the account has no plan and its one trial is used up: nothing is written, Free keeps working.
|
|
120
|
+
if (r.status === 402) {
|
|
121
|
+
let upgrade = L.ACCOUNT_UPGRADE;
|
|
122
|
+
try {
|
|
123
|
+
const b = (await r.json());
|
|
124
|
+
if (typeof b?.upgrade === 'string' && b.upgrade.startsWith(origin + '/'))
|
|
125
|
+
upgrade = b.upgrade;
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
/* the default page */
|
|
129
|
+
}
|
|
130
|
+
throw new BravosError(`this account's Team trial has ended and it has no plan, so no licence was written; this machine stays on Free (the research loop keeps working). Choose a plan at ${upgrade}, then run bravos account activate again`);
|
|
131
|
+
}
|
|
132
|
+
if (!r.ok)
|
|
133
|
+
throw new BravosError(`${origin} answered HTTP ${r.status} while waiting; run bravos account activate again`);
|
|
134
|
+
const body = (await r.json());
|
|
135
|
+
if (typeof body?.license !== 'string')
|
|
136
|
+
throw new BravosError(`${origin} approved the code but sent no licence; download it from ${L.ACCOUNT_LICENCES} and run bravos account import <file>`);
|
|
137
|
+
const { file, status } = await installToken(body.license, io.env, origin);
|
|
138
|
+
return [`Licence written to ${file}`, ...licenceLines(L, status)];
|
|
139
|
+
}
|
|
140
|
+
}
|