@spinabot/brigade 0.1.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/CHANGELOG.md +10 -0
- package/LICENSE +21 -0
- package/README.md +154 -0
- package/SECURITY.md +208 -0
- package/assets/brigade-wordmark-on-black.png +0 -0
- package/assets/brigade-wordmark.png +0 -0
- package/brigade.mjs +96 -0
- package/dist/cli/chat-cmd.js +120 -0
- package/dist/cli/config-cmd.js +132 -0
- package/dist/cli/connect-cmd.js +447 -0
- package/dist/cli/doctor-cmd.js +317 -0
- package/dist/cli/gateway-cmd.js +92 -0
- package/dist/cli.js +287 -0
- package/dist/core/agent.js +1123 -0
- package/dist/core/config.js +80 -0
- package/dist/core/console-stream.js +188 -0
- package/dist/core/error-classifier.js +354 -0
- package/dist/core/event-logger.js +122 -0
- package/dist/core/model-caps.js +185 -0
- package/dist/core/provider-payload-mutators.js +517 -0
- package/dist/core/provider-quirks.js +285 -0
- package/dist/core/server.js +459 -0
- package/dist/core/smart-compaction.js +209 -0
- package/dist/core/system-prompt-defaults.js +88 -0
- package/dist/core/system-prompt-guidance.js +269 -0
- package/dist/core/system-prompt.js +884 -0
- package/dist/index.js +30 -0
- package/dist/integrations/ollama.js +140 -0
- package/dist/protocol.js +49 -0
- package/dist/providers/catalog.js +100 -0
- package/dist/providers/validate-key.js +197 -0
- package/dist/tui/client.js +263 -0
- package/dist/ui/brand-frames-cli.js +20 -0
- package/dist/ui/brand-frames.js +36 -0
- package/dist/ui/brand.js +402 -0
- package/dist/ui/chat.js +929 -0
- package/dist/ui/onboarding.js +400 -0
- package/dist/ui/theme.js +51 -0
- package/package.json +92 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 — 2026-05-02
|
|
4
|
+
|
|
5
|
+
- Initial release.
|
|
6
|
+
- Polished terminal TUI for Anthropic, OpenAI, Gemini, Groq, xAI, DeepSeek, Mistral, OpenRouter, Cerebras, Ollama, and any OpenAI-compatible endpoint.
|
|
7
|
+
- Subcommands: `brigade chat`, `brigade gateway`, `brigade connect`, `brigade onboard`, `brigade doctor`, `brigade config`.
|
|
8
|
+
- Provider onboarding wizard on first launch.
|
|
9
|
+
- `/model`, `/provider`, `/thinking`, `/help` in-chat commands.
|
|
10
|
+
- Local-only — no telemetry, no cloud component.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Brigade contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
<p>
|
|
2
|
+
<img src="https://github.com/user-attachments/assets/44afe91e-bc06-4b85-a8a9-dcc608041e28#gh-dark-mode-only" alt="Brigade demo" width="180" align="right" />
|
|
3
|
+
<img src="https://github.com/user-attachments/assets/3cc8ad09-259b-4eef-889a-23ded6eabab3#gh-light-mode-only" alt="Brigade demo" width="180" align="right" />
|
|
4
|
+
<img src="./assets/brigade-wordmark.png#gh-dark-mode-only" alt="BRIGADE" width="600" />
|
|
5
|
+
<img src="./assets/brigade-wordmark-on-black.png#gh-light-mode-only" alt="BRIGADE" width="600" />
|
|
6
|
+
</p>
|
|
7
|
+
|
|
8
|
+
<br clear="all" />
|
|
9
|
+
|
|
10
|
+
Brigade is a polished terminal interface for working with the world's
|
|
11
|
+
leading language models — Anthropic Claude, OpenAI GPT, Google Gemini,
|
|
12
|
+
Groq, xAI, DeepSeek, Mistral, OpenRouter, Cerebras — and your own local
|
|
13
|
+
models via Ollama or any OpenAI-compatible endpoint.
|
|
14
|
+
|
|
15
|
+
It runs in two modes: a single-process chat TUI (the default) or as a
|
|
16
|
+
headless WebSocket gateway you can connect multiple thin clients to.
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install -g @spinabot/brigade
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Requires Node.js 20 or newer.
|
|
25
|
+
|
|
26
|
+
## Quick start
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
brigade
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
First launch walks you through three steps:
|
|
33
|
+
|
|
34
|
+
1. Pick a provider.
|
|
35
|
+
2. Connect it (paste an API key, or scan your local Ollama).
|
|
36
|
+
3. Choose a default model.
|
|
37
|
+
|
|
38
|
+
That's it. Subsequent launches resume right where you left off.
|
|
39
|
+
|
|
40
|
+
## Commands
|
|
41
|
+
|
|
42
|
+
Brigade is a single binary with subcommands. `brigade` on its own is
|
|
43
|
+
shorthand for `brigade chat`.
|
|
44
|
+
|
|
45
|
+
| Command | What it does |
|
|
46
|
+
|--------------------|---------------------------------------------------------------|
|
|
47
|
+
| `brigade` | Start the chat TUI (default) |
|
|
48
|
+
| `brigade chat` | Start the chat TUI |
|
|
49
|
+
| `brigade gateway` | Run the headless WebSocket gateway, no TUI |
|
|
50
|
+
| `brigade connect` | Open a TUI that talks to a running gateway |
|
|
51
|
+
| `brigade onboard` | Re-run the provider/model setup wizard |
|
|
52
|
+
| `brigade doctor` | Health-check Node, config, providers, prompts, and log sink |
|
|
53
|
+
| `brigade config` | Read & write `~/.brigade/config.json` |
|
|
54
|
+
| `brigade --version`| Print the version |
|
|
55
|
+
| `brigade --help` | Print the full help text |
|
|
56
|
+
|
|
57
|
+
### `brigade gateway`
|
|
58
|
+
|
|
59
|
+
Runs Brigade as a WebSocket server with no terminal UI of its own.
|
|
60
|
+
Useful when you want a long-lived agent process you can attach and
|
|
61
|
+
detach clients to.
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
brigade gateway --port 7777 --host 127.0.0.1 --verbose
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
| Flag | Default | Notes |
|
|
68
|
+
|------------------|---------------|----------------------------------------------|
|
|
69
|
+
| `--port N` | `7777` | Listen port (also `BRIGADE_PORT` env var) |
|
|
70
|
+
| `--host A` | `127.0.0.1` | Bind address |
|
|
71
|
+
| `--verbose` | off | Stream a one-line summary of every event |
|
|
72
|
+
| `--quiet` | off | Suppress the live console stream |
|
|
73
|
+
| `--log-level X` | `info` | `debug`, `info`, `warn`, `error` |
|
|
74
|
+
|
|
75
|
+
### `brigade connect`
|
|
76
|
+
|
|
77
|
+
Attaches a TUI to a running gateway. Same chat experience as `brigade`,
|
|
78
|
+
but the agent runs in the gateway process — so you can disconnect, walk
|
|
79
|
+
away, reconnect later, and pick up where you left off.
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
brigade connect --host 127.0.0.1 --port 7777
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
| Flag | Default | Notes |
|
|
86
|
+
|----------------|---------------|--------------------------------------|
|
|
87
|
+
| `--host A` | `127.0.0.1` | Gateway host to connect to |
|
|
88
|
+
| `--port N` | `7777` | Gateway port |
|
|
89
|
+
| `--timeout MS` | `60000` | Per-request timeout |
|
|
90
|
+
|
|
91
|
+
### `brigade doctor`
|
|
92
|
+
|
|
93
|
+
Runs a health check across Node version, your `~/.brigade/` directory,
|
|
94
|
+
config, configured providers, log sink, prompt files, and (optionally)
|
|
95
|
+
a running gateway. Exits 0 if everything passes, 1 if anything fails.
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
brigade doctor
|
|
99
|
+
brigade doctor --gateway ws://127.0.0.1:7777
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### `brigade config`
|
|
103
|
+
|
|
104
|
+
Read and write the local config without opening the TUI.
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
brigade config list
|
|
108
|
+
brigade config get defaultProvider
|
|
109
|
+
brigade config set defaultProvider anthropic
|
|
110
|
+
brigade config unset thinkingLevel
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Known keys: `defaultProvider`, `defaultModelId`, `thinkingLevel`,
|
|
114
|
+
`fallbackProvider`, `fallbackModelId`, `installedAt`.
|
|
115
|
+
|
|
116
|
+
## In-chat commands
|
|
117
|
+
|
|
118
|
+
When you're in the chat TUI (whether via `brigade` or `brigade connect`):
|
|
119
|
+
|
|
120
|
+
| Command | What it does |
|
|
121
|
+
|---------------------|-------------------------------------------------------------|
|
|
122
|
+
| `/model` | Switch to a different configured model (picker) |
|
|
123
|
+
| `/model <id>` | Switch directly by model id |
|
|
124
|
+
| `/provider` | Add a new provider mid-session — no restart required |
|
|
125
|
+
| `/thinking <level>` | Adjust reasoning effort (off, minimal, low, medium, high) |
|
|
126
|
+
| `/compact` | Force a context compaction now |
|
|
127
|
+
| `/help` | Show all commands |
|
|
128
|
+
| `/exit` | Quit Brigade |
|
|
129
|
+
|
|
130
|
+
Keyboard:
|
|
131
|
+
|
|
132
|
+
- **Enter** — send (mid-turn submits steer the model without aborting)
|
|
133
|
+
- **Ctrl+C** — stop the current response (doesn't exit)
|
|
134
|
+
- **Ctrl+D** — quit
|
|
135
|
+
- **↑** / **↓** — message history
|
|
136
|
+
|
|
137
|
+
## Supported providers
|
|
138
|
+
|
|
139
|
+
Out of the box: Anthropic, OpenAI, Google Gemini, OpenRouter, Groq, xAI,
|
|
140
|
+
Cerebras, DeepSeek, Mistral, **Ollama** (local), and **Custom
|
|
141
|
+
OpenAI-compatible** endpoints (Together AI, Fireworks, vLLM, on-prem
|
|
142
|
+
gateways, anything that speaks `/v1/chat/completions`).
|
|
143
|
+
|
|
144
|
+
You can mix and match — connect three providers, switch between their
|
|
145
|
+
models with `/model`, and Brigade keeps your conversation context
|
|
146
|
+
across the switch.
|
|
147
|
+
|
|
148
|
+
## Privacy
|
|
149
|
+
|
|
150
|
+
Brigade is a local CLI. Your API keys never leave your computer; they're
|
|
151
|
+
stored in your home directory and used only to talk to the providers you
|
|
152
|
+
connect. No telemetry, no analytics, no cloud component.
|
|
153
|
+
|
|
154
|
+
For Ollama and Custom endpoints, requests stay entirely on your network.
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
Brigade is a local CLI that connects you to the language-model
|
|
4
|
+
providers you choose. We take its security posture seriously because it
|
|
5
|
+
sits in the trust path between your machine, your API keys, and the
|
|
6
|
+
models that act on your behalf.
|
|
7
|
+
|
|
8
|
+
If you believe you have found a security issue in Brigade, please
|
|
9
|
+
follow the disclosure process below. Do **not** open a public GitHub
|
|
10
|
+
issue or pull request for a suspected vulnerability — public reports
|
|
11
|
+
expose users before a fix can ship.
|
|
12
|
+
|
|
13
|
+
## Reporting a Vulnerability
|
|
14
|
+
|
|
15
|
+
The preferred channel is GitHub's private vulnerability reporting:
|
|
16
|
+
|
|
17
|
+
- Open https://github.com/Bhasvanth-Dev9380/brigade/security/advisories/new
|
|
18
|
+
- Provide a clear description, reproduction steps, affected version, and
|
|
19
|
+
the impact you believe the issue has.
|
|
20
|
+
|
|
21
|
+
If you cannot use GitHub, email **bhasvanth02@gmail.com** with
|
|
22
|
+
"Brigade security" in the subject line. PGP is available on request.
|
|
23
|
+
|
|
24
|
+
You will receive an acknowledgement within a few business days. We will
|
|
25
|
+
keep you informed as we investigate, and we'll credit you in the
|
|
26
|
+
release notes for the fix unless you prefer to remain anonymous.
|
|
27
|
+
|
|
28
|
+
Please do **not** publish details of an unpatched issue. Coordinated
|
|
29
|
+
disclosure protects every Brigade user.
|
|
30
|
+
|
|
31
|
+
## Supported Versions
|
|
32
|
+
|
|
33
|
+
Brigade is on a rolling release. Security fixes ship to the latest
|
|
34
|
+
published version on npm. Earlier versions are not back-patched.
|
|
35
|
+
|
|
36
|
+
| Version | Supported |
|
|
37
|
+
|------------|------------------------|
|
|
38
|
+
| latest | Yes (security + bugs) |
|
|
39
|
+
| previous | Best-effort, ~30 days |
|
|
40
|
+
| older | No |
|
|
41
|
+
|
|
42
|
+
When in doubt, run `npm install -g @spinabot/brigade@latest` and
|
|
43
|
+
verify with `brigade --version`.
|
|
44
|
+
|
|
45
|
+
## Scope
|
|
46
|
+
|
|
47
|
+
In scope for security reports:
|
|
48
|
+
|
|
49
|
+
- The published package `@spinabot/brigade` and its compiled `dist/`.
|
|
50
|
+
- The `brigade.mjs` bin wrapper.
|
|
51
|
+
- The Brigade gateway (`brigade gateway`) — the WebSocket server, its
|
|
52
|
+
default bind, its protocol surface, and any server-side handlers.
|
|
53
|
+
- The Brigade client (`brigade connect`) — connection logic, framing,
|
|
54
|
+
command dispatch.
|
|
55
|
+
- The local on-disk state under `~/.brigade/` (config, sessions, logs,
|
|
56
|
+
prompts, auth/model storage).
|
|
57
|
+
- The build and release pipeline (`scripts/prepack.mjs`, `prepublishOnly`).
|
|
58
|
+
|
|
59
|
+
Out of scope:
|
|
60
|
+
|
|
61
|
+
- Vulnerabilities in upstream dependencies that have not been
|
|
62
|
+
exploited through Brigade's own code paths. Please report those to
|
|
63
|
+
the upstream project. We track and update Pi SDK packages
|
|
64
|
+
(`@mariozechner/pi-*`) on a tight schedule.
|
|
65
|
+
- Behavior of the language-model providers themselves (Anthropic,
|
|
66
|
+
OpenAI, Google, Groq, xAI, DeepSeek, Mistral, OpenRouter, Cerebras,
|
|
67
|
+
Ollama, custom OpenAI-compatible endpoints). Their security posture
|
|
68
|
+
is governed by their own policies.
|
|
69
|
+
- Compromise of the user's local machine, OS account, terminal, or
|
|
70
|
+
shell — Brigade trusts the user that runs it.
|
|
71
|
+
- Issues that require physical access, an already-rooted device, or
|
|
72
|
+
malware running with the same privileges as the Brigade process.
|
|
73
|
+
- Rate-limit or denial-of-service against your *own* providers via
|
|
74
|
+
your own API keys.
|
|
75
|
+
- Findings that depend on a non-default, user-edited configuration
|
|
76
|
+
that explicitly weakens the defaults documented here.
|
|
77
|
+
|
|
78
|
+
## Trust Model
|
|
79
|
+
|
|
80
|
+
Brigade's defaults assume a single-user developer machine. The tool is
|
|
81
|
+
designed to run on hardware you control, with credentials you own, and
|
|
82
|
+
to talk to models you have selected.
|
|
83
|
+
|
|
84
|
+
Three trust boundaries are worth calling out:
|
|
85
|
+
|
|
86
|
+
1. **You trust your own machine.** API keys, conversation history, and
|
|
87
|
+
logs live on disk in your home directory. Brigade does not encrypt
|
|
88
|
+
files at rest — it relies on your OS user permissions. Anyone with
|
|
89
|
+
read access to your home directory has read access to your keys.
|
|
90
|
+
|
|
91
|
+
2. **You trust the providers you connect.** Your prompts, file
|
|
92
|
+
contents shared via tools, and model output are sent to the
|
|
93
|
+
provider you chose. Brigade does not interpose, redact, or filter
|
|
94
|
+
that content. Use providers whose data-handling matches your needs.
|
|
95
|
+
|
|
96
|
+
3. **You trust the prompts you send.** Brigade exposes capable tools
|
|
97
|
+
to the agent loop (file read, write, edit, shell). A model that
|
|
98
|
+
follows instructions from untrusted text — pasted code, scraped
|
|
99
|
+
web content, attacker-supplied documents — can be steered into
|
|
100
|
+
acting on your machine. Treat prompt input the same way you treat
|
|
101
|
+
shell input.
|
|
102
|
+
|
|
103
|
+
## Where Secrets Live
|
|
104
|
+
|
|
105
|
+
| Item | Location | Format |
|
|
106
|
+
|-----------------------------|---------------------------------------|----------------|
|
|
107
|
+
| Provider API keys | `~/.brigade/auth.json` | JSON, plain |
|
|
108
|
+
| Provider catalogs / models | `~/.brigade/models.json` | JSON, plain |
|
|
109
|
+
| User preferences | `~/.brigade/config.json` | JSON, plain |
|
|
110
|
+
| Conversation transcripts | `~/.brigade/sessions/` | JSONL, plain |
|
|
111
|
+
| Event logs | `~/.brigade/logs/` | JSONL, plain |
|
|
112
|
+
| Layered system prompts | `~/.brigade/prompts/` | Markdown |
|
|
113
|
+
|
|
114
|
+
The location can be redirected by setting the `BRIGADE_DIR`
|
|
115
|
+
environment variable before launching Brigade. This is useful for
|
|
116
|
+
test harnesses, dotfile managers, and ephemeral CI runners.
|
|
117
|
+
|
|
118
|
+
`rm -rf ~/.brigade` removes one hundred percent of Brigade's local
|
|
119
|
+
state. Nothing important to Brigade lives elsewhere. (On Windows, the
|
|
120
|
+
equivalent path is `%USERPROFILE%\.brigade`.)
|
|
121
|
+
|
|
122
|
+
## Network Exposure
|
|
123
|
+
|
|
124
|
+
By default Brigade does not listen on any network port.
|
|
125
|
+
|
|
126
|
+
When you opt in by running `brigade gateway`:
|
|
127
|
+
|
|
128
|
+
- The WebSocket server binds to `127.0.0.1` — localhost only, not
|
|
129
|
+
reachable from your LAN.
|
|
130
|
+
- The default port is `7777`, configurable with `--port`.
|
|
131
|
+
- The default bind is configurable with `--host`. Setting
|
|
132
|
+
`--host 0.0.0.0` exposes the gateway to your network and is **not
|
|
133
|
+
recommended** without an external authentication or firewall layer.
|
|
134
|
+
|
|
135
|
+
Brigade gateway has **no built-in authentication or authorization in
|
|
136
|
+
v1.** Anyone who can connect to the listening socket can drive the
|
|
137
|
+
agent and read every conversation. This is by design for the
|
|
138
|
+
single-user, single-machine target. If you bind the gateway to a
|
|
139
|
+
non-loopback address, you are responsible for restricting access.
|
|
140
|
+
|
|
141
|
+
Outbound traffic is limited to the providers you have configured —
|
|
142
|
+
Brigade itself sends no telemetry, no analytics, and no crash reports.
|
|
143
|
+
There is no Brigade-operated cloud component.
|
|
144
|
+
|
|
145
|
+
## Tool Execution Model
|
|
146
|
+
|
|
147
|
+
Brigade ships with the standard agent toolkit: read files, edit files,
|
|
148
|
+
write files, search files, and run shell commands. These tools execute
|
|
149
|
+
with the same privileges as the Brigade process and operate on your
|
|
150
|
+
real filesystem.
|
|
151
|
+
|
|
152
|
+
A model with tool access is, by design, capable of doing anything your
|
|
153
|
+
shell can. This includes deleting files, exfiltrating data via curl,
|
|
154
|
+
or installing software. We surface tool calls in the chat UI and the
|
|
155
|
+
JSONL log so you can see what is being attempted, but we do not
|
|
156
|
+
sandbox or veto the calls themselves in v1. Treat the agent as a
|
|
157
|
+
junior engineer with shell access who you authorized to act on your
|
|
158
|
+
behalf — and review what it does, especially with prompts that
|
|
159
|
+
include third-party text.
|
|
160
|
+
|
|
161
|
+
A future Brigade release will add per-tool allowlisting, a confirmation
|
|
162
|
+
prompt for destructive operations, and an optional sandbox. Until then,
|
|
163
|
+
run Brigade in directories whose blast radius you accept.
|
|
164
|
+
|
|
165
|
+
## Hardening Recommendations
|
|
166
|
+
|
|
167
|
+
For users who want a tighter posture:
|
|
168
|
+
|
|
169
|
+
1. **Restrict `~/.brigade/` permissions.** On Unix, `chmod 700
|
|
170
|
+
~/.brigade && chmod 600 ~/.brigade/*.json` prevents other local
|
|
171
|
+
users from reading your keys.
|
|
172
|
+
2. **Use scoped API keys.** Most providers support keys scoped by
|
|
173
|
+
model, project, or budget. Use the most restrictive scope that fits
|
|
174
|
+
your use.
|
|
175
|
+
3. **Prefer Ollama or a custom OpenAI-compatible endpoint** for
|
|
176
|
+
sensitive content — requests then stay entirely on your network.
|
|
177
|
+
4. **Keep Brigade up to date.** Run `npm update -g @spinabot/brigade`
|
|
178
|
+
periodically. Security fixes only land in the latest version.
|
|
179
|
+
5. **Run Brigade in a workspace, not your home directory.** The agent
|
|
180
|
+
can read and write the directory it runs in. Limiting its working
|
|
181
|
+
directory limits what it can touch.
|
|
182
|
+
6. **Audit `~/.brigade/logs/`.** The JSONL event log records every
|
|
183
|
+
tool call. Review it the way you would review shell history.
|
|
184
|
+
7. **Do not bind the gateway to `0.0.0.0`** unless you have a
|
|
185
|
+
reverse proxy or VPN in front of it.
|
|
186
|
+
|
|
187
|
+
## Disclosure Timeline
|
|
188
|
+
|
|
189
|
+
Once a valid report is acknowledged, we aim for the following:
|
|
190
|
+
|
|
191
|
+
- **Day 0:** acknowledgement of receipt.
|
|
192
|
+
- **By day 7:** initial triage, severity assessment, and reproduction
|
|
193
|
+
in a clean environment.
|
|
194
|
+
- **By day 30:** patch released to npm under a new version, advisory
|
|
195
|
+
published on the GitHub repo, and the CHANGELOG updated.
|
|
196
|
+
- **Day 30+:** for issues that require a coordinated upstream fix in
|
|
197
|
+
the Pi SDK or another dependency, we work with the upstream
|
|
198
|
+
maintainers and may extend this window. Reporters are kept in the
|
|
199
|
+
loop throughout.
|
|
200
|
+
|
|
201
|
+
If a report turns out to be a feature working as intended, we will
|
|
202
|
+
explain why and document it more clearly so the next person to look
|
|
203
|
+
at it does not have to rediscover the rationale.
|
|
204
|
+
|
|
205
|
+
## Acknowledgements
|
|
206
|
+
|
|
207
|
+
We will list reporters who follow coordinated disclosure here as
|
|
208
|
+
issues are fixed. Thank you for keeping Brigade users safe.
|
|
Binary file
|
|
Binary file
|
package/brigade.mjs
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import module from "node:module";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { dirname, join } from "node:path";
|
|
6
|
+
import { existsSync } from "node:fs";
|
|
7
|
+
import { spawn } from "node:child_process";
|
|
8
|
+
|
|
9
|
+
// ── Node version guard ──────────────────────────────────────────────
|
|
10
|
+
const MIN_NODE_MAJOR = 20;
|
|
11
|
+
const [majorRaw = "0"] = process.versions.node.split(".");
|
|
12
|
+
if (Number(majorRaw) < MIN_NODE_MAJOR) {
|
|
13
|
+
process.stderr.write(
|
|
14
|
+
`brigade: Node.js v${MIN_NODE_MAJOR}.0+ is required (current: v${process.versions.node}).\n` +
|
|
15
|
+
"If you use nvm, run:\n" +
|
|
16
|
+
` nvm install ${MIN_NODE_MAJOR}\n` +
|
|
17
|
+
` nvm use ${MIN_NODE_MAJOR}\n` +
|
|
18
|
+
` nvm alias default ${MIN_NODE_MAJOR}\n`,
|
|
19
|
+
);
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// ── Compile cache (Node 22.1+) ──────────────────────────────────────
|
|
24
|
+
if (module.enableCompileCache && !process.env.NODE_DISABLE_COMPILE_CACHE) {
|
|
25
|
+
try {
|
|
26
|
+
module.enableCompileCache();
|
|
27
|
+
} catch {
|
|
28
|
+
// ignore — older Node versions or sandboxed environments
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ── Resolve the compiled entry ──────────────────────────────────────
|
|
33
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
34
|
+
const entry = join(__dirname, "dist", "cli.js");
|
|
35
|
+
|
|
36
|
+
if (!existsSync(entry)) {
|
|
37
|
+
// Distinguish "installed package without dist" (shouldn't happen — files
|
|
38
|
+
// field guarantees dist/) from "raw source checkout that wasn't built".
|
|
39
|
+
const pkgPath = join(__dirname, "package.json");
|
|
40
|
+
const looksInstalled = !existsSync(join(__dirname, "src"));
|
|
41
|
+
if (looksInstalled) {
|
|
42
|
+
process.stderr.write(
|
|
43
|
+
"brigade: the installed package is missing dist/cli.js.\n" +
|
|
44
|
+
"Try reinstalling:\n" +
|
|
45
|
+
" npm install -g @spinabot/brigade\n",
|
|
46
|
+
);
|
|
47
|
+
} else {
|
|
48
|
+
process.stderr.write(
|
|
49
|
+
"brigade: missing dist/cli.js (build output).\n" +
|
|
50
|
+
"This looks like an unbuilt source checkout. Build it first:\n" +
|
|
51
|
+
" npm install && npm run build\n" +
|
|
52
|
+
"Or install the published package:\n" +
|
|
53
|
+
" npm install -g @spinabot/brigade\n",
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
void pkgPath;
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ── Spawn the CLI ───────────────────────────────────────────────────
|
|
61
|
+
// We spawn a child instead of dynamic-importing so that:
|
|
62
|
+
// 1. process.argv[1] is dist/cli.js, satisfying the self-execution
|
|
63
|
+
// guard (import.meta.url === pathToFileURL(argv[1]).href)
|
|
64
|
+
// 2. fatal startup errors give a stack pointing at the real file
|
|
65
|
+
// instead of bouncing through this wrapper
|
|
66
|
+
const child = spawn(process.execPath, [entry, ...process.argv.slice(2)], {
|
|
67
|
+
stdio: "inherit",
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
child.on("error", (err) => {
|
|
71
|
+
// Rare: process.execPath itself is missing, or EACCES on the entry.
|
|
72
|
+
// Surface a clean line instead of the raw Node trace.
|
|
73
|
+
const code = (err && err.code) || "UNKNOWN";
|
|
74
|
+
if (code === "ENOENT") {
|
|
75
|
+
process.stderr.write(
|
|
76
|
+
"brigade: could not launch Node — the runtime executable is missing.\n",
|
|
77
|
+
);
|
|
78
|
+
} else if (code === "EACCES") {
|
|
79
|
+
process.stderr.write(
|
|
80
|
+
"brigade: permission denied launching the CLI. Check file permissions on dist/cli.js.\n",
|
|
81
|
+
);
|
|
82
|
+
} else {
|
|
83
|
+
process.stderr.write(`brigade: failed to launch (${code}): ${err.message}\n`);
|
|
84
|
+
}
|
|
85
|
+
process.exit(1);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
child.on("exit", (code, signal) => {
|
|
89
|
+
if (signal) {
|
|
90
|
+
// Re-raise the signal to ourselves so the parent shell sees the
|
|
91
|
+
// correct exit cause (e.g. SIGINT → 130).
|
|
92
|
+
process.kill(process.pid, signal);
|
|
93
|
+
} else {
|
|
94
|
+
process.exit(code ?? 0);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `brigade chat` — in-process TUI + Pi session in a single process.
|
|
3
|
+
*
|
|
4
|
+
* This is the default subcommand and the simplest path: one terminal, one
|
|
5
|
+
* process, no gateway, full feature set. Everything the user sees in the
|
|
6
|
+
* conversation runs through the local Pi loop with the 6-layer wrapper
|
|
7
|
+
* composition wired in src/ui/chat.ts.
|
|
8
|
+
*
|
|
9
|
+
* Wraps the original boot flow (auth + model resolution → onboarding-if-needed
|
|
10
|
+
* → buildAgent → runChat) and surfaces a clean SIGINT chain so Ctrl+C aborts
|
|
11
|
+
* a turn first, exits second.
|
|
12
|
+
*/
|
|
13
|
+
import process from "node:process";
|
|
14
|
+
import { AuthStorage, ModelRegistry } from "@mariozechner/pi-coding-agent";
|
|
15
|
+
import { ProcessTerminal, TUI } from "@mariozechner/pi-tui";
|
|
16
|
+
import chalk from "chalk";
|
|
17
|
+
import { buildAgent } from "../core/agent.js";
|
|
18
|
+
import { BRIGADE_DIR, loadConfig } from "../core/config.js";
|
|
19
|
+
import { attachEventLogger, getTodayLogPath } from "../core/event-logger.js";
|
|
20
|
+
import { findProvider } from "../providers/catalog.js";
|
|
21
|
+
import { runChat } from "../ui/chat.js";
|
|
22
|
+
import { runOnboarding } from "../ui/onboarding.js";
|
|
23
|
+
/**
|
|
24
|
+
* Boot the in-process chat TUI. Returns once the editor is ready; the chat
|
|
25
|
+
* runs until SIGINT/Ctrl+D/`/exit`. Resolves with the chat handle in case
|
|
26
|
+
* the caller wants to introspect or wire additional signal handlers.
|
|
27
|
+
*/
|
|
28
|
+
export async function runChatCommand(opts = {}) {
|
|
29
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
30
|
+
// Boot the TUI renderer FIRST. From this point on, addChild/requestRender
|
|
31
|
+
// actually paint to the screen. Onboarding, splash, and chat all depend on it.
|
|
32
|
+
const tui = new TUI(new ProcessTerminal());
|
|
33
|
+
tui.start();
|
|
34
|
+
// Wire the SIGINT handler IMMEDIATELY — before splash, onboarding, or chat.
|
|
35
|
+
// Without this, Ctrl+C during the splash or onboarding wizard would crash with
|
|
36
|
+
// `chat is undefined`. We hold a mutable ChatHandle reference that gets filled
|
|
37
|
+
// in once runChat() returns, and the handler delegates to it when present.
|
|
38
|
+
let chatHandle = null;
|
|
39
|
+
// Re-entrant SIGINT handler: first Ctrl+C aborts a running turn (handler
|
|
40
|
+
// stays attached so a SECOND Ctrl+C in idle state quits). We re-arm with
|
|
41
|
+
// process.once after each turn-abort so handlers never stack across
|
|
42
|
+
// re-invocations of runChatCommand within the same process (matters for
|
|
43
|
+
// tests and for any future supervisor that might restart the chat).
|
|
44
|
+
const onSigint = () => {
|
|
45
|
+
if (chatHandle) {
|
|
46
|
+
const wasRunning = chatHandle.abort();
|
|
47
|
+
if (!wasRunning) {
|
|
48
|
+
tui.stop();
|
|
49
|
+
process.exit(0);
|
|
50
|
+
}
|
|
51
|
+
// A turn was aborted — re-arm so the next Ctrl+C is heard.
|
|
52
|
+
process.once("SIGINT", onSigint);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
// During splash / onboarding (no chat yet): clean exit.
|
|
56
|
+
tui.stop();
|
|
57
|
+
process.exit(130); // 128 + SIGINT
|
|
58
|
+
};
|
|
59
|
+
process.once("SIGINT", onSigint);
|
|
60
|
+
const authStorage = AuthStorage.create(`${BRIGADE_DIR}/auth.json`);
|
|
61
|
+
const modelRegistry = ModelRegistry.create(authStorage, `${BRIGADE_DIR}/models.json`);
|
|
62
|
+
// Try saved config + existing key first.
|
|
63
|
+
const config = await loadConfig();
|
|
64
|
+
let provider = config.defaultProvider;
|
|
65
|
+
let modelId = config.defaultModelId;
|
|
66
|
+
let model = provider && modelId ? modelRegistry.find(provider, modelId) : undefined;
|
|
67
|
+
if (model && provider) {
|
|
68
|
+
// Skip the saved-key check for noAuth providers (Ollama, LM Studio) —
|
|
69
|
+
// they don't store anything in AuthStorage. The model registration in
|
|
70
|
+
// models.json is itself the persistence mechanism.
|
|
71
|
+
const providerInfo = findProvider(provider);
|
|
72
|
+
if (!providerInfo?.noAuth) {
|
|
73
|
+
const apiKey = await authStorage.getApiKey(provider);
|
|
74
|
+
if (!apiKey) {
|
|
75
|
+
model = undefined; // saved model but no key — re-onboard
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// Onboard if needed.
|
|
80
|
+
if (!model || !provider || !modelId) {
|
|
81
|
+
try {
|
|
82
|
+
const result = await runOnboarding(tui, authStorage, modelRegistry);
|
|
83
|
+
provider = result.provider;
|
|
84
|
+
modelId = result.modelId;
|
|
85
|
+
modelRegistry.refresh();
|
|
86
|
+
model = modelRegistry.find(provider, modelId);
|
|
87
|
+
}
|
|
88
|
+
catch (_err) {
|
|
89
|
+
tui.stop();
|
|
90
|
+
console.error(chalk.red("Onboarding cancelled."));
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (!model || !provider || !modelId) {
|
|
95
|
+
tui.stop();
|
|
96
|
+
console.error(chalk.red("Brigade couldn't get ready. Please run setup again."));
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
// Build the Pi agent session. Note: do NOT overwrite session.agent.streamFn —
|
|
100
|
+
// createAgentSession installs an auth-aware wrapper that resolves the API key
|
|
101
|
+
// from modelRegistry and attaches Authorization headers per request. Replacing
|
|
102
|
+
// it with raw streamSimple strips the auth and requests silently 401.
|
|
103
|
+
const session = await buildAgent({
|
|
104
|
+
authStorage,
|
|
105
|
+
modelRegistry,
|
|
106
|
+
model,
|
|
107
|
+
cwd,
|
|
108
|
+
});
|
|
109
|
+
// Stream every Pi event to ~/.brigade/logs/<date>.jsonl. Brigade's only
|
|
110
|
+
// log sink — when something goes wrong (hang, hallucination, mid-turn
|
|
111
|
+
// switch confusion) the user can grep today's file.
|
|
112
|
+
attachEventLogger(session);
|
|
113
|
+
process.stderr.write(`\x1b[2mlogs: ${getTodayLogPath()}\x1b[0m\n`);
|
|
114
|
+
// Hand off to the chat TUI. runChat returns a ChatHandle synchronously once
|
|
115
|
+
// the UI is wired — the editor's onSubmit drives subsequent turns until
|
|
116
|
+
// /exit, Ctrl+D, or SIGINT (already wired above) tears it down.
|
|
117
|
+
chatHandle = await runChat({ session, tui, provider, modelId, authStorage, modelRegistry });
|
|
118
|
+
return chatHandle;
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=chat-cmd.js.map
|