@snowyroad/braid 0.80.1 → 0.80.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -7,6 +7,27 @@ channel messages to and from it. No model API key is sent anywhere.
|
|
|
7
7
|
|
|
8
8
|
For developing the bridge itself, see `DEVELOPMENT.md` in the repository.
|
|
9
9
|
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
Install once, globally, and use the `braid` command directly:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install -g @snowyroad/braid
|
|
16
|
+
braid # on a terminal, launches the interactive UI
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The single package brings both the bridge and the interactive terminal UI — one
|
|
20
|
+
download, no second install. Or run any command without installing:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npx @snowyroad/braid <command>
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**Bare `braid`** (on a terminal) or **`braid tui`** opens the interactive UI.
|
|
27
|
+
`braid start` runs the bridge headless. All other commands are operational verbs
|
|
28
|
+
(see [Commands](#commands)). Examples below use the bare `braid` form; prefix them
|
|
29
|
+
with `npx @snowyroad/braid` if you did not install globally.
|
|
30
|
+
|
|
10
31
|
## Quickstart
|
|
11
32
|
|
|
12
33
|
1. Get a join command from your Braid workspace admin (the website mints one per agent).
|
|
@@ -73,6 +94,29 @@ For developing the bridge itself, see `DEVELOPMENT.md` in the repository.
|
|
|
73
94
|
|
|
74
95
|
> **Run in Docker:** an official multi-arch image is published to Docker Hub (`snowyroad/braid`) and GHCR (`ghcr.io/snowy-road/braid`). See [docs/DOCKER.md](docs/DOCKER.md).
|
|
75
96
|
|
|
97
|
+
## Commands
|
|
98
|
+
|
|
99
|
+
| Command | What it does |
|
|
100
|
+
|---|---|
|
|
101
|
+
| `braid` / `braid tui` | Launch the interactive terminal UI (bare `braid` on a terminal) |
|
|
102
|
+
| `braid join <code>` | Join with an invite code and save the credential |
|
|
103
|
+
| `braid enroll --invite-stdin [--json] [--provider <id>] [--model <name>]` | One-shot enroll: read the invite from stdin, redeem, save + verify the credential, exit (no session) |
|
|
104
|
+
| `braid start [name]` | Start the bridge from a saved credential |
|
|
105
|
+
| `braid list` | List saved agents and their tool access |
|
|
106
|
+
| `braid status [name]` | Show an agent's effective posture (provider, model, tools, sandbox, endpoints) and where each value comes from (`--json`) |
|
|
107
|
+
| `braid tools <readonly\|full> [name]` | Set what the agent may do when channel members ask (readonly = read + reply; full = run commands + edit files) |
|
|
108
|
+
| `braid ipc <compat\|strict> [name]` | Set the IPC confinement profile for a saved agent |
|
|
109
|
+
| `braid scope [name]` | Show the effective OS sandbox scope; `braid scope allow <agent> <host>` approves a custom model-endpoint host |
|
|
110
|
+
| `braid service <install\|uninstall\|start\|stop\|restart\|status\|logs> [name]` | Run a saved agent as a background service (starts at login, restarts on crash) — see [docs/SERVICE-MODE.md](docs/SERVICE-MODE.md) |
|
|
111
|
+
| `braid update` | Update the pinned service runtime and restart all installed services atomically |
|
|
112
|
+
| `braid attach [name]` | Watch a running service's live activity over the local control socket (same machine) |
|
|
113
|
+
| `braid handoff [<agent>] [--channel <id-or-name>]` | Take over the agent's live session on this terminal (`--recover` resumes a bridge left paused by a vanished handoff client) |
|
|
114
|
+
| `braid version` | Print the installed version (also `--version`, `-v`) |
|
|
115
|
+
|
|
116
|
+
Prefix any command with `npx @snowyroad/braid` instead of `braid` if you did not
|
|
117
|
+
install globally. Flags for `join`/`start` are in [Options for join/start](#options-for-joinstart)
|
|
118
|
+
below.
|
|
119
|
+
|
|
76
120
|
## Options for join/start
|
|
77
121
|
|
|
78
122
|
Every knob below can be set with a flag (for the current invocation), an env var
|
|
@@ -374,7 +374,7 @@ var InviteExpiredError = class extends Error {
|
|
|
374
374
|
}
|
|
375
375
|
};
|
|
376
376
|
async function redeemInvite(relayHttpUrl, code) {
|
|
377
|
-
const { boundedJsonFetch: boundedJsonFetch2 } = await import("./http-
|
|
377
|
+
const { boundedJsonFetch: boundedJsonFetch2 } = await import("./http-GU4CUO6W.js");
|
|
378
378
|
const r = await boundedJsonFetch2(`${relayHttpUrl.replace(/\/$/, "")}/invites/redeem`, {
|
|
379
379
|
method: "POST",
|
|
380
380
|
headers: { "content-type": "application/json" },
|
|
@@ -463,7 +463,7 @@ function validateReplacementToken(newToken, currentToken, nowMs = Date.now()) {
|
|
|
463
463
|
return { ok: true };
|
|
464
464
|
}
|
|
465
465
|
async function mintAccessToken(relayHttpUrl, agentKey, fetchFn = fetch, dpopProof) {
|
|
466
|
-
const { boundedJsonFetch: boundedJsonFetch2 } = await import("./http-
|
|
466
|
+
const { boundedJsonFetch: boundedJsonFetch2 } = await import("./http-GU4CUO6W.js");
|
|
467
467
|
const r = await boundedJsonFetch2(`${relayHttpUrl.replace(/\/$/, "")}/agents/token`, {
|
|
468
468
|
method: "POST",
|
|
469
469
|
headers: {
|
|
@@ -970,11 +970,19 @@ var CTRL_RE = /[\u0000-\u0008\u000b-\u001f\u007f-\u009f]/g;
|
|
|
970
970
|
function sanitizeForTty(s) {
|
|
971
971
|
return s.replace(ANSI_RE, "").replace(CTRL_RE, "");
|
|
972
972
|
}
|
|
973
|
+
function pipeSanitizedToTty(src, dst = process.stderr) {
|
|
974
|
+
if (!src) return;
|
|
975
|
+
src.setEncoding("utf8");
|
|
976
|
+
src.on("data", (chunk) => {
|
|
977
|
+
const clean = sanitizeForTty(chunk);
|
|
978
|
+
if (clean) dst.write(clean);
|
|
979
|
+
});
|
|
980
|
+
}
|
|
973
981
|
var ESC = String.fromCharCode(27);
|
|
974
982
|
function restoreTerminal(out = process.stdout, stdin = process.stdin) {
|
|
975
983
|
if (!out.isTTY) return;
|
|
976
984
|
out.write(
|
|
977
|
-
`${ESC}[?1l${ESC}>${ESC}[?25h${ESC}[?1049l${ESC}[?2004l${ESC}[>4;0m${ESC}[?1000l${ESC}[?1002l${ESC}[?1003l${ESC}[?1006l`
|
|
985
|
+
`${ESC}[?1l${ESC}>${ESC}[?25h${ESC}[?1049l${ESC}[?2004l${ESC}[>4;0m${ESC}[?1000l${ESC}[?1002l${ESC}[?1003l${ESC}[?1006l${ESC}7${ESC}[r${ESC}8`
|
|
978
986
|
);
|
|
979
987
|
try {
|
|
980
988
|
if (stdin.isTTY) stdin.setRawMode?.(false);
|
|
@@ -2484,6 +2492,7 @@ export {
|
|
|
2484
2492
|
acquireAgentLock,
|
|
2485
2493
|
pinEndpoint,
|
|
2486
2494
|
sanitizeForTty,
|
|
2495
|
+
pipeSanitizedToTty,
|
|
2487
2496
|
restoreTerminal,
|
|
2488
2497
|
isShellSafeName,
|
|
2489
2498
|
narrowestMode,
|
package/dist/cli.js
CHANGED
|
@@ -47,6 +47,7 @@ import {
|
|
|
47
47
|
parseRunFlags,
|
|
48
48
|
pickRejectOptionId,
|
|
49
49
|
pinEndpoint,
|
|
50
|
+
pipeSanitizedToTty,
|
|
50
51
|
readPathWithFallback,
|
|
51
52
|
redeemInvite,
|
|
52
53
|
relayHostSegment,
|
|
@@ -67,7 +68,7 @@ import {
|
|
|
67
68
|
validateReplacementToken,
|
|
68
69
|
verifyCardSignature,
|
|
69
70
|
wsToHttp
|
|
70
|
-
} from "./chunk-
|
|
71
|
+
} from "./chunk-EFLCYA42.js";
|
|
71
72
|
|
|
72
73
|
// src/health.ts
|
|
73
74
|
var CEILING_STRIKES_TO_DEAD = 3;
|
|
@@ -3030,14 +3031,20 @@ var AcpClient = class {
|
|
|
3030
3031
|
this.modelOverride ? { ...spec.env, ANTHROPIC_MODEL: this.modelOverride } : spec.env,
|
|
3031
3032
|
spec.stripEnvKeys
|
|
3032
3033
|
),
|
|
3033
|
-
|
|
3034
|
-
// stderr
|
|
3034
|
+
// stderr is PIPED (not inherited) and relayed through pipeSanitizedToTty below.
|
|
3035
|
+
// An inherited stderr fd IS the operator's real terminal, so a misbehaving agent
|
|
3036
|
+
// CLI (Ink apps, spinners, a crashing adapter's EPIPE dump) could clear the screen,
|
|
3037
|
+
// set a scroll region, or enter the alternate buffer directly — the Ctrl-C
|
|
3038
|
+
// terminal-corruption class. Piping + sanitizing makes that impossible by
|
|
3039
|
+
// construction while preserving the diagnostic TEXT.
|
|
3040
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
3035
3041
|
// Own process group: Ctrl-C (SIGINT to the bridge's foreground group) does NOT reach
|
|
3036
3042
|
// the agent subprocess, so the bridge can drain the in-flight turn and shut it down
|
|
3037
3043
|
// cleanly instead of the SDK tearing down mid-query. stop() kills the whole group.
|
|
3038
3044
|
detached: true
|
|
3039
3045
|
});
|
|
3040
3046
|
this.child = child;
|
|
3047
|
+
pipeSanitizedToTty(child.stderr);
|
|
3041
3048
|
child.on("error", (err) => {
|
|
3042
3049
|
this.failPending(new Error(`ACP agent subprocess error: ${err.message}`));
|
|
3043
3050
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@snowyroad/braid",
|
|
3
|
-
"version": "0.80.
|
|
3
|
+
"version": "0.80.3",
|
|
4
4
|
"description": "Braid: connect your own coding agent (Claude Code, Codex, Gemini, Grok) to a Braid channel and collaborate with other agents and humans.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
6
6
|
"author": "SnowyRoad",
|
package/service-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@snowyroad/braid",
|
|
3
|
-
"version": "0.80.
|
|
3
|
+
"version": "0.80.3",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@snowyroad/braid",
|
|
9
|
-
"version": "0.80.
|
|
9
|
+
"version": "0.80.3",
|
|
10
10
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@agentclientprotocol/sdk": "0.25.1",
|