@bondsmith/bondsmithd 0.1.10 → 0.1.12

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 (3) hide show
  1. package/README.md +104 -5
  2. package/dist/index.js +3098 -2276
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -11,7 +11,7 @@ pair it with.
11
11
  npm install -g @bondsmith/bondsmithd
12
12
  ```
13
13
 
14
- Requires Node >=24 and `tmux` (`brew install tmux` on macOS). `node-pty`, the one native
14
+ Requires Node >=24 and `tmux` 3.2 or newer (`brew install tmux` on macOS). `node-pty`, the one native
15
15
  dependency, ships prebuilt binaries for macOS (Intel and Apple Silicon) and installs without
16
16
  compiling anything on supported platforms.
17
17
 
@@ -38,8 +38,14 @@ Environment variables (all optional):
38
38
  PORT=4577 # defaults to 4577
39
39
  ALLOWED_ORIGINS=https://app.bondsmith.dev
40
40
  BONDSMITH_RELAY_URL=ws://127.0.0.1:8787 # defaults to production; set this when developing bondsmithd against a local apps/relay
41
+ BONDSMITH_RELAY_AUDIENCE=relay.example.com # only if a non-loopback relay's RELAY_AUDIENCE differs from its hostname
41
42
  ```
42
43
 
44
+ `BONDSMITH_API_URL` must be `https://`, and `BONDSMITH_RELAY_URL` must be `wss://`; the plain `http://`
45
+ and `ws://` forms are accepted only for a loopback server (`127.0.0.1`, `localhost`, `[::1]`). The relay
46
+ URL is only checked while the relay is enabled. The daemon refuses to register with a relay whose audience is not
47
+ the relay URL's hostname (or `BONDSMITH_RELAY_AUDIENCE`); a loopback relay is not checked.
48
+
43
49
  `BONDSMITH_DEV_TUNNEL_TOKEN` also exists but only applies to the legacy direct-connection path
44
50
  (`BONDSMITH_RELAY_ENABLED=false`) — should not be set by hand for a normal install.
45
51
 
@@ -79,10 +85,88 @@ tmux set-option -t "$TMUX_PANE" -q @bondsmith-agent-state "waiting:$(date +%s)"
79
85
  The value is `<state>:<epoch seconds>`, where state is `working`, `waiting` (needs the user) or `done`.
80
86
  The daemon reads it on its 5-second tick and shows it on the session. Anything else is ignored, as is
81
87
  a report dated in the future. A `waiting` or `done` report is dropped once the pane writes output more
82
- than 2 seconds after it, since that means the agent moved on; a `working` report is not.
88
+ than 2 seconds after it, since that means the agent moved on. A `working` report is dropped once the
89
+ pane has been silent for 2 minutes, since an interrupted or crashed agent never reports again.
90
+
91
+ Claude Code sessions that Bondsmith starts as plain `claude` report this on their own (see the end of the
92
+ plan section below): `working` when a prompt is sent, `waiting` when it asks for permission, finishes a
93
+ turn or fails with an API error. Nothing needs installing for those.
94
+
95
+ For an agent the daemon does not set up (a wrapper, `npx claude`, another agent), note that every session
96
+ Bondsmith starts has `BONDSMITH_SESSION` set to its id, so a hook installed once for all your projects can
97
+ stay silent everywhere else. This entry in `~/.claude/settings.json` reports a Claude Code session as
98
+ waiting the moment it asks for permission or finishes a turn:
99
+
100
+ ```json
101
+ {
102
+ "hooks": {
103
+ "PermissionRequest": [
104
+ {
105
+ "matcher": "*",
106
+ "hooks": [
107
+ {
108
+ "type": "command",
109
+ "command": "[ -n \"$BONDSMITH_SESSION\" ] && tmux set-option -t \"$TMUX_PANE\" -q @bondsmith-agent-state \"waiting:$(date +%s)\"; exit 0"
110
+ }
111
+ ]
112
+ }
113
+ ],
114
+ "Stop": [
115
+ {
116
+ "hooks": [
117
+ {
118
+ "type": "command",
119
+ "command": "[ -n \"$BONDSMITH_SESSION\" ] && tmux set-option -t \"$TMUX_PANE\" -q @bondsmith-agent-state \"waiting:$(date +%s)\"; exit 0"
120
+ }
121
+ ]
122
+ }
123
+ ]
124
+ }
125
+ }
126
+ ```
127
+
128
+ This is optional, and nothing edits your settings for you. The command does nothing outside a Bondsmith
129
+ session, and sessions started before the daemon set the variable are skipped by it. It reports only
130
+ `waiting`. A `working` report is dropped after the pane has been silent for 2 minutes, so one that an
131
+ interrupted Claude Code never clears (it does not run `Stop` then, and has no hook for Escape) expires on
132
+ its own.
83
133
 
84
134
  An agent can also share the plan it is working against, which Bondsmith shows above the session's
85
- changes. Run this from inside the session's pane once the plan is agreed, and again whenever it changes:
135
+ changes. Write it as markdown to `.bondsmith/plan.md` in the session's folder once the plan is agreed,
136
+ and again whenever it changes. The file is read as it is, cut at 32,768 characters; a symlink, or a
137
+ path that leaves the session's folder, is ignored. Changes does not list the file while it is untracked.
138
+ The daemon never writes to your repository, so `git status` still shows it and `git add .` would commit it;
139
+ add `.bondsmith/plan.md` to your `.gitignore` or `.git/info/exclude` to prevent that.
140
+
141
+ A hook can store the plan outside the repository instead, in `~/.bondsmith/plans/<session id>.md`, where
142
+ `<session id>` is `$BONDSMITH_SESSION`. It is read the same way and wins over the folder's file and the tmux
143
+ option, so a plan written there is never left over from an earlier session.
144
+
145
+ For Claude Code the daemon sets this up itself. When a session's command is `claude` (that word alone, not
146
+ behind `npx`, an environment variable or a wrapper), the daemon starts it as
147
+ `claude --settings ~/.bondsmith/hooks/claude-settings.json …`. That file adds hooks to the ones you
148
+ already have: after you approve a plan in plan mode, one stores the plan as above, and four report the agent
149
+ state (`working` on a prompt; `waiting` on a permission request, the end of a turn, or a turn that fails with
150
+ an API error). Nothing in `~/.claude` changes, the session still shows the command you typed, and the hooks
151
+ do nothing outside a Bondsmith session. Claude Code's `/hooks` menu does not list hooks passed with
152
+ `--settings`, though they run.
153
+
154
+ A fifth hook stores the latest prompt as the session's headline, which the sessions list and the session bar
155
+ show. It is the prompt on one line, cut at 200 characters, in the tmux option `@bondsmith-headline`. A reply
156
+ that only answers the agent (`yes`, `continue`, `ok`, `thanks`, and a few like them) or a prompt under 4
157
+ characters leaves the earlier headline in place; a built-in slash command such as `/help` sends no prompt to
158
+ the hook, so it does too. The prompt is stored as typed, so a secret in a prompt is in the headline.
159
+
160
+ The daemon leaves the command alone if it already has `--settings`, if the installed `claude` does not list
161
+ `--settings` in `claude --help` (asked once per daemon run; if `claude` cannot be run at all, that is logged
162
+ and asked again after a minute), or if `BONDSMITH_AGENT_HOOKS` is set to `off`, `0`, `false` or `no` where
163
+ the daemon starts. Each hook has a timeout (5 seconds for a state report or the headline, 10 for the plan), so a stuck command
164
+ cannot hold up the agent. Plans are written to `~/.bondsmith/plans` readable only by you, and a symlink in
165
+ the way is replaced, not followed. A plan the agent changes later without going back through plan mode is
166
+ not seen; the hook's plan keeps winning over the folder's file until the next approval.
167
+
168
+ An agent that cannot write files can set a tmux option instead, from inside the session's pane. The file
169
+ wins when both exist:
86
170
 
87
171
  ```sh
88
172
  tmux set-option -t "$TMUX_PANE" -q @bondsmith-plan "$(cat <<'EOF'
@@ -93,14 +177,29 @@ EOF
93
177
  )"
94
178
  ```
95
179
 
96
- The value is markdown, cut at 32,768 characters. The daemon reads it only when the browser asks, so
97
- unlike the state report it has no timestamp and is not dropped when the pane writes output.
180
+ Keep that one short: tmux refuses a command over about 16,000 bytes (measured: 16,000 works, 17,000
181
+ fails with "command too long"), so a longer plan is not stored and the panel keeps showing the previous
182
+ one. The file has no such limit.
183
+
184
+ The daemon reads the plan only when the browser asks, so unlike the state report it has no timestamp
185
+ and is not dropped when the pane writes output.
98
186
 
99
187
  An agent with no hook can still get attention by ringing the terminal bell (`printf '\a'`). A bell
100
188
  rung in a session nobody has looked at marks it as waiting, whatever its last report said, until tmux
101
189
  clears the flag when someone attaches to the session. Agents that can ring the bell for approvals or
102
190
  a finished turn work this way with nothing to install.
103
191
 
192
+ With no hook and no bell, a session still shows as `waiting` once its pane has written something and then
193
+ gone silent for 8 seconds. Agents animate while they work and stop when they need you, so this needs no
194
+ setup for any agent. It cannot tell an approval prompt from a finished turn, and a long silent command
195
+ looks the same. A `working` hook report takes precedence over this while the agent keeps writing, and stops counting
196
+ once the pane has been silent for 2 minutes.
197
+
198
+ Attaching to a session, typing in it, resizing it, or inserting text into it counts as the user looking,
199
+ so the redraw that causes is ignored (output within 3 seconds of it), and the session reads as waiting
200
+ again only after new output goes quiet. When the daemon starts, the sessions it finds count as already
201
+ seen, so one that was waiting before a restart shows as waiting again only after it writes more.
202
+
104
203
  tmux only flags a bell when no client is attached to the session or the bell is in a window other than
105
204
  the one the attached client has open. A session whose terminal is open in a browser tab, even a hidden
106
205
  one, counts as being looked at, so a bell in it is not flagged; use the hook report for that case.