@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.
Files changed (39) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/LICENSE +21 -0
  3. package/README.md +154 -0
  4. package/SECURITY.md +208 -0
  5. package/assets/brigade-wordmark-on-black.png +0 -0
  6. package/assets/brigade-wordmark.png +0 -0
  7. package/brigade.mjs +96 -0
  8. package/dist/cli/chat-cmd.js +120 -0
  9. package/dist/cli/config-cmd.js +132 -0
  10. package/dist/cli/connect-cmd.js +447 -0
  11. package/dist/cli/doctor-cmd.js +317 -0
  12. package/dist/cli/gateway-cmd.js +92 -0
  13. package/dist/cli.js +287 -0
  14. package/dist/core/agent.js +1123 -0
  15. package/dist/core/config.js +80 -0
  16. package/dist/core/console-stream.js +188 -0
  17. package/dist/core/error-classifier.js +354 -0
  18. package/dist/core/event-logger.js +122 -0
  19. package/dist/core/model-caps.js +185 -0
  20. package/dist/core/provider-payload-mutators.js +517 -0
  21. package/dist/core/provider-quirks.js +285 -0
  22. package/dist/core/server.js +459 -0
  23. package/dist/core/smart-compaction.js +209 -0
  24. package/dist/core/system-prompt-defaults.js +88 -0
  25. package/dist/core/system-prompt-guidance.js +269 -0
  26. package/dist/core/system-prompt.js +884 -0
  27. package/dist/index.js +30 -0
  28. package/dist/integrations/ollama.js +140 -0
  29. package/dist/protocol.js +49 -0
  30. package/dist/providers/catalog.js +100 -0
  31. package/dist/providers/validate-key.js +197 -0
  32. package/dist/tui/client.js +263 -0
  33. package/dist/ui/brand-frames-cli.js +20 -0
  34. package/dist/ui/brand-frames.js +36 -0
  35. package/dist/ui/brand.js +402 -0
  36. package/dist/ui/chat.js +929 -0
  37. package/dist/ui/onboarding.js +400 -0
  38. package/dist/ui/theme.js +51 -0
  39. 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
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