Development & debugging
How to iterate on a plugin, drive BeSwarm from it, and debug it.
Calling back into BeSwarm
Plugins drive BeSwarm through the same CLI you use, at $BESWARM_BIN. Commands run from a plugin act on the plugin's session by default, since $BESWARM_SESSION is set; pass --session "$BESWARM_SESSION" to be explicit:
"$BESWARM_BIN" terminal list --session "$BESWARM_SESSION"
"$BESWARM_BIN" agent list --session "$BESWARM_SESSION" --jsonRun beswarm --help (or beswarm <command> for one command's manual) for the full set: agent, terminal, workspace, worktree, plugin and so on.
warning
Don't let a hooked command trigger its own event again — for example, a tab.opened hook that opens a tab. It loops.
Development loop
- Edit your scripts. No re-add needed: commands run fresh from disk every time.
- Edit
beswarm.toml? Add it again (beswarm plugin add <dir>), or pick Reload in the plugin's menu in the Plugins panel. The registry stores a snapshot of the manifest, so edits don't take effect until then. Running TUIs pick up registry changes on their own. - Run it:
beswarm plugin run <command>orbeswarm plugin view <view>. - Debug:
beswarm plugin history <id>shows each run's command, status, exit code, and the tail of its stdout and stderr;--limit <n>keeps only the newest runs,--jsongives everything. History in the plugin's menu does the same in the TUI. - Pause it with
beswarm plugin off <id>and resume withon.remove <id>takes it out of the registry and leaves your files alone.
If you move or delete the plugin directory, the plugin still shows in plugin list — and as missing in the panel — but won't run (plugin_unavailable) until you add it again from its new location.
Error codes
| Code | Meaning |
|---|---|
plugin_not_found | No added plugin with that id. |
plugin_disabled | The plugin is off; plugin on it. |
plugin_unavailable | Its manifest is gone; add it again. |
plugin_action_not_found | No command (or view) with that name. |
plugin_action_ambiguous | Several plugins define the name; use <plugin-id>:<name>. |
plugin_platform_unsupported | The command's or view's os excludes this OS. |
plugin_command_limit_reached | The plugin already has 4 runs going. |
Example: event feed
A log of every event in the plugin's data directory, a view that follows it live, and commands that call back into BeSwarm and clear the log. The scripts read the context with python3.
beswarm.toml:
[plugin]
id = "examples.event-feed"
name = "Event feed"
version = "0.1.0"
requires = "0.1.0"
about = "Log every beswarm event and watch it live"
os = ["linux", "macos"]
[commands.record]
run = ["sh", "record.sh"]
[commands.summary]
label = "Log a session summary"
run = ["sh", "summary.sh"]
[commands.clear]
label = "Clear the event log"
run = ["sh", "clear.sh"]
[views.feed]
label = "Event feed"
run = ["sh", "feed.sh"]
[hooks]
"tui.started" = "record"
"agent.*" = "record"
"tab.*" = "record"
"workspace.*" = "record"
"worktree.*" = "record"record.sh — hooked to every event; one line per event:
#!/bin/sh
# One line per event: time, event name, the whole run context.
ctx=$(cat)
field() { printf '%s' "$ctx" | python3 -c "import json,sys; d=json.load(sys.stdin); print($1)"; }
printf '%s %-22s %s\n' "$(date +%H:%M:%S)" "$(field 'd.get("event",{}).get("name","?")')" \
"$ctx" >> "$(field 'd["dirs"]["data"]')/events.log"feed.sh — the view, reading its context from BESWARM_CONTEXT_FILE:
#!/bin/sh
# The view: follow the event log. Ctrl-C returns to the shell.
data=$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["dirs"]["data"])' "$BESWARM_CONTEXT_FILE")
log="$data/events.log"
touch "$log"
clear
echo "Event feed ($log)"
echo
tail -n 40 -f "$log"summary.sh:
#!/bin/sh
# Calls back into beswarm through the binary it was started by, and
# records what it saw. Its output also shows in `beswarm plugin history`.
ctx=$(cat)
data=$(printf '%s' "$ctx" | python3 -c 'import json,sys; print(json.load(sys.stdin)["dirs"]["data"])')
{
printf '%s %-22s %s\n' "$(date +%H:%M:%S)" "command.summary" "$ctx"
"$BESWARM_BIN" terminal list | sed 's/^/ /'
} | tee -a "$data/events.log"clear.sh:
#!/bin/sh
# Empties the event log in the plugin's data dir.
data=$(python3 -c 'import json,sys; print(json.load(sys.stdin)["dirs"]["data"])')
: > "$data/events.log"Add it, then open Event feed from the Plugins panel, or:
beswarm plugin add ./event-feed
beswarm plugin view feedTips
- Keep commands short. A plugin has 4 run slots, shared by its hooks and on-request runs; long work belongs in a view.
- Set a
timeouton anything that talks to the network. - Use
sh -c '...'for one-liners and a script file for anything longer. - Parse the context with
jq, Python or similar. Don't grep it. - Hide plumbing commands — ones only hooks run — by leaving out
label. - Set
osif your plugin uses OS-specific tools (osascript,notify-send, …).